From 4afa966ba9ab620a3d68ac2cd94c45f72cd93eb2 Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Mon, 6 Feb 2012 10:47:41 -0500 Subject: [PATCH 0001/2152] first commit --- README.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 README.rst diff --git a/README.rst b/README.rst new file mode 100644 index 00000000000..e69de29bb2d From 231c40338d9093b7b7c2c3c87b3d2ec4c071221d Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Wed, 8 Feb 2012 14:48:18 -0500 Subject: [PATCH 0002/2152] initial checkin, very rough, but it is a start --- .gitignore | 3 + .idea/.name | 1 + .idea/ant.xml | 15 + .idea/artifacts/raven_java_jar.xml | 18 + .idea/compiler.xml | 21 + .idea/copyright/profiles_settings.xml | 5 + .idea/encodings.xml | 5 + .idea/libraries/common.xml | 13 + .idea/misc.xml | 33 + .idea/modules.xml | 9 + .idea/uiDesigner.xml | 125 +++ .idea/vcs.xml | 7 + .idea/workspace.xml | 827 ++++++++++++++++++ example/log4j_configuration.txt | 27 + example/run.sh | 3 + example/run2.sh | 3 + .../net/kencochrane/sentry/SentryExample.java | 31 + lib/commons-codec-1.4.jar | Bin 0 -> 58160 bytes lib/commons-logging-1.1.1.jar | Bin 0 -> 60686 bytes lib/httpclient-4.1.2.jar | Bin 0 -> 352254 bytes lib/httpcore-4.1.2.jar | Bin 0 -> 181200 bytes lib/httpmime-4.1.2.jar | Bin 0 -> 26890 bytes lib/json_simple-1.1.jar | Bin 0 -> 16046 bytes lib/log4j-1.2.16.jar | Bin 0 -> 481534 bytes raven-java-0.1.jar | Bin 0 -> 1170708 bytes raven-java.iml | 14 + raven-java.properties | 2 + raven-java.xml | 198 +++++ src/META-INF/MANIFEST.MF | 3 + src/net/kencochrane/sentry/RavenClient.java | 281 ++++++ src/net/kencochrane/sentry/RavenConfig.java | 125 +++ .../kencochrane/sentry/SentryAppender.java | 70 ++ 32 files changed, 1839 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.name create mode 100644 .idea/ant.xml create mode 100644 .idea/artifacts/raven_java_jar.xml create mode 100644 .idea/compiler.xml create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/libraries/common.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/uiDesigner.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 example/log4j_configuration.txt create mode 100755 example/run.sh create mode 100755 example/run2.sh create mode 100644 example/src/net/kencochrane/sentry/SentryExample.java create mode 100644 lib/commons-codec-1.4.jar create mode 100644 lib/commons-logging-1.1.1.jar create mode 100644 lib/httpclient-4.1.2.jar create mode 100644 lib/httpcore-4.1.2.jar create mode 100644 lib/httpmime-4.1.2.jar create mode 100644 lib/json_simple-1.1.jar create mode 100644 lib/log4j-1.2.16.jar create mode 100644 raven-java-0.1.jar create mode 100644 raven-java.iml create mode 100644 raven-java.properties create mode 100644 raven-java.xml create mode 100644 src/META-INF/MANIFEST.MF create mode 100644 src/net/kencochrane/sentry/RavenClient.java create mode 100644 src/net/kencochrane/sentry/RavenConfig.java create mode 100644 src/net/kencochrane/sentry/SentryAppender.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..9dbdbbfd785 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pyc +*.DS_Store +out/* \ No newline at end of file diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 00000000000..f6e2de0e085 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +raven-java \ No newline at end of file diff --git a/.idea/ant.xml b/.idea/ant.xml new file mode 100644 index 00000000000..ea91b4abaea --- /dev/null +++ b/.idea/ant.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/.idea/artifacts/raven_java_jar.xml b/.idea/artifacts/raven_java_jar.xml new file mode 100644 index 00000000000..b3940b12c01 --- /dev/null +++ b/.idea/artifacts/raven_java_jar.xml @@ -0,0 +1,18 @@ + + + $PROJECT_DIR$/out/artifacts/raven_java_jar + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000000..a1b41c52c72 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 00000000000..3572571ad83 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 00000000000..e206d70d859 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/libraries/common.xml b/.idea/libraries/common.xml new file mode 100644 index 00000000000..80734af5ac3 --- /dev/null +++ b/.idea/libraries/common.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000000..1c091f99766 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + http://www.w3.org/1999/xhtml + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000000..9c0090beddf --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 00000000000..3b000203088 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000000..275077f8255 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 00000000000..2928a1972d7 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,827 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + localhost + 5050 + + + + + + + + 1328543448831 + 1328543448831 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + raven-java:jar + + + + + + + + Android + + + + + + + + + + + + + + + 1.6 + + + + + + + + raven-java + + + + + + + + 1.6 + + + + + + + + common + + + + + + + + + diff --git a/example/log4j_configuration.txt b/example/log4j_configuration.txt new file mode 100644 index 00000000000..e7b342ecadd --- /dev/null +++ b/example/log4j_configuration.txt @@ -0,0 +1,27 @@ +log4j.rootLogger=info, stdout, R, sentry + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + +# Pattern to output the caller's file name and line number. +log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n + +log4j.appender.R=org.apache.log4j.RollingFileAppender +log4j.appender.R.File=example.log + +log4j.appender.R.MaxFileSize=100KB +# Keep one backup file +log4j.appender.R.MaxBackupIndex=1 + +log4j.appender.R.layout=org.apache.log4j.PatternLayout +log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n + + + +log4j.appender.sentry=net.kencochrane.sentry.SentryAppender +log4j.appender.sentry.sentry_dsn=http://b4935bdd78624092ac2bc70fdcdb6f5a:7a37d9ad4765428180316bfec91a27ef@localhost:8000/1 +#log4j.appender.sentry.scribe_category=MyScribeCategoryName +#log4j.appender.sentry.DatePattern='.'yyyy-MM-dd-HH +#log4j.appender.sentry.layout=org.apache.log4j.PatternLayout +#log4j.appender.sentry.layout.ConversionPattern=%5p [%t] %d{ISO8601} %F (line %L) %m%n + diff --git a/example/run.sh b/example/run.sh new file mode 100755 index 00000000000..59502a869d8 --- /dev/null +++ b/example/run.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +java -classpath /Users/kcochrane/github/raven-java/out/production/raven-java:../lib/log4j-1.2.16.jar:../lib/httpmime-4.1.2.jar:../lib/commons-codec-1.4.jar:../lib/httpcore-4.1.2.jar:../lib/httpclient-4.1.2.jar:../lib/commons-logging-1.1.1.jar:../lib/json_simple-1.1.jar net.kencochrane.sentry.SentryExample log4j_configuration.txt diff --git a/example/run2.sh b/example/run2.sh new file mode 100755 index 00000000000..c173d116b87 --- /dev/null +++ b/example/run2.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +java -classpath ../raven-java-0.1.jar net.kencochrane.sentry.SentryExample log4j_configuration.txt diff --git a/example/src/net/kencochrane/sentry/SentryExample.java b/example/src/net/kencochrane/sentry/SentryExample.java new file mode 100644 index 00000000000..b7bd1478779 --- /dev/null +++ b/example/src/net/kencochrane/sentry/SentryExample.java @@ -0,0 +1,31 @@ +package net.kencochrane.sentry; + +/** + * User: ken cochrane + * Date: 2/6/12 + * Time: 11:35 AM + */ + + // Import log4j classes. + import org.apache.log4j.Logger; + import org.apache.log4j.PropertyConfigurator; + + public class SentryExample { + + // Define a static logger variable so that it references the + // Logger instance named "MyApp". + static Logger logger = Logger.getLogger(SentryExample.class); + + public static void main(String[] args) { + + // PropertyConfigurator. + PropertyConfigurator.configure(args[0]); + + logger.debug("Debug example"); + logger.error("Error example"); + logger.trace("Trace Example"); + logger.fatal("Fatal Example"); + logger.info("info Example"); + logger.warn("Warn Example"); + } + } diff --git a/lib/commons-codec-1.4.jar b/lib/commons-codec-1.4.jar new file mode 100644 index 0000000000000000000000000000000000000000..458d432da88b0efeab640c229903fb5aad274044 GIT binary patch literal 58160 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 literal 0 HcmV?d00001 diff --git a/lib/commons-logging-1.1.1.jar b/lib/commons-logging-1.1.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..1deef144cb17ed2c11c6cdcdcb2d9530fa8d0b47 GIT binary patch literal 60686 zcmbSx1F$H;lI68+8}Hh-ZQHhO+qP}nzVF&L-?hDOX8z93-`$w_yB!tXT~V1)-I*s& zWoDJU6fg)Bz`tD>Ku&D`{_>9-T?8$iEQwxGyr6@Rpb|&$ok>O~>Qx@+p zEx3uDGSCLLb{Q|l8bNHRtPyq$tYoj*CWe9&)RQ?J`k$eCGVv0653im zD|$rEI=#=i$sU2e>*y1qo614DajD=e)8&3R;!Aefa6Y(vyA=GCaV=^Fzk9k|-R15u zXM_35QmcFZ_|jfrbuwzJ!`hkb@eF{zsk`yjj2gL+?C#Ofp}9$8t7>s~b0X)hu7h@P z^YLwDBFrhbmr{xNEl{m8A=nG_ zwi3OW^D3K;#cS?9lWbJgxvx%VB!@yJ<5%z5g&n-^HVGvUiFUD1aK!#6dikgHt2^6? zDl`WN#JaNu3C&f@b9|FfiQmSAxK`pZ0?8;0oa8Dco>T%-(;stBgKo4RSXZD1#|{<@ zx7q@Vum%brOR=+maj(rtZJ=?Xx#ql^)VYGj93ynpCoHi6?2wkM&SmyI=D&El1}>H|%GQ489XfAr;lb+a2%% zv3VH|6|hI^3I~*X1d|-Fb)T_ZtE#Tk4j=mY>W|2lH;nR#g0_x`ghdAL*CDoUCW`?CilKj!B}$X|cyvqtjfX@0ZS}$2*$1(;pzv0V zRXJePwZ;VnwCE0i;PUZIHiu^F5%O^dv(XQdqI%WpABahmjGogbpwuQW!0Qi!kjxR- zxMBge4!dDL&wEH(1?0VnaXIUXl&kIGqkY5iMk%)) zH@tN}(P#GDhz0Cpr3`rSpSNBMq70e<{h2U($hyf z5>=a!H!m9~F(&-zI#^dkX`tE#{+8kdB)R}YXXB?3&{SVhD&Tqc}5n@llk*Yp7!{$mH1DXyfA)(!K^4z;IcL7b1Kp7^4lNgkhoVDZrAbt zS;gEf|M0l0`BFphNo$m0WO-2$aX87-k75$%i#EiAvH=?@;O7&^m|!+3KCr^N(}Ozk zD*Mg0yVn4Xh^O(9<*|!(Q!=S0oD@`Li3Ch>f?)rd;e&8=0FGcKprOlUfKgxRlW=E~od(fd=kS@pwZVFHK(0%NfYrREbIG3*>kF+*N#=Fuqy_3KCNBZ>xm#IYOo!e zg8sP3YRar@Xz}OG&Huu+)XRNEzed{7U!>4O1pM|-5LT6&ibW3wb_lzyqA8yxv8&A9 zHH@o;XtAYDWqId&7KDq3GUDVknbT4guqFMLCOE}@iBhg7YHJ}Z2^ z7SY}d{>Kyf;!jBTQBo{|h1L@K@}V#AR^@&;geJ#R%r@b74vw;-5BoJqZy6)j^OmvK z{T`Wu#h;>jzte))5rn{J0XGctEuKzmui$o!gz$EI3?oKO% zN^SsHxcIc2?6&DZh5ay6%MG{lIt0|JP##JN36N0pge6&g*l`3iXHGB`uW9MnMP~5= zlPaMQG|HIVWSL1!GjJ95cM`HvPfO;@q=BlBeRiL7%T3F_zwWP|U=nW}2)v9o-gx32 z%|4FSQv~`mq&XU>->8sLHRT|AlTn}2DbAKOnuvZ$WIvc1a+f1A-U^&SjHIoxAUo|0 znq5f_fH7i`;p|@Zj=8>X2s- zBzDN4-YWMtj8vqU?0K#yPrZPdRV(A0Kp{e$0jz5f%&F4d#fP0`!JRtI(GxewBW2Eg z;JO)v>y??#e99aJXq~ek1>q5~)Q!uRUc7D*4RcDHJ~UX5R}T7V{c*bwpDqSj?g{B7 zL56DW1r|FpEA)jL!&WKjw6q4c$q|+YK)e140mTuSl3CYm^D`_ zZmy`%;O3r>JX-8Xmn4C2n<@2>2uRwjlt7KG#x|P}E@6lzGs;-2z;OXW4)d1!9oq-F zr_0j7&xVfASzZ{MU5aN=q-gXZh13L{$iB;t(==|Ko)XiLi=#tY8pnkP@0fmJbASHe zx0vaXDPm~~I7F1*sk;B1uv$0mA9ZjAyPOuYUkbD6Ds_dMim3QIMjkG^5=9Th0Lz&6 z4x6EC50FBgp;cJx?EKd)Jg-nWu~J;+G(oOE8`f}TO|y5M^Oo0Zmp>mM3K-A<0vZ#Z z4yNEt0EL??ad7Ahx}~(`XF1>r&q7fh3n@ z&h0`g8h(IZpbi`ZWMO3%FkAhShf!3DzJIs)Z>h{c)CmLL7aheXAeWJX0f(qezL>+Q z2q!v+TF`htt?Cg+9%Mf1l_?kc>#^gnI5zz2k+G5OZ9|v7Zr!M?u20SHlN|MGUo=0x z+FJi>^IkyT;q!7S^7&vY6;?7COkpQQO#2G}8QP3UEFv$c2L0)ez&s3GzR@iCn(H_i3+N;;_zY}^mDQ{a14Y;t}3z>9b1xQdHzA_C?Ik_kwNGN z?ce)-HGoY89uVXZ4)W!WaB4Da&{>VgtdqF%{RBG>U>AJr7=%8HdAqGmEm;j|9^&8K z{y>C!K~2GX9p5Rp`1IO%yMEc)FF2LMzztd=aeCC4V_Mt4X({3kBrk?FS*z?Fo;#M0 z_XUUIR(W;1oXCt|)Wj;_X22SgCx~e05mFyComQv0$DK~s+qVEHU=(y$v~EN`Tw~0P zvCf2?JtJn<>@4Gojl|K`hgLvX`-2t0Jn-vWN}7DTkVIH6sgN4xQyRmIOoxfrXwa!Q z$>zd5OLBRx_7+NibU9n^V#nRd-h?RU z93V}+HP*x@Z`5cstEdjUf1R3|y4q|Hm{=d3t=)dYG2d=Jw1p->xaVyh-BI23zFqMo z?+)p}?iV`kuTLwKMGL|FWw6FkL2TaO3eg14H_fV2M<6H=60V?cFgprV;gdgUdS-~P z=8XGQ+k_y<1(i4Njk2D!w6)SNVXwXXsYL>>H?*KGmrJFb`RE9c{C7ooyJ+100##MPYIcYSMSS-_!KuWgxh5Zw`Bo2Yak=W{U~Knvdb^syQ$gOb+P4Uq4hn z1wDgu#oDU-77lsEE33^iL*)|s5GZMH172s2k6(TfJrYBJX!m};a?iPMzp*CgGl(h| zvlF>|nF=j3a$v59@TUqvyv+7EzT>rjSo(Y+DVLJ!4bJ5q;C<&Xp(MpD$?n<@N(Fre zlwrL7i1z{Cku}w%WR@cYW}Cwf!{F3x#v<7BL*`REkJ&xHSx6jQUO#F-p#K2=&uYYf zq~?(V>>=QQ006jui#C5V^#7Hb%gQPLP0nlLEwB#fo+M3ybMOh6WX>9JK-Gv9@f4tjX}WS5#FaSfIOl@= zAn59BtT5u_T&)0_zqw_Si3Uw&M@mR8fY%#p6S-~Wr>;6VinJ|>@z!Ml9bF8~ zkeldgRFqe`_iD6FR^(8kt@H|gvz2UXQd~ab2teDWu*z7K%xoc`=kSE_Z?Fj{B_*zc zaxQI2>{O*NRd$k^IYT*@z>ci8n<^dcMYe8)(#%k|lt3v`XO=X<)}lIA(sWRh)2Ywc z7L|)GwUcL}&?ya;nJVt4^orm#qTQ_nvhi5fNo6WB8q-X#ZM3Ap*IhrU&a5~BO>C*D zHnS~{WKlz2r%WHki)#6ZLRogynDNqdt^^4dS-3#+BH==XS+CA49r!vjmRhlZY*w5> zIGdZC+S-$hxNHL2pKq{obq{&irUzB+n1dZ|z9$tdmsw*p5vh?`OZc65$)@$mgm|N% zobRGLpR-RWX-VH{p)4(;v+O?9WyR*02r9uI@_;03C_i|Jfx`aAf1)X}$_7(oTyZif zBeLDX9E5?EgDI!%MxhWt1vw>dyd>cK@!^`bW>^TJTw1?+AG6e__)d?~-1C{BUi}6J_eRu>$VQ=~8YR*2+?)w7 z1c^IX!3hUOJ*8j4i^g7OO|aAnIbztU^8u*b0tvmAn7PAB{$HcFgJAfs{ji;I>{i2| zeKEIm;a~oxRXo36=I)gefet^ygYCnaW6>Hd!X6m|>JkcFfOg6W@f{|_&g_=R$^=}; zm0IDM$!~}rQ=qpE!QHGeDl+%_n+>*fQYqSA;RfN~BR9GkpHetLHS5Gv^jqmwK)gbRJk6 zbca@6`di{Fj*mGfp*?(MhD3KsJA_HhL4k-zc$7H5mUxTvM@V6PGN~9UCRy zParCm1{FL19sVfUBFdk|N@c_)DVZWJ8KXOPO1vLIR5T4Ne)KHiK1g%S^1o)LHX#$2 ztP`7r>`TU%nEWCxp0hdJlj91bxq!P(%RPK|`fU-#sUApI`VPUsH)!XAs0$%@c2E4N zi*CRQQLq|!E2b0NVN^Yo=Yn_=gz)|e;kPWp{^HlG^+O4r5{N{}FQp6b-63bTS)IXru z-v@pFOm+!uxC+x^WeR|vQ`fkyCC(g?uFY@E-_AuNXFMcJ+FgDUd9E`jA0Ff`% zP;k14vUg96hD_OeKF-ryB3ln_*2vczR8Qs3og??w2sIbwfo1>Bg|9cj?){DihiXje z-dl5;$uJl7Av^Na=ap4AKJ9Sukn8?kC*qsOqUYDihoLOQTbgor^#M4gZ}|Z#=#ILh z@vu0BI_7$v+!+n#RCG)DtaNm<@Sk2SRCgrLEqVFF=!o+_mko+IgT18Z#$>208%Ca- z+a~Z;uH0QPR?Hkn?WrOqUS5Xd4z6TO5rm+V{y;zSWO#fi3}n&if&SQ#=Y}{pXiNs0 zTBu8)bR`W2bP}!PVHlPY!_pW!8L#;X7t7#AtXIa$aaYSqrQN65cvef&RPCgR(lO0v zhn~($<`HCvcg93H@HV4kMl^14G28A=!=syZVq^Cl6vw|nuLcT}=}a{C&T(Z(;h~VT zI@wHwlPFA$V?JpAG<0D$Q=gDjGUuA!A{ix~+l#2wI}g`y&`oldfs$s*&8^<;tl538z>(#6qxV#y3oo+EsN}ekWWl%aK8i@{aQ!}ZeQcs`XufA2p}8QEsR*GQG(^9$iwwG`cf}-AME%mXA2VLUXqz?`bYwDyF%>;IFY>bQ zLu~m&QsqFLvm$nK&d$a!Fm9qb5Bq@G6o9!ZUXrM7xL$92zJ-mCj6ac6xjX=y#-nCx z$X<~@DSd-*jSC66_h-@n>Kfs!J1bT;BEd~L^PCr4h8OPQ7PinE2BpM#yegY z^jNE~VXNZ)Wi70A5k;li8P%t$T6oCjY^5Hu2FrH9w3Hmj3d=Vb-|2_0$SS~FMksABcbFBnY`8mi5sQ;yJ@WG)j?or)eicuO5F>yJY2b{!}-4YoZ+Efi9{78iQdGhwB$TmOQ#znGB>NPSap)3N&;JT_H)xj zV@Jb%;PtF!L_nnu0*$BbQk&ukx*DbekJYHbzHrQt)+=YR(fb)iPtiCH^dW^_4FuFM zLWdd}Q58gos#Hf(oz`|127WW`RwN)>n%yT_3l7MrmD^J5iUlU+q@`3V* z66B0vd55@tW50c-i)1N-re_f6#Ts7HbvF#mK)=nDgpsA&G_nUBCBmaD98O$<);`+h6Th$iKcGaoC zx>p5hXm4ub-L6!)9BXK=oM2U2p4d&?Tq!7)s^IUVs>Z%d8r zgpJwr#VYuW-2v#11P%M9pNV-)@CLTsmG^*$6(Ihgm^4S-UEUInG>6@-9%x8broGu7 z<;{_|gX4{I4YK=ewaAM8koY(gCoW(X%k50_L6H)aSoMGxmx3%@f&prJ*u z(k6s!r!4OX4@@RtHW>nf;@qpuKOurrWJPjZEJRIqJl<4{zOez>Z3Wm(rrJ$4^u8f7 zHcs%K@+dg2b@rYA_53Tv(hrv{yLpk1d>OYn6_@gX@1H}IrRk>C2++Sd?B4{I;a?0< znEz{FQlqM6k8FbS&Dth2*jmCSNsvqwX|sV;OIhGZWkC_Fys7x>#imgr&9;#x4NZO? zT<=lG%zF{j%zFS+!5lMZU%D%f1 zcb%NCI|$qRodH6Y1FJtANeBk4VmIpWCPoqiUNF2_Nc!!eHbN3R-7u&@F4BP^K$<;A zI8{64vkr3IYKQ=8l?9=qU@>Sj88I<2m62#DEnzzCsbd?`Mz^Ur1gUNLgJrY!LQJ`V zX_F@}c8hcv>1(hcmH3+Bsqy;R@kAAejG)xWn7jox9qJho0@!1>E+|uL0(C7YtB)# zcQ`*rHRX^^X+v9r?SVRgrZm`pKsp4P>9SiHd^xL9c{6QMlF2j^s>m_+T04QGvpGw- zq~+Z4xg#@9c6UyZlnRui3^mH~xr3>SQ#^dl+KF0>UrTnb3Kfcs>hO0tX-YDXMon_L zt9*Nx*)rb317;G-ViY;(i@Pp}zU%dMZd58KZN8NyWvFm;W}yIGEP_FaP>DSi&P-K^ z_tmx*k|~&OzG|Q!fr&C#3Sowt{T4O4;zm5}hV5ab>jq7ud_8a(sQ)TEQ zjR)wic-#)1u+~Y(s%dk}+e&s&t+j@!`3UxN@=+tjER;5KS~u%e62UxHunHr;g-o50 zM!yef_RA5slfU`|a8DbC2;m;Eu_Q+GIDVkevK)0;CO6DQ2Wh*ohn{uulQMySwQ zLqc@iQR`KS$mmoULQ=vN$r%EhO+Y-Sk?$XA2R}GUeS_^RAswf&?`a3|A6h^R8GpVA zcj?Yx-tav71jWWrx6z@4fubNrjqlbF9$xrFnFItK$9LNxj;)w^d@l8bhZtWl)`3nj ze2`??`z2)(wn-g3rCZhN@zoq4R_$R{8KNz+1tRgVew?Y=U^MWFL_vC{bNaaE#_|Ys z4Pgw}3Wv8Nkk^L1O%8AebZb9Xddrr__3^2QG^)OuJFAxg3)?$BI$dP(HsB9hF3yMF zXuGk9b_*I_2s4XGt+iz-%rT)6*mfP?uq^?xZ#j1elKr`rLvTooy-1DoV2^OaFBSM* zRkHQ`QK~R9y4rcIr`pydZpJ`@Fpcl;tcFd@F`Jq#*#>+5`pB4 z^>g5`{?Xfj?6CNa>pYoi*M+=G8+cPb^pZ@o>q_416Z}VySdVY)%NfN3P! zU~uhTLUXG@f-8+yq>)5JjDJgmY7~q*W22FodZ@|KIwm6*Re&MsozL#e)o_?MkNUXi zyEld1WZD?X>_pdBHus5?%4|IheOG+iMaD&C-*@G8?RDSxOBTL-l+G(rB+jVG86#QY zxDlhIC2?d9`+T_V*4SWl;;->~qWYrn`$rVtAttyeqMy!~Gr@=v3A8eG>8A9E17_bT zvX>_Ip#!%+&mR!q{r&LtzyZ(tAn8SdAXT~12Nc{ihnSeN zlkT#k4~)9;cZI>7k5@e!kPXw2SH-D}EL3?|uDC;!H)(VF8*km$;%mtbIo^_43yw*i zk;z`N#f*lL(HGP;`BLGXv8jtp?-JX|^(Gx(h-3A*Lpg+T+Z;+%Xoto~FVY7bUGnuc zhS`S-XYC5sy)x_Cd=6j)I3_0(dRSatfm**zqraW#8}4m{PZ+VWwAscy8`0d!0O%IbK(0BA-sqJryoUuY-)pAG4`Wjstb6Ej@hhD&N77kO*x7e~%X% zb@2ZM0xQ}sED&TQT>H7ZX9EtY7FxqUu3D9` zLu2r<5K(ns!=Wneh4FNboQsGqH%bMiJ7NX(d`J)l4CQ*L3A(zE1uACGRnq29 zub=6u%jA{qU6HS4OCX(jDXyaER-rnoQ4;23y%enW|2M zA(EL(e@`lAlwBOXirPKktcc?W|4iwQY)9dq)wg4CM;rB`-W}J&_&rYjQ>+g$1jXAQ z1{Hh1I_uCAY`dW(VJAoWWt8?9r;`|`!zix-TEt$fkN;r<^$J-y@#dSq2gbTqKh6J$ z42r*R#90~;sBoQmj78MtdjrrMReo`O)KY2EMY)bux=MQ?7V7Ewfto!+&d95Bi^=;k z9^57kSwpxvCgs(#9oo9OASCBi3I)(1VUEljnuE1cv70e!5$E@icqL9_Ht5=TfM*VS zj@mq50g01)BWaVPW6<{d9_Z1sHn$Cz(qH-;&$nh`I5s4ElhSSGXa4r$;~jp-0otW0 zz}hoD)QCsOnP=q%9u^jEKK&Z=d}3}x^~}&2JlHjPQbw-e`lO|V*)B6Senn3(dvZk(p(n>Bxcsz9I$&l7eWlnRO#YFJO2dHrm$}^ruZRouvth zpM>96FvgDmn8>61 zR-~uF@}R~{p#|FS?FAf7v_g@Q&r9VtHlT7ElO?obhp0HusyI&J@)jw1x6piF>2fpB z%8PY6q2oHhEwa>4dDN9lL5)1(&vjIF{K`R3onMzX5KkW}O*6^{xX>m(8Mi-;K$p^} zD-H51e&kDj)tOKGl+{{78b3+5KP^C)Ln`WRp^e`O@_w2um)58&HU-r`GOs@x0WPgj zm#y?GNZUDzzbiH1RSkN0qg#p*zBf?S|Gks^Br3SwY`OB7{&)xe8M>AG3UzP7Ollsj zJq%`RS^U52S zar0zRbGKldH8fg+unJ1Bl1zAA4${C2V(9m4cwSP&TJvHTJ9DH#5+zgU7Yl=$%)od?83IP8{kt~uw2esJD_l9&uN<(@cvFnR|spU zUIbhJCyk%no9Yj?h<;RY7F=9sO_9mqYL>sD3>{)Q@X84LOxie>pJi|44eR7bq^ub;hQ!u+7 z>&mFUA#^Z=n7y)3ZV02g>_Z(rAzo6c+(Q|A(fiP%@*_CozTp3PtwmpuRWtaz52*Hc zACUE5$f5o&v!Vt@&UTI-5`WM8k}&+ozE+CDl;jXU3QtmL>8SbgyXtUA6y=(5TO>h< zAYplad!B5VGs(m_3F;^67o-ja?I!@AWOszl+BGM=(+$rH&b3jmA8()h0HUk*nt8kW zKxGgM_$3@GhUtuQ<-Jp=b)86}GihFM05afx!7qX%$WumyBZ3FpkWhFGE$(OyYBL%t=2uqf@UGTM-jv9hHA z%u*Pt-$A#ze2#o4!#x+0*oPOY126QzV*{mOG}cTYi>&?i>>8;C zZNZEbp`yof-z^GpOZ+4s=tOFnoi;LRp(lHS%)1t&fhvJGN>ri+GE z0Mwn#I3g_f-i^&p%J91bJCl#r5(IOep;EmsV*NO1@}IJ}QN#{bB&~e@=1%JKEvDPJ;vf|8g4RKb)qhV}&e$!qb#xnssGqsoB>e znzg{JqV`S@NF-h$AFdhx=+l^CTY7yRye^^drz}z+ayNj#7l(9Xjb8CPiuO;A*Y%dy zOb^q;!|DsZ{T&E4??wA|KhU+h)9M({)=T}#en1#&5Zj@|Y0)nX%`YR~LxaI4b{q_v zrdLuSBY9&}>gedor37}v^1j3j<~mH)t_P8agc_mHjan?oP*XkAgTKf2puPg&kqt~# zhLeP9OGZ@cO-C8c0EFuEOayPpxMx8{T;C<*3|Cs z;-VKucn4`2E8G(dHrQgG0l_ z09WP>+@Jeu=&AK={S9hhj)^iYEh2CEr!ZTPqUN*4#YKF&bi91gDZxc-g(vG#pq=uU zh0TRlUk-L_4S9oEd!jgTTfo6-xX9ml|Ex9w{|LZ+0oEe)O73V4($@UmQLEp1#?0P1 zWNO!&946C{f;j_b`xnuihTp-+bhw0(m;xTEaB6@sxY z0CUC6je=acs8B;gHbF2}q||6f#w!hx@HJV;C@cQ-p@%RIZ{D%8YIKb6?+~a(& zGCBmvf0k)Ejd*hHT1 z`+C$GWEhJ#bTlG=1N<{6K$ z79b`Xq%o*Ke||M&d^(8uAQGBGx(6X~QU*Jdfm78=$%@V;&>4Bgku?uXlIXUnc1=>wAIwJjlx91%yhD+}3qRu6q!~ z@7@XGyWNA;RgY!!6d1{JKRWSt2X6DQ??S(?8KwDrBlNR<+OyH#sXfHirF64Dl>Ocs z$?J0I4x94ws7~U0Id|tvgQsr<=V@Pr9=bEN{VYfS#=t+ArG>vfxej}KfbjDj9KPE3 z(81f9I_Ay2FOTIrzGwLUlpY&%@g9UOx|N`#9xdx`$A`Z?wb&V%eRIP1%iW(3iax@` zg?E^3!t<6L({sN~(`5&>Te-?!bMu+i`#`&!?R_FW~7W=bwr?>TDhuddb_+50! z#_21i1LPdgiO?zUT;Ld18pTb^BV0S@k}zAMhThZ8>^z?Z{&8DYNBe$W*hlLf{XnJz zv$qq_i7^bKWXxd5 zFAE2WP0tQcY;z{_GCI>o4x3yU#L%5w8064XI?E4w-#XR%n`xIsScX<^PnmU=E(&GQ zpSqukbf{NGkQUtM^P@!R{-DTos!ukhGDIGCsux6=>Qpa{Q0i2#j9}?x*G9E;vJ0UD z>Qpa^$fFbLq<7b$#i`#Q)AVD(Cqg26=2R6i* z;u!Q-YEwTrlT4R-JecFRCyv900ACh02s7enLbanY?F-oDtzioUTt4pd`Nzz>;gkky+N!nGkjGht6XIf}%FjGI6*@L1?lWs@V8&fS}pPyRRzbpT|r zC{C}{mKdU91DAQ#4MLckm;@*`ToQ(B_$d}z>LWbOA9#$-wkh7BgM^VB1)vB)oIqGU zh@-!KJdE5DlgjjbjC4qFP-NqhXxGxfAD*K!@Cvz3-4bd(({Eq6;Gh&y`KwA_{DQ`# zo;P|frs7wcHC$LH-KIY@#}mjRK&p_?JJ(>32h$oNJb4?tA_;d-pe<4;j%&{jN!p`h zb66`#hN0eukWDBuZu$V6SW!=ksn-?^SYr3j_cL`{9c~H(U0WLuZFV`NO*(Ujs^(ip zz+NZ^Ht>G9$=X--07ZsQ^K>$nnp$0X2Zar+$5nuPK1~9u15N@JB3Py;2n{Kg*5D-< z^`cV~T97P8qI;I+VS|h-2IcRD^@}r6M|JKF3IuXAlRY9eKB6)(RV6WQtZ;Q}o2^9g zOq-~~23MwB5;^W3v*X$QHk<^CBcn5HMlJEmU{41ETTvz!S3|a#mZ#%ePb%riB_i=7 zxWt7D*t(*<<`L3D;x((tqZDdcAR2NjYzs|1S5Frbvny*&J2&+*DLej2ep3PS z(^12th(gRYiu@D)?nDbEAsWQQj79~?&=?jgQ-Sf=m0^%rdQ)tNRlG}?Ts}l~yO}F< z3j<#GtJZLq!RIMVh>T@VO!o)sHo0i==BxpGsWljr%+q}PW>Pdetqr^=*3ixH>G!2G%a80R@V| z$)a!$NwMtWxv@+fmZW1TY)g(pt7;vKf8dGji4u4lJ zaujoNu{}e_I)DkfbZcqJ1ouVyKabnBZFVcpsU~9DSX`u=yemGD{g|P znuC>e$p{zEd~K;yC*6lCTm#!knjC_WWB0g~PC*+oH2OS<@`eM0i5OfDge}j?K!{xj zuWgb~Sox-Xefdp8bw(jz5l81k*^%kUkvS4;5-Ogog+tf;kzs{#X7COA|0XZZwy{A4 zfPSGrEPG7fpQzuWnOQCEfIp#i5oTLDquVBE#4w+{y0-nNw;_OfgS)V=p#!o6amuD1 z#f+Q8DeHI@)ICr6MB%&{JCS<--xS^9U)T9R@!p-YeU9*5-&&UdK;h2TE zV^{~&h;o1(Bqrg(;#h@3*383X4eJP6>7>B+;m*oDZ!*AT`v^kTsAj}4R`C|>F|X(P z=er0oHj(d`E79aI4scie*;CB97qUH6p8{aAqlVuQSEiwSXR-dl;jEY&2W9qCgb}V% zor0=Ote7yOFb=uwy9m1j5qJ&(gtRETEW;wrQaB>-0pxq^LfX`{gfW?2<_bzzg&Zj= zu%YeB-f`krguA3=_08YS?;=x|6pp8{Zo`8r8Ic^D2x$>FUc<+s?Sk49F;(O2pEM}t zpT##;pJ1PmJFLUYBowdE%l4Q~ZFZ`ak@kx4nggvGsVqgR_pAMu)r4_GNm}czB$HVA5;!Y^SQdR_r>zrXo780H;dH0lU#5!e#jtt$yc)%0r5YG$QdWva6Y;xxNpKBOscS| zPH1e(Z7p>V^(f_{jRn5fI*kc%p4z5xw&-}ANRA&)TQUb=tuu{9zXcU11n;o{wZ()| z_T%}mq~1C34+-O(W3?~UtmY5o#*u;DVyGX(kiYpUkzbNvrzVV(w#Xi+byZaQtYi8p zdPB^RG)U^Gt4y&5qv!Z>A1F*3OVuw++y1y+Uwl@r72A>~X%dqYN4J$OjX4gri7Ufc z?%`xht(SU1dT~`S@AN?x3=gF7q>COq6G}JsE6oIjPR9&8GaZ@v9UEp?$z~o&+s^G% z;IIhU)-Dlp3^0)Kl%E9E>6`}^H3F;HkTLe*P; z?ngUGX<(jjE5@2yB^>xtfC4qeNUtlRs;a4$xK|{7I)OAeXDrLnkbpRk>umr}uL(v{ zD5y@lP>ATykTE_k^)I)B84M

lTsdod`N4YNOd~zpxRbR=SJH8j3v}!D-3xf!lHN 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�av&K3Pp44!jSERtR4zH4if z5i;-Tk`>@C(~NQTR2JY`7F|fkGH~f#k-LZ{OgHdw3POMAqvW;vmiIATmbQe{aX#g> zcQTHc2V*6sVuuq1jQ4fD_Z>IKM05bTbuuYhNB4cLQ2&LsrO=o2TS57K$+~>T8bt0S z6l^|Ir@N%UXkdhHEi^H?yR-~wE!lB-LL4OB0)s3)u?<(>yVGjLlXt$DI|(~j&M@^; z?a$PG;6&HaY?NnuMpH_(X&O;5Ibnae$=F>%b2IpiIi!S8wh9Yh5fiF?YtR^WaIBH) zg@E6vPJrmGGhp0JlMJ!0sUFfcR(fM~=c~yei zck~g+;@WVzJ9Qkq%prEX=3(3H(MaZK2!hFkEY2rQyqO`@S>o~5NU?SLcuRFD$@&-6 zvK2euJn5ln5qc#sqO~G%N4;Y3<;sQD^{5u#>wizU$X`v1|KJ!+`9Y1b{|5>8pExlE zTO$KAV>5$)nDBq4*(?Q(e;Dsqa5I&bN@b}l+M;5MDhA2-IGS`?4DH8CEc-Ypc6rZ3uW6^`5>0i3R@%bJ7ZJ`Ub5a#~pQU;mt9IcB zC#y!EksIez#E&DbVDphH@?Z;YtHlsI7TK!=^uoZ&$4cbWYFmy?QeEc634q{2i%9z{ zMdToY!>OSK){Vv93)xp{yH+zTGZR(z4N;82IJFFX9~sfp_U28a(l&+VHZkbiXdKQm z7N--FFG(fZry;*wH%s)ib^lR~@t6zkz!8Wu*Lb{s$=PfY(|wGt4dORr(?BXN6vd4v zT^%tQ>W`KHO}03cP62nF&n&t3es&Dy8SZ>*)rfKVSUVC})<_o)F8Xu*xTjX(7+#oV z(?GG=pPi+Oh~x>%y!HY&o}I-Jmg+fYakNBEAW@yD_pnmDSlLMGkW-*j`aqOepc98c zVA?XoDg=ed_f1dY$E!jP1g|NpUBm@SJ@EHNtAI(;SF?v{Vw$O&gv`gUe>aP>CiG0; zpE@l42S~{DpO}S_v7D2UlaY#zy@k>Lgp6k?S*c(t!F#j(Vkwk}$ctUp$C5=rOh@(v z6LdTEu%vF3t!&m}Nip1*vfjKveT;d5yI}W@cK~kWF7a+Sexdx>y{_ePaY52nw;!Z+ zIG*;pnq+h^asK`|9%BSBXw4C%B~g?lp-U2k&LN`P3OjgF44)}X71Kv`5Su*sK+x0W!yXa@R5?;l+;7ot6;9s zWoy>DISZCcqVb=YL*bZR#(B=OwNcj0OF=Gl9)h+Sbd*eMK9!%p1l=GMsA#9 zCpPw|G+p0M*U=)z@LEEQk1#k{j=xH`|BbQH8Lp;cd&L`~4tr;Sdbty5ldFvRc(#GH z+o4W4TD9ypKL1^#cu;Ml02|V-s$hGU%U6G42_nMs#~AIT{t{y4(IT5H>w6iVdB)LF z{~hiQsaY&v`?f2-Vfv)bDaK<)?Ll*ahLVkktNz^DG%2d-3dj zMt+b}JkR2@l`d#*6J?>{Cu&MsFDg2B#Uqtp^K5y0$ZyP@ehU<+%$tMMC=!^-b{a$B z!L)WE1#9iYnBDP)3-1!l=NZakEDF!tm{O9aFPQ(}2;0-@EWz_>O)i?q(p^#c4ITiO zvQA&O>%3asH9*^wYgu>o*-)7~aijGVw6c2b_4#-3xy9VaxtOwinvkO8NpokXH%MR_ zoELDMmW=grUQof8Cy#RK|^e)W$x+S)L@0iWo?s$ zqrMU(&1E?_q73OEG=)s$p zp`D=-+OPH85TpmkWVt_A0w;x%T6diyku2&J1d7>*tb`4k`ZmZxke~=(^h#FgrW+FX9Ao@0^$*iZ?%vj*XW+U!V9^8VC4?h( zoxgK=dPDj+y^!;GLBBx$J*w#Z;kP>ei6-(t3FkinLH<-m|BNdCMqum|H6;$j4^{NbNRJSuO-u`(K$(;@BC073dU4r@jjOdD~!SkAx7&03?Q^o4&A1gA0~+4il% zDgy&4@jhriiiNm>Ay9a&y)4m=fTD4D@l1wZukrJtUbO)N!*6!LxKw79pZ9dD(0w$1 zyu$)6AR4`RD!vEK^Z(FJ`3L*+$*;uP`Jb=m^b?c0{)2e@zbZ^4!+*5I|2*aSuee;K zupy1akNy>Gt)VF_rzm&}a3)k1f?^R03fBi(T`q^OnJ#HE;_L|blc@t+{ISG3Z+>}_ z4e(BYf?JlKNHZ{g@4bI5{3}tb4to7|ntUtWEAH5`->_=gDl@2#b_-sY$TEFT$G3Kr zUw|6q8|{XCTZRS1fy?JB7DHT>9A{fS!6NQ=x)o|7tU}x%S>TKd&1RS&?4{hA#KgZ0 zVTw{lvS?00W5MWj#-s=yi2{TjjE373>W z6APm0N|J@9DjJcUx`eLzs*v25s|F4hF)2^&yC&DDrQXG;aKD$39<)}9^@lN1E{kuWRJh@pg!1c-E)H+kZ9PN->9ru8MqTVZb-#5Tr z``0D%GM+6MVx9J|xCGcL?w-*${x9sNU3TTDO+u@v;gvIlLg0g@3Dldfv`Gcav+No> zQT5R{8aqC(Eg3jBYaC*&3*tvHvSb2PZz_`92ISa3YMY~Fhb0}<1qgvoOlUXH`O;#@8+3Bk2hcfG08n_bTr zpIP_sSq@iQUysiC95=6iQ?yJe1-XsbE>@ zbSJ%YA5+X~m)gGiq8)-UB!}GQR4HMpyaDMdPFR;)Xv9_;tO<*(3|KyjZKb3Y572$s zi?HW89m8+f$WY%Z{$LaLTbxQtf)FWetpByKg zin!Dt6(N01Ez->BWIJ|pb0?(b_o6}gKvc0&*uszo!DHo307l|4KWbNKOqN<1hL;X2 z1YcPjmJ%)&n_=2%okna#f2O663kyo#kb0u&8a@Xq`<4$19819p4J`#qdq}rHxFskX z^P?ObY)lRl+b-Y0Z{-tvx-Np%5i=lYZ^Ui@oev~CY+6t6v5A;Cp> zfP!tX+T%?{0v(W|Xs}4LL`h`eDRiJz1M)_L4A~&|+%2xWxJ1+@Npn}I#tOY>Ob;t1 zj6lesJxqh)rN0`bWoL^a!AAZBJw}#CB``vbQL~pD2_0^A^qhgnuh_Y57d@PUqjcOuHnT-Oz#^v@1<#P&L7@grkO+*1Fi>Vh`B=1{fziyC zUMK#*Qe!ajgewX(MTISGVr2zlWdkKdnQ*Vu)0geInN4ngV0qQeY1jT(}tPj6Z>XhD^qvdFlFlC#keS2^`{M9U* zk{#VDwT|Q#c~!#EG!x__P5KGNfvYp$_mbYfSwi8paWSo3{=|OP6FSj6R{}XaHoTd3 zGDg?6c6M#R7USX>uBIL_eR+Yl*7l6DKS+-F)h%`EV%>_{vYs`Cm({up3Zd7(#T(}0 zB?$Z9r%4NBmIY})d0y>D?cx8fllH%lxK#Aa9L=mv{xu5!SNP6S+EVz@N&DV{uNPD8 z21KEz-da}2M{a%^SeCE#Yeo(CLllQuj+y$4*t~TyJY}u%JG0CNmhr9d!13oP_qydb z0j^}rr*#_U9lzb2LIe~Xk|ue+|CoN=+F^gb%JKR9p!7w{9hr+lfS?tzL7g8eq1x2)&SKwqnRkZOrm7 zPh#?ltB9!9kdM+I3sEoESbogsFz}$6No=*ILVsiO1IKPFz@TwKM@+IUHbz-o6AH0# zTTg1tyll-}t#zyCHc<`c?6)m&aoNoFmD|C_F`b-t2woN&*f*<5J(#XcW^CIvJx^yZ zVGm9Pi_mkKBV#Q6#<<|fTC9a!8W;n)Qf^~-czU{2whN10=sqa{e;}$K85IZu7dRH) zty*U_Ct!xgY0}r3)#O3HcsS+zTSL|)K_0}-YHo>NV~n{CU!&fKGr?dg(x6U zGs|wQn&>`4DVOLv{DhsQ!{C|KBFap*$>=!anrwmm+Um4HhDGExUT>)arAg{Cnyb{Z z)QE%4Qb2u!j}c-Qyf^zdZA2;ojfwFHxs!lgyGWA1qMlNy=7Qo-e#~y@PSQd4#Z79M z=OSe;0Eht@+mNkxz$#ev1~W%10~KQ>&mh`8I1SBZ*HGNfD?40HnMomXTj@%%Q8v`S zlxb*s>#&^@y+oX;%|SUXZm2VG#b~pap584WF-oO6Y1IqKW{fS+1jF6D)QW88o_X>~ zGO6`Iq8L2`6mdH_Z?9GI8sT&~q1oYy_dY%jNg3$-oJAJF%0A-`2O(p7>{9%7g)u)# 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

r>I?Yi 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

!TTop>N&YN>ZlFuA>w*G%yk>!b7da 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∾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 GdcW$j+wR5eCsK|=U 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 z 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

UEH4Htjgd-gl3aM}e`iFw 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 C&j5%3 literal 0 HcmV?d00001 diff --git a/lib/httpmime-4.1.2.jar b/lib/httpmime-4.1.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..eea3b3ff1761f4683a527726887a193ffbf7af49 GIT binary patch literal 26890 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 literal 0 HcmV?d00001 diff --git a/lib/json_simple-1.1.jar b/lib/json_simple-1.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..f395f41471a796876278a6c5667ded4632546893 GIT binary patch literal 16046 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
JN5nX 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
Tj1!v+$xtx*dI+GHLe_`0!x-QO(~;6cfZ-l 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%0AKWfE$qTJhrf;L-7QRzS1a7;?kZ7Am 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

y3E z293 ztf@XDj!67oeWb{A{xj#CC``F#BA9MS=ZQP`IvDO)4qXoWVoNNSFz<(Dl6K^O{vYv% BkMRHi literal 0 HcmV?d00001 diff --git a/raven-java-0.1.jar b/raven-java-0.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..f9a455b12f933700aa7f9607b40a0db8432fab27 GIT binary patch literal 1170708 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 aYdKad_uWclAq zAs-U^gab}i#VSk$>HWrhe@;k-h4aR)9=){EOF6DIGjC}oLEP8$WfmqwyTy{Hzj(8qqjoPwh~`ITCD1}=_()nN-b zl2yVoS4J6d7i$tVA|y5#*~E9mpe)I)#A7udlv?)>i)e?pJ*=pPcM@C5h~j3^$6KPe zMTR}!cnOkOWL6v}N-o7$7;<`nyg-cqRGdG1{^<$QqP~dKbb$9wWd3Z<+W}Rn=U>uQ z?9x@puaVe?EjCarq3*vO9TC>4h?~aV45d0Kj2Ncy46oLUTB-Q}`S zzRiMQ=MTXMb4|_AK-6``YEZfO&4LxVjWU_ZUPlcnjDxg!)R_42BU^ zCxXck{d#l1Fxn9PpxV(1ZwOZiP}plLduD1#kOk2r$2$C45)-brRt0*-dq&&Uwl3&f z;XM3%jZ+v%bI=iO=1i-`{Dw?t@g|xkeQK=Rd@Idcj5G%uFfjNuj|Cp;*_CxRqFH7` zv!dSi#I%(Cp`k|S;;2Rvjn!%NrO4gX&c8u2SDg~UZp-cUlfBQW1{xRFrTZ(>3`99i ziB7vDr^ShCKw4f_NnH&DPh{pVtY4(0^_WHaXA$qybo` zk+c(?6-WfsuWTHQsE`-la6FDJ(OA}1X_WF;sD9O)ETs=f18fN1>W`b@ipJx#oERLY zjzrE&6%V+@OQ*7zh$rfo!}bog(NosgSh$g-^%_VbU7BJu=k^MVwUzr&)lkfnS91=q zYAo8zw?BuSAB_0*KhA8IZV7Bp*h4{r3gKk9vL`!~(+&HQRu60UFgRrbDl>J+#Na7Qe~s@qM}I@%XSAxZ%@X?!s6zL zN=U`VmgO9@M$B4?YCWQgANRB4gA>xs+Bvj_aD;M#JAh~!6qxkJz+Ko2$V~be86!z0 z>{-*hhbg;G798M5N>^0MnBO@WlyjomT~|}1XE4?;=Q?NuK3WV#!gdPLs)pTXrY*(4 zzFMCT!^=oh#p&dyMq}D_-WxS%i2&%O%Tp|bH4UXNTvpaGvsiHre=1TFu3GEFt2)YB zmOv;%wAZzut!wI-=&FEt%Cx0lXNxAso#T!|W-Q`(av zw$DaIyfmsI(kagF;5HvC}+`2HqV)%(}C;25w_>@ zf7Z;7>NVl>BVXzkC0+U5U{d^E@~WF*vkxdm_qsvEoaW^>(o%x-`$Y0*Zn9ubMDPr? zzwi>y&SiOmmo%SMfiy!hz3RxO%UG|2FzZGeR3o24es8AXGgvbuWWd# zR-MNgor@HyGK5dbfK7}kcZ-6^HisSk2ycx{(pW7!$ZldIh!N(|8;)micOT1$O=<^E z<&18(Yav;Sk)PJcNMV}((<@|cy?$VJq4qx4$~TL< z;{ti9w~waVgj~H(W{nGz{HTYo!+HAtlMc#$uVr=?qq0m8qsN8CH{-ioM+clGph{)n zwW{dcWayS;m?WzP$z=;Z$o6%iHFk-wX~8Yy!4ZI(IY9fmx)cKK%qoS^8H(I0d}*@7 z!07Hz{aL7I{mVcVBDEZKMEoA4dx+jeF!t=0OO*Wj=d@!RZ{yv$5LM6j*V#Y?5qeSh zcSGjgbfx6IVnJj=C5oI97T+7luizloj8Fz$^f9raG2vWGLg7!-0dbwx0lt;8wAw04G0nziJw z%9x6^Ub#AwW?jLJxn6sEhJVP&@qAx#vYsb8eg=LmsB~l9MZmww^Jx1!oy9ei^~T5) z^YwPD7w7;700g974zSaT-xXtb7wy;px>6Gka6mBt3X`G^3U_WQllwdrU36pq;Zo|# zH-7#k03r40J)tlMjrdCc7~(H=B%vWdCAqqay-FV$43CQ3GYs_v!k+)G|87{kt*T7L zq{|4^l7i0eAorKs}=Gu>dZ zwJ;^cDafsA594=fUG&+VyaDES&rwVd>rHUS0(Lg?Y@`9G(W%M81T|yIDWnmp0@^SO z68rX`jAcaSstV>h7YHZ={9$hziqt3({>vxWkuIDUsbkoV&6=h19&(PTk6p>ZyUKT^ zI2zK}V{&O+DzujEyGbV_vswp1oVRhd4vN|JV??VSmWwb!5GI0m3 z0o$qjkqsK}j84cAraS?g!B1SfEz35058muLmm&8RWsi(#RQ~gDoHqZ3^mWe?ct)Bn zmt}Ulx0h(pl_7O|NZvPnQb=ZSe?TVFQvcvHVPu#nl9gWcJg4jHfUaQZ%ldvBFIET? z%tsE~8`um;x7X=%Hu*FT$Tr~#3ua#{a+fP}P=4R%uS`z>M?6M!}yRedL}! zurbgLzn~81?(01NZGY!UrQb%4oKX~lc*EGmrxGTCd4^z|5*dm87@eZ=0P*7hW1AM_ z(Hqa$JGM{oe<4KPT9*7G@Q)uc-&y{Dx}ZVw3&BANE{3uJ8hTg7YyNM^Aiod$pAVY$pATBe+0nw*?EhOCtLQi_h@$XVNvT?+WMb|KE}-^{?Zy0l6`){d zMv(rYG-7HfhYlodT8AcrPplTta|wSEvr}c~GbY5Pq*%;!7&rYO=HjWQ${3w5eH-9< zz1eY`?RAy(KGXB7TLcqLenwyfO#{0x1(Q~0rvg%YdTwla-i0Kj;+Zr=L_49|j)mHy zbz0uqZRu=^Dv#Z=$6C{HCNiwsC{DWx35vg3FPzFZqy38ME39vva0HtG`y~e%{H5 z9GK|Spm;qQ2O>r?jZtLw3_STfQug%Ix$K?1?5wf}Yn)z>6Ww8!NJ_j1fse6tAWl&! zaBfTQ&KW4NbMzgtL;d+yks~Y{EoU{E^ zvmFt|adPcj|9#XP$Q(2?if(8+>_$el6|}68iuCXGSoel6D)uhq#S@m@hd?sVivGWC z5+E!Q*nExIID$>l8n2WmKTt9UiK7XFxHTy1OYnb@-S(xbMRmqbL8;AWWh3j?hdjK; zR#KsinNO*F{-_|YiF>8#^bveD2R(xLVl)VF42gS&U}|Laj+7%8%-1eL_efUz5_~mi z&uzq`1a0~_$5kff?&bwch|w1%#^4<)gRth;>tL?B|KndDFiC}7M%@|w1TCu!ZFw@P zhDzB(^azIh_KaF)nj1}h6^FE~;15=37c3zcgI`eS=$5{yziZ2?Y?CsIkr~yfL0F@V zuT-Y^*-C4bScPMVCZ^!Ef{_VbhcT`6iI|MI_FHnWsg)Lf*jeH4L6+F*kGQlzFrH#A zIXD7BEjr{B1quB4wP?iMFyV6bP}(oyImHo67x2|TL2k^f%G2BO3+wgYLwt^9IYlIW zdIBoVBZ`@MbHLOl2+oeb z{%cK^e9)<4?6;pw{zp*f{AXWRG_kfbF#dLTc}F{Y6GvwcAv;@B3o{o-182K`Gh$ha zW0JrE2tG-Jk;A19+fm_rK%8iZ1o^o;7V=@x?ia+DD+ZZxSz$Y79tZ+`@HYj6XAHLJ zBHYd{YJa?HP+w19J`p7<@pA+)3*d$s#q}G3ZP4B5clGD{Q1ZG6flnjEkx;p9yww(k zUj*Czo|en6^5$49^3Qc}vZ;CugbTl=L${Rk!kZz+hKiH~E!EUgxA)8Buh?j%=CLCb zd*gWJQbg=2BkO&cQp0JLvuZ!P){;QmMKH`26!N}=Zl}Y2BWl-%^VI-q{jeqn*(hE8 zZht&w+OnT~EW+!H&%;%!p_?y_2S5U^YPZbO*Nw*(}FQRDJ&4Md21h6NuQ@|8m8)7 z2EHba94Fjz55bA2uVOnz&PYXF>n=?}V#T14Px_eO^i3xX_(s_B(0pXNYu0*zE}dGQz)X0zG{NsS&Uv_R?_%D_s)5`tW-6wg|{0*KI8TQhIM?wY5P z@!XbKXpHij=VEBAg{@lPvhmFMCHw0`)Nl%DV&+EZ`gAJ8&a^v?O|J8)C&exwlk8Rx z2d-FIx?=QXwEs`r#c3-Z2aa6tvIE{7CJ+YnU253?^^x=Beir3ckrA0A<`&Jq*BQBQ zO)#0s`WwjK4@_SCc=)r$m)Ovp`C9{Sng%mvZ{Yzk7VmE{2p8-jGG%YYVYyTGV7ZFt z$WCunF=XD7-TV4yP>+}VU^5qQ$Qj7d6c+B9eX8g3;2H~ef#PKqZ=C@)W^X0xUs3}t zCXZa4s5d%*ZKYcu&_5DX^0V|iFQ;%+W+Y^n(UB=v>1XIS^A_uT1^BF|hixRh^d?j4 zI2aQ$Zjm6}`^6&3_uk`)zMyWTOsc%w!kNVoB@}}e>2=;CmYypU#@2d$SB#0e3i#ri zqn%jNYkmpJ@?J|0S-Ba$EG^n3zBMWJfJJ!rY98g$==IN1mh8GAEIJF5YADw2WmKcS z!UQ#YXDj+VWSltUJ=Y~iZP2$2ymd@v^QF55Ca5Q61gcVsgDU2#ORMTi?X0O1NCF4) z){g$-aZ4zIn53|L`!{YrD^RrKcvo6`?vx`H39%SC_NIF+E7nWOW zgVPwhAQMcKM%Qd(?bzt4Iqd*hBEVA5Vc|KL>cb|9Wd%htY-{eW7|T`>NeFS)*{Yok!}okjehl^8CdS$ zwtyabd*m8)cZ6eiU0Wq*DA%YJi5Z#A@EK+MS?9arbI@XwX>|-TlkXVXV90WlNiLp2-)3s|gGD~uu!pVE<;j$8Kr8sF~)i%7k;<>@EXQ#$_Kiq2O z@WQw^sv8Hzds(_3nc=GIDDc`us{EiI@+j@HCu}Jj>exT3ZkfthkwDF3jp48|nfp## z2}yKgjb6`S>1*0t+VB(jOZk=#_-puAKGI5e34_SF1U7C?{&5j^WzG-RNXQvHLz_fx zSd)T;ZZ<3yc^(_^09W2KJFg5a%BG%uEo=_E!z|;PV_Ei7e>+gzt}?0Wt~D%fth5<$ z*W{?-r;j^c$F!Am8B@6+lT+8bWP-dVg?^OA&ElEF9NN>&NZN2+=uYwBC5st}oM@K6 zQpj6f)f;$!nAnb~`RwjpGLpCH(8iV?5o)qYC#14UatA5**B5=U(y63C%*p)5qk*X! zVa$wTcvrL)@X6OIuR)%Y=6K`#hLqXYn^s*It@#vc8DC_6in+KHA1buB7eRQ^%pbno z?@qDp+7wW_omU<*g+&p2zbXG&ng0zSru+?~OumWm(#yI{Pc2rjmTpI$enM)w`g9(m zr}_8x4L(#Lp}Ief6o(mmm3ttL1{2DYfRFOaUE!uHrooGy659%isdduMN*KqAHEK)*QYP z8Pq#;mP7O!Nk-7)r%_9;i^+fXdXNExixIKRT$>UY?yc+AEG+mK5)?F=*D)-+}1*rlDIG!M_*mDcd|l=i8|K`bU#lS z9Tiu+Pu1$A50GKxtMl&w)7p2VD`>O9pb@$q95ZR;4atfyk8oP+{*9cOYuXmiSYU^cbr$PNz!F7~sk@(-p#WrO_8yav zVATqJ&5y+g;AEJNmt_>pn1MVqE@cU`_)NK@&Y8K_%R$=-)^@3*bTf?Xi3y&B+hbTN z6s*K0XsXAM!xr2Qt;I$XFnzCqYQGMJj*BcLZt#M3xjAxGn$@)FOSW@(i#5e!4 zaR`-=aDSF|HuMFQJc-7+w$p6aMOeAw8{^m&_tPjCjk@iYBN=}P)kj|Ztr(OquhWtyO)B&OpyxSRocN=)5g_&T+f;zc~F(wg`nI&%Rk%d z@lzy5vCukgHDt}Z6cWJLMr4hrW|1g%JV#3L*h>KxD;AV^U>#<|%YlHCgL0#a-*~(Fz#k_-tEeh??c;aLO2Q?n%Cuc*P>c+(cB`>shxfX#RA}@(Z zQr>2Hg_O!0m*8i~TK>i*17!5}kPnd?l~t;ie8lpxsPUsj96iNpQNik9kSkILF(2O} zIX!V77u`;<{t~NwpyyVfz!G7(nTfLGO6=FTVDYRSj8qr@sc|KUt7MILLmERXTQ{v& zw82k79!s(3Ae|Y>I}57gIf=?SWO3NSj^M8m%N4`Z#DpiYDvQ#zlQ`dm)D*3B0UeOh zTn#+F%WmY&%q77qTx`d5sQ&mb>1>nBq4KovgtYrVb{9GRIVlx!|8Bn9ezzDs6#tc; z8h=~9iNL@2_x_ubR@Rb3RzUcawOg--7Ez9kNFlT=>-Yb?CLRi=5E>=YE9x`1@U%j! z;mWpdQy}*V_Z2`#N*G4U`??duIBhLOLGa9vXF5HZ_AMrsRn~ z*KWuK)6dyIzA=L`jKfd-Rs#tbh+*at_-7q0LKPd2->v`ovh?Ygq)uA1mevY`)_hB0 z+mmBJE|OHT5$FU;Ru-kgYq*vQ(5e4n>G9NN{auU6s_NuGm3YTsnz37EU9K$)2&$Z? z;iFKlx7ga#h|F@c%43*Gg;;h;X8GNzO;w(MRIDv^$8?QV6EJvP9@Kn2k~$4WX+D~| za{ce_L)MRQLwvu zT))C8K}lmw98jii&DG<(hEVb7a3Iqh2!*Io3+C~YDS5x{#+70&Ngm7<8S`NK)? z)XR02chfoz)G|@0;7eUPNBLoh=x4$GeCr9t6L@Bga!4dD!M;it41#wkVmrl`kcJ@U zJ#Fy_(Kdw+tWwzo>vOu%cPLD_g`up_NhZw2j9~T)%Q#v$1T>2)Ax?#DFCfBkJ=qXO zA`lEpasCMGAm10rfvDLx1(*c0BFy**9Y~>3B68|IB80hx8Ml-85YlZQmKBl%dk<5W zt=Lhs<=13q@`o$Tim}g=~<;62C-q;+L(P7-|yq( zxQH+wcOToh8@HUwXGM!Q($#5d<9P{ZkO;69iIVbXDFk1W{-0AuNK(GxUMhXh?17U75pA zRfq)6>U~iNJE{T-ST>f1n)s9*b#ER9YH1>ss`&mqSv(Jn~6Yhc2h7>EuCVrkHS&hvG1 znfqE!_1^8{ISSYzZa_{RdzStkD#~6QAvVPP`cCJTJ?C{}Er*2`B{6LrbQAzfzX3U- z0x=J)6aE^%0r#a|24Rq=>sTAu#w<}qjzSpj5F#KNuEWjWgI?o|Hai~J0P|m*ewd~( z>ZvUD&|R3;z)mNCl@8bv>Xs}FmYv>1%M@)s>WJ9^+_;cUkJF$A)%0*&SPbBnzIGT2 zRZ;dL0;^sz*?~?!F3ZdCI09hk@zSba1VjnEr?A`Pt8zgNKX)TtmV4jEcTWO|fQ8cb z)`nmo1K-dpdz(_naDnl5glun5A<JWWL>tpD~Z;&}+W?NP{;9K8O5X@-VTn zk!pEDa|X$cCF9{dUHeq^{(7t!ImEpV2Z%18w_xtok5c#;*V+3WFrFIFWxzfv@0hk5p2a=n>8%j~@9{S9hgX=9TMpSh z=*+!Vc)h!q_i$c#^j#~M9W36o?Ym3RN0!Dvb3!$e(dFbgCCmul(*T6@ouMf}&q6;L z2xCzI;i1SUGzbKq9IC>tb7Hf1o$*+It8>fh(n|ksjxbnKk%fFI>fpY0WRutG$hChJ z8mbvd;Z{Y^1HJq(Avw66B#a(}W@c%2sovQkOy47spp%4GkU0@cL%dlAJmB{l#FxC5 z{Vt~xuZd#|CvDco+s=dAHp27O55!A5&w+R~i+_Tq-BEk3WE{5>-$5L%8~lQ;h)f=l zp(YaHhyqORxal^=05i(jlM&2&Bhe+qgDk5xLai->G(u^9e^5eg5>^4#fUrEC{rVRa zlHFAQX#0jigMhO<(>66VJN7YkeOkW0 z=2IB(*?8E=T_}7O1)jitkp%Y(|;wxhO+|0uy!?67uUC z3JeHlgv*H$u6!Gsz*Y!^p<-HG&~zW;%LtEYlNMp!$x*JJ%HEm-!4goWonBY)zzjvJ zUm5jw*?9!!-Lq*mZ&nZs@SLdy4Zf-??{VY079nm7pDUts`yv0=Us@r-a9KS@7JiiJ zZaRyoRtSo^an$p`&twI}Txk&(Z8uV2)`;wP$5CW?t1VImfFJ|n*gm=(YS(jh^%LrR zB5|q4{8Et%KJ^qWl-{<>`05K$J?JL&;$Cik|o6{t|3ksF^+F$ zmROKT|50_QkjrcVL_rc%NYf~PN}NT!AZ@m19M2l4sHz)ITk(5Ts4${kC0Emc4fZgv zgGyxO(a*m|$3;P2TYFJQO+e$;ZJNi)Ga;MH2|0eV2yZ&G?Qiz|N7fg|UpL&e<1gn6 z3&#bhrJaJ26T7YlH(kv8IR(tWyLbD%oH5ad3&<}Ig)!OrB_4MqbUv$MC9@3K?>s$s z@Z5i6xc>sOy-0JS@9Adl4k}(AGE=hu&b2+r@xGe9V#;-271_C;W;og3g5o7Dw0J+* zcsju8)7@^kJuhq@wp~x8K-%g>B6AA!fd;|Xf+ca(Cz z!wEU}2Q1m|{$yX%3CkXLQrYj|bZ=nGcNN~REMIq6xDj6u^S?Y!TOuQuCeqh20M;8g z7}3nbL}6RQ)6SI1CprVU(%>TCs_PDWwsuim0pYlm6_mal!0s_3Oz%fgpn<@X%>H=XB8>z$%p!l z%Vn&JU0r@2n%@OvRRT5z83l;XVw%GD4|Mv|C-q!4V&c zhuokRmvQgpsR#el_Etayf#g}l)efCCSlXnz?$tH7h&dH?V$WP{fr`iJ;h+$%kY*Ef z(#KvSz-KJH8QW@xP+y%1sgX>T@uoP|<-G`1Ma{!tOLiq_$O$+VEnPBE*eTWPyS)f^ zh0S9TrJhR|9NB`vE66sdOcKuJ{{HiLOIkQ_vgXc_cA1jM&G$$zY0KaYYrI2H=_1A% zCFP;W|MlU4M^8e%&;-7hQU!wiZ0pa z=%SNBOnnHS%tS0KQ|dBPOsY!9k$)ChjaY0_>(VvNDoG9EJIu@`&%|z}2Z%eUD^||UrigLQ(@qIe|A?CzKh-%bYLs_)pq|^N54Cfd)m=4f zRC*0iFKu#0xYXppMr7p=WSl21m~L5ZH@#$x*Fy0=e@SStR;e=_r;#Z}ma~WvE-ge) zdL&xmErpiIcF$!?w~!jVE-}F_^FAnkwrx6im>-^_kYQIb_$u!qhseTqV$yxIOUr3XU7vwj?B}MA(Yw!QDR@)(IdSZEymPKn5Q!=sU6sQ+JEVJb z-=LJbJ#$p=5edaY#v7TO$&3I7ZfKauFFire`smAn(-2hII|Eg=J`=9q zD|cN~@llzYFpt@&v$c?rq0V+osw=-9oLt_}x);h)A5=d5ZXG|ei?yfkM zfSFBw-=Nu?NoJ6}cw3mA(zPLnHp@8(H;*NK3M=Rta3aOWBOUX+3gtQBE$lRG?z9iJ zk;+$gj^W`IPvHb=5SFV6rw{Tdx{x9a4o=kdtf*YUlLmbP_P7LzW2OsAo4f+pY$+Ql zmdE;~@=93dVqwi{wVuqj?`5oypT06w$~#hFgk$h7>}FqTC9fD^wZ7Ike!nmlS>Nf- zJeP5WT+(fmVBS0uAFf)7xIBCgxp+2K+vn=CnUB;$GBg1*>q_#8 zM7g1;me)C{w{~}R0ghagv$OSCui1I~)H$o+1H9OtkOJ>(gjI`{S3X5rq!`i0w3Jl_ z*(A6-2H~a!!!VRp25BOO#628c`DNy8iO-m_Cd?@0el?BX?6@YYx}CLjIHJ7rx@FfI zrkv>f-juFgS0^urF)oDH!k9~$va#_z9>WEh&}9w|t*i2qg$-A+M#jQx$|<(MTOryt z{$FLztm&fr&`}Wq-YNW)nJFA46Xe@3*F88=Id8kZmLzYeq5s3#TShmsEZM?scbl1+ znVFfHncK|F%*@QpZZk78gSySkZZl*1(>Zr$-g|5Axj(+ORH<5$N~KIiWku|ay`!AM zC*>9hkF^j#>?T`aVX4Ys5Fc|86h&9aZH(wzsQ2TK{N(j{9URMS>-Ua)^Ler&(M=E# zcUJcX&3ik60GZ=!Cd9qQ>>3?!+o^&$nWZ>Mv6YxG0DfW!gk%S+*mFE?;a%n$Fnf(j z=aIne5t-XP{3jTo?4DCaeU|5!Kuu@*o7&E$iDlG+h`}>LnzjuZS~WB;_eZRVOW}iA zZ|JCf7zNCp@^S7vX%-seXoW+|@jTBIc^XzR@!bN+c|?>E1R9YqLQ#8Gw+s19VH4s+ zlj8#}bupW2c&(L?`GugwL(z||cst;Pd-tW5KMXrhwAnvG^D~8gv92=raH+J2V^(6g zCua9dJK<=a#|5e-Is2K}0>ud&vTvm6;soa&R*yUkWX@HQ;x+bZ+l=cAem{$yPittr zigi0-fL|BFrYW!3$ohtB?!KxM>2xvIareBhDjg1_3FgOm(gxQ2?37l-P8pdr4Yn0CqmCrG5V6#MJh}bdl z%NLzvn7?9At|26T;ugabRO?U;8jwt;lOcy-r))M$nc1C;^mO>}cPF(V`)_73T52{!}ak-$gN)w+uM__i<>QC(6>eIJZs~|{BzQB zzRaS;nAP;yr!)CSbaYN+qAT z2W|k8c$k zxW&lYsx`tt*9y13oM{Mtj51ZlCn5iYKqY7?wK}KU>3BsG{29A+J5lXE9$qD#F~z(m zgZ4u;#*W-6eFDjVy26eLmIY8z0zPdxo+xvXV8bu#1%lQ93d3BG01^sIX(CAU)-q0w zqGcPc`|ZGpP$3tbxi;1|Hp(`Zt_7)q53YgF7`tJ!xRhQ6VA=|3DbW97wZvKhMc1OP zWNllSE!cw!h_h6Dvk+8vpTr=m5VsF>n4RY|)c(QqqGxyo_GCqEj5O7Sth!O)r4e+` zFFoOI%E(cpukag!4qkf@gpSTqq{EWtzS*}RHh}#sfSuOT+j$B5zBya43#%D{v*02b zSWLWGobSSI{_@L(F}%e^l3Q$%=kRlutPZby`KcvBfE!{n1m3(j*~J>J?GNnorH5t- zK_1vO4BYugnKf6gHFX|&^ApSXU^m!i3cPuE$+d?L4Qg(HV?kc2(WCBc5#d|jh{_~dt?usRNYVReF_e#2g9!%A4lE7 zeh?^nmQl-8{152)I^~ES^7$Xr(5Rilg*3pX$n#SC0G4QvoN5oeIE zZc1c?zAiXVVS?@KJA8^?LI*mi+85Qtud3QbVNM12;J*jur&}N^$lw)+kXtw)q_1jg zxeLw$tsDZ@D3nOJ6uqonbHM)kRpX!P9(FUdIxl3E) z68~z6aQ%s0(2m70P|O}|se(^A{Za=rwd^yikOUR%%`5w-KC z8I$tIdCD$$YA{%8;-$K{R}brALS=y{RlU2?GDX3zAGJGPbGwD4B_tIbQ7KUgYe-6T zSW5JxwO;ciuI5iI-FqTPPt32s>y0rkmOCrZOj%mvK-u5{Ok?zGb*@S@lcQpNpa2#@ zzTYciZ!PpzVyo>ZFoM1?6&QG37Ja|OYAA6qK9Cdzz|Tzobwp^R`@(YIE19A_r`v*G zO*C~%PjoL7Ku7Rnq2aI7sutSl0WOH@c;cfrU*hzd4SpB>+9$^=|B@q2?E-=O_m}>V z#i*IxE4m!{PFINhv}oD>*O~r)@vP`l+(Dt3`DxPqu;@b@qlQ_pnL9kXb6H3PCACY( zY+P)AP0v4e?GCVl1FWw8vXM@Vj~D0u;doK&k33@*q6fAHKVR$C{X5>;{Uh1Ji^p|L za2-y2Nwc<8H#q{_NGv#bwjRb>y8LKneraqH zfLs(8Z0>q26D(i8*0AbWyKW<_Q3&yz{P!!~?!J)C;X*v;0d9heE?s}5!q2$@N(J>P z4m?`YHmQ`GwSD3juhxy8=PdnrwxPZcqWUt)&Mn~6 z__cs{z9-Z!wV!nIh7z3A!LME3MNsNv!hC#`D#iY}ay-Kxs_!9%KbUpy_l(Nxv;p2% zP&>%yaMEsx)>h3yjEow+8rym#(4HXBQ6rh)XtW;9g=n?l=+hymjZI;Hs!XN{+GJVL z&R*eZWA+uaes;x5$4)SvvU*eKzncCs(#O_w&9`5*V2_~Rv&%#9(pLH=<7(PQ^ud8l zQk}98Ihk{bMq?bC2EBf_uj?#i=lqc$7H&4hbZ&?%to7dIA-3r&>uA47^ucJ{A~Wu7 z>N!BXS?URLklj>`uErPFteD^Y;K=!2)9^W~>} z5i?|yIiaB0$5FhNqh{%~%_OU)35W?QP)1aR4pJ5}bN@bzOpvZ^H_|);OmIljj$+ZC zygfgn-AAkNOr4Bf23(1fU&1q62}{dKRzgv+5mk~mDU1i#0t>~kWqOb+kedb*9>8iI3ugq3bc$ zV>P`dv!bU#5}qDG!ys;56ONF8d=afp?9|4AoHL>v$or|1>Rht*L)?lSvD7Gx<*Q@g z0fLlY#{mSVz;}})&fJjHg!-tUJQh0SYMIWM5GS%iT`5Y8rd+DsZ}2N3fE1smuCr8 zD%aG!d9@=n=O6N`JSp+>ss~uYTH#v3%IcLVYSgBw(psf|?@F8UJ|Q~k1D+ZAf|7Je z4E%1np?8kY$44o~d*F;>iw|Cs3a;UDWzMEhH0+Q%pX-F~#udU(@Aj-fe1+cO0q>r! z{CG+Nwfbad5+V4dO?jm+VCL~lTW0b?j{p5_8nU0?KbALgeYGRle zM#x{jBuV`z$P-9a{@;o6@}eT7qLzl{cJ|ILmd3zTN*7ZPmw#bbvC8XqK-7~jIU8l! zatT@5vf~@%NQa_8LK`STyRb0yVPOSwJdXQ5x6^Xs1z~T1fzTeYYGfI*B=2Kjyzn$P zA*piVrIoqaT+D2a)ke7uU70~?e^PxI3e-vW$3;%XKkNgXugUa$u&N3CkH=1 zgbinIt^YDLsAps4JQoz37RYXRXG9Fy(l^WiPIAhW()Mso#)L29DtMB27gfV$Q5GJ) zR=@daGG)ms0_p1A8%J|?OeGQg84pD`7A_H{|8p88UyQcNVE2U1znb?={|yyznM1hD zC(`r*_o7iw>C#ug34~2D22UIq#a_ORxKsX~+;bfDkG$ZPmVL(udkuj06;0(gjOO15 z!7HA98Lft(H)c~=oN87?*E6IY^QJTIio*6zc1GJ6M4mSI%TN}#2gT=rpeEUWCE7y+ zP2$U!Sry#eg_(0P#KMSnzn^xz8@XVh z?jF5M?5LvNHIBm&G$+JXt4nLv)mbmFi=*mWqe=gK{v}GVbsG&v${H;yctLjf$`_T`HpaM=HOIpQD3?|&1RW8-I}zcQhQD!j57>-cLA*MI)a7cthbaQg&~>EZvsF$LoD=D_@MF*j4Y z|C;J*G@*P@mpwoIf25kVU_+_zX%7?8YgLnm!1Z#*L?SlSlh%`pnBWc($CiZttj*9P z>X+CfiA?6?w{GK<+Glk;0FaLaz!SDMBnQMd%V(EZ;*81VuuJ5wMDC9Q_dj|$nu=FN zg5I_LPG+(>u6&8JV;FEjqS!h zgz4k>$dCiWw)E3~dZ|o!Z>$+t;6|HLB(76A0I0@-PUlOrQPT*c$=xlvWDOK8%D(VU zDF|iFeBCm1tk_U+wy1@@d(V)#GnEMyhk0*KK!34l=w>iHtW?I-C9G4!rh71E3w_s= z`icGX*%9ogRkLg{H&O3~1jg$rv3B8tbS(X^6J#R)N=d&=*z~D->9C*7_1~;5YNl(i zm$*BX^_QdD2@uU5_}2)xu}wUSJpxDas2)#B5GEbekk}T=*D?Fw0^? z4SAidC0LOU)#Rw6`+R(1Qk3>l*?g|Me;mx~l*XcI+?Pvd@7O=Vi?><1Jqx1?+}8-S z2L~Aitj<}AxTtKHNE}&`P@z2==p#dVYiD0QK0yXuWU>NHNVVP7I2W>EucENK8T#L{ z_HOvTe$j5vsa$RqYeRPE&Y~q{mMYcODn~kl7(TvIx-Wj2UROMR?G&H{wz?{^3U+;mK(onvf zmceWlL#L5Jy^+DLQ5SbysG$BrkapFYl;nIXh%Hf*)Dvu=dJ?`>3e+ZPe8R2DdgNy6 ztW*bT>(v9}H)AOkGK)6&s~eVCmrwHe_a=$9u+?Ahb_`z0J&8b0&Dp78Ad6 zR5D5j7APXJf7WdFAO94;htFxX7dNLgl*L>|uGuFCZQ*ulVdr*(N)Bsc&(%PrRLehp zEL-*&6A_7s+vUlG(S6><{7dD<;5*XSpTeSnA}*4bM8l)-P|cqvpX)rv$clAalJaW;SWe!PTPIu z^UQIO*EL~wQ~c1F*bHlDad%IxmYP9f6?I)H=8b`E+)QkPubWM4xEy+ip?~>;{g3+o zmOipA_W4!oM7JxglS{0HiUpLo+N*Puu}+Q|M_Vhr2&rgi|Ji7npS1a+wrTk_V_(4i zj=u{RyP?7E(*OR#ktam&Dt>0`S4PBvK)OVii4jkMd+KCw3?Ef-ky8|sxM73_0oAJ; z%FLsbk*q)+br>m}StUPx;I(lun`$cGesI+khmj$kR37r3wF{T+Jf1WO3hiWBYhjxo zo(`0LR09WM4YpN-Pz_GCa-XaV)YcJg=$=oVi*}z6r_OS9!DyKql8g$rX7s({^=v9m_542uIuoQOYZk8ZsUpl~5g05B1XFc5RQ8OMvSySyfx zpCzYa?r}dFCe+_|M@KTA%sV1-Sh?iNuRF?)$(<}X3Q|5!Ik4Cn3LFUbIX!yec@MaH z4shwHM2T~gBVrRA)uwmJopQ!VuyzWS<5iPg6y#!u9-2y)>j5fNFz|mi5Fmw>W!f!w zP`jd_=PT?kSB87BMV&NfKc1kXCA$?%6!YgA4XfjgWQc!oUywac$xNr9s|b(f7QI$S zR+Wg$dI%N?$&A|i^2aHhG{=Lxt|>scY{ccgXdV{Gb1cfi-h761RxQoA$Y`5D z0Sl*`97ijvpExQx`Pyxq6W<~63TYOa)SfWY0tmP&G}T?gx$e8HsAd05X5##=lpOU` zarYq1I{1Ao;o6yMw+|God(BHD>G;9ZRuE|oEG;dgp3^UdH#TAzKXJY6AzaMk@|xk} z4Ys{_vd`@s)FbX5B_<#RB(zBROh&bvs_W9KI5tc_&d1+$#-*PwaraIeh7s(s#a=F+ zvUso^Qc=LPi(0z0$v@5aaBoWRV9lA9^vFNKG(|QVce|Bsd6%Uk_yP5cPrlx6kYuOjCg{}_`?2!Tf~_Z zJC$EoJM}9|IQdS7N~EQh6D4crGKn5}&BSUu6Gh5&Sy4g7j&$!FdF+R*jhQx&)AkD% z>4M{^28%Oz!6|OCx2j5bd;J{uUU(BoKmc0 z8J21prckicBOWvie5Wm-|DtRMsc|$Oc20nvCLiW{r|C0j$|dWOw|iuY$K?h?tlR$N zu%nw740yaNDXsVST6G_>`bF2rm_&V(jMEhU*;Ej0$dZX;Q%7p6Fsi_*UisZd6$lM}&`A>q!34EMFd4f+x zf_UjK<9q=!uY%PKTWF{1eIpelXj)>lFs%zEBI;;Sfx>7Gp_DDpu7?$|;6&wTrOt^z(xJjyM$OE|s1 zE#5ez^ABEy#8$_7w7HImYOW zu`jmJ1=Cc43hHwAhWLc{4_UWOzVNTEZw7)`_=cIi5zy~wX^h&nqs5xDj62Yrd*;Ey=WD ziW`XEpxKK%`?i83mS0%QGuPP-c=lns#X#MB7{@|jTm9F*qm4JX>?2$CcRbThKh(&m zxKnVvB{=Rf92Z%R@v=8((@vo=mtg8PRyOZ|rm1qXr2J5;M~oapKQ^9_@JyWDrso_y zi3{r5fY8GI3`cY!_WtuY70N5j5nO$0+!0+SM1S5r&0R?D$C3{LyYlZm>pqu-ZHR<97_s{OUW`#}^;|I&*w_N4!~gc>ly?#KZFsB1G3N^2Vf^G%OI3K|2}F9NJpJ?H=MY^`9^*+I>5RZwgelcJ(b(!%DbKEt$o%kRI_xNA-&?yyEsp zH|@nwYHnYogyngr)a+neNHodWVlkM9ZrNLJE+FUeYUeX|HJvd@xROW{GD)8fWs{UH zVOFJ(Iir2JiIb54rqXweGIv_2i^tBSkrK7DywVGw4Vx_n2RmB5Y2h%d@pLWbt9;I* z4oJ(7M63iO(CR(|mesn4a9(ppeixYZ`l$rd3_S;w^mqfNKe`*qPVfc}*W--Twy3Hv zGgzY7Y&VV~F~sZBtP_b_e}qcac*AS(#~4|+{y~IJH5E|Qo(tiKVd*U)b2fGSC2cu6 zsgg7G0^3Xs`{m$;@QK?K-qASy7|Lg!9UuhGSTME{AwpR;w(4pyfa?XSc28<`%BVi? zSiO(GELprFZG2Lej#!wyB3JGJ!xzhQqSd>bKl;)AhmbEk{KV5It~XBN#W$iiaO^bu znL2j>vM;}&iBpPUhqWgHhJ^m$%`SX!;^35_SjLRYOsm= z1K+5Pe+rT?9wu};?|4;;?X6RK1|zrehPXG-zOn1>#S`3IYZ*h;#q&8DH9lRrc8q?D zq>?ed$4{-o3pG|Ihd^S#zqZSEt;)|ee^>tAcS&ZxHLOr_1f zu*+_^BI}6I9*0wuRb@yvczYDZg|@si)ooV=5q@KXJ<-^&eCmtdnj%*`Em4_VgEcLo zH2YN)fjf?oSp97`Pi}hz%_G%G^3G{*8JN;?GN@75BjN3rKiE}Cv@(bi%Q3Uczlr5l zQa*fRTviE#;a$>2R^vie+a<6ORSmtW5Xb0uzv0t?7m^H0nkrd$VO4?Q;3aUbwyT`6 z@W=zBysn?)jV|y;C)ue~Js1Av7_OJOc?8XQ`KiPvL=aqd_2U^~C;xhmeP8BcQiE2= z3p~pma;uwSX{y}3IxXlkM)~9NDw>{^{pxPa&=2QdTLe35Z`aZ~paScEI#E{S z$1Aq-d(2kZ9Qb{c7Pn_ z<;Lw#J}Z1L>vB!M!?KRWWIYCT3(X9=nyq||$8B%QUX9pEJ&x-|Iv+>%PcXI@hz7P- z;&DCMdY0O!--Ju?-1%Sde#y_GXWLV?>2oi-Kzrg%_wz)(o2ORms|{|z-Rp(Bf&9Kh zdCy+cm}~>Ul{vMEYx(4qwmD|tSH}AJ@-NAz$8S{+BtX^6cJlv(W&K;S*kACWvZ;f; zvn7zF@2O&HV`}_AbbX!&`(=J`GjKg{c{gx3H*k1y@ZEa=a{l}MJ)o!n5cIw`o%CKL zEeQt_f`HolQ`E_WHAl6bPQ@) z;2`7#Kf>oAlvA|`O$s9#Ex6a(QvN%3L(kehcd>6Ykf1+aIEDfqX>b@q!18D&``Gi( z%kMV?%}^1KU%|onyl9LmT94|*$@DY!3gs5UMZSW;^8HFFn&n1XldNOy^=F5qkELnyzV%#EhAP{s`6wC#UmuV1hXLT2E3(x+)!KzU>8l-;`_^+6in5LYv zzBPny`}MAlXUcx{)-CFg@|&&s*tY~&`ORMLaYPNmXx@3EF?$<-s}_HNbCWzSajKP% zUKPboLdU5MqTf?d(Nn1q(h-pk2J7o6701TXwG1EAVn+zv$GEoA`u5p+w3IVT4<^!8 z-LY7eby1#wGN?{J!lFkACgAK7SNhP|yAr+D5^yYI*F^}kqF-{m>l9>p0*zP?`q1>V z5Jm_h;2Pcq%e(&94);FzhUxhb`99x@l|!~8gd5fs#{OguL3Apd^FWpeP? zy?!7?-`~0wzX{cLTY-3E(tp&b`L~jkf7$;3ly?3bb(FPqb}_a4Z%InqcqV9|CXtxA zu`Fxq_fs&`1zQ`^)CeP_dc^awnR=;+F2g2pCpH*_ox)+!dM5chL;Ne2JD*L0`Czbb z{IEWl%$SC__EABqvSu2Ijj+F#BylDh1QQYGT69f)qSH#)U01{mcye;FV&L>##ROsp zq_$%Ue6vu3Cy2;`#YrE+#VswR61B`UiGn@L`)HN_7+BWYljRjBy?abZYQ}?ng;;ac z{ndR2%DQUbM5NyZ@h=n>27%PD326Qh{}F}#x16KDMnqmg@n6@-zpjiL^%>`NQB?om z@th60%~IJVa@oj~ws^-wRB}{gr9^VJkZdpHwoB;LIye%Qv{+K|AyZwQNoYxpk-Fg#IHZuC=Vbdmb7v5J-es8_%{vU65_K0CnE3qP=CG?bn z4KOAJ=a$iQrIUicH5;eD~nOvEyYx&m$@^W ziA~Vb(;Yu3M`HhIF3OH7#d%|S!L!tutVmLytkw!EN;MPbfD}D)%|zx*(_rn?q{FCa zphr{PIGy?}CbgLW8`{H@jbyFE1Qi3>JzG!~m}vj$Nz(tu@+F zSrb-2nb}CumD3E$F34$YPpl~8!})hjDCVu^dyewzG3G5;;E#$YN|W1+JG={SopOJ&Ez-hl-QP zLUBf$R!|dmbklTG9JbVmi<^^|ph$anrk`qyG0?xyyK|*E0$p_Vb*)%7NqrD&Me}p6 z1%;_-+8gD5583m&vgk*MQ>?~An@E_{bf@sCp8rgQCwF3ZWyLjfS}q^|Bw%)APpe4O z$ut-i=CIJ(E`6w3*|+I5{dKkMNNY&mlH=lmK8=;H!dI(3YUiE zmkk2BA~TGmBj3^G^*izS+Ek1)y(f%Z#h4mG!x&3p?~8$qX#-zK!nH}7nNF)c=#8R< zb4ez>qkJ8@=&%m|d<=tQOqJf$6>gx!k5YVuqr_b)Jyl^oX!_lN`TUA_$0x{6*ZVqF z7*fFH3;5FhTwuu!Z=oEu_ApM` zKXHwKQrunOD-d}p6dNf9VH&s*8aL?;@QLD6V|gs0y23VpTM=RV#bok#QPRAMcdNd{*elJ;%Xo1nQ7boP%*e_b0l6# zgXw1V0e!*ZkX;v6Aavf~^7fjVL4~o*80x*~ue~X{3H--(R7J3O!JYXN>h zO0+l1e{y*K(YyO^%9N0c3s8v9(8%V$$fTi(4S%iohWWRZ2BxGbhakexOnlK_VKxn@ zL>3_fXXoGoldwbPk|&4@gxD!mylAEp7TX!9D)IF%)A+y2x5f zO|85L%Q4Umz`{tC+=}?Y>^<(O5j%(x))XUKt@@pz3nfiD`!*dnSD=l4JPVbL5$+ViX1o%h$dSS4{vOFhc^ne($hx z1oh4?!D;K^1qiX#oP>S zT>p>Q)xYtc2d4KDfW<_C-t%;~%W@;28(AEj4esB&&Ok5QC4<^}hEg27cISPu=s>k? zI!9F;T#MCNcM92A8b#(d`o0l)3KOrN*!P)<|DT%kwp-f=E)`8nUJJGEF)N@f&GecJ!m;d8M|G$Al zs?vb;kRYOOc9|SbaZQan7~RPTxzYYW_MX@&6{Y5>FV3=y$axwo+B*%H*!2#?v(g?Z z9a@R7>FTOyll@k=sX*VBK*A7FtL#ZdDy0ESy} z7!KtD$CJ3Q#RpZyh-WWxUPH>9$w1jz%$%bjKDTrs0gbkx!t;s1cHHeJQJ*1iB(`7gKb z-&p6u#_+jnAHeDF_>Oq4doO9Y`v7QhdT{yG!;PXJIm6v9A3I5Z*{=2fj`w^c*scFB zQ^hSXBZ{ckF>;dfQ!2$DClm)KgPj=x%Y3Dz1_pbGX|EKfrR3@LOw5eTj7$MvNMK0j zFK7wTztSX_7=8cxw~ZP-`Do<>yEOkFZItYvI<0JP@A7{;vZ2a)(My7;d`opE#v5zZ zUF=)7*AkKLi}h%o(NgV0 zJzP&YEbd27`9QAd$k%V=x-jUibk&CDaLbNE!*XA{(6O&+&v(-T*TF9tS@TNvS^X#L zmm*7|MEG}yL_;!T2y#ObGoo$!yoVKkPbGf05C<~K@_P8t&k_jCRUL`B4pxygDrcvR z=oA*IhPJ5pU6R7=OQuP%#)L0e04y~F^SzR}qQ;3&DDpt^Dah8P3qx48N@6vx=XbD*0b!kECqu$lUGHAZ?F-M45TXR|d-n1H-1`1(ffr}xrSL<@` zNpDoS#iGiC-B6VkVM7d1bzYKK!jP6)yq;T#m|aDO+9%qif_beBT41-0N~iJ;h3*Ns z;5kP{6L^j~qnM=8EN_>$nEz>0Ai+nJm10YG-5#Vqpdp*TqTL=flf+h%x}g086fU@l zsZ~NHz+im@Ra|VoS4x}tiu@DV?iwaeBv*4`{SVAvnN>9VVoA5aE64RehKK){=JS^k{lDd4LzSI4?) zqqAo=;_c3ukIk|>Lj$+jA(RuLgiLUP{d}wmq{Po@>MTwD6AjP+kwFGvpOiPftS%#V z86i(j499uSmt9wX*-_#cyxXKEi{V~xBo=SRfJk-vZc4r3U_S{;wukPZI@CJss1P7F zNhGW#i$QJY2H!q>FxvF`eDH2Waro_lB}`bw?b}c{f$K>2K$ml&{EMe7)_S@ve!e5x zDW?p265|<*jXcYvccMG3>Eb~oWJ%|SIpwH!sNo^IZxWp7A}p7s`#X|Hi+UfP{$(`j z(9GhM4w@7iSvu@dByL5k2byJasuV#NhR-?^pCHk!CVfZSVuxyI z)PFA0(6dty>zgnW?XP$1NR&nZ++wC$Id$#oEnBQ+zHkWfCDhO5jr#?M5alG+4II{Y z9BdN9^Mz8D!hBE*(0Yp=kp(2Dd(brNCYpndQH=uurJNz$8fO>hK)TL?_1AGGg@r}P z_^yf)pVJo<9k8eRj0Ue}mbK_hb5#Dg(-_u{^hHe)L2;FZq3I|c(kim5ScCeDJp{}e znv~wccM#LI8?aDoe|Fcexbv?$yxslKSt$5FjN^Ycj2$*i8~*CJQJ6E!;v=@MqiBy| zN>6k=l$2XmZKzuE|J$=j8}gJO22R!||NqQR{cjXb!^!f0ipiv=t}CMmA%=ZQff^1G zBcZ7xBLR~K5QTVyEI2I@Zvuj%OglnA(zF`^X2GS)X$X6V9RK(or#VP%V#9b~DS$sg&hVg z8+RtLjEo+Jk=%vRq<^zw$8Fky#+CLI(-Jo%bM|(O6%K2MH%$Lsvr*I!7{ufO9B9CW zJB6c<{wbX94AH-#9N9$mmm#cL)B)S$J$|(aZSTN^2)#Hog`PT@tG$?M ziZkxsVeUi^YJ^HYqs0b=0BtjFa+{c#l1kR;>}$04J*S&TU)jn`GWE3MhTkDXCv15_Lrd<}zLodkgHs>yXJ|2lu}Imn4mj<1Gjxt)9n6uC_dKX+E%h+ybidXDFnH@H}#M-omF{>pF+l=a?eK| zaIQ<@zBMwu*AU`h4`z{8?%RX+APC<6>yA-b2gYp%ylj61)z|*nHU3BV?C-=-k^hnG zQJdG34;Dm(xXxTeuX&WI8O5+Kc-sSm)H@@GgwtW=PekL;6fP(P4881t@TNFg6s=w^ z{rSVgGBNu!cZ{Lw0HeV^ZPPA_$>7mXvP+&v^3J&jNr@O{r)Gzv#;#DcVhMAi_>3Eg z_DN$SQZj#M{PI04w{;WYP^dJN@gZa`*HDEtr*`bAj2vb=xsLmf(W>tTvJ zPl2dwUgR^M>v@vg@B4h&XE1SuG2p@+N>-}=-V#s_UsHp2;|cN`{??<200$n|23?VE zCmOUB^ESSep%^X9#?vpKs12X>+t6Xw{fKZ#CA?Al4Yw+6I%Rwhp`>$W1Nmm@Tr|N1`N6r-%DHIl4n+PgHB+^F z$|63qV>6!^EqR+fY0qTYwq@c^Vyx$3#|v8i${y5j9d4wln%1nHHEd-j9<0?3$8DV! zOLyJrq-qBfjXy!C!gwWSC|m>mNjK%IGvbEs3X0E)s2?WsnsK+ta;>1!uf;*u$$TQU zm=CU#>x%OeQBYI!b|q1ZNPcIhvfQVgEpPj4{=F7WYDP=;oR=ublBS90Yc z%-gh0HSrukR%@Bx3hAL0nk!0)E(NzoT{gbx7ABsF&rRK{7bY%nHab8*;>a?Y)0^pZB^9M1D9MN^y?-I_+hPv>jSKcJNYFFN0nUehKOI9GA+sM zz)#|6nLjNyPyb+1KZPm(yM~3D|G*2%YH;X?%E2pE-ouH5G9Em7lIo=DZH9Gpd!tCl zBkZy+u4|mk9Dmv87pyD>tmqN2$;=+6A#Q%7LcS9zCtA5%w=9!0^k4DsegQh26Pr?a zeHNT_zE0l!vy1Udp$TVmE)T~D@=6||MtxCfYM&n|d*{tPgBAjujd`sQ=n z-9tUZT}A;N5T=+adql>u@QsnmM$DlfyZ<{HA)v(VDFX(q4F7Qw{KsfS*526A#nRqR z%FfLGzh*xzIG~PA>SzAMba&ZaQ8NW7Yhmg^=$|EKTO zj7bt~U#;BHrt=)%aW7EA#%Dt+-*-QfLMeg}4#M}G)|)n_4`=9-LP)!@+(0{_kk z1z#h6pFgL+JuI7#v+~Gb!C^8STDD@s@*zVtzE3%x0M_OHO=Emc zKK0o)U>^-mK*gJ{Z1(f7Ec7J9{j?e|0Tb9cbn>O+9pVa5d6l=xi{c%`CJ_K`RiH$)POjTwbO~@LtK03YJ-3eQ!h7yJY4sV^Ps^&wXLpA{a zI`qlyhLsbKjY!3&XYOTGksmpf%QPO>ROHxCkZ6|0})yu-wg%ulzU7bQ%#QL#rHfB`A zz07EV_DS(i5!B(0i9K&}G&wu`4O^oQBI{1hp|upe-Se=Sp;f2uj;20xi&E}*!;D*P z8qrEr@mD?$Ds4v7@3M`Z0?#tLDhgE_&!(Z9n8=JQM|x5foF$PQz&X|8<>mI=otlz4 zX$%rcW@&&st1WIb8{R0;l@Sw3vZVkji+HMahmcBSeIzNuTJJW#+}%m^ zAqLunL8^?|K8g5EqsjoqtqqZnIaitliwEH<--@{RX+U@Bd;9*!s3PwK-=28VuB*O_ zONyujH+KmwDd4s3*5GvM78YH#ZB~u0@cwf*!>~Y^FaJo2rD>DIP5ERblRQIXTd_(r z1%nLB*+Se)>&{lpbjTL0^Sc-n3%0aq=l=W%OZ>KQ%A{L~(?sCjofB?nM4UE`%*v^v zdL1k(+M>6#jczs-AlHrN`uwY|Ht9y{*~CZ~-Pfq?!{1Vei%i8Nc4QVxUK}*0aGyW+ zKOOoAUFdg$Mn>vbLfLVG(-gULrGI{DcxIpsFeE-&hfYm&(B4% zu9tRlmdK9Toi8#K+&@xIDykB=A$cijlQoSdB>ozqqQ~Q*qOe;yDPIh%ik9g1ab*#B zcyGR4Du`Av?9sNxZnW{#vLTb_Hp>+Rf(Tc$bxc7L6r0>AFxVjQEGe zNjLqj#3?K|HSyHNj@5da4}`tE=upyKNHCdNuWF=D1}?6Fr^of@yd&IJjWNUHDYF7- zCt%V?4_#6*Jyf}TinvpKHhmxJm^}-QN{cyt3L1}KEZ3@2 znUCHCI_<2Xx|}SFCRi+YYSH-`%nyJCw-nIV^|ELj^t7`CA6tZAqaK?zF{adnGKuxb&|8m zd(3nrw)%}>#&XCrocfRIoYWgoTgKE@dqp$0X_KO=D+p;dLG@}dd)F5w-_SxoI@fJ| zfwtf@iz>L%*Xni7ewC?d(1hh3(_s__-9Sx|)!UX}t6zK-s>Ed30qa%{L2Ad8zndzM1xQ7h_tp!K+s=a47F4;E=QaOIQI;v|SPUkO7M)7$4gl$DC zl|pOK<(`q8nme9RC)d!M*n8O>Z;k>$a`oRBW3SS8S_d+qP}9 zV%xTD+euYy+dFph<$2Dzc<+1eX>a@X+Uw_PZST3}m~)Ii`{>=w!*Ef-wt(R9sgudW zn^N?ftZ89|x2^1J68F2C&*v)gtLQw;UFY5A^aCvz^HNS-Oj9$j0;drwXnFfQ=SaFa z#MF&6?-N_1+w1zU&Rezm7G7=QR-DVvKM@w|g&MfMb-(dl*Y@xoQ5S~TUzEhzL{*#} zjm}s`QhDH)3-n`t9byh5-h&Mk;{7;E>1ThyI9zhbLQ82B{W^>`GT-+AKg~%|Lg(N` z0G`gW6DZMWy=GqDG0m+?54as+tvG@se@Gp+eAPDG_m?Kbv391kW#VYHw1#o}7@!H2X4 z8usW^G5P)ZwA{b~WjQ|ub%sqPV~E$6PTw@#a4X3E46cAKOzAtXfYL&9_V5PZEILH@ z-IS#NaI}wL+IHd|O@L(Q=rSVMvKtdLGp{c}ann@31yNfu!jV1{m{S5)S>?tX0r>-b ziB7KDLbjdRTO_O*U0|$x9Zt2)s5)%FB0p(+^C{fR;@^USYGa?wax)daQoi2jS02aN zhA8QNrdB56=);hl;TV1B1%W-)hHdCjus%83qi&;2JaET3jlP#v$Ty1EnZ*A@sI)y$ z+IIoDO|0f0lmE?iTfqw!ud~u=Pljw+TPm$14L?T@u=#5ZAntH=@Z0+dIC|S2cZlS2 zMBNMg_wG0D7>H{@+yN>#$eP`TCt`f`ZKO>S0yMtiLin5%;U}URN!L*cue4-tvU@kJ zbOv#%=m}DGLOgbgEOt`WxE2tU*@!s@mTpQ0Zi?6IMBLuF8~n_qHGp&*%2vGRbtdjG zj05ym$`K%+JBDEN5n$+!MK?wVKzSzBNnQekcSh%Mi~}4)Pe<9vRmn8ndiWolIzXcrA}!ZKFT;&;Vk}}DZ zjRL0gU$&v!Rit-sawn?cs>v)(7Qj@O)74r0ho;k4!TO9b0)h9XONsV11hNrosK1|o zH}oY8h^#V>tn%O>z+jlxH2P{5pJts$Fx_N^mg1T9qnk{ozgrzgF$$PbEP)9e|!5u%_#yg*HfWM=62^Gr!v8EM-*@ ztP-u?HD+7PwOB=?IYMLys!T>zs5b3=W+JA_Qr;x&Y{F2g#6>{YS`b75YaS3op)`eB zuK112R`8crI7_|1&2nf{Jrt6;D2AeCi55SG6^k|^(J^{(3^WPEDIx=ic-wcI4&l^8 zcVdF(sCC8xG1lk_sr@RZs7kB>$ zphz)@_VIZs0tu1G_UWMwV1sk)XZGDuU6|K1z`G$iP|MB@gvM9f-DZLvdf zY`TLRdsos=&)M-9XNM<*?baf-O3V1>b+@kg@lG`pn&5f#cIK!*k|Jb`GE5K~$jNl+ zpTjOTq;~F+^qbb^#Zd{EJpQ=a>#2tN;eR8UJ4 z%(c9DPF8iH+Y7vZLRczsZ7lS0I;3tdJbiCovssqj^KYtrI-@60trI=ZAI6`qF5c9! z&=HLKasTT697)(mb;7@xlfmARwe-hxlw&gD$XjYwHo+$rW5%#EfOnj#)ZqfVy>kGz zXl+XHtnkoOoYbupAtPn>^@lwS9>OWbDH0PT0gIsVx$Iwn))*{Vvg?;S!ywkb1GMP= zx8q5r|0rqyD&`!l|E}x)QPV{!{SEc=Bm`PPM#zH#2SDpMep3NM`-TLWtJjGXSbt{> zC8$PuUdW0=<2}~{fbm+Ozj+IZ3;m^OkWvT#J#-K@#G# z5uJ(j7b*R-@4%mqf#xK!r-1qkQ^Bqc3P~$$mohlR4vrXF1TB)Hb=t0Vl17k@*1p0D z@^)vP86?BhY#Oq`>a4h{5QD07$8jX)imqxIBv{0A1Nrw|5|OFdgw3(6b5&^unDzD2 z^$$UknzhdFl}E7J>iTHRU4=(WZ7Qfid2lp)+VO^ec1eQ^Vjg$)ttRMUoG|d2qz3R! zkHwyf#c3G=84Z$7Lyh4QwyLbE?JQ@iDoLqsge1rq}&I_Q~wN=!#Oil7K?f!}Zh(`s~#r5D`-c^8x+=#|}}17G28?KR@(vmxD42GjeA zn6dM#beu&c9DmEqn(W7&)IqpKdLA8BLt|QK3DSdW4f@WHUJ^0?#lFpBqlU(P z=s%G~f@C+Fc{5AxSu|pteup1jIJ*XUk?-%L3Hh036Te8|(ar?Q(hwurnNAx$R~WnR z0B+hvl5Ut_QT7PgStSshHQ){*;*7_gs*^e3PUjZ#BqlO}iu)0cm0Tokhp=(e0Cym> zg(0L<&}NGfhsVrlG^ZODEOn10zY7qFk3WP@2hIh7QX*BSA{X`sx)kW+LB8Xi>H|4_ z08UC2M3$T%FpgBvJp>efDNRXB1lWL}GVnv1hYZ(?{>uB!fAtCVSHw1fQZg%lfl#>r z7TNz_2?c-YPyhELxuDny8DM@yVcR~~g3$aYlO0Qa#DWI<68m2aX@Rvf+X2c<%zumu z$GGo0w9;AuAa6em@9Ftbq>c+;H>=AP-=AJSo`mo*O3~F_@Xk4w$x&_Q)rMLEy)G)H zYq8tOp$>|%Csc0MlXeP@!Ewu&o9F_XA(K{2`Kj>BL+#e*cY4uKb`Xkij{6oyqsYds zN%;Y&AqXf5eX` zJdI^&K3ThQ!!Y_zxHzDS=nPh<`U0fcwd{!d)A_ZFHq3!UN3VNQ^^4}fec`!MlGH9< zxQ?Tw;o)n7nAL`mYl_UjCCeL;R!(l;I>opHwh_dbY8x@RpE0zCG{8MpKn{E7P%C8< zu*|fu&eR*=lv3$!61215RaOmgxQg%oc9T`i;x82Z!o&Fg|8ZMn?fyc<#Pn^9{?m`{ zUvOJK2KVD9>j&eNJ_F(>-#QIMz)j$$=>HpETnq3~V&cz1H1Cx~vaoLKw< zk5lN1gW4(=JDhhwJW7Q^SPNRpe1I8fkillZsJbm!SMZ|S7 zjQp5ABKk#%^9P-I$i;k>0;J~?l9)|w+!$$~0ua*9u;rhue(jHXi_m=*PIm-<1t5u) zV(eecbku(ffBs87q`%I9{&UOxk1_dQsZ7y{Z@KfGiStAv;57f8y%Zan|8_H2n5kqr zWk#~|PcGB{m<0^27NP28!XDpgB4I6_g->3>z{19`%3e60Psr3?{-Q8_^}l{mm{gwr zZwk}Y|DZ7KN&5n)eEs3kfBWtU|F?Jl-<_L+rWO3_-h7%(%c|XN-ymY!?0F1P<((ahX9lA$>@J^u{leW`*PTw;$f#cQ2f?@x z5s1D~3P(uPp(k(`+2o>O7IvPrrVxnw+fc|hE6$aMEy5eo6QTsfy~m#P1d+j=mbvi* z)R0Bhl*WB46_jK5Y(2-*a-6uN6Hztwi~aZfX0gPff_y_6rST0SdW%t$=ogW?Zz>n# zKh^Wd7sfK4>@g|vSc4TZy0E!i70uIEF3)`PB9mKaHvswWRT{5big+WxNMa*brloXz z7EuJ{`+(7@e)N%6z6t)Kw_WQJJO0CUdS}$@$VQHQ^R5fE5oNS%1=!+z+g4QH=fJgH z=IiB=Dws42z6GX?G#vp*4?7yVWuuLzAGk$O;cpE-&OfmIF<*4ZFr=XB?!}8v>ij0j zJ^%5%!MC3UyT^GwfEnkIFQ{fhhrTs3E~b~G4mPp6`5fL~B*9TZ#?3sHn0!+m2bOU_ z_tVX9!7?K=4{#8k_Zx-HJ=MAD9sI9gQ0$yLo%#v{(SK4zpuc9p|E>9i`LE`N;or`x z|3>zS=ifc`PkUBTW2Y~63CVxDwSL(q8R#2Y{?C}w`Re;}0k;tW-)wxF?M(E`e+!t; zkN;W%ZU0+f{c{<#B#HY+OuZ4wX{YP|kJhh#dd+o3NeP9ByPurPSZ+!kx3i3M_wp>Iy{*$>930|dV=Ixcz^MY<+3xOT!qXgKLqlalKhcga z4UFW7r#b&?-PH#T#)gR<8tjE8TfuDgW5xgxgX01r$07flN(eXSDnIcxCK~^C;bHlI z7&XPeM@`tx(D?6t=bxuAaT7Ku45-1rrVe>zU9pXF0&R_csU!h;rBvbVP)fjXkQAga zE|+>_)x~?Gd#G*@n*kU=P^E(bq+*>g2)*F;+_2Up{Q*;T-iMi2UiiMR&nHaWS|iHQ zlqy>6e7~S+?X(7quvIM84An*p<%zJw5-75G+k8{}dGt~*PM8~rnn2^);EYkB0&!y31)GHrK2 z0M4dmi07EJjqZEW=?qxo#T8)CBGaCiwHDt%rO6%`gp4Y)2n`7`baS1>Z|2L+N{ zJn1vvIqecMm&zW|plM#6_ACsG>fjx%pa{@r$&jbiKcLYs6l_Oh&H>G@8L-eyEHAJQiKxj|C%7l-sg z^LXAHyY*u6%wqo!$E!;Vj4B}CVb|XruG5p5X}%v!s=Az?^GrWP8#B;l$7w^YRFW<=x-`QRgP`AJD%naN6Q}Tz; zT8t{3muS>yMfp=T_GdQ1*3wuHnA|U4)uT^A(a*9DU%l-RpSRK87!Wr&J_bNL!RNt< z@9VXq^NBC)8?_$}z!9W}9v}kLGx&MBrwaZdtNfusaP;1~jUiVD$tI44QM8UI3&uHk zhLNKi3B<`PR1>yEhxWp&Csw2@)uZ%moUCi|TBPwfotF#hj+!R9WSp&QL1|YqkIl&E z$94S-eZDjn0QBHUL|Aq|A%NB+K_2l|ujZR_yjkQ^RuOAlpLvvs)~;}sQsG>$fY(a) zT|Z8uQQ}uF_onYp#B)x)D2*z4D%SP(f=@r5*v;<%viNHu3AvWv-fOm+bxLP4w5wxx zaWpO#-?79C_r$}4A@Wryl?i0wmo%X)^#-eBhC`O{N@Yoj?zxap#JF7|&~PGG#rraW z6))N&)~Gz92WN?0I2pf}IxCmSbwMvY7AC|k*Xo_XvbvBFUmKBl3)xMPb}pw&4JWvh zd*Kk*iM^QcOS`>{;XFWcUtIHR(sxiHH*_$Cyj&R7ED$uYC|rM|Y9Emhamv=dD-iauHNceDDzn2Def^QFjxUHg+HS!P%fYSj?F-=e;fPrw&iuq*BN^s-#$ zjqyXOp774ZbSJQg>)Ep3MPORKG6h#w6Gq+1f;4ILakbHGoY`68aWF6k+p)|K;4on0 zrXp9y{IEhER4l4s1(k?giZ>H#S~|7MNRbPDs&;d|UYR23m=&$>LUZ!?5e!(yE=sve z67Xv$0?)!=Hyw<1PW!RiPwm{L8AOwd44NQ53M0u+2^ zD5d>0w=hKYEXf>3!w8mbe-`_Z+tzGKM0bB_U_()va+>7*Ud~{YLt&fnE{sVzwHg4X z=uX_UU~HRA?ELC>^WJ_6FyC-c*}Xw016(*@(8aLv-xaUZs356?(F`*uh^@`_eU-P} zm+NsD^o3iA->{g6hTr!q(rVyQ2>L{&--^P)I@~KKd&OqjI$xpW@r^?l`feMKgGE8B zY~%B!v4%&mdTq!ZycF3`Wzn%H*R~ACLlq|9(BOlLapNlQ>N8GNGB8JS5}+hv>-BUx z268D@XN7)%l`n38v=fq>(l~cmQK7{&E!62w&?)@HBkhgwy5<-op@(O8`VJu-M#&XP zselLBRQjH*WsN&~&oLy9qcS>`u5lJV5)2nc!9&i}OkrPD61YBqh@d`$sv7SQ6E;vz z$y|l$pq)=hk)WzLOwvKX%1}HzA(1SjKVmkXipy}F(Yh5|P~0il!1GEpj*1!S+Ts`i z9y)dr*v(QI2gjCp8fGC8$4e)}_h^DP4VTgmc9>!$Q&cNqGsO~RB|5;Q?J>7H(MFmq z-a|^q_tM0@Zue{poO2+Tq_<#f(;{H8Wf$jGO`4;=o)dl4gIK&UABtCGNMT@_`WcEE zqLbuwv25otXx_DAIz_dRS)VY?uaPkB{v#zCSz1$i3`1xvG-(xIc2%?BfsiX%hJ#GJ zHHpEKI!n6~ljzxnBvwLm%*n!5NSh`M!-A2h6Jgi|_S$J@T82l)ZJ50^@KU9Ht~Xt9 zy^Df%IpIWhCMJr>MFzw5l9W(8PJzXS(V{<#)Y$8R2P^puHb;j$hQka&Elg}RTqL^Q zLq!M4qtwmin}%l=cr|DIYNCX2u;I8J-q;f%+}&Mz?v(m^N6MXEhxU&i^l)UxD$%Ow z!Bp%`(DOZ3f;MT-myYTu6w`EwlwnVNI2aR$Q2r=@0QV^ImKss%Yqea@QEd{50S?^; zkL>-mgbF|SWj<9Zs4{A2|8P&c*-G=V$HGstG$W+_!&}wI@cAU#+lf~+@6Z(0GY@nJr=cyPe_OGB&l+{n8DpDA1Gbd!Q=ZY+Jdv8EwdyoI>JHU%k};f#Cm7!Yo#3sCnXZocs9{?!1Vc+FSMnjKJ6cOs5S z&Q3MJ^_&^xz-yZ~B%`#gvOb|+MHRKx>#c5miZ)jT$FFikUeDRW!u!n-_(NRDiG?*w znYR_gvnQ8BR`J@yE@et=$jA^lKoWwV6hmGcPbct<5ZFck-h9 z+J@=e=9s+HtwsxAm|_7WJ9y>*N}-!omY+5fPInzoJfg4EZ@im=<}<&ER6SSDq;yHY z%gUP{o8UX>XOQo6L{)%}>@(oeAajQdS#QRs5J^Ulc?xWvCJ$wIkk{VnKMm7)@$6ft z3u$N2?Q3)U8(VK~-^98CQ{f9J*KT6rITAl}SMwI*;9J7Flh-)_FEwHj?g^_qBJVV$ z;#s8%8E)-Ce(4V;Ari~Fs^BH$!e{6v{#0aTyDQK@vt-0&NE4cHG_$_73>EMhF0CR` zVvy!`tLg~Zaa1Z-%r2>Gui@UmO6yJU+QW!!{s|9scC)s4Hd<0}RxbjQ(CYK{MrG|U zrw7c&8`B&@PsO!v2aA@l%BYOy4a^ho1+SO}dx$DPv@!_VDjAgb=lTmh zCC>I<$h!|%8VcRC)9;1Eoz8QDH7>#ja;oez9% zXY_28z-x*6Z~iMAUg(yN6f65GzRq@z*~o7wW^_RFB+Zd~!NVq1lT~|W=C`QD4HPL; z%0Xjy-2T>+C5)r!>%lf5m&}?`ksxXgI$r(-botn(3qGL6&C^}^FK2u?* zoD!EpAjD54*kFgOqJE;+^uKARk3nQm%k6u^a&dx0Tk~=M^e(?(RTSMds!x1Ut7P(v z>RuwY?0a1~dGiHn=D@|l+A1oz4c|hRD{(FtokdL%+CHa4S=ru3DW=Jnnuh$c_U0mQ zRUDD1=J4)rP~%7dTqwFY*0>&cosK1sWoEi29K*RoOROCwv#x5J3Jg=6`>3RumODh5 zCvlOovGDc*nwj2OZa-51a9$B$B|Ut4eb=f1&jDxT7a&_EYPzItJ`mDRw2RxGt#JUG z5-&E%Qx8g_17y%0fzD0HuVwZ=!L7Q#G4b+W$fiu~c3Ck6QJ|U8) zo_Gpd9THG@o5g$NGe^-=Osx$y&!`XfC)UqJ@9lRkv>OLs*0P%$+~3YHBNLAK@Nw

vFmBgu0DpUV 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

u+L@`E;vj)ZRj+iuD&v=<1fC}q^XqGdSE&iCYE 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 z zPch?c9Uk+j*HA_fkICr&e9}~NBtY=gx)|lYK@jmA{;fmAtIiloLG+F|2pFgY^wsKk zr>#^Cywq5wE@|u0s6MzZ(;BQxYshFUX*JWrm4W%jVgN5(rnS|U!m;Ew(-o`tIbgNf zH@3-d2x<7lr?gdHW-(a<_-OXx^;&|A69h#P3Uf3`#|HF5jsH$OI6eTWVi+nK7>s=FsBuq0gv z+bn*Sg$c$ljN{!P?;Bv8m9A05F^ujk@Xeytw8i>N;q90-PI$2Q$LDl#wHd82w%- z(Io3V3BKm7g}CFm&sU&55>UAU%-K~cxLuW<&6lXp+Tl(n3@KpBI18epF*TSXJECYh zs)S9>am7__51b*v(8LlvhKteBR`0^17?`S!QBT!$2Qs3NC#k6!Rc-gJAl1<36k&d< zDvi0Ip;!1aO#nYy4qSBMn)7%mU|OjVetxoIVEW-Wz{5RbVjjL|%=`6*#wCV%(a|7h@jMf%roIa*MO}lHcBi`q@W)#e@$&g*oHiS#*NC1=^ z;KcTnAGO!(5NAxV_>XJ!B{H50g2dw?!J7=Xq4mwo$_TseLd;Efy0& za+0wL1v%YKQP%op^5_!;wmrv`%cDSA5JnN^d)wk2VJ3}vrEsR9a@2(MI(oiKiHEE+`WJ+#cr5YRo)OTUy3P3M)`P0fxPW z4)UEF<@dqMpyK0c%}G~&^jp9W=ATE~;3$yloaN))eMA+kNyW4vPLRJxegbjB*|waO zD&V$KQb$uOr@KAO+h7W1a_VxeR5@{ca5~XYl{3+2pODle`O5MBQGs%yKeQmV%_=7( z#wC;nd38ki4Fp)JkbC274$srL_T3bbR;pv2kS+6yyw3jmr$3rz60XDVdu!JI*Q4sc zZ@}Mgnw*2Z)i=)bzaP2(dfQqxY&=j_(Y8%wNMx|ziPtDYhXs{QC&W29OG85q-+$l) zpJD{j-PF z0Me+q!hRS7fVWzm-_w0^I8(N`|3x#=7X%TG^yPUgOdbr*6!`#Ftu7yf|9lPWO1 zG(c=cKlP;bM_+XT8&_X{fGyxk*4fjVldDh=ORd3to)%TcyO6c?^fXrdHoVJ31mf+r z+EuKM5_0ZnW}2uufhIF8*iLF`lJr5vo7#r7V?Ka5G2f5SiQ9q}Q=Qn#{IAxD)R6O| zN3spM-SqGK7v)7xyCbwM6@ow>JFsg-f=Q=)d@!5h36xhWJ|=_7W0VQq#+Gg$=^%%B zSsZBXq|RU`Pyg!g_Wa za5m!^WD*-~)s#PL8Uc+|m@vielqyFX_;^?PWD0^F0O;Ufv)!_X3nuN6i`HI)YM+#6 zU3fjg5@}=psVPK!x%{T6(cXZQ+7`c-DzkX2y{i_q8XD|Uk=JB-@R&Jn z=h4|Q5kl2B-P~66YpgCR9FBV1bR0|Lm-{$pnKJjcvNLF<$|zG7DGJUuSPvDKGV%Fw zKj@RKh|@=5awerF=dkJP&A`?52GY!F?UP_sAw0IbDyq`-m>8?v#7V8PpTBZ+czH{Q z9aqK5uEN8?(H^#lIksXd_`7Q#b!%;o7M{2$)#k@O9q!N{5}!cy0xxZ)v;az;#Od^A zSa+6B70Jw^br^PyZ0r7?*)TPb)`QJ&eDCHq3Y3Pa%y zG=VoZ&p@+OYo0O^8SvF!B%y}iL>%LC04_7OyyQK%f5pCpr|NgP%qPWjB|s>~iZfmh zJI+veXwLK@G-fAZIe+Q_!}tUYu79{5by&m6$ci(f1YiJjsKj{P_{Cq$raN5vW9Qc` zj_#tPRu&DVjVG=Oc3o815@X4W;}0}Ahy5(Tg(pwou60Kcf1j({qU6xce7TW&Q6d;a z#0pvI-62Jc>clLVRARP+b(Gfut(I4u^;5$utG*;j8o!0?QQkr4TXFHBrfchC&kyl**Y^l;H$J`sZ;3$@ zK}SU_@|<)s0wQQ?4BRKXW2X+8+isStyYHZ$r%J9lSP$3jZf;RZx_xge^{_CanCSIF zsVHx4FwTYqDnaa`8a=>S6({JShqZl*(z03RpQds0ZFFj-6x}9F9co#BppKWjT-foJ ztwlPyiWR3b!Km99(wPrnX4wkNud2q#{qe}3txiQiHy6ej`ro*02HqarkK$B?)2Ky8H+Qt?Q&hna3RhBr$zr1$$&EbJq0BCiz#T^4Hz z=_85Md0AAFXn%OT&Kik;*-%@`kM4@fVAWo3Oj-)WKcdyR9eZ&8aZ4FvSkZLNAh9OjcPOk>G$JNTQ761ZMRkKq-Yt}2gCo^ zxF#50yK#5}_T{fU6TDSL4^XI(i~HUW@pYJujOezIjVAP-xLGUw(N{9UW&MV#AHnVw zM?U#axiE}-0BTAIWCoHZS7IWceJk<(%&KvS-f;-0hy^5vQLJ2UN1VxyX7p>VLB4k2 zEfaTeYUiPDM;~2IHgpU1W#ioAg&xJQQg2P_c+2$I5u3FU>5ibMr53;jW3MV2c|T0? zILM*Be@3t3^C6mTMe89h!o?#s&wC!8|6E*g9^R%dk1yJmn$0MVn)x#?%d>lj&u+21 zVA2^kyS%~3HbNCrYPh?C=oMWtO_BJ!nXU0Djfu-#w`F8SBXe7d(~TD>i8TU5V}$mo zpA7!+8OXrF(bTVC8_lt?bNZf{#qOY5-!S*Rr-wa+mgD#~I|y9=xuIT8K>XU@>sCLz z1_^@Fngx76Se6G-af0$`g}hu@obA6J*kgMAqZK5ta*Z9rGy;k^`vQrNbNY=+bB7Lp zp0jONz;QT^51`}o{W#HY&ece1b!-a120!cBUHglF3x)HYHJoa%xzO4295tUWKAsWt zm%?xFu6=z36uA(8FN!i8V%(8-5iJ#3GnI1DzaR>lT)&Rvc1Ju|?tB7r# z^tQ7hS4|&=^c83i<{&RH?jN3yr8Qw6ky;WyI(S#|Z4_4#Ctv?av@S4dKIz|BD^BEp z`$0qZuX@=xR?xxC*~ILh&gB34#xG5lars8djD3NGhMB>68IkgbSOyBNNE}34s!^*| zN_T#{gE|pRfupDBTe~%nXB-aI7%aVR&we79t#>Ct5R#?gdYfnE8GLp7{5`FA*hLbD znll?8e?Do8q5VGQMzdjY9r>x;&U86MAxk5(V-{@!%sd$|2Rq-9L`+xt(P5=#8bWvM zRzvRrtFnJ+E!}goCAOH}?+38%vH(*FtG4BZf8gsm!mKx9XVR?mY%BHV7?(lzH}uNs z;m`Ndu+VV^c^ivWIolFmyX;}8FUV41xoJ4K~ z0WkQ)Od+~}KVda3nG)q$-J9$)(i-etWo@~&?jb-!VWEpy2j_JbRv{p;0(!q_){*n1 zagD>f<0cq+ey3Fnz2_mxI#|Nu_*VpjqJmLhOLf$Lw}MNrsWya3Amh_$4xoT3_}#;r zfo&2VZKRl#c}vI?m#M%6$1UKDA4_aXJ{(0vTXXjo!FA>1_j|5$pXfyf^**LU=mns414y=lF#Iet#^NjjKMJ93T2RX-Ewc5P)hwA!VvzxWz=dmI%JA~l3UoOZC<1z+%fEYgg_E~ z)hFVCu{c0T~={*&0fy#Df4 z{g!w2{`J1~@8kNPx)3A>FDAj{oT@dEHde2pE53& z)k+(AtkQ%hQh5r#EqOMKHnkK-r;^rd(G4HZL?;lwi-~6+IUdTLls>KQce|spK&8JkN)DT3ytGwuh&lN^ zh*(&S6{>D*v_FE%;f|AI0bci2N52)g+$Lv~yUG@_ylL`-bCf$>X-|&@i@B0N&^l8W zTn^=n+A_fe(WigXG|zZ)lak%0#Aq-@PEl5Ja*eq`zmIroxn|24c-4Qh)NkjYss?9g zHBKympi+WME{{98m_!~*I+!>VIMm&(``1=3bA&s32&FNbk=Ju_m>0zWc}nV3daQrf zd`IPHw8?ay=wOyAJht;ENo(&zBb3)g(;(=mb#f=W@sG28(56kP1=8aTFei_P)6!;f zqNf{ICQlESc#qn1a=Z1-?XC8d&6mmPxCML)GYZ*~>d3;{>JXcq!wi_j$hf875N7lF z?T%}X{-{3Mbf16t{subGI4aM;=4d4>$U{{!%Z{34)s~zP}lQDCldIL65fe#p&#%Y_? zKKG+x&pv=J7w{Tcv22v@qOql?Y0;fp83VUmb!GP`9GCGy)GaGHF&PPku};fv!6bm5 z&U1N{@C$CTt5)TQ?lwp~FDzpYSulPn|voI8oJ$6r>Gq9 zn<7dP4{_wQFigu#uFN?k+udj96b_x;LGIc8Nzg7KSj({t?pCb}H0!O2?e5{xPUyhu zB(O;C57s}~(NC_>D_|lV#5fK*nn`s#yO&xQUj;@EV zCwSQYP;(>dwAOK_X>h{U*neemjk|s5T_BkcqpjKK+^00ei~AHqY^sd8HYZ(N>AQ!l zGLP3uJmAiyqk2Z^+*VaXRT}#% zsSoRZEx$Zvdk}6ADoDt0&Wg$XA<|{t+OgU1IPqE3U>#WQLKR7bKk^idh5ZC8Ooa8& ze=iIBDONB|6b4pDSB@^D?@&-ghpD*ln93$kIMSlS80;B5k0KZ@$g+)UIve2YG!+F&;v{@kD^p|L7R>}d+1Af<2)?@6?f}Rn-3-^mq+FoH!U#JdG zZkt~pML?X?ri*I*4GkRbYHd0}5O$qGY4u|So3}RUmM3LbYO;o#zd!Yjy~9UbqAxQg?^uT6y0CZMr9}rYS|dBV?3*gxJ;dvlAhn7GQFY+rq7D%CIUR_t zN)@LMAg&Dxc5gddKACMQOTo*s^uAYi_F3{ioIH&1sH@WH6W+|=MxmO(_D*Bb8k6~p zb}c|7z)J@49wGt}PoS>pu_QGm;r4uQawRjSl{D65Qt7JgPx8VYALS9E>rCdL$)$uV z)YK1nOBbixPXn(tcb=^kuslZTOwkEb(_J}CBRYt-T0gR#&6|qZyc_;N>GT{-dg04! zFP29$K>mC&f72PYLLXSlS|E=S^U0jJ@uv z_S0w9hbg5DP5^JEx&@2vw9T1z&IlK99);eBhmr35Ekeg_@p~G|V~AG zWW*w0!oDA){lQD(gdi90h^a4e=_0KMFU~p9VLd)s{Q>3C!Wwt?%^l+uVO10_eNhhl%Ii9v z{m$|6_%)d^ykUYs<`wQhZmX8;FI8Zymyh*-!?8DEAmy&>JsH}>)!#N(QRiC%Pmp%; zcmJL$l2Ei`%r}w$(iFV#>rK~x7k;xAn2>0!8+oE{naBjTPbAs-pzD)R?2b@NE?kDO z^l?YJGJel0UvPr5d3u$qmbA13x}Y!Y8?U3NE_O;?X+1Q0=eA3}89Rd;)8VSwzlZI7 zvwEWijiKKD(iF1p%6J83W5e@rY>Z4?=BZ-#^ItjGmV8QZ z{NcGoZ($V&@)638yXpa!ly^jvqoJi|FJ-h}AK0y)G#b9le%yB^O)suTI2etEm>lQ> zwT%}`UrMZd?R=Gsn$oG_?@4vN;Qs!Z;L=U~#TkTWr|pXqU#P!f*3NwGw1exH9OG5` zDI^V6h-FVDC$K*Q9h2X|uG4(8HHXbBf6+Bg@|$~};^%K%INbG0z^-$zALt00*6063 zuUmkaao%m4;5XObwhkEEggNUvU5ws6K5$1%#=YoMUOZ>F{xRFQ{GbI#vLwmCemG>y zGG_?W+XFM#MCBKA{0aqom>ly_-m>EtXHT{&+X1E%+l_VZf!qtOH2O-Uy$}IpJ|OZC zz;ZoE64SSnWZ(q4_QfCMD@ZhG}gW&H*wz$w5h$q9%llsBS_KaKVo2Pdk ztz91B(pI9vvQs=n_RB{~?N&5*HcfglM^Ud5p<8)XT$tzlY|~gTAJG5$CwWaQ{R-b` zdE1{$-EaLW>}pLUh5h5|tT_z{tS^+XPu9Ud%)!7=D568=ij_91UXAWOcK4=2S5BoR?d$$71hM5-Ar2!2|;@>#3G|D4s`?5(5sp2T0Q;6ZjQKr zO?*J2Y#Du)uRHKlQzH}@92)c2up&@6t5ZiHNmc~9qbiCA24hX6CJ;#$Cf!~JxxU_j z=Bk^YcxHu4tED2GW+`@~ei)*X+*)bYLbWzyi8fP_{ZBq?v}+Z+Jak^QCX2~d3$(Zc zsU6g$^mnvtZFi+7-xD_t%bx9YT0`L{#Nz5C4XdTpy7@c!oe4iZyuLTnpfUz4N;arg zcz|}icPmh>HOfjAUEVA*l@*fH4Q^r1tg+&fO;&O z+iu_wBMzWEs%`4*77#*@&&Y{+KA7i51fz68DuiE%K^ScB61VBxzu&!z;`jgShJc_t zupOUo{j#gN++5lPdBt|O(|sR+ z1bjZr1=`Z7p=LAIP89@Q7yCt3NNO`r{qwc!m~Op(-75tqX#B88u&C)m=TU6RS3J@z zU@BX^cCW*&YlMSVfT0}>oEs%>5>+ox4FNYTpz4^U*qnE6i*Ic#3CB~d+EV5N;YP(n zSN!*xNPpZRXR)#NnBA6=lg1d|^w~IvyZjktfq~&!y)3u+PaBudeE77m&ahY=BHAR9!c86HM5^cRpahs3TV+|D+pQ;9Z4#TCe^C#fxxCLd_u9Ye5|+IyolD9Ddugw0F=xMp)?MPO zuAn%qE=^(XL1C?8KBbf!UoVlYR}rlX#lwnc(^ku+Jz$XH`-iH-uaEzlF$f3{Qne?|ACyv zDosg)Fro6D4_TELFKh4C(?kC)ln?tY?xcbkT4Y@M<{Gm^Wg>n;^o&rnOH3IBBQOw; z>@X_aZA-{8F)_nM@$~ub??)1;m*QH|m?8!}#jV~@Wh6J$1%LxoHr#+7gNw9Yy|hi- zbS`6*U98Upo$ughD4lhfZ#FC7Y3Mo&Abui=wB1t<^+DoAL&o8qhf=`uq()*@=>b%y zx{+XC+DJycTvt&(*={;RQ8)5Nw&|bn-0*{`%#wj%M>A6f9Xfd-L5Uuv#HQX%dCw)1 zjK#3#dGwETgw2MFOgeo)J_q2=D6!`g783(;6-k6tB;-@0+~v5(B(Fef)B_|A*k&`! zDt3SsG72q+eek@DRm}1voW>m`tGlLt?Ib*lIzQ;gXXZ*6o?E*9uhOW1nX`Z<^ zytaP-fVUt<`~A3J%({4?(WqdY4k{BhAyBQx@rm&%N=8uwEbVL-+4Ss<#vlG<0eEETg=VcTg9leL*a01e1E<;K3 z3457Nzgn%rZR`Ls^-FSYdx0fH+k~e`;gw4{(5vtrBMqE6C-~8MT6vE9H_SyEG-iO3 zIhKyvMfhB*Zg~yqF3ZEv-4eILC==W}&e4K#&FH0sRjd<repS_58i>9XCq z(^m5OP1tIT-Ri|)ay}8V7aav||EG2chzntkuj6D)w&u>ud62tWQU$U-*Y<~i>rdGR z(XS%RAFqUB_f$z#Wm@wt!Rj98GiP`+`GI~#grT5(Xx<2M>F(BuG!2NVa5+k&Sr+ zrSoBzyGH;_c{db9wQhKb8K5a5?CT%*n^RyNWi#4MAL5XG70~<~W9m911}rC8q?fkE zbPFkl)Ra5ZaAvh#lW0quj{|Ti4WK6toK>d+{&5aWf*Vu)hrAg7uTK*B|2D<4;%q{W zj%N0z|D(pHYWVp6hbmIGerdw&M{2c1-^yx7RU)?$*rKYePlk}qE>##wM`xXDPu{&G z>ppmCi?8NILs|3)RI~y?U2C}9wF%2&Be0PQCT6Fk1E z6B0k&5fRfd3zDyTUmjb2@_7i7uYOA(VF-ixEg4$6p==rf`cDR!m#7*)wQ&+*cri!K zk=5IZgfZi1?l{p~xEFtwl76xyOVBY?)vLx!)An3;y-lTtwkh|dnb(FqNvs)N&G!0r zY|UL>W9(wU(v}%Ty~m}{5Y!O|b;bIU1MHdB)D^X`(i3ck)i(1J4lZj7V2hR4w&V@2 zoP-#S5IT?^*wIutch7Xm8V=21#6Okp~goNnw}mg8H-&^z^W|yibQ9ED{5?& z3bcBFw60_k)=n9nq$TTCnULE=`fF(~qTF;O+kA`>Ce#mi&}+MDYz z2)fZ|Co%1-?X`Ljd8ZZMGS2N~+F!8?0ow;pV3CVc(5*wmzv#rLCz*dL;xaes(HbGZ zUBoSnfvl3ld3dK#cC;8RXV2u%C)h8JxWRGE^{_HPxKc>r*vQb{d%6m?^PilKwgK%r zU~&1CpRK^AYN~K|3SMl!w_M3u=AL3F!lH(3Z-mt!Hr+7dO zD|_O>M5(?h$MI2PNu_pzrOp5{WtE!jtxpFafYt`FV7$;;S7*7Qiic82^EF(lY|K>P z(2=q0#IqA@SbN|!#2s~qpmKiQb|mB^KoKQByMroNa*~DI6le^2+>sVP@fI-!m$UN5 zE@AhDFCp<3?`yf*JoioK{B*(gC)`4C(gtGXjsHf7FzH9^QDEy2E3m!83TVtfm{SJ= z4EEBo{ln5(w_-f3*P=s%Upb)F;OIJ(hw6>eHtS^>tv#{oBTx*?jClH!YK=(h788PM zf1>@tc~>8@VPt!Ur#+0GLsCL|*B>Idm4FKv%qMw{2J{;z1nCnevA)BFU@ta>fSB(# zVVxYyUEs0O4y6{cshE2&s4{NO9hB@poGGuLums4IWR^_1BkY`gV=EXbd^WWG68P&a zUkR0f9$`+vN`D8ni~o9#NyWikLMK2PYW>*Oaw|Fe+x0r%kk2>5rgkt=Jx?QJr&Hc46d$h&!Q`Ha`{bPc&~XVP=|{x0+3K;N0BN zhnG4Ev;}Yg!^6i__^6h?rphc#>F{~%hHPL;SpZ6iUmU%gqg`g1KC&pRzVmF&$&is~ zd(-zcfTgGZIql2`TU=)e3)I%HQ#&s0O>1;n`il=Hvx}&?{8oP8CFXTjKmhZ%sF_FJ zi0SYNqTd{vm%{qbdWxG|b?4OdI7?}p(QD3tb?B9227fOt&E*?hu0BYz=K0>#a6X)w z@?T1w!1DU|t{J~JJGiVrmYLT0VV%PC7*h5R&C3o_{FZu+nm8(SxpyJO<|njpIFH?G zoX2bww1+I-AOiv@L{_{B8@tH9TG%hSFk-LB=dHMUL0U(FXTPN=Q8L_O-f49us{X*V z5Cyy2{(0cz(v4U52P>uq$R5Gji0`O&ICx#*WDsaffGhXk$zirlF*AO~;4N8nVAA!o$FaP_wxmLAKn& zwscU-!bejg=85u#!@!%k%c$w{#kD&&ypmSbO;FUGG#p{p_Mtr;@YBN2@&|HS0jHjo zOTbMZ%HkzyMxm+?ijo+>T&ziI8>;wY>NvvKn<{ihUR~&oD?6uDzfIib6WEjd-4^Y~ zdpe}&{Z1f2I3eKg_w(MJGFEXCjF`J770*HE@riELw$vgc6vJ${$d#HSbV6%C5Vj@4 zEYyMYl-f(vk^PQ68f^LgxvtGnT#$(VaCc(dvicMLk`y4!bD#L@ObjeQ1%u!(IXr4u zyfS3I-WnbyyO)N1ZWPH9D*^Rlj@tau63P4)DyY6Jy`d94hJ5^v#Chyklrf$9tz^uO zUwiuU4Z>a(SR6+G10r$Z5adjQO%Y^M*AFeY+#JC_dZ>cz5Ej*_{BXWrlsO}R_)PpI)vu1JzGKT{ukQ;aQN2v@=a!r`haBK-5JK_iRQgN_%8zg@qS}qXa*-kej6nJ)ha>pe_M0@XTMRgF|##ub+G@h zT|HJ^#}P#h^HV;k4OdLcEMk|(J=bUA`ehru@Q|t+P zf`P1HIP1=J@@;@e4aM3G#WKjbQ`=p}%5@su42gTrpK;GF!?E4SNp|_`yBol@`$b{! z<#R8iS_DH7Iu9*2s0fLs7~Fv%#Z)ppFv5r{jEx0l4Jkwk6ch0h>RANFNR93d3Es3E z@tqK1A4!1uDa;?pba$x=SU$;HIWkj4OdTf}*r6xxhDE9I#*NCWFHhsE&+ zeP}V%sO(T$UCP{3mu1mL`~d20K^#24F2)()Xe^4Qa*$P2e)y4A`P<4Hy@i>4&J31r zWS%wMB)<~7%sO^SDl@yx=vwt5WmFD;=7MnAWZlz#h8QG_Raeqhokd1Sx05hv;IDQuiq%7ZL93IyfLk~zT*@DykTBKSpWv5cItCqEV9L00BhwQA+EdtPgO>} zGFvt`S;3_c?$f08-PE=o5Nxx>?3!5pSICH{;KVyAjhnoN@*u?cuCJyPo0|zNH3dhd ze{M$6cHomsW`Tpxhk$d+M7SRu zwCxte5%x>MrK-&w_RHKDVD$-;b-{2ToI3D=br1 zM^#mMP!N{6EA-u<$?&r;rB`*~k(JL~y=3uGI6;$Bt&uFz_$>{S_Ksv1MV(_f#oL|Y zDenKK!dn@2ll<~{q$jV_ta8Ltn$hLJ7#|FoCK9Z023L^DYBfj7=R!sHDuwFIm0)kR1}aedgA1MyHC~7dL5hD`r)ZRRV$L88OQEGw6zyKNxH+ znEP^IsD%V$KvU#xP!!h&?;u1}m&xGZLu~ki-AM%K5;0ogV%+C@(V#(LMmOEAyo9Of`YyQV!)#1tO_dz;(%DvELqaT zF>$e8-2&a2Q_ScU%#W(^z?DE+ClH@oOU?`vZuFeG z(0%7tUJ&lyfi}`V4ZO5>!YKF#qGf*WdIndX^X!4?J{LGtGU97<_a_OA;0#g!OSxM)&74tJL{OZ+pb%;P@uTG zyAJM9+#LpYcXy|_yK8ZGcXxMpm*TEHJg=SaBwwE7Jq(jfLilgb@4ol7*Io;ah53W; z^fgv}NjAUp*GQGl@k*8AVVC^+H2BYPZ-f-aEy&F9i~p>*4z(ahbC5?dw>1`n7AKKu5>S7 z3+T@9k)rUKCngD(U630mNbN7jo}*mj(>#B(S^n7%Qd8D1N*G0sD4-~?p}Dp6nJC=y z@qD)Cvb;R_j-11;97`SiiPa(LHR5a%?2mU0Rd{%W!^^fgE>n%#RxEtCWy2 zWHU6bL55)DP7I~pHJcrI;Y9t(=f)eRI16W#MrF*(X*YX&AO`Mr5Nub)apm7RFHg1g z8jKI~5+k(Z?=IO5y}Khumho7;h&jx363lq|Y|{pcYFH7TdhhjBg|QgDmNubi09*wM zRm1$x%*~6G@CtRJIBp>Tl%U)ZCpw<5V`d;MvFqRh#n8w#1=ZEHrNn5x4@+3{;m0?$ zj}0;1mHfd}xc5dk>eK;9{EnhpTWw+&xi1;q8iL?lMm?2 zWpEQivcdzSf9R#zPf_M|-@d8Lkn{m*_Ar&J!>QC&`=B4r?IM#4+c@+xsi@k8X)qIE zcazv#!p@pS+NCW6KcEuToAp#xM!wo#%d#A=Z2fgLDt=}Pnfv6J2!CrY`uF7`Xk+bY zZ(}K^XKiFj^6xlu;J-XsSXGJ#7;wJnxY_BeUzx9Ts&Jy>_k zGGf7k(_j1ZcHRqrLp$(&O>cn$bwl(}&#sljIKHt5-fzM>LbG0Y9v!rhbP0v2hb>8%#g$r+HCV;DVMUO%RF=@fiU1UP^PYYsXyRsL-5I}dQc!pR zcdr4~MK&o@1eGEi3EainV$fA_*h*b^F+>TwJ;fV=$t~5rWj-6D4xE0tu zQ{jt~L>|w_g8d!Z$e?XzKB{5;XY472!{KAB9oj>zQ|asQdE5_wq)fLPa31{(8ObqH zscrCiyv_a!;_~GE%p9AZ72<#&`*5}gU!IXMfI2=z=D%3x`*U6q6XLA*;Cva@J7|v4 zLEhcJ9?u)`yFlKpwPa&r`GuwSqZUm?;5MBf>RMne#kyH@i9R!!bXW)PMpnH>Q`^LaSx&SrGnN-Ja(6#8 zes?hATv+-g2icXi$n-C7EY;lbGusBO#ufl6BCU_P0f3Oo?EM!zpq=ViKfV5N>01u0fe~e zJ#rYV$!k%z*f6(=te3Z-)ax$oh4<#NI50L8RFoPrDLTBoz?DbC?KYccbR!8KCvH(| zhW2!J(^=L@s(Muev4oL8LM=P)nzgfeVFWNnL+Z|n4J>$^g6Igy1q{%Th~zACzuK`r zxtM{B>ccni2J;xLZ#Py=J*2*e(0@=UUV7sfnYJ#R#9S-ui)Trq4TA=tHz2@++Y3!Ij@=h7!5WvH6@jVOAWJBXzAzHtvndSOd!@c+nse30-My_rqTqN2{y|)3 zV=zom3-%@8XHFde%TxW6-(*xW6ks)D2z14h=!06Mkv8o8wOc1I+Crg=C<>_<_JR5b z%xX&DD1x3evW-+iA{2(H?B*{$KaXQe!&Z9##0+3v%xtj=m&N4zFqF9}SbV)VMAb9E zLVQ+{oK7di?sJN3LjAsAp#B@GsS~ zqO=3HFoKuCNSw0vP$LpH%8+8 zXmsm0cp1e&s&GZSZ3$Gi4+P;yF|k_a0F+?2Ij%LELY=OX=e;T&A3>P#;sS=qM?eN& zs9vaEs$Sp@Xpja>8H@!Qj}V8nZMt6O4qTsBP&5WB1}ngFM>`E;P24l1pB%)@XY6*t znB@J(*BUb2XBR=b?;XcA-Ig5RH~iM)7jTt|Aubj+dU<)nLH4}GMMhzJht6hIKvHii zxnlxbEt>-;DDJRmNeABV3*x#{x*CwF)GSO2-_}Z35@-YC--umFsGX>Y*5woju3rly z$=>bd5@XSslN3oh+<^pkeHN}W(!Tii7E{HJu1$ZM^goxXNV}xdB>5xrr4u@W)VCg~ zsn+$q)(PX}wvuv&wlW7?>-S<))~o$aqwcVzpNp$Kv$5nwSsRD|uiSNpQ&o0u^RdmA zY&v*moxfRh;8=VV;2NHDbLtPXR45=SCF7jz)LjHlRkz<95WZQe2zv?Ww{UB9F{uBB zOdC7+?9LxwTCe;w-~Lwdgn>xIMV(NpgrsAipA zX3p>xX`ghv!5;N62^DLp6i@K&{_?FBseK#yj*O9t60sGi8poes;Ik9q?uP}<4ySwz zU@~dkbAQ6lA8u)>ez#-Zb>v{Npq8pH1*P&}D`Ug^PcP+kgUv0T_qwkP_*8W!3!(C^ zS1ob$tGUA$ea(;2Gocu*=5QzTx$`V#nsPdv%S>&by>0)HnIixtxIz;4nH8L*uHuj- z%wPgUu!jTsGc4N!o8xh<^yWv%)Wz6W457uz)6EGF3@gw+gM?ZApaY^da|76uboD-) z&fZI2ROj$0?E&HP9i$)UDsqJwj!HB936Ww>rSKqF%H&0fM;=q}xeAZde)K?W>jW->FW_o)0Q`64M? zqFa<9kDFDE^}HMW;~SGzjfX^^K=5QA)G8M^S~<9?2+m-P=aM!h0XpS7R%fo9wh3(4 zI!QDw``;s9R#@V;dRag@c*H8PN0uV@XZUZ}MC@n8WMJapBlfXH?CoRTDq`OnV=ZCh zx|rfllo(@x{`;4(hQ2ONiyl7zILF$Tj)L1iZ(NXnyRrMfN&)_JN&l1rn3!4r~I z{i*ly^_;i6ikoC-+2Kxf>Kgy5Oa5}4%nD0j;K9(C$>Gwa>vbbF{r&dx^TB}aymJKi zyI-6kLm{>|!Osk=NGYQyoa%E1JmX)d?<(FiMy1$JWH|+9r~QfO{ZkO@2%Qi*wHD24yl(r;Es;zG-DT8NlEWRp3n}hCTkVJmtZ2hQ7WOih~j;Z$@W$+wkib?2_ zmC@K~j_M>CX)hf3B9$d6c>{^2a4R#)N=t7kK6!HM-n(l}G?3{=o7^HP0x1|1f?(PY z8Ywm{hRaMuk)nKo!q&_|*UWpYci=>w9D|7blbqam{zkJYkuCrCGM2UK(6ijYVD*t~Ko$DWm%R%wZ0 zP3r)4lGB;6e8<2ucN+-%YsUdh$Mi{|`U5H%lQSJ2zwgQmokqbzn5dy@;3_i6rY2g? zAUu9-n$8G1U0F3FCU@K+(84TczdBr4J<{R{Zp$Ksm|t6n{9-0llJlA~(-j#PHrP(6 z2Oi72Up8y+{eb^H_k>w4y#Pw;7))EXkT%w9!~)kms%_74{62%-Y34&Y+1Q~BTH;4e zUjIfPC*61Xx0$+H8)2i@)0Xht;)|P%bDFqNGoy2|>5)_p1zEpFlP4(-^3`~hWY;C} z3UiLH9jg1vBCLxnZK8WKSS8f;Ai}{ZC>VKx{;3gtfrcBAU$LEsJ%D9SK~CT7)@fU^ zrumm1{1|bbc&+v-X##OHu;#dw8foNNWhvawOb>V28T-}0r&-E7z(3%5MTbeW%>Pm` zJ8RhSaFGf)f#`(z`AZwDSac>{mYIeF*p|&fQ3-+(MDPJ}hdH=c|N8Am>i&|5gH3su z?zj%WsO2`RswJeky9Vl+wO|`zj`Q5I@FjgWObf;_ANW}hr74>1togEk4{({d?{3$O zsAqqU$u$WU2a#~c?SsrS2ZBnK?hqE$anRdWIcMv~OJ{&wYglBC zG^!;7*aBEftVM-Vso7vdo9tp=61Tk0K7Ed^O$RQ`*aMN8b`)BiBq&c8jdSm0Uf{Y`Vs%{hWPQ{FZO|Fa74|A zm~z8EUr24hwzsXPTTxNXPc_`ut4hVA!0SIHcWGo2!DxVo`1v3Q!7}g zaQulMc&YPOKYdyVMeOx?*AxER9Mk{R;Ql{Pc41dDM}7lGvwu$;0u?QQf1wu*bD%)A zg8VmmJTyyrY|qcQo`l>51w{T19;CF~rG)xq--Oo!3Q3YpJPMJc_-Y-gQyQh^n;jUS;}DGiN`5|nVz zOdfsNDDKjcQ{A$yA0@SIz9ff%a^$Ob&~)neOvss3P{+bR<729-TY&Xqvqd^sqUg7C z=rwCOR9^!I5;EK+$gG$}OY2ZV?U*t@LM3NS`IU85#7!cd5_uA>B(y4k-IV;RF+H9l z?yqTX?%xepi}_82%9!A_Gd1maDf*JlzL+PJrS}1xVtb@(iFmPhf8ZrR^2{+sYJC!F z5nQ7zWcQ;=XcwDD=sll=g~99W=d=KVgx)_>*T2*|va{3?ncTFA~KZiOUMkbkk&EANN7CXfoTPZNOxDJ28D1 zTiz}$Z^M!R*WOJGs`ceigt^-W@vjx+`TVUH{OE>zPBI3?j4(}MyTY=`%|M1V*~Xb< zx}&^?ovVt6b1)x(=Qe-w9Z?7lD|(1Srt~vSYEK7oN$El2&OzEaCm7n`=NJ-^f=PzfniHJj>frpr9 zzA#L(sZ>CZyk_A{$QO+7Bg6g}=48p=ar0Vkk?S4p(a)`3r!`25#G#LMl7+Iw#$$ea zCQOq6%k(q}Tk~&VI)L@4qrl?3WuE2W8}xdB&YG|%d{Jkh!Y&EfL-KUa);c7o8H!4u zDVExPuLqwiv_V%{7Tj}5x9ss#l;Y~L=+O-v=O!_KtXIaS?*mxL!HM!`0}WAOebdkY6~%-#WCRcX(Yj#eGYGMK#qwq86M-~ZE_=oXJ0){RlXpLOo>V#S zbM~Y#bKtFtI`6BF4gQ49ILKt#=T#BCMrd5JvWm-zgPY84nn`&5&xp~MDWwv zb6KV>+S8VIlNS_YFeGB;%U}$Wp=L}Ozh{KSXR4<{3?=3g*lRq2H34qF^g7Z|L`)i5 zTY1D9qp{(--W8Wo$7-@+h%t=^Wsb69#W-?M19N@G^k}KI@&X;wT$zLU%=VmeC^j+7 zd5h8v@_uGu0Oc`?+*A@OUXhA zP@4kL1V<x_m~LRuPl`QSC@AZFUYPJGoCBrL>A;(zSh&F=I=%|e&8_ui7?MMv*26i?46@!R)PpT zy^_~!z9h&~0SnoC<$$qzc>vWrj(Whqvvr25jUFRNUNBFpV!yB;kBB?L>zxtw@1h~X z_#=zBPm|8cJrvkt;IXX9Cnu?RP2Npw4qE6|IE8U!;C}1q^A(;%L zC$t)o>}Ji})HES2Q73G`>Dv1I>f&RYH7BGoF2CKy3SWvX0sZgjuBay;wY5!X2v>7R2W+U86)*V4AVS-iH8rz(zu@LKFZ|0{ ztXwVhcN4eZ#Q=}3hkxk25g?KEh(GO^$p5yv`!68nzvKM=jnq@9%LaP>h19jfLVhuO zrs^|;{u8M;PF8BIj{P)ceuRx+C3;{woLku{Od^i1f>e6xPk{eI13i{>3$$_8nx8 zeVz9=8;Z()^(M(T|RSo zQe(q8*|0KnbZXI@h`eoBc&D0iAn)A)cwbvie9-wmcWwcdV5=12y!HV6z=L)hnSSPQ zOqCEGd@1)sA`g9`k^zivwRTWW$k~3pB}EdzqVXg^4Hkd> z^c+X6yW5MLl`uSM8ZV9Y?@0-wwl3}AesSi(SQWA`4 z(h_{9Qd=aK&GdQKAS64fuh;BGvE~EpORr<;c4Qx;csWcW65Aa3Hv1Rk-B_R$FlCP8Hf2$*9%426A5$R5^r#0L==e8j~)IL4~r z7`P=mTBwj#V=nwFWcVlIxk;eH-GPfP}jUMaNT-zJ-%;r zJ$iWF@Lswh6qv^FL)-Ldz_w3xd!cPcxu}x-VzKKAB+OvB`U!@71k*m}7w8c7L<_2h z9FK;!S572@>Dm-1(LU5&ANr`ST6i3O%>xa;n*+5T_L%t+E>o?rpe3^|stE3-dPLEAzJAOLrxC|CH$dwwkNn#c-_w z-PLyqc>C(Zd_@lG(>U$tK=zUA{l|RQ=JfGW;?XDNsVLNk^1}!FDLRn$Z3!s^LqT7H zQjJ7k0`s|G9i=rAS9 zW|t=DTrKj9cp2`K{Gh!K{#8lXOQZy6+-7CDjf0}#y~&h#47)vXAJ^AV*JNJq--RRc z@nW2*Qm~lj0zp_4a-V-~R~$?&J~^z)uKm)XbiTb9wmDa#+Yj3wknP!&eB;-VpxX8v zLNI#@-|APOr9FC%iH~bL=QYVUtEBz{N$%D!?s3W`N+X?R|1JH}m7;(TpA2hN9^WjRacbrgCG zT6I;HbiF1cLyUgc2&Fer3HU*1vO)onfig$e|4{*Zp`5XVpcxeb&I~(r=Y&~F3s?nf+D(b|E2s{a5$v$Bu~F~91zA^X#m%iE1l)?&#tfS& z3;j=x^##2-MedeU;wPK$CXF>t4a0Sh-1KP3(BLxyrmk%BDb>~ zf&7KBUngGYW85{c0`iT7U$U}yu3#jH&N;tsiL9sO!DfP0z~uU;A6|2Twem(N$2vDM z2<|poCR#xUHCFqUOmCWvq1uy<km60ESI?skK341adTO=s5{@(H}lZ}xuf%w zR~w9_83aG&e57r#D+XiQEo2ZlE@XbL=|htlBLna9jm(nJXIH(YO(%G%OPcCxcLQrV z>Q)9;O$rc+CpCexSy=8cQdN_RqtZ=5C3ys^uqV|A>MJIa?4jLuA~Ar{XN* z{gxag9o&U#$X?}M95j|5XNSAT=ad!FmK5jFgRC?A()Tr@(H{JKV9l?^VZRTTEwdxNU5( zaHxbSmg1Hp|7|2IL4(yte_L&XODhW-4o@qua<_B<8{|>S#7zF^0OVMb@H8W9!jdO@Xm%;rV{lmQ?JMca-|2@>Uv?fSvQY1Bk zsazyubz%T+`e0yE`f!VAlK9!cuTIwL`b269Z*rR;xRMB`1=RIo+<5{=*=noukIDLq zx=;$9bNKgh*dA(V^fUNAl~`M|_7$t=pOrnfFXiJAPslC0jc#y(kk07E z%cs#%&y=Mt7W{TC-(9HmSkNDuIimy|+|aI?e_LFb9lV?Gf$(#71#W8hjswY2?;#he zMon~zEyBq8CE$~1o)sUe!ChI96u{%IOyP0$<>`#Wfx=;KnzsHJHtsBE z)liyPp^Ldpj=V!H2B%b=?O(h?y2H1Zb-M`K7~yY%WREt)J8dP#UuW@Q?^PyQv?rc$W6mil{0A5wPFqc%Ng~Wr-s6jp(}D)J zgg2b-)t2W}*YUL&LkoV~!>Vc8J7OiiJ86)8>JOz1)O_w{#c6D)EQ)Eza!*a2)SO7d z$0l_xfcsy|LzK&=5}jt0W)=#7uKk$h-%pZ{j%#8neHW>+#r)L?5mpuv&9Kg1Da#8> z%4x%47mQ{i)hyFQDu}GDsj6t{CUUK@G+xTg_`Krft%R@(r&EO6(+(}qt7yE;3+83@ zHPBR~_9SZU_8!RURx`TXm3+(^%VldJVW*VK)Sr) z_-r&KIXt{mi_Y*(H&g>VHtj**H`?{Gy!z-(b$&=XBA{n=!(FW?GMHjw5%*q-WCa$55>NQs_e0Fq$W2y?C0?5h5mSX$1l^-Yv*K{8>VLV|Z^Fk?M9)9(8VUUG z#6G7*K$tIIJ~01o<@UemtN#;?^eO)N%cY<~tCbd{0pU%!TE9&WEs+bBO*o=38*=$o zkISsd+GONHZd*P=B+o_{B*k?H^hs`zNiY@*G(p<=A%p4Dpn!|__3SbFmsp-3DYjtB zNKdbq7s~y0_aF`4Dn*WEPXoP)URKY=P4)0nZqRp3i3SR5chB@!z)qmNL&^Cw0B<

wPVBDRyccL_EW2-B6d(A^dS=* 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 z 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�t3-m^QP);&?TXE;Z9f>gbv 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�E$O}HlmCCLj5(MnlM%f89JLre#AKo&gY!Y95c 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

R8E){nb9be9~fK56@d@KCA*1mPGU`-~n@k=Z-;o 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 z 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 KJldAAfbxF@s|oi2 literal 0 HcmV?d00001 diff --git a/raven-java.iml b/raven-java.iml new file mode 100644 index 00000000000..87cf4cede75 --- /dev/null +++ b/raven-java.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/raven-java.properties b/raven-java.properties new file mode 100644 index 00000000000..111813c44a6 --- /dev/null +++ b/raven-java.properties @@ -0,0 +1,2 @@ +path.variable.maven_repository=/Users/kcochrane/.m2/repository +jdk.home.1.6=/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home \ No newline at end of file diff --git a/raven-java.xml b/raven-java.xml new file mode 100644 index 00000000000..0e42012e9ff --- /dev/null +++ b/raven-java.xml @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..87a3423ac9d --- /dev/null +++ b/src/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: net.kencochrane.sentry.SentryAppender + diff --git a/src/net/kencochrane/sentry/RavenClient.java b/src/net/kencochrane/sentry/RavenClient.java new file mode 100644 index 00000000000..206e54527c6 --- /dev/null +++ b/src/net/kencochrane/sentry/RavenClient.java @@ -0,0 +1,281 @@ +package net.kencochrane.sentry; + +import org.apache.commons.codec.binary.Hex; +import org.json.simple.JSONObject; + +import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.UUID; + +import org.apache.commons.codec.binary.Base64; +import org.apache.http.*; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.protocol.HttpContext; +import org.apache.http.util.EntityUtils; + +import java.io.*; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.SignatureException; +import javax.crypto.KeyGenerator; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + + +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.util.zip.CRC32; +import java.util.zip.Checksum; +import java.util.zip.GZIPOutputStream; + + +import static org.apache.commons.codec.binary.Base64.encodeBase64String; + +/** + * User: kcochrane + * Date: 2/6/12 + * Time: 11:59 AM + */ + + +public class RavenClient { + + private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; + public static SimpleDateFormat ISO8601FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); + + /** + * Computes RFC 2104-compliant HMAC signature. + * Based off of the sample here. http://docs.amazonwebservices.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AuthJavaSampleHMACSignature.html + * + * @param data The data to be signed. + * @param key The signing key. + * @return The hex-encoded RFC 2104-compliant HMAC signature. + * @throws java.security.SignatureException + * when signature generation fails + */ + public static String calculateHMAC(String data, String key) throws java.security.SignatureException { + String result; + try { + + // get an hmac_sha1 key from the raw key bytes + SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM); + + // get an hmac_sha1 Mac instance and initialize with the signing key + Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); + mac.init(signingKey); + + // compute the hmac on input data bytes + byte[] rawHmac = mac.doFinal(data.getBytes()); + + result = hexEncode(rawHmac); + + } catch (Exception e) { + throw new SignatureException("Failed to generate HMAC : " + e.getMessage()); + } + return result; + } + + public String getHostname() { + String hostname = null; + try { + hostname = InetAddress.getLocalHost().getCanonicalHostName(); + } catch (UnknownHostException e) { + // can't get hostname + hostname = "unavailable"; + } + return hostname; + } + + public String getDateAsISO8601String(Date date) { + String result = ISO8601FORMAT.format(date); + //convert YYYYMMDDTHH:mm:ss+HH00 into YYYYMMDDTHH:mm:ss+HH:00 + //- note the added colon for the Timezone + result = result.substring(0, result.length() - 2) + + ":" + result.substring(result.length() - 2); + return result; + } + + public long getTimestampLong() { + java.util.Date date = new java.util.Date(); + return date.getTime(); + } + + public String getTimestampString(long timestamp) { + + java.util.Date date = new java.util.Date(timestamp); + return getDateAsISO8601String(date); + } + + public String getTimestampString() { + + java.util.Date date = new java.util.Date(); + return getDateAsISO8601String(date); + } + + public String getSignature(String message, long timestamp, String key) { + //System.out.println("message = '" + message + "'"); + //System.out.println("timestamp = " + timestamp); + // System.out.println("key = " + key); + String full_message = timestamp + " " + message; + //System.out.println("full_message = " + full_message); + String hmac = null; + + try { + hmac = calculateHMAC(full_message, key); + + } catch (SignatureException e) { + e.printStackTrace(); + } + //System.out.println("hmac = " + hmac); + return hmac; + } + + + /** + * The byte[] returned by MessageDigest does not have a nice + * textual representation, so some form of encoding is usually performed. + *

+ * This implementation follows the example of David Flanagan's book + * "Java In A Nutshell", and converts a byte array into a String + * of hex characters. + *

+ * Another popular alternative is to use a "Base64" encoding. + */ + static private String hexEncode(byte[] aInput) { + StringBuilder result = new StringBuilder(); + char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + for (int idx = 0; idx < aInput.length; ++idx) { + byte b = aInput[idx]; + result.append(digits[(b & 0xf0) >> 4]); + result.append(digits[b & 0x0f]); + } + return result.toString(); + } + + public String calculateChecksum(String message) { + + // get bytes from string + byte bytes[] = message.getBytes(); + + Checksum checksum = new CRC32(); + + // update the current checksum with the specified array of bytes + checksum.update(bytes, 0, bytes.length); + + // get the current checksum value + long checksumValue = checksum.getValue(); + return String.valueOf(checksumValue); + } + + public String getRandomUUID() { + UUID uuid = UUID.randomUUID(); + String uuid_string = uuid.toString(); + // if we keep the -'s in the uuid, it is too long, remove them + uuid_string = uuid_string.replaceAll("-", ""); + return uuid_string; + } + + public String buildJSON(String message, String timestamp, String loggerClass, int logLevel, String culprit, String projectId) { + JSONObject obj = new JSONObject(); + obj.put("event_id", getRandomUUID());//Hexadecimal string representing a uuid4 value. + obj.put("checksum", calculateChecksum(message)); + obj.put("culprit", culprit); + obj.put("timestamp", timestamp); + obj.put("message", message); + obj.put("project", projectId); + obj.put("level", logLevel); + obj.put("logger", loggerClass); + obj.put("server_name", getHostname()); //TODO allow overriding of hostname + return obj.toJSONString(); + } + + public static String compressAndEncode(String str) { + if (str == null || str.length() == 0) { + return str; + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPOutputStream gzip = null; + try { + gzip = new GZIPOutputStream(out); + + gzip.write(str.getBytes()); + gzip.close(); + } catch (IOException e) { + e.printStackTrace(); + //todo do something here better then this. + } + return encodeBase64String(out.toByteArray()); + } + + public String buildMessageBody(String jsonMessage) { + //need to zip and then base64 encode the message. + // compressing doesn't work right now, sentry isn't decompressing correctly. + // come back to it later. + //return compressAndEncode(jsonMessage); + + // in the meantime just base64 encode it. + return encodeBase64String(jsonMessage.getBytes()); + + } + + + public void sendMessage(String serverURL, String messageBody, String hmacSignature, String key, long timestamp) { + + DefaultHttpClient httpclient = new DefaultHttpClient(); + try { + System.out.println("hmacSignature '" + hmacSignature + "'"); + System.out.println("timestamp " + timestamp); + System.out.println("sentry_key " + key); + HttpPost httppost = new HttpPost(serverURL); + String header = "Sentry sentry_version=2.0,sentry_signature=" + + hmacSignature + ",sentry_timestamp=" + timestamp + ",sentry_key=" + + key + ",sentry_client=Raven-Java 0.1"; //TODO don't hard code raven client version + + System.out.println("header '" + header + "'"); + httppost.addHeader("X-Sentry-Auth", header); + + StringEntity reqEntity = new StringEntity(messageBody); + + httppost.setEntity(reqEntity); + + System.out.println("executing request " + httppost.getRequestLine()); + HttpResponse response = httpclient.execute(httppost); + HttpEntity resEntity = response.getEntity(); + + System.out.println("----------------------------------------"); + System.out.println(response.getStatusLine()); + if (resEntity != null) { + String content = EntityUtils.toString(resEntity); + System.out.println(content); + System.out.println("Response content length: " + resEntity.getContentLength()); + System.out.println("Chunked?: " + resEntity.isChunked()); + } + EntityUtils.consume(resEntity); + } catch (ClientProtocolException e) { + System.out.println(e); + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } catch (UnsupportedEncodingException e) { + System.out.println(e); + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } catch (IOException e) { + System.out.println(e); + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } finally { + // When HttpClient instance is no longer needed, + // shut down the connection manager to ensure + // immediate deallocation of all system resources + httpclient.getConnectionManager().shutdown(); + } + + } + + +} + diff --git a/src/net/kencochrane/sentry/RavenConfig.java b/src/net/kencochrane/sentry/RavenConfig.java new file mode 100644 index 00000000000..3dd41a13bb1 --- /dev/null +++ b/src/net/kencochrane/sentry/RavenConfig.java @@ -0,0 +1,125 @@ +package net.kencochrane.sentry; + +import java.net.MalformedURLException; +import java.net.URL; +/** + * User: ken cochrane + * Date: 2/8/12 + * Time: 1:16 PM + */ +public class RavenConfig { + + private String host, protocol, publicKey, secretKey, path, projectId; + private int port; + + public RavenConfig(String sentryDSN) { + + //'{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}{PROJECT_ID}' + + try { + System.out.println("Sentry DSN = '" + sentryDSN + "' "); + URL url = new URL(sentryDSN); + this.host = url.getHost(); + this.protocol = url.getProtocol(); + String urlPath = url.getPath(); + String[] urlParts = urlPath.split("/"); + this.path = urlPath; + this.projectId = urlParts[1]; + + String userInfo = url.getUserInfo(); + String[] userParts = userInfo.split(":"); + + this.secretKey = userParts[1]; + this.publicKey = userParts[0]; + + this.port = url.getPort(); + + + } catch (MalformedURLException e) { + e.printStackTrace(); + } + + } + + public String getSentryURL(){ + StringBuilder serverUrl = new StringBuilder(); + serverUrl.append(getProtocol()); + serverUrl.append("://"); + serverUrl.append(getHost()); + if ((getPort() != 0) && (getPort() != 80)){ + serverUrl.append(":").append(getPort()); + } + serverUrl.append("/api/store/"); + return serverUrl.toString(); + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSecretKey() { + return secretKey; + } + + public void setSecretKey(String secretKey) { + this.secretKey = secretKey; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getProjectId() { + return projectId; + } + + public void setProjectId(String projectId) { + this.projectId = projectId; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + @Override + public String toString() { + return "RavenConfig{" + + "host='" + host + '\'' + + ", protocol='" + protocol + '\'' + + ", publicKey='" + publicKey + '\'' + + ", secretKey='" + secretKey + '\'' + + ", path='" + path + '\'' + + ", projectId='" + projectId + '\'' + + ", SentryUrl='" + getSentryURL() + '\'' + + '}'; + } + +} diff --git a/src/net/kencochrane/sentry/SentryAppender.java b/src/net/kencochrane/sentry/SentryAppender.java new file mode 100644 index 00000000000..7211134276a --- /dev/null +++ b/src/net/kencochrane/sentry/SentryAppender.java @@ -0,0 +1,70 @@ +package net.kencochrane.sentry; + +import org.apache.log4j.AppenderSkeleton; +import org.apache.log4j.spi.LoggingEvent; + +/** + * User: kcochrane + * Date: 2/6/12 + * Time: 10:54 AM + */ +public class SentryAppender extends AppenderSkeleton { + + private String sentry_dsn; + + public String getSentry_dsn() { + return sentry_dsn; + } + + public void setSentry_dsn(String sentry_dsn) { + this.sentry_dsn = sentry_dsn; + } + + @Override + protected void append(LoggingEvent loggingEvent) { + + //String sentryDSN = "http://b4935bdd78624092ac2bc70fdcdb6f5a:7a37d9ad4765428180316bfec91a27ef@localhost:8000/1"; + + + System.out.println(getSentry_dsn()); + + if (getSentry_dsn() == null){ + System.err.println("ERROR: You do not have a Sentry DSN configured! make sure you add sentry_dsn to your log4j properties "); + return; + } + + + RavenConfig config = new RavenConfig(getSentry_dsn()); + System.out.println(config); + + synchronized(this) { + try { + RavenClient client = new RavenClient(); + String message = loggingEvent.getRenderedMessage(); + + long timestamp = loggingEvent.getTimeStamp(); + String timestampDate = client.getTimestampString(timestamp); + String loggingClass = loggingEvent.getLogger().getName(); + int logLevel = (loggingEvent.getLevel().toInt()/1000); //Need to divide by 1000 to keep consistent with sentry + String culprit = loggingEvent.getLoggerName(); + + String jsonMessage = client.buildJSON(message, timestampDate , loggingClass, logLevel, culprit, config.getProjectId()); + String messageBody = client.buildMessageBody(jsonMessage); + String hmacSignature = client.getSignature(messageBody, timestamp, config.getSecretKey()); + client.sendMessage(config.getSentryURL(), messageBody, hmacSignature, config.getPublicKey(), timestamp); + + } catch (Exception e) { + System.err.println(e); + } + } + + } + + public void close() { + //TODO: cleannup goes here. + } + + public boolean requiresLayout() { + return false; + } +} From ac7d91da7b0c93db445f831446ae2bd828711632 Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Wed, 8 Feb 2012 15:02:52 -0500 Subject: [PATCH 0003/2152] added more documentation, and cleaned up the log4j config example --- .idea/workspace.xml | 93 ++++++++++++++++----------------- README.rst | 34 ++++++++++++ example/log4j_configuration.txt | 7 +-- 3 files changed, 79 insertions(+), 55 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 2928a1972d7..2f8befa00b6 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,7 +1,11 @@ - + + + + + @@ -351,20 +354,6 @@

+ * The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, + * and an arbitrary client version string. + *

+ * The client version should be something distinct to your client, and is simply for reporting purposes. + * To generate the HMAC signature, take the following example (in Python): + *

+ * hmac.new(public_key, '%s %s' % (timestamp, message), hashlib.sha1).hexdigest() + * + * @param message the error message to send to sentry + * @param timestamp the timestamp for when the message was created + * @param key sentry public key + * @return SHA1-signed HMAC string + */ + public static String getSignature(String message, long timestamp, String key) { + String full_message = timestamp + " " + message; + String hmac = null; + try { + hmac = calculateHMAC(full_message, key); + + } catch (SignatureException e) { + e.printStackTrace(); + } + return hmac; + } + + + /** + * The byte[] returned by MessageDigest does not have a nice + * textual representation, so some form of encoding is usually performed. + *

+ * This implementation follows the example of David Flanagan's book + * "Java In A Nutshell", and converts a byte array into a String + * of hex characters. + *

+ * Another popular alternative is to use a "Base64" encoding. + * + * @param aInput what we are hex encoding + * @return hex encoded string. + */ + public static String hexEncode(byte[] aInput) { + StringBuilder result = new StringBuilder(); + char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + for (byte b : aInput) { + result.append(digits[(b & 0xf0) >> 4]); + result.append(digits[b & 0x0f]); + } + return result.toString(); + } + + /** + * An almost-unique hash identifying the this event to improve aggregation. + * + * @param message The message we are sending to sentry + * @return CRC32 Checksum string + */ + public static String calculateChecksum(String message) { + + // get bytes from string + byte bytes[] = message.getBytes(); + + Checksum checksum = new CRC32(); + + // update the current checksum with the specified array of bytes + checksum.update(bytes, 0, bytes.length); + + // get the current checksum value + long checksumValue = checksum.getValue(); + return String.valueOf(checksumValue); + } + + /** + * Hexadecimal string representing a uuid4 value. + * + * @return Hexadecimal UUID4 String + */ + public static String getRandomUUID() { + UUID uuid = UUID.randomUUID(); + String uuid_string = uuid.toString(); + // if we keep the -'s in the uuid, it is too long, remove them + uuid_string = uuid_string.replaceAll("-", ""); + return uuid_string; + } + + /** + * Gzip then base64 encode the str value + * + * @param str the value we want to compress and encode. + * @return Base64 encoded compressed version of the string passed in. + */ + public static String compressAndEncode(String str) { + if (str == null || str.length() == 0) { + return str; + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPOutputStream gzip; + try { + gzip = new GZIPOutputStream(out); + + gzip.write(str.getBytes()); + gzip.close(); + } catch (IOException e) { + e.printStackTrace(); + //todo do something here better then this. + } + return encodeBase64String(out.toByteArray()); + } +} From 2b87ba9ab31246a19116767559868beeab8b065d Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Fri, 10 Feb 2012 11:56:45 -0500 Subject: [PATCH 0007/2152] cleaned up the sentry example a little and added more logs --- example/src/net/kencochrane/sentry/SentryExample.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/example/src/net/kencochrane/sentry/SentryExample.java b/example/src/net/kencochrane/sentry/SentryExample.java index b7bd1478779..16daedf280c 100644 --- a/example/src/net/kencochrane/sentry/SentryExample.java +++ b/example/src/net/kencochrane/sentry/SentryExample.java @@ -10,11 +10,14 @@ import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; +/** + * Simple example used to test out the sentry logger. + */ public class SentryExample { // Define a static logger variable so that it references the // Logger instance named "MyApp". - static Logger logger = Logger.getLogger(SentryExample.class); + static final Logger logger = Logger.getLogger(SentryExample.class); public static void main(String[] args) { From b7770419e459096a701be850e38c7ca233911d58 Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Fri, 10 Feb 2012 11:57:49 -0500 Subject: [PATCH 0008/2152] refactored the code to make it cleaner and easier to use. Also added more comments and javadocs --- src/net/kencochrane/sentry/RavenClient.java | 300 ++++++++---------- src/net/kencochrane/sentry/RavenConfig.java | 39 ++- .../kencochrane/sentry/SentryAppender.java | 21 +- 3 files changed, 173 insertions(+), 187 deletions(-) diff --git a/src/net/kencochrane/sentry/RavenClient.java b/src/net/kencochrane/sentry/RavenClient.java index 343268aef19..0d1137e5775 100644 --- a/src/net/kencochrane/sentry/RavenClient.java +++ b/src/net/kencochrane/sentry/RavenClient.java @@ -8,20 +8,6 @@ import org.apache.http.util.EntityUtils; import org.json.simple.JSONObject; -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.security.SignatureException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.UUID; -import java.util.zip.CRC32; -import java.util.zip.Checksum; -import java.util.zip.GZIPOutputStream; - import static org.apache.commons.codec.binary.Base64.encodeBase64String; /** @@ -30,171 +16,65 @@ * Time: 11:59 AM */ +class RavenClient { -public class RavenClient { - - private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; - public static SimpleDateFormat ISO8601FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); - public static final String RAVEN_JAVA_VERSION = "Raven-Java 0.1"; - - /** - * Computes RFC 2104-compliant HMAC signature. - * Based off of the sample here. http://docs.amazonwebservices.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AuthJavaSampleHMACSignature.html - * - * @param data The data to be signed. - * @param key The signing key. - * @return The hex-encoded RFC 2104-compliant HMAC signature. - * @throws java.security.SignatureException - * when signature generation fails - */ - public static String calculateHMAC(String data, String key) throws java.security.SignatureException { - String result; - try { - - // get an hmac_sha1 key from the raw key bytes - SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM); - - // get an hmac_sha1 Mac instance and initialize with the signing key - Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); - mac.init(signingKey); - - // compute the hmac on input data bytes - byte[] rawHmac = mac.doFinal(data.getBytes()); - - result = hexEncode(rawHmac); - - } catch (Exception e) { - throw new SignatureException("Failed to generate HMAC : " + e.getMessage()); - } - return result; - } - - public String getHostname() { - String hostname = null; - try { - hostname = InetAddress.getLocalHost().getCanonicalHostName(); - } catch (UnknownHostException e) { - // can't get hostname - hostname = "unavailable"; - } - return hostname; - } + private static final String RAVEN_JAVA_VERSION = "Raven-Java 0.2"; + private RavenConfig config; + private String sentryDSN; - public String getDateAsISO8601String(Date date) { - String result = ISO8601FORMAT.format(date); - //convert YYYYMMDDTHH:mm:ss+HH00 into YYYYMMDDTHH:mm:ss+HH:00 - //- note the added colon for the Timezone - result = result.substring(0, result.length() - 2) - + ":" + result.substring(result.length() - 2); - return result; + public RavenClient(String sentryDSN) { + this.sentryDSN = sentryDSN; + this.config = new RavenConfig(sentryDSN); } - public long getTimestampLong() { - java.util.Date date = new java.util.Date(); - return date.getTime(); + public RavenConfig getConfig() { + return config; } - public String getTimestampString(long timestamp) { - - java.util.Date date = new java.util.Date(timestamp); - return getDateAsISO8601String(date); + public void setConfig(RavenConfig config) { + this.config = config; } - public String getTimestampString() { - - java.util.Date date = new java.util.Date(); - return getDateAsISO8601String(date); + public String getSentryDSN() { + return sentryDSN; } - public String getSignature(String message, long timestamp, String key) { - String full_message = timestamp + " " + message; - String hmac = null; - try { - hmac = calculateHMAC(full_message, key); - - } catch (SignatureException e) { - e.printStackTrace(); - } - return hmac; + public void setSentryDSN(String sentryDSN) { + this.sentryDSN = sentryDSN; } - /** - * The byte[] returned by MessageDigest does not have a nice - * textual representation, so some form of encoding is usually performed. - *

- * This implementation follows the example of David Flanagan's book - * "Java In A Nutshell", and converts a byte array into a String - * of hex characters. - *

- * Another popular alternative is to use a "Base64" encoding. + * Build up the JSON body for the POST that is sent to sentry + * + * @param message The log message + * @param timestamp ISO8601 formatted date string + * @param loggerClass The class associated with the log message + * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.) + * @param culprit Who we think caused the problem. + * @return JSON String of message body */ - static private String hexEncode(byte[] aInput) { - StringBuilder result = new StringBuilder(); - char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - for (byte b : aInput) { - result.append(digits[(b & 0xf0) >> 4]); - result.append(digits[b & 0x0f]); - } - return result.toString(); - } - - public String calculateChecksum(String message) { - - // get bytes from string - byte bytes[] = message.getBytes(); - - Checksum checksum = new CRC32(); - - // update the current checksum with the specified array of bytes - checksum.update(bytes, 0, bytes.length); - - // get the current checksum value - long checksumValue = checksum.getValue(); - return String.valueOf(checksumValue); - } - - public String getRandomUUID() { - UUID uuid = UUID.randomUUID(); - String uuid_string = uuid.toString(); - // if we keep the -'s in the uuid, it is too long, remove them - uuid_string = uuid_string.replaceAll("-", ""); - return uuid_string; - } - - public String buildJSON(String message, String timestamp, String loggerClass, int logLevel, String culprit, String projectId) { + private String buildJSON(String message, String timestamp, String loggerClass, int logLevel, String culprit) { JSONObject obj = new JSONObject(); - obj.put("event_id", getRandomUUID());//Hexadecimal string representing a uuid4 value. - obj.put("checksum", calculateChecksum(message)); + obj.put("event_id", RavenUtils.getRandomUUID()); //Hexadecimal string representing a uuid4 value. + obj.put("checksum", RavenUtils.calculateChecksum(message)); obj.put("culprit", culprit); obj.put("timestamp", timestamp); obj.put("message", message); - obj.put("project", projectId); + obj.put("project", getConfig().getProjectId()); obj.put("level", logLevel); obj.put("logger", loggerClass); - obj.put("server_name", getHostname()); //TODO allow overriding of hostname + obj.put("server_name", RavenUtils.getHostname()); return obj.toJSONString(); } - public static String compressAndEncode(String str) { - if (str == null || str.length() == 0) { - return str; - } - ByteArrayOutputStream out = new ByteArrayOutputStream(); - GZIPOutputStream gzip = null; - try { - gzip = new GZIPOutputStream(out); - - gzip.write(str.getBytes()); - gzip.close(); - } catch (IOException e) { - e.printStackTrace(); - //todo do something here better then this. - } - return encodeBase64String(out.toByteArray()); - } - public String buildMessageBody(String jsonMessage) { + /** + * Take the raw message body and get it ready for sending. Encode and compress it. + * + * @param jsonMessage the message we want to prepare + * @return Encode and compressed version of the jsonMessage + */ + private String buildMessageBody(String jsonMessage) { //need to zip and then base64 encode the message. // compressing doesn't work right now, sentry isn't decompressing correctly. // come back to it later. @@ -205,36 +85,95 @@ public String buildMessageBody(String jsonMessage) { } + /** + * Build up the JSON body and then Encode and compress it. + * + * @param message The log message + * @param timestamp ISO8601 formatted date string + * @param loggerClass The class associated with the log message + * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.) + * @param culprit Who we think caused the problem. + * @return Encode and compressed version of the JSON Message body + */ + private String buildMessage(String message, String timestamp, String loggerClass, int logLevel, String culprit) { + // get the json version of the body + String jsonMessage = buildJSON(message, timestamp, loggerClass, logLevel, culprit); + + // compress and encode the json message. + return buildMessageBody(jsonMessage); + } + + + /** + * build up the sentry auth header in the following format + *

+ * The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, and an + * arbitrary client version string. The client version should be something distinct to your client, + * and is simply for reporting purposes. + *

+ * X-Sentry-Auth: Sentry sentry_version=2.0, + * sentry_signature=, + * sentry_timestamp=[, + * sentry_key=,[ + * sentry_client=]] + * + * @param hmacSignature SHA1-signed HMAC + * @param timestamp is the timestamp of which this message was generated + * @param publicKey is either the public_key or the shared global key between client and server. + * @return String version of the sentry auth header + */ + private String buildAuthHeader(String hmacSignature, long timestamp, String publicKey) { + StringBuilder header = new StringBuilder(); + header.append("Sentry sentry_version=2.0,sentry_signature="); + header.append(hmacSignature); + header.append(",sentry_timestamp="); + header.append(timestamp); + header.append(",sentry_key="); + header.append(publicKey); + header.append(",sentry_client="); + header.append(RAVEN_JAVA_VERSION); + + return header.toString(); + } - public void sendMessage(String serverURL, String messageBody, String hmacSignature, String key, long timestamp) { + /** + * Send the message to the sentry server. + * + * @param messageBody the encoded json message we are sending to the sentry server + * @param timestamp the timestamp of the message + */ + private void sendMessage(String messageBody, long timestamp) { DefaultHttpClient httpClient = new DefaultHttpClient(); try { - HttpPost httppost = new HttpPost(serverURL); - StringBuilder header = new StringBuilder(); - header.append("Sentry sentry_version=2.0,sentry_signature="); - header.append(hmacSignature); - header.append(",sentry_timestamp="); - header.append(timestamp); - header.append(",sentry_key="); - header.append(key); - header.append(",sentry_client="); - header.append(RAVEN_JAVA_VERSION); - - httppost.addHeader("X-Sentry-Auth", header.toString()); - StringEntity reqEntity = new StringEntity(messageBody); + // build up the Post method + HttpPost httppost = new HttpPost(getConfig().getSentryURL()); + + // get the hmac Signature for the header + String hmacSignature = RavenUtils.getSignature(messageBody, timestamp, config.getSecretKey()); + // get the auth header + String authHeader = buildAuthHeader(hmacSignature, timestamp, getConfig().getPublicKey()); + + // add auth header to http post + httppost.addHeader("X-Sentry-Auth", authHeader); + + // set the body into the post + StringEntity reqEntity = new StringEntity(messageBody); httppost.setEntity(reqEntity); + // call the server and get response HttpResponse response = httpClient.execute(httppost); HttpEntity resEntity = response.getEntity(); // not needed right now, keeping around for debugging purposes //if (resEntity != null) { - //String content = EntityUtils.toString(resEntity); - //System.out.println(content); + //String content = EntityUtils.toString(resEntity); + //System.out.println(content); //} + + // need to consume the response EntityUtils.consume(resEntity); } catch (Exception e) { // Eat the errors, we don't want to cause problems if there are major issues. @@ -242,12 +181,25 @@ public void sendMessage(String serverURL, String messageBody, String hmacSignatu } finally { // When HttpClient instance is no longer needed, // shut down the connection manager to ensure - // immediate deallocation of all system resources + // immediate de-allocation of all system resources httpClient.getConnectionManager().shutdown(); } } + /** + * Send the log message to the sentry server. + * + * @param theLogMessage The log message + * @param timestamp unix timestamp + * @param loggerClass The class associated with the log message + * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.) + * @param culprit Who we think caused the problem. + */ + public void logMessage(String theLogMessage, long timestamp, String loggerClass, int logLevel, String culprit) { + String timestampDate = RavenUtils.getTimestampString(timestamp); -} - + String message = buildMessage(theLogMessage, timestampDate, loggerClass, logLevel, culprit); + sendMessage(message, timestamp); + } +} \ No newline at end of file diff --git a/src/net/kencochrane/sentry/RavenConfig.java b/src/net/kencochrane/sentry/RavenConfig.java index 835cc6167c9..006d77a020d 100644 --- a/src/net/kencochrane/sentry/RavenConfig.java +++ b/src/net/kencochrane/sentry/RavenConfig.java @@ -13,10 +13,13 @@ public class RavenConfig { private String host, protocol, publicKey, secretKey, path, projectId; private int port; + /** + * Takes in a sentryDSN and builds up the configuration + * + * @param sentryDSN '{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}{PROJECT_ID}' + */ public RavenConfig(String sentryDSN) { - //'{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}{PROJECT_ID}' - try { URL url = new URL(sentryDSN); this.host = url.getHost(); @@ -41,6 +44,10 @@ public RavenConfig(String sentryDSN) { } + /** + * The Sentry server URL that we post the message to. + * @return sentry server url + */ public String getSentryURL() { StringBuilder serverUrl = new StringBuilder(); serverUrl.append(getProtocol()); @@ -53,6 +60,10 @@ public String getSentryURL() { return serverUrl.toString(); } + /** + * The sentry server host + * @return server host + */ public String getHost() { return host; } @@ -61,6 +72,10 @@ public void setHost(String host) { this.host = host; } + /** + * Sentry server protocol http https? + * @return http or https + */ public String getProtocol() { return protocol; } @@ -69,6 +84,10 @@ public void setProtocol(String protocol) { this.protocol = protocol; } + /** + * The Sentry public key + * @return Sentry public key + */ public String getPublicKey() { return publicKey; } @@ -77,6 +96,10 @@ public void setPublicKey(String publicKey) { this.publicKey = publicKey; } + /** + * The Sentry secret key + * @return Sentry secret key + */ public String getSecretKey() { return secretKey; } @@ -85,6 +108,10 @@ public void setSecretKey(String secretKey) { this.secretKey = secretKey; } + /** + * sentry url path + * @return url path + */ public String getPath() { return path; } @@ -93,6 +120,10 @@ public void setPath(String path) { this.path = path; } + /** + * Sentry project Id + * @return project Id + */ public String getProjectId() { return projectId; } @@ -101,6 +132,10 @@ public void setProjectId(String projectId) { this.projectId = projectId; } + /** + * sentry server port + * @return server port + */ public int getPort() { return port; } diff --git a/src/net/kencochrane/sentry/SentryAppender.java b/src/net/kencochrane/sentry/SentryAppender.java index d40b80dd840..aed14e21bc1 100644 --- a/src/net/kencochrane/sentry/SentryAppender.java +++ b/src/net/kencochrane/sentry/SentryAppender.java @@ -28,23 +28,22 @@ protected void append(LoggingEvent loggingEvent) { return; } - RavenConfig config = new RavenConfig(getSentry_dsn()); - synchronized (this) { try { - RavenClient client = new RavenClient(); - String message = loggingEvent.getRenderedMessage(); - + // get timestamp and timestamp in correct string format. long timestamp = loggingEvent.getTimeStamp(); - String timestampDate = client.getTimestampString(timestamp); + + // get the log and info about the log. + String logMessage = loggingEvent.getRenderedMessage(); String loggingClass = loggingEvent.getLogger().getName(); int logLevel = (loggingEvent.getLevel().toInt() / 1000); //Need to divide by 1000 to keep consistent with sentry String culprit = loggingEvent.getLoggerName(); - String jsonMessage = client.buildJSON(message, timestampDate, loggingClass, logLevel, culprit, config.getProjectId()); - String messageBody = client.buildMessageBody(jsonMessage); - String hmacSignature = client.getSignature(messageBody, timestamp, config.getSecretKey()); - client.sendMessage(config.getSentryURL(), messageBody, hmacSignature, config.getPublicKey(), timestamp); + // create the client passing in the sentry DSN from the log4j properties file. + RavenClient client = new RavenClient(getSentry_dsn()); + + // send the message to the sentry server + client.logMessage(logMessage, timestamp, loggingClass, logLevel, culprit); } catch (Exception e) { System.err.println(e); @@ -54,7 +53,7 @@ protected void append(LoggingEvent loggingEvent) { } public void close() { - //TODO: cleannup goes here. Is there any? + // clean up normally goes here. but there isn't any } public boolean requiresLayout() { From 8b30234a2ef15443eecd409aa86d092343359de5 Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Fri, 10 Feb 2012 11:58:29 -0500 Subject: [PATCH 0009/2152] intellij files updated --- .idea/misc.xml | 8 +-- .idea/workspace.xml | 136 +++++++++++++++++++++----------------------- 2 files changed, 69 insertions(+), 75 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index 1c091f99766..3f368af3c49 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -7,18 +7,18 @@ - @@ -549,7 +541,7 @@ - + @@ -557,17 +549,17 @@ - + - + - + - + + - @@ -627,27 +619,6 @@ - - - - - - - - - - - - - - - - - - - - - @@ -699,48 +670,71 @@ - + - + - + - + + + + + + + + - + - + - + - + - + + + + + + + + - + + + + + + + + + + - + From 845b643ff87da22010085b7221a4270e476babfe Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Fri, 10 Feb 2012 11:58:55 -0500 Subject: [PATCH 0010/2152] added java docs --- docs/allclasses-frame.html | 37 ++ docs/allclasses-noframe.html | 37 ++ docs/constant-values.html | 142 +++++ docs/deprecated-list.html | 142 +++++ docs/help-doc.html | 209 +++++++ docs/index-files/index-1.html | 141 +++++ docs/index-files/index-2.html | 150 +++++ docs/index-files/index-3.html | 190 ++++++ docs/index-files/index-4.html | 142 +++++ docs/index-files/index-5.html | 141 +++++ docs/index-files/index-6.html | 138 +++++ docs/index-files/index-7.html | 152 +++++ docs/index-files/index-8.html | 170 ++++++ docs/index-files/index-9.html | 141 +++++ docs/index.html | 36 ++ docs/net/kencochrane/sentry/RavenConfig.html | 570 ++++++++++++++++++ docs/net/kencochrane/sentry/RavenUtils.html | 509 ++++++++++++++++ .../kencochrane/sentry/SentryAppender.html | 364 +++++++++++ .../net/kencochrane/sentry/SentryExample.html | 252 ++++++++ .../net/kencochrane/sentry/package-frame.html | 38 ++ .../kencochrane/sentry/package-summary.html | 172 ++++++ docs/net/kencochrane/sentry/package-tree.html | 149 +++++ docs/overview-tree.html | 151 +++++ docs/package-list | 1 + docs/resources/inherit.gif | Bin 0 -> 57 bytes docs/stylesheet.css | 29 + 26 files changed, 4203 insertions(+) create mode 100644 docs/allclasses-frame.html create mode 100644 docs/allclasses-noframe.html create mode 100644 docs/constant-values.html create mode 100644 docs/deprecated-list.html create mode 100644 docs/help-doc.html create mode 100644 docs/index-files/index-1.html create mode 100644 docs/index-files/index-2.html create mode 100644 docs/index-files/index-3.html create mode 100644 docs/index-files/index-4.html create mode 100644 docs/index-files/index-5.html create mode 100644 docs/index-files/index-6.html create mode 100644 docs/index-files/index-7.html create mode 100644 docs/index-files/index-8.html create mode 100644 docs/index-files/index-9.html create mode 100644 docs/index.html create mode 100644 docs/net/kencochrane/sentry/RavenConfig.html create mode 100644 docs/net/kencochrane/sentry/RavenUtils.html create mode 100644 docs/net/kencochrane/sentry/SentryAppender.html create mode 100644 docs/net/kencochrane/sentry/SentryExample.html create mode 100644 docs/net/kencochrane/sentry/package-frame.html create mode 100644 docs/net/kencochrane/sentry/package-summary.html create mode 100644 docs/net/kencochrane/sentry/package-tree.html create mode 100644 docs/overview-tree.html create mode 100644 docs/package-list create mode 100644 docs/resources/inherit.gif create mode 100644 docs/stylesheet.css diff --git a/docs/allclasses-frame.html b/docs/allclasses-frame.html new file mode 100644 index 00000000000..6204da27456 --- /dev/null +++ b/docs/allclasses-frame.html @@ -0,0 +1,37 @@ + + + + + + +All Classes + + + + + + + + + + + +All Classes +
+ + + + + +
RavenConfig +
+RavenUtils +
+SentryAppender +
+SentryExample +
+
+ + + diff --git a/docs/allclasses-noframe.html b/docs/allclasses-noframe.html new file mode 100644 index 00000000000..a2ae2a9449d --- /dev/null +++ b/docs/allclasses-noframe.html @@ -0,0 +1,37 @@ + + + + + + +All Classes + + + + + + + + + + + +All Classes +
+ + + + + +
RavenConfig +
+RavenUtils +
+SentryAppender +
+SentryExample +
+
+ + + diff --git a/docs/constant-values.html b/docs/constant-values.html new file mode 100644 index 00000000000..175fa398984 --- /dev/null +++ b/docs/constant-values.html @@ -0,0 +1,142 @@ + + + + + + +Constant Field Values + + + + + + + + + + + + +


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+Constant Field Values

+
+
+Contents
    +
+ +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/deprecated-list.html b/docs/deprecated-list.html new file mode 100644 index 00000000000..cf2ee185dc8 --- /dev/null +++ b/docs/deprecated-list.html @@ -0,0 +1,142 @@ + + + + + + +Deprecated List + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+Deprecated API

+
+
+Contents
    +
+ +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/help-doc.html b/docs/help-doc.html new file mode 100644 index 00000000000..a50abe552f8 --- /dev/null +++ b/docs/help-doc.html @@ -0,0 +1,209 @@ + + + + + + +API Help + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+How This API Document Is Organized

+
+This API (Application Programming Interface) document has pages corresponding to the items in the navigation bar, described as follows.

+Package

+
+ +

+Each package has a page that contains a list of its classes and interfaces, with a summary for each. This page can contain four categories:

    +
  • Interfaces (italic)
  • Classes
  • Enums
  • Exceptions
  • Errors
  • Annotation Types
+
+

+Class/Interface

+
+ +

+Each class, interface, nested class and nested interface has its own separate page. Each of these pages has three sections consisting of a class/interface description, summary tables, and detailed member descriptions:

    +
  • Class inheritance diagram
  • Direct Subclasses
  • All Known Subinterfaces
  • All Known Implementing Classes
  • Class/interface declaration
  • Class/interface description +

    +

  • Nested Class Summary
  • Field Summary
  • Constructor Summary
  • Method Summary +

    +

  • Field Detail
  • Constructor Detail
  • Method Detail
+Each summary entry contains the first sentence from the detailed description for that item. The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.
+ +

+Annotation Type

+
+ +

+Each annotation type has its own separate page with the following sections:

    +
  • Annotation Type declaration
  • Annotation Type description
  • Required Element Summary
  • Optional Element Summary
  • Element Detail
+
+ +

+Enum

+
+ +

+Each enum has its own separate page with the following sections:

    +
  • Enum declaration
  • Enum description
  • Enum Constant Summary
  • Enum Constant Detail
+
+

+Tree (Class Hierarchy)

+
+There is a Class Hierarchy page for all packages, plus a hierarchy for each package. Each hierarchy page contains a list of classes and a list of interfaces. The classes are organized by inheritance structure starting with java.lang.Object. The interfaces do not inherit from java.lang.Object.
    +
  • When viewing the Overview page, clicking on "Tree" displays the hierarchy for all packages.
  • When viewing a particular package, class or interface page, clicking "Tree" displays the hierarchy for only that package.
+
+

+Deprecated API

+
+The Deprecated API page lists all of the API that have been deprecated. A deprecated API is not recommended for use, generally due to improvements, and a replacement API is usually given. Deprecated APIs may be removed in future implementations.
+

+Index

+
+The Index contains an alphabetic list of all classes, interfaces, constructors, methods, and fields.
+

+Prev/Next

+These links take you to the next or previous class, interface, package, or related page.

+Frames/No Frames

+These links show and hide the HTML frames. All pages are available with or without frames. +

+

+Serialized Form

+Each serializable or externalizable class has a description of its serialization fields and methods. This information is of interest to re-implementors, not to developers using the API. While there is no link in the navigation bar, you can get to this information by going to any serialized class and clicking "Serialized Form" in the "See also" section of the class description. +

+

+Constant Field Values

+The Constant Field Values page lists the static final fields and their values. +

+ + +This help file applies to API documentation generated using the standard doclet. + +
+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/index-files/index-1.html b/docs/index-files/index-1.html new file mode 100644 index 00000000000..f1959f7c122 --- /dev/null +++ b/docs/index-files/index-1.html @@ -0,0 +1,141 @@ + + + + + + +A-Index + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C G H M N R S T
+

+A

+
+
append(LoggingEvent) - +Method in class net.kencochrane.sentry.SentryAppender +
  +
+
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C G H M N R S T
+ + + diff --git a/docs/index-files/index-2.html b/docs/index-files/index-2.html new file mode 100644 index 00000000000..f880431d12c --- /dev/null +++ b/docs/index-files/index-2.html @@ -0,0 +1,150 @@ + + + + + + +C-Index + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C G H M N R S T
+

+C

+
+
calculateChecksum(String) - +Static method in class net.kencochrane.sentry.RavenUtils +
An almost-unique hash identifying the this event to improve aggregation. +
calculateHMAC(String, String) - +Static method in class net.kencochrane.sentry.RavenUtils +
Computes RFC 2104-compliant HMAC signature. +
close() - +Method in class net.kencochrane.sentry.SentryAppender +
  +
compressAndEncode(String) - +Static method in class net.kencochrane.sentry.RavenUtils +
Gzip then base64 encode the str value +
+
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C G H M N R S T
+ + + diff --git a/docs/index-files/index-3.html b/docs/index-files/index-3.html new file mode 100644 index 00000000000..084b065f0b9 --- /dev/null +++ b/docs/index-files/index-3.html @@ -0,0 +1,190 @@ + + + + + + +G-Index + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C G H M N R S T
+

+G

+
+
getDateAsISO8601String(Date) - +Static method in class net.kencochrane.sentry.RavenUtils +
Convert a java date into the ISO8601 format YYYYMMDDTHH:mm:ss+HH:00 + note the added colon for the Timezone +
getHost() - +Method in class net.kencochrane.sentry.RavenConfig +
The sentry server host +
getHostname() - +Static method in class net.kencochrane.sentry.RavenUtils +
Get the hostname for the system throwing the error. +
getPath() - +Method in class net.kencochrane.sentry.RavenConfig +
sentry url path +
getPort() - +Method in class net.kencochrane.sentry.RavenConfig +
sentry server port +
getProjectId() - +Method in class net.kencochrane.sentry.RavenConfig +
Sentry project Id +
getProtocol() - +Method in class net.kencochrane.sentry.RavenConfig +
Sentry server protocol http https? +
getPublicKey() - +Method in class net.kencochrane.sentry.RavenConfig +
The Sentry public key +
getRandomUUID() - +Static method in class net.kencochrane.sentry.RavenUtils +
Hexadecimal string representing a uuid4 value. +
getSecretKey() - +Method in class net.kencochrane.sentry.RavenConfig +
The Sentry secret key +
getSentry_dsn() - +Method in class net.kencochrane.sentry.SentryAppender +
  +
getSentryURL() - +Method in class net.kencochrane.sentry.RavenConfig +
The Sentry server URL that we post the message to. +
getSignature(String, long, String) - +Static method in class net.kencochrane.sentry.RavenUtils +
build the HMAC sentry signature +

+ The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, + and an arbitrary client version string. +

getTimestampLong() - +Static method in class net.kencochrane.sentry.RavenUtils +
Get the timestamp for right now as a long +
getTimestampString(long) - +Static method in class net.kencochrane.sentry.RavenUtils +
Given a long timestamp convert to a ISO8601 formatted date string +
getTimestampString() - +Static method in class net.kencochrane.sentry.RavenUtils +
Given the time right now return a ISO8601 formatted date string +
+
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C G H M N R S T
+ + + diff --git a/docs/index-files/index-4.html b/docs/index-files/index-4.html new file mode 100644 index 00000000000..89678b717eb --- /dev/null +++ b/docs/index-files/index-4.html @@ -0,0 +1,142 @@ + + + + + + +H-Index + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C G H M N R S T
+

+H

+
+
hexEncode(byte[]) - +Static method in class net.kencochrane.sentry.RavenUtils +
The byte[] returned by MessageDigest does not have a nice + textual representation, so some form of encoding is usually performed. +
+
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C G H M N R S T
+ + + diff --git a/docs/index-files/index-5.html b/docs/index-files/index-5.html new file mode 100644 index 00000000000..14122f84cdb --- /dev/null +++ b/docs/index-files/index-5.html @@ -0,0 +1,141 @@ + + + + + + +M-Index + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C G H M N R S T
+

+M

+
+
main(String[]) - +Static method in class net.kencochrane.sentry.SentryExample +
  +
+
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C G H M N R S T
+ + + diff --git a/docs/index-files/index-6.html b/docs/index-files/index-6.html new file mode 100644 index 00000000000..4999257eb84 --- /dev/null +++ b/docs/index-files/index-6.html @@ -0,0 +1,138 @@ + + + + + + +N-Index + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C G H M N R S T
+

+N

+
+
net.kencochrane.sentry - package net.kencochrane.sentry
 
+
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C G H M N R S T
+ + + diff --git a/docs/index-files/index-7.html b/docs/index-files/index-7.html new file mode 100644 index 00000000000..3c84ab2df94 --- /dev/null +++ b/docs/index-files/index-7.html @@ -0,0 +1,152 @@ + + + + + + +R-Index + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C G H M N R S T
+

+R

+
+
RavenConfig - Class in net.kencochrane.sentry
User: ken cochrane + Date: 2/8/12 + Time: 1:16 PM
RavenConfig(String) - +Constructor for class net.kencochrane.sentry.RavenConfig +
Takes in a sentryDSN and builds up the configuration +
RavenUtils - Class in net.kencochrane.sentry
User: ken Cochrane + Date: 2/10/12 + Time: 10:43 AM + Utility class for the Raven client.
RavenUtils() - +Constructor for class net.kencochrane.sentry.RavenUtils +
  +
requiresLayout() - +Method in class net.kencochrane.sentry.SentryAppender +
  +
+
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C G H M N R S T
+ + + diff --git a/docs/index-files/index-8.html b/docs/index-files/index-8.html new file mode 100644 index 00000000000..cb97bd58c67 --- /dev/null +++ b/docs/index-files/index-8.html @@ -0,0 +1,170 @@ + + + + + + +S-Index + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C G H M N R S T
+

+S

+
+
SentryAppender - Class in net.kencochrane.sentry
User: ken cochrane + Date: 2/6/12 + Time: 10:54 AM
SentryAppender() - +Constructor for class net.kencochrane.sentry.SentryAppender +
  +
SentryExample - Class in net.kencochrane.sentry
Simple example used to test out the sentry logger.
SentryExample() - +Constructor for class net.kencochrane.sentry.SentryExample +
  +
setHost(String) - +Method in class net.kencochrane.sentry.RavenConfig +
  +
setPath(String) - +Method in class net.kencochrane.sentry.RavenConfig +
  +
setPort(int) - +Method in class net.kencochrane.sentry.RavenConfig +
  +
setProjectId(String) - +Method in class net.kencochrane.sentry.RavenConfig +
  +
setProtocol(String) - +Method in class net.kencochrane.sentry.RavenConfig +
  +
setPublicKey(String) - +Method in class net.kencochrane.sentry.RavenConfig +
  +
setSecretKey(String) - +Method in class net.kencochrane.sentry.RavenConfig +
  +
setSentry_dsn(String) - +Method in class net.kencochrane.sentry.SentryAppender +
  +
+
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C G H M N R S T
+ + + diff --git a/docs/index-files/index-9.html b/docs/index-files/index-9.html new file mode 100644 index 00000000000..86c49a23322 --- /dev/null +++ b/docs/index-files/index-9.html @@ -0,0 +1,141 @@ + + + + + + +T-Index + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C G H M N R S T
+

+T

+
+
toString() - +Method in class net.kencochrane.sentry.RavenConfig +
  +
+
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C G H M N R S T
+ + + diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 00000000000..2923f414395 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,36 @@ + + + + + + +Generated Documentation (Untitled) + + + + + + + + +<H2> +Frame Alert</H2> + +<P> +This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client. +<BR> +Link to<A HREF="net/kencochrane/sentry/package-summary.html">Non-frame version.</A> + + + diff --git a/docs/net/kencochrane/sentry/RavenConfig.html b/docs/net/kencochrane/sentry/RavenConfig.html new file mode 100644 index 00000000000..6b5a73a30d2 --- /dev/null +++ b/docs/net/kencochrane/sentry/RavenConfig.html @@ -0,0 +1,570 @@ + + + + + + +RavenConfig + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +net.kencochrane.sentry +
+Class RavenConfig

+
+java.lang.Object
+  extended by net.kencochrane.sentry.RavenConfig
+
+
+
+
public class RavenConfig
extends java.lang.Object
+ + +

+User: ken cochrane + Date: 2/8/12 + Time: 1:16 PM +

+ +

+


+ +

+ + + + + + + + + + + +
+Constructor Summary
RavenConfig(java.lang.String sentryDSN) + +
+          Takes in a sentryDSN and builds up the configuration
+  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Method Summary
+ java.lang.StringgetHost() + +
+          The sentry server host
+ java.lang.StringgetPath() + +
+          sentry url path
+ intgetPort() + +
+          sentry server port
+ java.lang.StringgetProjectId() + +
+          Sentry project Id
+ java.lang.StringgetProtocol() + +
+          Sentry server protocol http https?
+ java.lang.StringgetPublicKey() + +
+          The Sentry public key
+ java.lang.StringgetSecretKey() + +
+          The Sentry secret key
+ java.lang.StringgetSentryURL() + +
+          The Sentry server URL that we post the message to.
+ voidsetHost(java.lang.String host) + +
+           
+ voidsetPath(java.lang.String path) + +
+           
+ voidsetPort(int port) + +
+           
+ voidsetProjectId(java.lang.String projectId) + +
+           
+ voidsetProtocol(java.lang.String protocol) + +
+           
+ voidsetPublicKey(java.lang.String publicKey) + +
+           
+ voidsetSecretKey(java.lang.String secretKey) + +
+           
+ java.lang.StringtoString() + +
+           
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+RavenConfig

+
+public RavenConfig(java.lang.String sentryDSN)
+
+
Takes in a sentryDSN and builds up the configuration +

+

+
Parameters:
sentryDSN - '{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}{PROJECT_ID}'
+
+ + + + + + + + +
+Method Detail
+ +

+getSentryURL

+
+public java.lang.String getSentryURL()
+
+
The Sentry server URL that we post the message to. +

+

+ +
Returns:
sentry server url
+
+
+
+ +

+getHost

+
+public java.lang.String getHost()
+
+
The sentry server host +

+

+ +
Returns:
server host
+
+
+
+ +

+setHost

+
+public void setHost(java.lang.String host)
+
+
+
+
+
+
+ +

+getProtocol

+
+public java.lang.String getProtocol()
+
+
Sentry server protocol http https? +

+

+ +
Returns:
http or https
+
+
+
+ +

+setProtocol

+
+public void setProtocol(java.lang.String protocol)
+
+
+
+
+
+
+ +

+getPublicKey

+
+public java.lang.String getPublicKey()
+
+
The Sentry public key +

+

+ +
Returns:
Sentry public key
+
+
+
+ +

+setPublicKey

+
+public void setPublicKey(java.lang.String publicKey)
+
+
+
+
+
+
+ +

+getSecretKey

+
+public java.lang.String getSecretKey()
+
+
The Sentry secret key +

+

+ +
Returns:
Sentry secret key
+
+
+
+ +

+setSecretKey

+
+public void setSecretKey(java.lang.String secretKey)
+
+
+
+
+
+
+ +

+getPath

+
+public java.lang.String getPath()
+
+
sentry url path +

+

+ +
Returns:
url path
+
+
+
+ +

+setPath

+
+public void setPath(java.lang.String path)
+
+
+
+
+
+
+ +

+getProjectId

+
+public java.lang.String getProjectId()
+
+
Sentry project Id +

+

+ +
Returns:
project Id
+
+
+
+ +

+setProjectId

+
+public void setProjectId(java.lang.String projectId)
+
+
+
+
+
+
+ +

+getPort

+
+public int getPort()
+
+
sentry server port +

+

+ +
Returns:
server port
+
+
+
+ +

+setPort

+
+public void setPort(int port)
+
+
+
+
+
+
+ +

+toString

+
+public java.lang.String toString()
+
+
+
Overrides:
toString in class java.lang.Object
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/net/kencochrane/sentry/RavenUtils.html b/docs/net/kencochrane/sentry/RavenUtils.html new file mode 100644 index 00000000000..52e1d4e3e38 --- /dev/null +++ b/docs/net/kencochrane/sentry/RavenUtils.html @@ -0,0 +1,509 @@ + + + + + + +RavenUtils + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +net.kencochrane.sentry +
+Class RavenUtils

+
+java.lang.Object
+  extended by net.kencochrane.sentry.RavenUtils
+
+
+
+
public class RavenUtils
extends java.lang.Object
+ + +

+User: ken Cochrane + Date: 2/10/12 + Time: 10:43 AM + Utility class for the Raven client. +

+ +

+


+ +

+ + + + + + + + + + + +
+Constructor Summary
RavenUtils() + +
+           
+  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Method Summary
+static java.lang.StringcalculateChecksum(java.lang.String message) + +
+          An almost-unique hash identifying the this event to improve aggregation.
+static java.lang.StringcalculateHMAC(java.lang.String data, + java.lang.String key) + +
+          Computes RFC 2104-compliant HMAC signature.
+static java.lang.StringcompressAndEncode(java.lang.String str) + +
+          Gzip then base64 encode the str value
+static java.lang.StringgetDateAsISO8601String(java.util.Date date) + +
+          Convert a java date into the ISO8601 format YYYYMMDDTHH:mm:ss+HH:00 + note the added colon for the Timezone
+static java.lang.StringgetHostname() + +
+          Get the hostname for the system throwing the error.
+static java.lang.StringgetRandomUUID() + +
+          Hexadecimal string representing a uuid4 value.
+static java.lang.StringgetSignature(java.lang.String message, + long timestamp, + java.lang.String key) + +
+          build the HMAC sentry signature +

+ The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, + and an arbitrary client version string.

+static longgetTimestampLong() + +
+          Get the timestamp for right now as a long
+static java.lang.StringgetTimestampString() + +
+          Given the time right now return a ISO8601 formatted date string
+static java.lang.StringgetTimestampString(long timestamp) + +
+          Given a long timestamp convert to a ISO8601 formatted date string
+static java.lang.StringhexEncode(byte[] aInput) + +
+          The byte[] returned by MessageDigest does not have a nice + textual representation, so some form of encoding is usually performed.
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+RavenUtils

+
+public RavenUtils()
+
+
+ + + + + + + + +
+Method Detail
+ +

+calculateHMAC

+
+public static java.lang.String calculateHMAC(java.lang.String data,
+                                             java.lang.String key)
+                                      throws java.security.SignatureException
+
+
Computes RFC 2104-compliant HMAC signature. + Based off of the sample here. http://docs.amazonwebservices.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AuthJavaSampleHMACSignature.html +

+

+
Parameters:
data - The data to be signed.
key - The signing key. +
Returns:
The hex-encoded RFC 2104-compliant HMAC signature. +
Throws: +
java.security.SignatureException - when signature generation fails
+
+
+
+ +

+getHostname

+
+public static java.lang.String getHostname()
+
+
Get the hostname for the system throwing the error. +

+

+ +
Returns:
The hostname for the server
+
+
+
+ +

+getDateAsISO8601String

+
+public static java.lang.String getDateAsISO8601String(java.util.Date date)
+
+
Convert a java date into the ISO8601 format YYYYMMDDTHH:mm:ss+HH:00 + note the added colon for the Timezone +

+

+
Parameters:
date - the date to convert +
Returns:
ISO8601 formatted date as a String
+
+
+
+ +

+getTimestampLong

+
+public static long getTimestampLong()
+
+
Get the timestamp for right now as a long +

+

+ +
Returns:
timestamp for now as long
+
+
+
+ +

+getTimestampString

+
+public static java.lang.String getTimestampString(long timestamp)
+
+
Given a long timestamp convert to a ISO8601 formatted date string +

+

+
Parameters:
timestamp - the timestamp to convert +
Returns:
ISO8601 formatted date string
+
+
+
+ +

+getTimestampString

+
+public static java.lang.String getTimestampString()
+
+
Given the time right now return a ISO8601 formatted date string +

+

+ +
Returns:
ISO8601 formatted date string
+
+
+
+ +

+getSignature

+
+public static java.lang.String getSignature(java.lang.String message,
+                                            long timestamp,
+                                            java.lang.String key)
+
+
build the HMAC sentry signature +

+ The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, + and an arbitrary client version string. +

+ The client version should be something distinct to your client, and is simply for reporting purposes. + To generate the HMAC signature, take the following example (in Python): +

+ hmac.new(public_key, '%s %s' % (timestamp, message), hashlib.sha1).hexdigest() +

+

+
Parameters:
message - the error message to send to sentry
timestamp - the timestamp for when the message was created
key - sentry public key +
Returns:
SHA1-signed HMAC string
+
+
+
+ +

+hexEncode

+
+public static java.lang.String hexEncode(byte[] aInput)
+
+
The byte[] returned by MessageDigest does not have a nice + textual representation, so some form of encoding is usually performed. +

+ This implementation follows the example of David Flanagan's book + "Java In A Nutshell", and converts a byte array into a String + of hex characters. +

+ Another popular alternative is to use a "Base64" encoding. +

+

+
Parameters:
aInput - what we are hex encoding +
Returns:
hex encoded string.
+
+
+
+ +

+calculateChecksum

+
+public static java.lang.String calculateChecksum(java.lang.String message)
+
+
An almost-unique hash identifying the this event to improve aggregation. +

+

+
Parameters:
message - The message we are sending to sentry +
Returns:
CRC32 Checksum string
+
+
+
+ +

+getRandomUUID

+
+public static java.lang.String getRandomUUID()
+
+
Hexadecimal string representing a uuid4 value. +

+

+ +
Returns:
Hexadecimal UUID4 String
+
+
+
+ +

+compressAndEncode

+
+public static java.lang.String compressAndEncode(java.lang.String str)
+
+
Gzip then base64 encode the str value +

+

+
Parameters:
str - the value we want to compress and encode. +
Returns:
Base64 encoded compressed version of the string passed in.
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/net/kencochrane/sentry/SentryAppender.html b/docs/net/kencochrane/sentry/SentryAppender.html new file mode 100644 index 00000000000..640c4899a34 --- /dev/null +++ b/docs/net/kencochrane/sentry/SentryAppender.html @@ -0,0 +1,364 @@ + + + + + + +SentryAppender + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +net.kencochrane.sentry +
+Class SentryAppender

+
+java.lang.Object
+  extended by org.apache.log4j.AppenderSkeleton
+      extended by net.kencochrane.sentry.SentryAppender
+
+
+
All Implemented Interfaces:
org.apache.log4j.Appender, org.apache.log4j.spi.OptionHandler
+
+
+
+
public class SentryAppender
extends org.apache.log4j.AppenderSkeleton
+ + +

+User: ken cochrane + Date: 2/6/12 + Time: 10:54 AM +

+ +

+


+ +

+ + + + + + + +
+Field Summary
+ + + + + + + +
Fields inherited from class org.apache.log4j.AppenderSkeleton
closed, errorHandler, headFilter, layout, name, tailFilter, threshold
+  + + + + + + + + + + +
+Constructor Summary
SentryAppender() + +
+           
+  + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Method Summary
+protected  voidappend(org.apache.log4j.spi.LoggingEvent loggingEvent) + +
+           
+ voidclose() + +
+           
+ java.lang.StringgetSentry_dsn() + +
+           
+ booleanrequiresLayout() + +
+           
+ voidsetSentry_dsn(java.lang.String sentry_dsn) + +
+           
+ + + + + + + +
Methods inherited from class org.apache.log4j.AppenderSkeleton
activateOptions, addFilter, clearFilters, doAppend, finalize, getErrorHandler, getFilter, getFirstFilter, getLayout, getName, getThreshold, isAsSevereAsThreshold, setErrorHandler, setLayout, setName, setThreshold
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+SentryAppender

+
+public SentryAppender()
+
+
+ + + + + + + + +
+Method Detail
+ +

+getSentry_dsn

+
+public java.lang.String getSentry_dsn()
+
+
+
+
+
+
+ +

+setSentry_dsn

+
+public void setSentry_dsn(java.lang.String sentry_dsn)
+
+
+
+
+
+
+ +

+append

+
+protected void append(org.apache.log4j.spi.LoggingEvent loggingEvent)
+
+
+
Specified by:
append in class org.apache.log4j.AppenderSkeleton
+
+
+
+
+
+
+ +

+close

+
+public void close()
+
+
+
+
+
+
+ +

+requiresLayout

+
+public boolean requiresLayout()
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/net/kencochrane/sentry/SentryExample.html b/docs/net/kencochrane/sentry/SentryExample.html new file mode 100644 index 00000000000..de225a0af7e --- /dev/null +++ b/docs/net/kencochrane/sentry/SentryExample.html @@ -0,0 +1,252 @@ + + + + + + +SentryExample + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +net.kencochrane.sentry +
+Class SentryExample

+
+java.lang.Object
+  extended by net.kencochrane.sentry.SentryExample
+
+
+
+
public class SentryExample
extends java.lang.Object
+ + +

+Simple example used to test out the sentry logger. +

+ +

+


+ +

+ + + + + + + + + + + +
+Constructor Summary
SentryExample() + +
+           
+  + + + + + + + + + + + +
+Method Summary
+static voidmain(java.lang.String[] args) + +
+           
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+SentryExample

+
+public SentryExample()
+
+
+ + + + + + + + +
+Method Detail
+ +

+main

+
+public static void main(java.lang.String[] args)
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/net/kencochrane/sentry/package-frame.html b/docs/net/kencochrane/sentry/package-frame.html new file mode 100644 index 00000000000..969123a68f2 --- /dev/null +++ b/docs/net/kencochrane/sentry/package-frame.html @@ -0,0 +1,38 @@ + + + + + + +net.kencochrane.sentry + + + + + + + + + + + +net.kencochrane.sentry + + + + +
+Classes  + +
+RavenConfig +
+RavenUtils +
+SentryAppender +
+SentryExample
+ + + + diff --git a/docs/net/kencochrane/sentry/package-summary.html b/docs/net/kencochrane/sentry/package-summary.html new file mode 100644 index 00000000000..a1ffcb0e7b5 --- /dev/null +++ b/docs/net/kencochrane/sentry/package-summary.html @@ -0,0 +1,172 @@ + + + + + + +net.kencochrane.sentry + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+

+Package net.kencochrane.sentry +

+ + + + + + + + + + + + + + + + + + + + + +
+Class Summary
RavenConfigUser: ken cochrane + Date: 2/8/12 + Time: 1:16 PM
RavenUtilsUser: ken Cochrane + Date: 2/10/12 + Time: 10:43 AM + Utility class for the Raven client.
SentryAppenderUser: ken cochrane + Date: 2/6/12 + Time: 10:54 AM
SentryExampleSimple example used to test out the sentry logger.
+  + +

+

+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/net/kencochrane/sentry/package-tree.html b/docs/net/kencochrane/sentry/package-tree.html new file mode 100644 index 00000000000..fda61d39a88 --- /dev/null +++ b/docs/net/kencochrane/sentry/package-tree.html @@ -0,0 +1,149 @@ + + + + + + +net.kencochrane.sentry Class Hierarchy + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+Hierarchy For Package net.kencochrane.sentry +

+
+

+Class Hierarchy +

+
    +
  • java.lang.Object
      +
    • org.apache.log4j.AppenderSkeleton (implements org.apache.log4j.Appender, org.apache.log4j.spi.OptionHandler) + +
    • net.kencochrane.sentry.RavenConfig
    • net.kencochrane.sentry.RavenUtils
    • net.kencochrane.sentry.SentryExample
    +
+
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/overview-tree.html b/docs/overview-tree.html new file mode 100644 index 00000000000..11bb8ac39c2 --- /dev/null +++ b/docs/overview-tree.html @@ -0,0 +1,151 @@ + + + + + + +Class Hierarchy + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+Hierarchy For All Packages

+
+
+
Package Hierarchies:
net.kencochrane.sentry
+
+

+Class Hierarchy +

+
    +
  • java.lang.Object
      +
    • org.apache.log4j.AppenderSkeleton (implements org.apache.log4j.Appender, org.apache.log4j.spi.OptionHandler) + +
    • net.kencochrane.sentry.RavenConfig
    • net.kencochrane.sentry.RavenUtils
    • net.kencochrane.sentry.SentryExample
    +
+
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/package-list b/docs/package-list new file mode 100644 index 00000000000..b263a2d0e9d --- /dev/null +++ b/docs/package-list @@ -0,0 +1 @@ +net.kencochrane.sentry diff --git a/docs/resources/inherit.gif b/docs/resources/inherit.gif new file mode 100644 index 0000000000000000000000000000000000000000..c814867a13deb0ca7ea2156c6ca1d5a03372af7e GIT binary patch literal 57 zcmZ?wbhEHbIIT!9-C*e{wE9>Kx3D)-;0v)C; KYxQGgum%9JOA&7X literal 0 HcmV?d00001 diff --git a/docs/stylesheet.css b/docs/stylesheet.css new file mode 100644 index 00000000000..6ea9e516161 --- /dev/null +++ b/docs/stylesheet.css @@ -0,0 +1,29 @@ +/* Javadoc style sheet */ + +/* Define colors, fonts and other style attributes here to override the defaults */ + +/* Page background color */ +body { background-color: #FFFFFF; color:#000000 } + +/* Headings */ +h1 { font-size: 145% } + +/* Table colors */ +.TableHeadingColor { background: #CCCCFF; color:#000000 } /* Dark mauve */ +.TableSubHeadingColor { background: #EEEEFF; color:#000000 } /* Light mauve */ +.TableRowColor { background: #FFFFFF; color:#000000 } /* White */ + +/* Font used in left-hand frame lists */ +.FrameTitleFont { font-size: 100%; font-family: Helvetica, Arial, sans-serif; color:#000000 } +.FrameHeadingFont { font-size: 90%; font-family: Helvetica, Arial, sans-serif; color:#000000 } +.FrameItemFont { font-size: 90%; font-family: Helvetica, Arial, sans-serif; color:#000000 } + +/* Navigation bar fonts and colors */ +.NavBarCell1 { background-color:#EEEEFF; color:#000000} /* Light mauve */ +.NavBarCell1Rev { background-color:#00008B; color:#FFFFFF} /* Dark Blue */ +.NavBarFont1 { font-family: Arial, Helvetica, sans-serif; color:#000000;color:#000000;} +.NavBarFont1Rev { font-family: Arial, Helvetica, sans-serif; color:#FFFFFF;color:#FFFFFF;} + +.NavBarCell2 { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF; color:#000000} +.NavBarCell3 { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF; color:#000000} + From 02f045f50177bd3292f9fe27068b8c8a007c1992 Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Fri, 10 Feb 2012 12:00:12 -0500 Subject: [PATCH 0011/2152] changed the version to 0.2 --- README.rst | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 22c1fcabf94..3b2126abaef 100644 --- a/README.rst +++ b/README.rst @@ -6,9 +6,9 @@ This is a very raw project at the moment it still needs some more TLC and testin Installation ------------ -Copy the raven-java-0.1.jar file to your java classpath and then configure log4j to use the SentryAppender. +Copy the raven-java-0.2.jar file to your java classpath and then configure log4j to use the SentryAppender. -The raven-java-0.1.jar is a self contained jar file, all dependencies are included, so this jar should be all you need. +The raven-java-0.2.jar is a self contained jar file, all dependencies are included, so this jar should be all you need. There is an example project checked into github.com where you can see an example log4j config file. @@ -27,7 +27,7 @@ Log4j Config example:: Sentry Versions Supported ------------------------- -This client has been tested with Sentry 2.7, and only very briefly. +This client has been tested with Sentry 2.7 and 2.8, and only very briefly. TODO ---- @@ -37,4 +37,11 @@ TODO - Add unit tests - Add more examples - Add more documentation -- Get compression to work on message body, it isn't working now,not sure if it is sentry server or raven-java. Might be incompatible versions of zlib Java->python. \ No newline at end of file +- Get compression to work on message body, it isn't working now,not sure if it is sentry server or raven-java. Might be incompatible versions of zlib Java->python. + + + +History +------- +0.2 - code refactor and cleanup +0.1 - initial version \ No newline at end of file From 6c0f6ea1edbf8816cb394b90b708a21032607972 Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Fri, 10 Feb 2012 12:04:03 -0500 Subject: [PATCH 0012/2152] bumped version to 0.2 --- example/run2.sh | 2 +- raven-java-0.1.jar => raven-java-0.2.jar | Bin 1170095 -> 1171046 bytes 2 files changed, 1 insertion(+), 1 deletion(-) rename raven-java-0.1.jar => raven-java-0.2.jar (93%) diff --git a/example/run2.sh b/example/run2.sh index c173d116b87..44a5d7d7bd9 100755 --- a/example/run2.sh +++ b/example/run2.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -java -classpath ../raven-java-0.1.jar net.kencochrane.sentry.SentryExample log4j_configuration.txt +java -classpath ../raven-java-0.2.jar net.kencochrane.sentry.SentryExample log4j_configuration.txt diff --git a/raven-java-0.1.jar b/raven-java-0.2.jar similarity index 93% rename from raven-java-0.1.jar rename to raven-java-0.2.jar index c6eba2279d6aabfcd3953808d787fdafa9465549..53f63eff201e27b0d4484afad3c68991971bb911 100644 GIT binary patch delta 27540 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 baCMt?ll6tsBvsl}+Pt`K>J<3{1+(x!=OY*% delta 26690 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 Date: Fri, 10 Feb 2012 14:05:37 -0500 Subject: [PATCH 0013/2152] fixed issue with formatting --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 3b2126abaef..0521d506369 100644 --- a/README.rst +++ b/README.rst @@ -44,4 +44,5 @@ TODO History ------- 0.2 - code refactor and cleanup + 0.1 - initial version \ No newline at end of file From 78fb2d8e47be1472160a386eba736ae9b62b81ee Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Sat, 11 Feb 2012 12:22:01 +0100 Subject: [PATCH 0014/2152] Add pom file, proxy support and remove HttpClient dependency --- pom.xml | 64 +++++++++++++++++++ src/net/kencochrane/sentry/RavenClient.java | 62 ++++++++---------- src/net/kencochrane/sentry/RavenConfig.java | 38 ++++++++++- .../kencochrane/sentry/SentryAppender.java | 11 +++- 4 files changed, 135 insertions(+), 40 deletions(-) create mode 100644 pom.xml diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000000..d2236b46b3e --- /dev/null +++ b/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + + net.kencochrane + raven-java + 1.0-SNAPSHOT + jar + raven-java + Java Raven client and log4j appender. + + + + commons-lang + commons-lang + 2.5 + + + commons-codec + commons-codec + 1.6 + + + org.json + json + 20090211 + + + log4j + log4j + 1.2.16 + + + junit + junit + 4.8.1 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + UTF-8 + + + + org.apache.maven.plugins + maven-resources-plugin + + UTF-8 + + + + + + \ No newline at end of file diff --git a/src/net/kencochrane/sentry/RavenClient.java b/src/net/kencochrane/sentry/RavenClient.java index 0d1137e5775..0819ced448f 100644 --- a/src/net/kencochrane/sentry/RavenClient.java +++ b/src/net/kencochrane/sentry/RavenClient.java @@ -1,13 +1,13 @@ package net.kencochrane.sentry; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.util.EntityUtils; import org.json.simple.JSONObject; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; + import static org.apache.commons.codec.binary.Base64.encodeBase64String; /** @@ -27,6 +27,11 @@ public RavenClient(String sentryDSN) { this.config = new RavenConfig(sentryDSN); } + public RavenClient(String sentryDSN, String proxy) { + this.sentryDSN = sentryDSN; + this.config = new RavenConfig(sentryDSN, proxy); + } + public RavenConfig getConfig() { return config; } @@ -143,48 +148,31 @@ private String buildAuthHeader(String hmacSignature, long timestamp, String publ * @param timestamp the timestamp of the message */ private void sendMessage(String messageBody, long timestamp) { - - DefaultHttpClient httpClient = new DefaultHttpClient(); + HttpURLConnection connection = null; try { - // build up the Post method - HttpPost httppost = new HttpPost(getConfig().getSentryURL()); - // get the hmac Signature for the header String hmacSignature = RavenUtils.getSignature(messageBody, timestamp, config.getSecretKey()); // get the auth header String authHeader = buildAuthHeader(hmacSignature, timestamp, getConfig().getPublicKey()); - // add auth header to http post - httppost.addHeader("X-Sentry-Auth", authHeader); - - // set the body into the post - StringEntity reqEntity = new StringEntity(messageBody); - httppost.setEntity(reqEntity); - - // call the server and get response - HttpResponse response = httpClient.execute(httppost); - HttpEntity resEntity = response.getEntity(); - - // not needed right now, keeping around for debugging purposes - //if (resEntity != null) { - //String content = EntityUtils.toString(resEntity); - //System.out.println(content); - //} - - // need to consume the response - EntityUtils.consume(resEntity); - } catch (Exception e) { + URL endpoint = new URL(getConfig().getSentryURL()); + connection = (HttpURLConnection) endpoint.openConnection(getConfig().getProxy()); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + connection.setReadTimeout(10000); + connection.setRequestProperty("X-Sentry-Auth", authHeader); + OutputStream output = connection.getOutputStream(); + output.write(messageBody.getBytes()); + output.close(); + connection.connect(); + InputStream input = connection.getInputStream(); + input.close(); + } catch (IOException e) { // Eat the errors, we don't want to cause problems if there are major issues. e.printStackTrace(); - } finally { - // When HttpClient instance is no longer needed, - // shut down the connection manager to ensure - // immediate de-allocation of all system resources - httpClient.getConnectionManager().shutdown(); } - } /** diff --git a/src/net/kencochrane/sentry/RavenConfig.java b/src/net/kencochrane/sentry/RavenConfig.java index 006d77a020d..4118a9b8d4b 100644 --- a/src/net/kencochrane/sentry/RavenConfig.java +++ b/src/net/kencochrane/sentry/RavenConfig.java @@ -1,7 +1,6 @@ package net.kencochrane.sentry; -import java.net.MalformedURLException; -import java.net.URL; +import java.net.*; /** * User: ken cochrane @@ -12,6 +11,8 @@ public class RavenConfig { private String host, protocol, publicKey, secretKey, path, projectId; private int port; + private String proxyType, proxyHost; + private int proxyPort; /** * Takes in a sentryDSN and builds up the configuration @@ -19,6 +20,16 @@ public class RavenConfig { * @param sentryDSN '{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}{PROJECT_ID}' */ public RavenConfig(String sentryDSN) { + this(sentryDSN, null); + } + + /** + * Takes in a sentryDSN and builds up the configuration + * + * @param sentryDSN '{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}{PROJECT_ID}' + * @param proxy proxy to use for the HTTP connections; blank or null when no proxy is to be used + */ + public RavenConfig(String sentryDSN, String proxy) { try { URL url = new URL(sentryDSN); @@ -37,6 +48,13 @@ public RavenConfig(String sentryDSN) { this.port = url.getPort(); + if (proxy != null && !proxy.isEmpty()) { + String[] proxyParts = proxy.split(":"); + this.proxyType = proxyParts[0]; + this.proxyHost = proxyParts[1]; + this.proxyPort = Integer.parseInt(proxyParts[2]); + } + } catch (MalformedURLException e) { e.printStackTrace(); @@ -46,6 +64,7 @@ public RavenConfig(String sentryDSN) { /** * The Sentry server URL that we post the message to. + * * @return sentry server url */ public String getSentryURL() { @@ -60,8 +79,17 @@ public String getSentryURL() { return serverUrl.toString(); } + public Proxy getProxy() { + if (proxyType == null || Proxy.Type.DIRECT.name().equals(proxyType)) { + return Proxy.NO_PROXY; + } + SocketAddress proxyAddress = new InetSocketAddress(proxyHost, proxyPort); + return new Proxy(Proxy.Type.valueOf(proxyType), proxyAddress); + } + /** * The sentry server host + * * @return server host */ public String getHost() { @@ -74,6 +102,7 @@ public void setHost(String host) { /** * Sentry server protocol http https? + * * @return http or https */ public String getProtocol() { @@ -86,6 +115,7 @@ public void setProtocol(String protocol) { /** * The Sentry public key + * * @return Sentry public key */ public String getPublicKey() { @@ -98,6 +128,7 @@ public void setPublicKey(String publicKey) { /** * The Sentry secret key + * * @return Sentry secret key */ public String getSecretKey() { @@ -110,6 +141,7 @@ public void setSecretKey(String secretKey) { /** * sentry url path + * * @return url path */ public String getPath() { @@ -122,6 +154,7 @@ public void setPath(String path) { /** * Sentry project Id + * * @return project Id */ public String getProjectId() { @@ -134,6 +167,7 @@ public void setProjectId(String projectId) { /** * sentry server port + * * @return server port */ public int getPort() { diff --git a/src/net/kencochrane/sentry/SentryAppender.java b/src/net/kencochrane/sentry/SentryAppender.java index aed14e21bc1..10dd3efc4aa 100644 --- a/src/net/kencochrane/sentry/SentryAppender.java +++ b/src/net/kencochrane/sentry/SentryAppender.java @@ -11,6 +11,7 @@ public class SentryAppender extends AppenderSkeleton { private String sentry_dsn; + private String proxy; public String getSentry_dsn() { return sentry_dsn; @@ -20,6 +21,14 @@ public void setSentry_dsn(String sentry_dsn) { this.sentry_dsn = sentry_dsn; } + public String getProxy() { + return proxy; + } + + public void setProxy(String proxy) { + this.proxy = proxy; + } + @Override protected void append(LoggingEvent loggingEvent) { @@ -40,7 +49,7 @@ protected void append(LoggingEvent loggingEvent) { String culprit = loggingEvent.getLoggerName(); // create the client passing in the sentry DSN from the log4j properties file. - RavenClient client = new RavenClient(getSentry_dsn()); + RavenClient client = new RavenClient(getSentry_dsn(), getProxy()); // send the message to the sentry server client.logMessage(logMessage, timestamp, loggingClass, logLevel, culprit); From 7f4980bd27ea90f6b0c0fb77e266d7ecca8e0ac9 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Sun, 12 Feb 2012 20:56:14 +0100 Subject: [PATCH 0015/2152] Add stacktrace information and actually mavenize the build --- .idea/libraries/common.xml | 13 --- README.rst | 37 ++++++-- example/run.sh | 3 - example/run2.sh | 3 - .../net/kencochrane/sentry/SentryExample.java | 34 -------- lib/commons-codec-1.4.jar | Bin 58160 -> 0 bytes lib/commons-logging-1.1.1.jar | Bin 60686 -> 0 bytes lib/httpclient-4.1.2.jar | Bin 352254 -> 0 bytes lib/httpcore-4.1.2.jar | Bin 181200 -> 0 bytes lib/httpmime-4.1.2.jar | Bin 26890 -> 0 bytes lib/json_simple-1.1.jar | Bin 16046 -> 0 bytes lib/log4j-1.2.16.jar | Bin 481534 -> 0 bytes pom.xml | 24 +++++- src/META-INF/MANIFEST.MF | 3 - .../net/kencochrane/sentry/RavenClient.java | 79 ++++++++++++++++-- .../net/kencochrane/sentry/RavenConfig.java | 0 .../net/kencochrane/sentry/RavenUtils.java | 12 +-- .../kencochrane/sentry/SentryAppender.java | 5 +- .../net/kencochrane/sentry/SentryExample.java | 55 ++++++++++++ .../test/resources}/log4j_configuration.txt | 1 + 20 files changed, 190 insertions(+), 79 deletions(-) delete mode 100644 .idea/libraries/common.xml delete mode 100755 example/run.sh delete mode 100755 example/run2.sh delete mode 100644 example/src/net/kencochrane/sentry/SentryExample.java delete mode 100644 lib/commons-codec-1.4.jar delete mode 100644 lib/commons-logging-1.1.1.jar delete mode 100644 lib/httpclient-4.1.2.jar delete mode 100644 lib/httpcore-4.1.2.jar delete mode 100644 lib/httpmime-4.1.2.jar delete mode 100644 lib/json_simple-1.1.jar delete mode 100644 lib/log4j-1.2.16.jar delete mode 100644 src/META-INF/MANIFEST.MF rename src/{ => main/java}/net/kencochrane/sentry/RavenClient.java (69%) rename src/{ => main/java}/net/kencochrane/sentry/RavenConfig.java (100%) rename src/{ => main/java}/net/kencochrane/sentry/RavenUtils.java (94%) rename src/{ => main/java}/net/kencochrane/sentry/SentryAppender.java (86%) create mode 100644 src/test/java/net/kencochrane/sentry/SentryExample.java rename {example => src/test/resources}/log4j_configuration.txt (99%) diff --git a/.idea/libraries/common.xml b/.idea/libraries/common.xml deleted file mode 100644 index 80734af5ac3..00000000000 --- a/.idea/libraries/common.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/README.rst b/README.rst index 0521d506369..99fdbb678eb 100644 --- a/README.rst +++ b/README.rst @@ -6,14 +6,33 @@ This is a very raw project at the moment it still needs some more TLC and testin Installation ------------ -Copy the raven-java-0.2.jar file to your java classpath and then configure log4j to use the SentryAppender. +You'll need Maven 2 to build the project:: -The raven-java-0.2.jar is a self contained jar file, all dependencies are included, so this jar should be all you need. + $ cd raven-java + $ mvn package -Dmaven.test.skip -There is an example project checked into github.com where you can see an example log4j config file. +The last step will build the standalone raven-java jar file but also a jar file containing raven-java and all dependencies, which +you'll find in the target directory of the project. + +**Option 1**: add raven-java as a dependency when you're using Maven:: + + + net.kencochrane + raven-java + 1.0-SNAPSHOT + + +**Option 2**: add the plain jar and the jar files of all dependencies to your classpath + +**Option 3**: add the self contained jar file to your classpath + +Check out src/test/java/resources/log4j_configuration.txt where you can see an example log4j config file. You will need to add the SentryAppender and the sentry_dsn properties. +Log4J configuration +------------------- + sentry_dsn ~~~~~~~~~~ You will get this value from the "Projects / default / Manage / Member: " page. It will be under "Client DSN". @@ -24,15 +43,23 @@ Log4j Config example:: log4j.appender.sentry=net.kencochrane.sentry.SentryAppender log4j.appender.sentry.sentry_dsn=http://b4935bdd78624092ac2bc70fdcdb6f5a:7a37d9ad4765428180316bfec91a27ef@localhost:8000/1 +Proxy +~~~~~ +If you need to use a proxy for HTTP transport, you can configure it as well:: + + log4j.appender.sentry.proxy=HTTP:proxyhost:proxyport Sentry Versions Supported ------------------------- This client has been tested with Sentry 2.7 and 2.8, and only very briefly. +Other +----- +If you want to generate the javadocs for this project, simply run ``mvn javadoc:javadoc`` and you'll be able to browse the +docs from the target directory of the project. + TODO ---- -- Add a ant task to build the jar files (I made this first one from intellij (10.5 community edition) File-> Project structure -> artifacts. -- Make this maven friendly. Not familiar with Maven, so maybe someone else can help with this. - Create better documentation - Add unit tests - Add more examples diff --git a/example/run.sh b/example/run.sh deleted file mode 100755 index 59502a869d8..00000000000 --- a/example/run.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -java -classpath /Users/kcochrane/github/raven-java/out/production/raven-java:../lib/log4j-1.2.16.jar:../lib/httpmime-4.1.2.jar:../lib/commons-codec-1.4.jar:../lib/httpcore-4.1.2.jar:../lib/httpclient-4.1.2.jar:../lib/commons-logging-1.1.1.jar:../lib/json_simple-1.1.jar net.kencochrane.sentry.SentryExample log4j_configuration.txt diff --git a/example/run2.sh b/example/run2.sh deleted file mode 100755 index 44a5d7d7bd9..00000000000 --- a/example/run2.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -java -classpath ../raven-java-0.2.jar net.kencochrane.sentry.SentryExample log4j_configuration.txt diff --git a/example/src/net/kencochrane/sentry/SentryExample.java b/example/src/net/kencochrane/sentry/SentryExample.java deleted file mode 100644 index 16daedf280c..00000000000 --- a/example/src/net/kencochrane/sentry/SentryExample.java +++ /dev/null @@ -1,34 +0,0 @@ -package net.kencochrane.sentry; - -/** - * User: ken cochrane - * Date: 2/6/12 - * Time: 11:35 AM - */ - - // Import log4j classes. - import org.apache.log4j.Logger; - import org.apache.log4j.PropertyConfigurator; - -/** - * Simple example used to test out the sentry logger. - */ - public class SentryExample { - - // Define a static logger variable so that it references the - // Logger instance named "MyApp". - static final Logger logger = Logger.getLogger(SentryExample.class); - - public static void main(String[] args) { - - // PropertyConfigurator. - PropertyConfigurator.configure(args[0]); - - logger.debug("Debug example"); - logger.error("Error example"); - logger.trace("Trace Example"); - logger.fatal("Fatal Example"); - logger.info("info Example"); - logger.warn("Warn Example"); - } - } diff --git a/lib/commons-codec-1.4.jar b/lib/commons-codec-1.4.jar deleted file mode 100644 index 458d432da88b0efeab640c229903fb5aad274044..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58160 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 diff --git a/lib/commons-logging-1.1.1.jar b/lib/commons-logging-1.1.1.jar deleted file mode 100644 index 1deef144cb17ed2c11c6cdcdcb2d9530fa8d0b47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60686 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

lTsdod`N4YNOd~zpxRbR=SJH8j3v}!D-3xf!lHN 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�av&K3Pp44!jSERtR4zH4if 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%0A-`2O(p7>{9%7g)u)# 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

r>I?Yi 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

!TTop>N&YN>ZlFuA>w*G%yk>!b7da zT;duw)$L1kuJl13(90Z)?iAol%npR7YiMd;u@bxqVD!wVHJENZ+EQS<;UhA``dhE= zcXq-IMu#c$e#48HW;mUe_2=mtZt@7Y2sq-#(_6fM$B?WpR3XnO9wZ2!42!?<^2 z=&>rR&1t}S0Xf9W*XGut^&~JeQhDbs_0rU||Mj)E&T(q+@ViR9KC4xOlw&$i+iSyq z!N}%WXW{zdDr12EQyzDx%9hcaW#xq3BGywo(((b@f7)d#Sp4d!RA_`WD)>z~`{4@) zr_3}JinR*gwxj9`p#EB!{Q$V>VA^LnupLcx2~#=@+|f zyg_qv_*Am}9-C%1o_;#|c|zdyuj3z%f!i(G0e=`d+P6s4v;kSv&P1}zGBL*RlhrOm z@z*;X2L~Fk4Ot#x1XTcWUUF9TAod;6mH0&>+~xyO2L-cR6m}q$8UwKvz~=1Sy%*B& z;R$OT#%Q$M#U!48L4Elo&TxNce@C?_1so|?Gq#uct-Vq%YxUqx0-8NRwU!!ke7qI+ zaB!6-IA{oZ)y8x|-Cs6t2p*aJ^;N2McK+TDG-~zoDV_i5l3j-phU8(MpH<z+7M*4{nD)d5o5x3*Zwl*a zsZ2y!cT!u(h=R{~+{qfGkxwdPctLA%$b9MQ-t|gXxq(nVk1H=K(%m&9-NR`2IlAFE zL1_=MaDnh17<>Mu-NSnW_zYaSwz(Wtl%7uQfy(GL^OH@s5Pt)&t1m8DL8B9$l75E6 zHM(G)+hg_+C%M2pVrZdmxX0OjI#7p+{w=w>e3s{4&4c5ni{X3t(C}fbWpz~{G8a)r zUc&fnt~9yt5}^}vmuD&J%4fR9)M5!HliOtsQzP<{^vaDzqaWDRVWX| zg9Kia%Zzo-xHLnCzeCr5fBx(vCg1`l4k6t^83z^vic-BXwig#6Wz@svlbe1eSDsO} zYS@OFXR%naTF?iI9BtevwYq49YirYL+Gt(0u2?H=)6lRe`|y66lp;+LH}p>1qTSkC zzsq=Xo_slc!}GjzB~%}U<2?D%aghCSPg&!pQE-&L{GKGxrF_CFGn(hD=%!uZtmGzJ z@bzbw=M^tO8-eXBO}4Ax3FS3IqRUynhVPxPfOmf`_a?=&;Xl+gs7GiifB z^;OBAb@FWk{n2Ws#IaiB4*-E>8STjy{a&Hjb&A_GNPYHpx`F7*94(0|5DUss@yvjw z5=~kk3zW`f@uWLppiY?s$^Jw61S~~s#pV(5HqfyLfQ@bJgnre~Kn(rjM` zGtP}E-MR)`>y8+ZP8!`{j~A4JHooP~+I@k(D%kX@E*rYe5U~=~Y7a51u-kPl$Apf5R z__zU9H4uZTv)VrL*TBG<(-y=9rgLvMMs5SSnk@4>0Kk;|qQvHJcP z=m7nx&|zv(x&<~6NtAR;9(k1J>A}Q2f7ejg zhH3^*_95AnvmCw#E`-jYbX0%F2BRFz4g)%bjz!jT!C!!i8G9u$q#x!v)qfg<1&kV< z5=8xRzhf#K`uGhbuHB1h6OG@N^FH2|t&W!AhlMUQXdsf3B#qwAO4c@~dvBP7+4hf+ zRs9XA%yMv3DG*AXRN={V802 zUA@E{DRnXWnIKa|JWHk$%?9^jmV0wpD$-6WSsxUHTvK~emW1lVv!pv+6@ghnD!80=2M>Of+fs{2jtz8Ep<-oUB zx70b5v8E{1@p4vZkXJ_aZeadu7S$WB4aS{sR)O}!R(!^ce6knItD1|0e6qkU>=N3X zh#^m*`SdOzDXbO0q?#LzxZ8A`g=b-Qsuy!(9K||baAl6V_8ELT<}YMddpzg!F?4fO zUwS;Wq)f16{YZmhP0O-uHkmz-e3K{Xy~-c_&5>g#mCQzC7#FyWN#??fXfr8iiC&}@ zGz~=%v=e!Lv)hK&GH1cgd&<<;y=r~j>O#RXSMKv@)pUK&rH{>?+x?pD^D zweZZ+sgrI9 zA0u#257wQcd$iYRG4W;FdG5@e(OvmBc0uD%WJU(&#`mgLA+{qQm^k@8cCa--svaIc zPAqTZD{uVY3?WLv09tF&DB)m^BI&Buff@%+b$RG6~(td1bOmS5~x zfvHm}5T2q1L6&%ZIqZCp0hj=-$L>hjc{=rc;I{e_(JGCofGuA8ufOlYP;#uA=>xl_ z4F_W=1&wIfw&P56ez>wiX|ztlhM-B^$jM2HLVb{_bx-Ar$9*bHF%O+?$Y=g*`>tuZ zf#Mzl6i8AYily*%vKbB}d+%URa|?aAYqie3lJQ?@@QU~~1ocf>H(GqTKvvtcCO->& zw(Q`31*css(vKP*CC)nDoB}Y4vy~JZHx^V*_|RT(d*M1)5(W;bnZU4LZYrX$N?A8Q zH35vw=5!Slmgjm(`oKyzqFUIbqzI{M&Ai(P@@tiF!clV z=*EheRbC^7NYELhro^Ajdh*JLZxVT+3XK$3|OK% zg5n>rSXdkz92uyyUUOp3*j!}l-dS@z0_$jbL&rw;nrd`r|FZH?qTK1dO(_QPo8t5EFi1n~`O+KfU?;$^U*%$>cIr6X; z1sgW=+qZ+J#RuJUG7Odvq~m9w&xC5A%sE9YCI=yJNG1k_FLOWP0ly_FHyU?+=9M9f zJ-q;F?54eIx$9{ks8E7c=&`GnlzorGUO;Jp9CmhjwHcZvcgmA;}GnHR8g)2BHlfZ2dmz83BIf#gBydMcZF}yY8Jcf8e zehw-ru05q{Azo!SmMEfl+9O`Ig1>qdPHQBJ)C zJhp8RFqM5RHh-n;?tcq;q(Y*3p6ynK+c$u1?)aA=q zU<6`x>6>E|5WGUkZ3g+cVZTb8_^5$u?3BdLum6xzwXVE^*19$vBX|ACV_M=BxLZeq zo$~I0cT(J~ z%hz)Wt)*E6Gqc|6CzW~zGVxBInqYE*h#;H9uz;dF=hHGxv`5OCD&OkyIXAyQpOsTc zkb3B#|9Hb#zj?%;X@;yAo+4r@s5L`wb?+XQStw0Z-!ge%*OGQ{WE!pmR8q+3s5V5` zaDI>MYo$fcccvrc#U)tbhkV=+nzNBdtaN;bEZ$eYps-KjQ(W1qx2l|>?gCk}dNL!W zj(dc$$|HqiQd~Eg!Lu*3H(HXQOELK}ZlKF1w2(DS9C;fkVQ~YA$OE)q`s?7Qq>*c7$@pZ8f`{$3Q?LOK{>@Vhgzsq1UEBDVHZxe+^d;} zSbB*gXozN);!j1z{Q-R;HLA!Uk@OaR5`=KOl(2iMZI=$oRdA`*=Xe0mUh|5qoLt$< zB+>TqZBJZwHp*p`m;bi0k?Ds3nO=a4W!S)H{~#37NA&IHsv;eLo*BzjEW|A{A^yB; z2Pw_fFr>hR@ZA&W!4$Q#XZMpJqZz=OqI+m>m*;Ft8r$>N75*en_wWdK$*PIJs6nnC zSBY2M#mDm+rtV`d1C?|4)uTC|g0!rMD`2fFyM`E+{281>zKwU|B4Lyv=I@*Dx;wPT zeSMxM5B$iSMF`^xy?=!5ki|=G_yrN$C9#Bm@qsOVDNQ`4gU|j+Ch!dHm46Q@iOn>$ChVD5XKsa!Z0FwbGemI&JI z7p~wk7n-U=*Y=-n)a^cNt_03@pl9tNMCW@N&9TvT%v*ioFCerB!LAs*gOtw}nnOh` znL0ZS?_fIPHa8q;y-CZO9wkh8jI*R%mymYs%J*7u#c&5Y{zXV;7AYE@>CY z&Z{zYTYnO(sx$Vq8ke3W*3vu@F03}GsrRhSV6bM5FB3RgWLF3HN47iUL*5F|Me~Wc z@HYnD@ZPJr{sQi`EHt^ z>uj*h&`0PpYeoGgGUo*F3bX}uhHm}|Ixn)u6b@FU3DYR$FfdXw5%$W=WOn~6pjmh{ zeyR!Z#%cH>58DR?uXa#fG0S(-Ic_mW>YFUm_gHRdkp)c3hcZ zlGjunBTBxQ*TkqeZcq`}yxYbGgs8Q>Cn z@L{fA*sSzNK5!)H2$K6tpETE{XB50=G0}~bv4Z*v@iBki$2!xMCQJ5QSXZ>+i#yuGN z-azzvlAALQ1E*51b<&wyn~pv8$?aHWftFXBg<2BtR73k%RkO0=i_+34O&8^qQuuGX zir(T@Wdq0NMP0{M$B36o>xfiJpPKFk|C(tdT`K#FlUFbpE+Zc7L0nW${Ef6i*e<@$ zhU_llSH^!8aiQul2m`)(AT!wi5R3AEged=1Cj3v{`5!_B>F88&D!a&5R7Uyi08VrQ= zXgwrcrQf@*1Yk&LikkbA8Enz`mzJ_+630Dh7PR20kd^ybXp z>*Z^S7OK|LzHDi~;c4e_f*;9Fts5>g>n#c`Ruh3p%ChhH=NL99Ky!M4O^^eME^_&U z>ka^h)+gVKs!7{oTWD3daK)XW8*6K99j&9xRxO6%Z)H59o4E9pgGcq zS7azER33W$Vzg~H%vZ}5$ILn|Y4X)<1_3cuR3nd#$tjA{oXKt3w5KPv)(Nw9l?Lhs zBUA?Gw`#`cyNJLV&f#?ENu?q^%wZ0aN`Cpt5lS=$j7g9p2`@;E6(oJ!C+Kd0%lpt} z`!vIBa;mK-{lT+rawq!qb^8ogJ?B5e%G6;5Fs!g+7bbh^Fd*s~b$GouZHFncGBdTR zah4P&Uondu8kGoP+cTNTr3>5QZxT3;aZPL_7wAh{4bmCRl?1GYk<572XJ`{V9V4?{ z0*Kt`eNuPIBrYb$$qmq#r*>!bN&$5GV6@*c#)wl8FV;U|3I9|I`lNV@%yCHe1wV zf(k!LmmSJ?#*(Z_FH#4OCs5rn;EeE@yMWKjaIW^nmZTlzQh}KzmC={%Kz3sEG%;xo zG@+co>1=uwl(7lJE{_9?cZsDP6Fo--UG2JKYIlcYYVYp6J^Kbt?LlG6>^299UaP^- z?y14>_ESQ_e(}OcP|q5=5PJJ=1C}?eOD-XK2037VohuaFf>Q9; z1E{2K_|bf^CQLa2OGlmTuH5IZk;Of^RJBv6l#r9vHRk)c^@=8qLbgucSc&aEXH=JR zP5A?6k*0#HS?Yu}e%$5e4#yaGryA9ILR4)-Eyl5ou&&zL z!g7Bx?zYHwqE8OrnM-S4Qn4G9=LrUYahfU8%#fRo0(fzpgshI9pH0lI68`PL^vdAU|>ZJ z169^QM@c?Ds(x6pt&H}N+~7PTVKC2Fx6L7)aRJ>I6zs*nqFTmI%rWIO8{ebiR3>2@V+e)U%4EOgPQ zAH}Q{?8cPx9uCWiI%StJ;v?NY(&WgqeQLN?NC+GG8(%-`t;0hon^s;olWiMPcC4%$ z3sFAa(Y5gQDcYxCI1^IU!hp7UR?$r>%jc=e3LOGc`3mipl|C9A_TVjt(Wqrgw}67mLPV|GTP8X>qF71C@?@-Z=df;)u z{=94b3s_v!`OUV|^`7S8XSjp+=!199y*EG}cf6+G=uq?#ujuS9#U9Yj;0TX|MMm%T z9;EQ5^l*ALf-xD+cO>KQjh896xvB@+LYQEu;}-qzObpX_vze z7X0ESJ~|AXL3NquBYTUE5sW$A2v;_dGxRhQGR~4x}P&(o5#Qj^QT6vsz$ z;`9=#vejc!GGfcJ6%-Y7vQxEU^0U=7<8q_rp&lUmjJ^vh77h?aL3Ioq`+x!>V|`j6bpFxX>6d{U4Kl6;~5ZS(aHp0ZKhl~VNsOP>7{75)Fl z2mhaBiL#rmF~fiA5|!qp<9dv?1dgWRHpWfh3xPc+y+j#{G3GYxL_Zepx}uZ(#kS>dPCZZg))t_E1B-X z8FvRW442Ld=h{hfp^6OB)Q;6)wH2xP^mkE1>oIMhLa-?mZD0Q7z}Io~0may{bt2){ zX9)aeqFLBU?g?-MKH;CqJ+r9P;i}>FsGBr}a9}m^p`dM;^Svl4CVG#!1s06n9uRhl z3JmpOPoV6bF2C5cV@1h;eNhFig$^BDFDgt5Zu%Q|EYPi<7GFw9(5-M5Cpl)QyPvX#L?)gq^f?yo+-BE!nI zz4D6(BifnoECl#frj2(=IP|LpsyX0;zv3k*9T#IgXzt)B)W0_`7gbNZ>E79_Uo7Fn ze1i8{5P~KoMXpRcW=lA_B2wrLr8G5JHI=c!+C4LcesXkLLVtN?P0a_(iwGt$2xrzKe85^oVpgl*A!m}yt&-sbai~c3$P3QZi)|=!7k@-si*Bj=f___KGHQDI zeV})~D_t5)EyHsl>F)X&oh<;(01?ldOF0Wr1TOH21@VYiuz1Yg@54~^JC5WxBZ|n7 zZ8FR*4W~t{O>BtH5DL8n6jBT1z(@fOi6|-o%6u*P826;|Z9qW|->96VM+TL8H-2lk zP<~T)y=7#4P)8!()oumK`d@Soz_|C;K3Q)zH_HTkHTH&QdQ7fSH@IW3FeFNqseS-; zR1MY$1lBM+X9T1RM9B+A>pF>qRphH$Lfje*O9rfr$-yu&xRoa-lhPkpgOrBSWPimW zfe&BKG~#QBG>7)h;g zH<>q+5VHcyc2(vcV*TW61R6@Y%L*5okk{YZRyRn)(>H5Y0eZ>yfKc@I(J-7mBA8BI z&@*P6RxmzBBD@p!TiYct(;99Fp0^yXTaRzKj=xyHzn-6Q0N!uh;M;A>5V#mHd&~$T zMqOEbZt*`-z<<|4{Gmq~(BMWRE;M790H&wff5q3dErX!j86@opNDdtNB_4*u;*${L z2Java7VAs8uMKbIN&=uWu?i3YAYD5#%8nZV)~>2 zu_b<-0K6TOniuaxl-4T-wMyJfs-ak%ITKRK=ozMw79*9;%awmfa)HB8`4erElD1nL zVRdYP*&0&oml;Z;uB$|Lf8UwnBc=R^y={_&z#1 zT5DU{LFHtmttdZWj8RSL3R6wx3iJsHYbf8Bq?oI=l^d^ell;?8-fSx}v<_L7r`F5m zntbxpeC~cy_~$C{>HPiPbyiw5A{t!WnwKdQzK6u-?Jg+-_CsH`XW!OPIV)bVQ8Ato4%IcdOokANNAh}MuM`+*Bgp{e(qf{9yLmx~dVjKgW zNYeagLBtiV^=>`{;=nSQn|h@7@jb+I5QIYF3R>1q1e*;T!8%E#c$mYPY)0J64f1+d z{}!EqXQWG7Y29xRV$~qo68VUDguuCuw6-JN(5cX;11y+soj049;qPX7r8$r!Vu^D@lWWz+(&1wT z^!0>gYJDHvG@buR>eui$M3tH6LfVSgCjEMf8&^ZnwQaK72WQ-ect&zR8TkR`*8$b9 z1B+h=T4xv6B}dyzcYMDQ`TTZC9X|rFy@rfG1aCe?V?LwkY{vnwO41vE&@vJMBpLe{xs+%CGalUluDTwE8Gs3aEa|6s5lxUvY|(GM zVF}ssaui<8I~rbmd~!Po#v|q@#{Ia-H@2Ti3`4UC7WHR(YNM5DyVK>T)#U5-dAb;& zDz9Xpd+V|fe`~(9wJHtE+*PUn9n-|o^aVc%PUSoH$Ec9_SQ|xtv{2^;(o&cv$6R3e zqAW&-%53ELV5mT&r8sVFp+0LHoJ*yLni%FN8As3%YY_6*Mm{;Ika#VjBnxN~>59yH?I%W1eQ2^Bkp=XwMl(N$>>o=p+Qnwp!eo?m zl+wEvHw6u(tjZxPvixs*s+dbs2iZ#9%C;;zC_0ske^C{$jnqygr*FeD^_$&YTPJ zc@hmW^%Z@kv%wk2CRyQ3G!`a`fTg>sJshOC2vHWKJ%!w8bgg9Lh2cX< zRVe}AO}qdz1?zp*;|m-34Wj&=L8kSzVf6^l92**c2N$o~bpH?$bebgZiWxTkn9PFN9>8SK(0lL+p{`;ZAqX0NIX;G^mezls}d4PBRb_95$FY3V2qZBLMr|IK$08fGzkn z^ST9kVOMm80Cm4$L_lkV%D~tEaB!C23YGo-@vfWv$kYGDu=V48b~Mv>H2y!?IODD4 zlo*hQzdPOi7&bG);BxX#HbnP8kdX$1;f>pj1oproI;iSb!`eI9o}JOVdx%sVRQFZ2XGs5?)<_An96ADQGp14O^q9*dcf(eCC;4 zzI1G5L^gz~j5(zuk~sCk?7zV9DG(H$%wH!ncKjY}nwgCE)S-q`<7=#6E!u|C$|Ic= zk&Ema5^MK}p1&H-nwvv%Y9u3)$kb36Th^A+o7I=>C&ROBcbYyoo1ZwurZr#;rY5x? z+4a*n!l3+fMa{E)-}NiVkHcsEu3E}@DXjdL9yYmbRRg|Ey|4T&EElwtnwY4E3w zKKsua^dVcAWy1k2&gf8@4?*+tHo4+0u-dt8u!@FBPd900ef%;XtQIp78GTODHfNXu z=+kwGi7^I_Qr`@%79PS?j#7Om;W~e=r78LHce`hw|8Y@jvUuOt|MXvQ(*JMKJpZo^ z^`B+Wzb2|i4G355MJL|l$#gfl{92wkwn$<_w`4KQ@%G4$-&Re=QiepMKJ=k1NuqNr z`4|jLb;iG<7%Wgxk?;ZI__2E+B(T6kD-7V-8zkbHrRw7&scFpSNv-0GjHph%uiXDK z#SJUZEPNexwP(F=*Ke28bv#X=BUJ81yO)NK7mz0#WFaSuGw6juQlKK|sGk&q(=oc$ zGAFsZQP459Dt3(<%g*dj(!I87hQ%OL6_msp6iTZb-OS*L;TgYtT zJF-gJl}=Q$xWu(Va>Qd0MwM{e7jpN>EWt@7O-D==ebODn|w@IAP>4 zxDlXZxgn%@lAN`&jqX*TyF~7K7;B!E;tF_^y<%fODx}AYGpI)1B*jZI9M}}zdalAg zB~w1wX6(M;`fb~HP>{^&HgjNc7(o#~uRC=U(<=teYkor?Ut8Z;mD^~C6JN~{8&ZV1 z7wgEGW@6vSS|##lxVHBsZON1q=QMn6auF%L{GzCLI0dpFho*As<>HdYi3|1uijD2U z%#w2kd8nA9!#^}VwTOin=~S9Hw)Mv%9@gfQo@!DCg5zYxmBtCn%-YD-%)&Aj!^?|o zy;fVC-&&P_%I|(3RRjHc56!aJI3hn7DD(?X2$LaE7PHKvwXUk(Gvf+kA|>(9tvOn% zkK|zdkZR@DNm-20%!UL}dpai^v(948e4`8umeuYM5La5C^dU_!p%@{{sp+#kWfF(# zJZ++>7V&I6_R0@vS!?FL*_9Lp1jBW{wwBIWQ{0G;EAg<{@JF#$7_SJ$sW^{kR+tnu zl*x0L=h(; z*uKIl!75Iu9)iSRftAZy>K8gP;YytJMRCsjv}$nLm9({-`n>`a-s}|2ggm4WbmiTY6A?zJDsZGgy3Ys5X=S8as*V0nY+^ zZG}kDx24CX-gZVc6CJG1OV5EUG14Z$?&MGD(=d@IdkqX=4}4ewilCm{zZ-1$nVqo;0Jvc(Pg$T{OXb4uwjaI{}#H*K$QQRqB|RO>WCT z@i!ClPzee{`Jr9Xe3YYGx=!@d0`7AYz9ahUNIMI5spLWGrAwz2ImVQ+9AhLp^mdpn z8*^(%==>76y!>^76**k(6G(U~7!uD~=A;Sr$P>aQ9yKGl+;yB>9hWlZyI+4_t9>R; zqK5I0Gn8D7r-{ldbsqh14+NlA2P&Jt@S0SyrW>d0+i+MxNG9WGC&yXcQ!R491RS=A z@vPU44J+ukQP&l)}sGa{Dp z+#bwsDhO|<#?Ry^ZK!!yRO>~`VTC*lwhaT(P0bLE)8O~BY?d1Ory9u!S=R8S#W~?y z^s)r{RbmQ-sirCXhFQbi?n5&{mJnmOZrk|1A^j643 zFTHBmzOeuaW?G1-``muM2a6A$2#@)@WUSB;2rl z>8+;&`7*9H9@5jLiO{tc!Gpd0`Uf*SPsuQ{6FWRls=# zjdHXl5~>7HRZnCW>f-H2^}P{V0(eZ+m@)t^hl1rK2qZ46w|w#9e*Ig;~{Ij zfAGZn)K06OnO?B6$?Sc8dExP>j=s)U^L%i7V@_a@i@3Y}gy&NK(#7kR3oyB|fYZPn zhMP%-_L>=ns~r~W&eJoreU5qpSx2SO^_u|1EiNj}?IO+Cz&b_Xv$=UU9*28*@&Bla z#@mG>z`m>}l$N574;6bR0VOQ$-&-!*7WcWmHS_IurDOo5=sDO?;6o077AIs~2F-6d zk146Vxedlhx=F$IKE#5dHW01Wjd=PsUJxxLl3yhDbM@rAB*sGQp;W9(LUypwCnhJCwNS`qYD zE)@?67wRKE5dW46rGIh~sneMpQBF?sgGM<$iD>NQKs8ndTZZ{^9Q>+11RKQOs?LUF zKBG-rIweWwk~JttCB>oGnDgb^>$Wc`s4e>9v~wg=EGh|?Go@*jMeLVx*SQR>MReh= zbPa8sq)3z#85n}AzuU@MMzK`j)m%8ArjI!xXGU$kqQ!i!H1*FTs(Y@~b)-ZKlL~GQ ztojq@Y5m}>Nn*tqjl<%gjRV?rcQEL#07?HZ3b94MYP$BQFXg)uR|Tny)Pt_xUehvy zxF)A@-kpbwu%(cVeQh9nu8dT&bQ4W4g&Q=|t+oTG8@R+tw>ZW^RG!hx+5jUV`_BeO z#ho>#1Ptsr3nO4xl0|Tt(=Q7yE;pu8;$fL;aIGTvEY29#CP8zC@f2r>iqFSa7^6dE zVCK_Ui(MnV+y~?#)OlRt_q7aX z$V*3AF8?Qa*>~_YF^9E&Q@n9Az2K*H`?ckKPa)ZN_C(1Y(ar$tf_T|(G`<3}5_b~c z{$@x%BfAtkE_1B~EfNL&8)_8b@XqX_zT$;Gmz)gQy9o@!%%A8b@5HUpr0F!->pYk7 zbMj3A7lWplH&B$3b&|`nH|og}Oai^z286UPfLDJ&o*mp=jK(Y`WKOZKpNBnVWeebP zAx3z5(GvBrRKBw$gB- zlqc4lB0>1I3X+$&gUF?wU@S6=S0a;z=FhoB3y#yM_aD3$J&Bt%Uh6-AY3r$cl>@Es30n;1^Nr_$ z_$@ap{bn?UT7Mc*hX!=^jbN;=+X|~=yQEN-;;~eZH1#*z%2`g@>b4#Ub=!t+=y7~0 z7|TcVW)Wm~ETU>Y?=KW*I9gVr$TZma67p& z@w*qG9}irS2Wc>b(*xR=hWMm`pz6cVU#;b|S;>zBj9vO@dN9bxH-BL_eQ8;KzGhS= zb7;|x*Rxy|JT1ZbO666T`x)mOSwi?xt4fwjgw1@*PoGHaJ*cXTgb#>F??|v?6AkU z0sR6#33!nW?OUqnPRDiVTFA-UCzY=tuq8z0r~520O$HEC;l;r&)5&&I-@P6NZHLkg zN84*&Zx6F@=Nv%06BN8hi@o=FTbT(ItPB*4uArQ{0BAG5mgTm zBZ7$Hvc@*^gtGd$hK@mtn8j@S4C`Ebw*vENA7;Z1|MW8&q>L(v`SE56@@rCV|BZ`p zJvO&1KbfHux8eG7zB>i8nzFwMs`zsR&{2vql?XQhzlO*wC$35_0{o5aQBdp}*P16a zD?6A<*IJ_KoIE^A`}PzY(GnyjFA)y+Wz;v^7PyrIOm8t3A|S-|3c8zOb)g^;A;K&R zu6(c<+<)b z*QihQcv+ud&Suh&L=GlL$y-S+rg*=RTdnP7e^p>q*#{<+L-lg9EuRr|6X?0zqOvqE zDRnp{(E8V5wAor=DMC(zFD|Cd?M|JN-@NzA*z`DpbfB^Uft2)JscgO*h1()Bs_Pxv zD%#K3$zJujh1w9)WdozxXovN>K`kfGE!ysS!(~WTE0GG^AP*|W)4)GwjPoW30)%AN`uCeCj_m-P&XNEhu4}n8H zq05mo&}`APJjD*=(g%09bXY5aWfXJ=`b)2clRKUbeVkPQBL>bvHj1eT9zj=s609?^gt&MlkL(Gn4YvQ$^`W11#mApN_vztlAi72C9Y;)s;}uMmuq)C z#5KuR+Xic-^J{fFtx`a(ar`=XNHv@%pq$2GrhL#~fI5L?rztmB_?br~@2v)4oxP%o z!pfyd2R{~}NZ&}Ne}VzVnDonz$qZ#m0BcE&%cBiMt;UWCf(Vc#sO)apl+zmr@f$Sp zp3RKGJG|^=y&y^wA>wAF# zg$?`Omo0H!1qNG0DK1o`6G%iYZTBk3lcNg7RBF{D$nO-6cK%x_UY&C&!7?(V z$AAK{2UH#P>bHQYH@}=SB}sP&Ukb(;iZ&jRWD1qCYRS>QiLpGi3?eJc$+;q>LRSAK zQ?Ih+FwitUh2Tbj&BDZ49!(BU{&cZu^W+(_hF9i1m6`Ch zUvGI$F4pUiL&M%sk}-paXg%9jWvs~R!q-ISxM zg4N!++~cUkdfm!#mBAn1kzY7>?T{`r-|}l-XfP$rBf{p+;)i!2D~DFENFLrMBQ(#xi2!VyA3DYsjN2_7G;A-JeT^-e65b=v#{u7sT0^sa zvlljBPM5P6qWQ#wyn-B;m)ziH9)7Q~SU6@}L%$ZBd0)PiMx|r?{eDnF!tq&>EJ7vC zZVFy9Po5X8jKpk!H5c$scP?}Q$Fs`cp1ZEGr}4YIK7~8W{avj{Xa{ongdjsc>%AV# zZZ`bX*n7vg`}1qRTenn>*s8H?T4(RLXy7zSoT8(9m${mSs}&WjUm)(t*EaV3+AS4xp&e)ypi7 zr->x$z%J^rs$|Q9OxAHbN;#aoVYl%HS54=Zb2(yNh9?v^To}EMjv~{I=APJ*|HTf& z4bDsFVI3U5C=TkItJ_ud7SWh~L<7)CxsQ+^36g1wkr7F1?Q!e2sEf7&CQ(UeIHeiV z$?%`H&B}m&q$GNQev*~sOM%J^o_X%^95FU-?QjC!xLY~x1)FNkTL zl4z!I!9ZynM+wM8B>?NQfBZXI#_E->S@j!Y;Se&Z>Mcv*Iwf$V?|_B*7-!z54vx(` zy!0>(Rg_m^EZbqqUm2XKn1ha|af&OGyIFdV`>rd4jWf%dssSwd5CV=p=VV%L_Wp(KqL{NmF4#APa?QVM1b< zC0oRML1!Bt5Wzvx=QMAf(`4gI<5THVk*{=x(KCC%PK&+YLmQsyz{S^B<2Mb zN5?Z9y6U^%ji>a!KW?FW+&*%4!He=zL}dqIVAx`p;d?4|Cw7{`xDY%F1O_X@{6JwG z6gqs{J5z@JK0G$UMLY=Uij+6Pca&h>8L$TbylD61cf~C0mBAxWn;B7;sjKRfgR2En zQL=!z-S6@Z7G<-%y z6_h@DSm0TvuMl-ffo^V?&N@)HE6WU^jDlR{E-CP_bspOgC{9wGGkieWad56|+s3ty z-dWbq4@_PhTbp(u+i0g2p(&FPQ3QD3SBB-{Q8OW2d$p7iCE2`!xnhSW7A4MvzEB~< zO^vBC?P!VnlmLAlRe{;hq@DFl7H8Gy@T#J@jFNwyAZvkiS!2W%D$q1a2vypz6TtWf zMqdRPTiWqp2~u{G1~a}$FM>{GQ3FEF)~Lza-DGfN)xu=b$kW7X{Z^QXwBJeqrSkkC zVt+DG=coW*H6kL4UA9DhRzXmsx74~Ww{M{~teUvGFfXq^D1S#gU2zquIiPW`1;wy+ z5w!Axf<2ZbK!PQ_%j$LymDGA^Pqgr?QVK`K#EHvuK2OyI@^H`-N6bz-uslDA0S9+T zdBLe-*Viw8-FH&KR6<>&Svi@cq2K4HR&~gDXb+_p_fNr|l;}p4mg7}!l#@s0K9)!M zK9@)J@9lC3C3t&dPNxpIsU#+yDKL=G%~(cSn3a?ggul+s9XD&#O-|^4h95&mFE&50 z=j$wjosK3QOZ;U0ePpPW4ii7^#>@~UnTJ&C&INj|9*2j8%Ns9g!His#h+f~!*k?-f z54#C=-|a8FUolLc?jp9&>dHNrPZp%=MW@C&bJd*9+d3P}kvpZiNLFRnC^__UzW?OG zIvJQyW>ZNwn@mJ$*+2Z1-E!;Ue_5<&X>vB;gv|oTbTuBkv{t(T_TChG(cFm^ej-@j z=YISRPH@Gc_QE4ECQ=2tJaYq@pyxS~{$Z}&n+36lDQj2E#_tMM+zM(33PDKDFLARV zwYFyX5CNJ^SmVn42;2)3ASzV*n-XV<8~JbfjfpVkwSwU*5%5HEj>?)$6L>XzOL zbjWPq3A+avC$peYNqislvn3SdVAr>Nj~I zODb9l4>?;E6nCXirHd#hkLb0v8=CGzg&)ILya#22{#q;kFr zT`nDenD&{UCHa5Py#4p^qW@3AJCFc;g+_b@slq;2T0Hn35R+>Zcpfm2z@T`~z7bAb z?B=Smi|TFl12OpvpjX1mdyoI90G$rcrV0zsq=#3hCjh%}egvVaRYjsciF$LbwV{8m zV?hJsFtid^yJt{esW%zMQM=@L_zKp@jVQEs8MU*gSc@k@*+B*xlt}?0=+=DnqLTGOjlPkuY7ayhfV< zg|4FZreUymv-_yyK~fFXa!9QCfv@#4bl9&(r65g4DwjaqyYb#m$BBS;FDvS010;|) zoCe-EN>lreclWn>4ldl& zc`_|ln`CHb8?jK!Z}%CD>Gj!HsRMTXk8tzVg9yNl9|r&a2f+QmMyr_q1Fb4lv#`Wg zL-{hfzL=W00_L)i+qA-MiA-ltXSdie0&EN3DYtqgoiwQ}P zOAt6i7RYQyh07W8 zGR5sdqIAQ9zhH%$jPITy3I1~c4eu)tJ| zn9^A({1-du6V9G2oJEUHKLW_@j`-l|s#a4t3y5U5pKxz>jZXEm_)ZKN<6Ll&$E^*>jt?=hJa>$(g~@X9*XEuVYbM4g}E9LsQ2 z^3N|IVggE2j~SowNQSabGq_aoWU2S}&TcDJZI;1PMINh89lzy9X_IlP#c>0@QZw{u zOfJGQ={Qmq;guP$gbOoj$@~>fzJFWmWvdljcs3trO76UEIfGbWUAB0cjFii$QhKr2 zLVD_M(aHa@fm8O6lWI4DorBeDoGx9oq&0tX&r;e{YO-M2>aUb4+m`E!YCg_=s2vix zHUBpBE@98cpx~ti`5QQf^HdH3qA>k@w;)DcQ!WxLn8e24&32_mW_gvV&d>~N&LEKQ z(P1>xu{8A)zO{PJh@y#lbwR2d9csOypNDkL;?Xxi zX16dh`&;Thvp4%3U*J96s-FWYwy)ft8+aNxuU1r1I0!3XY zOA6ZF-Won3lfb603fB>m^lH$PbWYofD4f<(ex?@ri^A0XnBq~k?eJH9E)Dh4)vLg} zc9_*A>D4e0X_t?M4EIPLmLBshNe1gJ{8S42_?|wufdSof$6;Pd`S1I<<5~_jn%W-3 zOb;MOS{4+9o~dIq`{AQ2>%T#0ty!prj~5j*+!jkCb-B(}$L)3;JlNW2^3xwgb?=09 z-Lbk~@n-4dmwg58U(p4OhnCM;t?x}Wms!3;4orH-bs}ffrs2ht!Fp|`Ezr88%k+3_ zA5;9C^F4TF$|pb3Q~bpcyXA**gRmj-LzZFBC%EUDa|l5h$MFRR4i~dP-c5lI-rLK) zii>;`bTO9`%|()nr7J3bycA}qzPxZ5E(G;WbA;s(m4w~;y z?Oh8Ou-(Ail1_zt@3E*1+ANZPRSew(mF?1~s2P}hL@7I35@;AmA}`O*&Wpi+1$qf0 z5H6>;WX149diTQwD7T4!g<$8BbKY=W#2ZQMJ6(_!%!=GnS`!x_VR#3ngJTHy;_aFU z+U3oTw*<=j)p=n71xS}MlDk-duS<|YR2F(mHz@=;74X4+ZvUt9dGKQ6p8ruj@&7ni z{_C9i4`%hRA(L3i@rMxn7ig8JtR;t}0RNR_-M$WmtjT{xu4*-I+`6q<+ep1)L=i+I zgNZkysHQ$=EwOsHCwC_zNhq4XyNYL=;h>H}$WSA5w0=F|;b3AieHxvu*9DZ~dlnqB z3UEYEr?1>+0I;cUV4@)^=aWH2ZJ;8`*Pjpp7-WXw51l=ZZqPz%n6e86fdSaNDcBW5 zcT_Lpq~oLJ0zHfjKepbqI-(L8k905G6QF8?O+Hksz`>j-G|Zt|v1X^jkT#X8NN%ph zS-Cucj#dmTY+qfMR)osB*onhZd?ZEf9TImrp5n<+hQgm>mM!j>l zn?Q-$2At@kB`PRpPzr(raJ0P`jv(j}9#Ool2+Wh#L|CoN zDLw2gimZyIwbR>36`PXVIav~zT05Jz8?|)3>v3{YDb=jaWYf{5P@1nC3;)8!u4hmH zEm#}dWQC*0L{u_%4{r|vI06BS$Ve^+WEW|TcK20Wy2r4N_bNR&8}w-)QjZ@?4@Q)L zl&{|%A{JuJ8ayiwMkjC=V{bz)o|J3o+nM_zA2n`Y2<;~GQ;b$WV=)Q>mk9zx$*7w0 zl|QrQ_){(9Fyx@s#{-s+3@|nxpNxQx>@vggg&o3Sj>2wkP(t{KchkaEvYUX+jReVKmvI7O)6jj0Ccbc0OMp->b^MwSR3?EssDFcBF00>Oh02mDz-L>M4Wj$Yk9pZVb;Vi_|Y_ zKp|Viz26Y~)Fv9Jzta6lGz4Yq0%Vd5UE=v^4=ALxBp&%h1fpFwXWHqZw2J<2fE_U0KqM1$VTYq7EuR}nCH$@+l=aHGbCTA-0I zaT5{^1-JwDDS4}Rv;9-TgN^S4!o-+Aq~PywYv6gxcJ)}eRsFsD_bIdFZ$V(>KxgsW zrEU_SziIo?EN)0VGzQiD)PvkvxM{k-3Hq7p?p09q=5MkE0(5Gp&0qeqWc9NN2iq*& zTxc=AJHvGOUyt2%gk6Kw;(izIg1~Gq-{fr^bQ_<8VS17KWkDD~eHDY!?HZ$010xKV z*WGnve#!NJm%qdY;aR*$z$OfO(beq6vH;+L*p12I-Z* zM1r@e0cR$x+OYUZ7R2SnMa!c!%d8Wg)JhMHxfD(a_EN(8-DY;qT6wy^8Egi~PxCr- zV@RZ?#)@xDkuedagE#7|O82rdJ<&3$AOj|gy>sP%dfFuqCyzzJM`T%g5gYte!Iz*& z7|ojC)gDR!1(QM6J?U2dYtoRbo?Ri^W0@PK%7?IL^+8W{2jkXLQ?&LYDplo@z;p)%r z^eD(Qs$;h)sx*!o4Om9y0UuMz4A8)crc9lDAPI)JFTxoQpFnCMPL)_ohd9TaeoSjm zcSiWoS=Co{ah78=d_|7)L|M+NH_1z> zRv7vncR4kXw33j#g4RfsbUAj#GT))OyrN8z<%nQod|}jgJG>)Jl5yXDw^SpaTH05H zfxPZ^(!8b80Qpo@l-i!eVJXqDKwK06pVaj#kip=lMxDDGQCtU-2ZpFBo5w#_vOac5 z(~1Bl?CBkQSXikuDj3%}A3JJfHBY21ZxTm=i!i*ju5U7d5YA*MVK_gb{^Xqc^x0iK zX<~VFr@@szQiNSLTEO9N>~F8TF?Nk+wfIo5g~DhndH%aM*Tvs(S6XuQH{&2@4`@oO z4kO$AOI7U<`-xb6pyHZb_t{g$ejz3opL1*C@xy4{BMVt9hEPiCU>)wgjyG|Ath(MS zgt-BtifTq?Eql$XKi4Gpb|0!%g=q%HCkj$uwjFgIgL}7e(Of`loHq14ghJ6gwYdD` zSn8|aB8z#EB6Pm=Ha8P^m@^}aew>Hc|3leZM%9&V+rj~Y@8It4?w%mQ-Q73t?(P=c z-QC^Y-Q9x(f)gOvx9e2B*G|=U&b{sZSdG>AwO5~G_Tgg)*v2?xJX`<=MhxR?{E3e% z=ACIA>N-N`L@(7%)L#UdRcFTmo;ge568y?HQpjT%x3HF$in*mWIJ1r8NzTUhRg$0v z3~?2Sg|hSj9%;ZSR$plh8w<~peojmw02XjoTdVfyz;oIu+O#@CnNoP>hA5CfPnTtK zFp#;WlpI}WL26P?`dLxhktcC;+@Q%@U%zCps5Opb1RyJ8G|Iv%;)$en*>$>m_JN_ATvOR8!pwjLa1d4Nrag5R4+l|@-U6O)=InUT_xa*J?5fq zPCFL2OIA)VQNo|1q~p;EL)D}a5*(q%NjmkOE8`8xG$PaChsTg(g}TID2BTGQg6R~Q zH3y?#G)JypC?i-YYg8|38Rf^2MLeB0WC866Q6*R7aMBd|#FIrCb)3*Q$6uiB@YBpv zXQMr@smhuEy=v~7a(|xYdKn{$Vb;7XXe78%y5xJKP)X8asbYexfyHuJa60x!*>9LU zRr$d|O3P8xF&F&2`8pd~TsT-HQI-k6N||BNLe7zJYN)Q9Wp&usHCGySJ8hgTwKmr8 z&sLhok8u!wOHS}q3rd!#Mj-Wj@oaD^Oj7==lgRL3j+5-(xF6C@U=2GkVs9#I6fJ3L zj43c|_jcBl!zEK^+&3YbmY|kAsuGHUj{;U6{%)AZVnJQOa#^-78Vppps{P%6Iv0QR z1?U&UG)aca(A~gGCSLC@$0&QHxQy*^HMH5G64kD5@_EOkwGtu8-GEf=18a-ph%%TX z>~T`>0+L8(b~cUm{3%L%V)T|RdF7#x-&aMz;GgI=aWgM_0A*QF#`l zqmjdl*5KgbrMm|VqkdMc!T*cV4=CnX}au2#&pZVo-8X{6~SRl1Ri0*(MnG4 zI0E0q!j>baIYf%QU~&=2yu{h~?JPer~>vmD!supY8qQ!GLX>xEuq;b+55wEO_lO)KP#!z8L1# z*GZp3Uo5|hDW8S%w9e0FVqF6qQx;H8Seo-uUTCVN#AAk*G|0uP#%k^*Y4mr<)WMm` zHifYoH5c}2*U+vhG)8WA*ugoPuAcBg9=mBdNVg1ag=JLnPI})&WRrg-vmuv^Zyc`R zX**45vGBvrYLWYjd6Q9fze(Vy{ZnkS?YoVu*fS*r>=nqxV*(FF71Sj2{pH|AS zkUOS8@*Op4g=2+73@U~^QxjaQDbt#y6IU)%W7{Hz6d5fl1av$Fh$mwn`m~1JxM;O9 zIm`^#n6(={RNn$i-D+YlC2c=iW|&-@$ZLd7)FA$sqTIBe8iwcXu-L{z8Ft*h;i76(JeksHJH<3@3DY(I0lS2N~?fBn?&MDq%X7QJb@a<4B`u zU~x?hl1Zgq$M_6_Ah2#loOpkOMGhc-;RSzXSZr@t@5uX&*mudR9gky2)c{1nOd;#W zU2#WIOSq-Tp+TX@>hGqI~O#{P79fbU>J z*bAL@Ak6o0D+y8UO7*syU>Ddh&EHUBQBhLj)u42sPEUpIvuLr=G)?sZk#sUQWIv$H;b0@T2hkPb($%#@4N#@@-#$t-=*d za--Uk8n+M*l<=|4t`{;&C*;a>EYu3wicl1>s1r z!!Q@aCu_Wsqd;kjiGBw_3N->RWyC-Wa5nChh=*c!tvcxm=-#H+eyD0ykd$}9a*QYD z#MI~m`#c3iAz}iVL;vgHO&70;B78uL;4sKkE^Z z$sS=HObbiw_OzcnwC0tZdZgSg$>vn8BMkFqAlC$Le+kyW=Yc1epxZ~s$!n~Op@Ys_ zNL_anxdZCXD8@Z!FKo)|PXe#BhXbsI2NjJ-dIeB?Qc&Fz6W7+7(U-GDj||Rx?kv~* z?Fc(1xK||JXrFU4u7!VN>E=UUvEYx;-cjimG48W_fYmHmK5}>Y>rt^k7S$u!QsG`( zc5m4x3T7%-58I+=h__{L^adE5r7%WoI;y`=sUhd z*Sr3(J9Bj$B_+qY`~qns!fv_kmvNODWB;W4&dek;x~W;d44ni;Q7;_WTuB1E3@~ zw!N0G8=+NjOs9;il8cQlMsi3dVxsrYvHl!6%~+x4C|SxE+Vw6At!GQ06oP?hi=H0R zyr>+~ED3A4v)JE{0z zDWs)=$3M0JjU&jW{a|@*hIk#?c%=tu+(AzW-CbMX?QTuVv1;^t0@l}NGUUZl^dYQ= zG1>w4>jAFvl%m`y^=Qr)b<;~t#zT0=)A-X{4w?L}JRH;Z2wP(<^R1}u6X8h`pSM4O z8FN1n*vJ=tBC=7h9#ql7ct!ZT<0s=UNtgIXJ_r;UVgG+|{Qf;WlBH}dw;+$^Gv3~` z0e9fs8FpEUO9lB+ZIu3ENqe4TBP+T%kM%#Y5~Vs1xMSuwdy&q4}^IB(%U)r3zufY^uD-?BT!$@ zS@XGioJUZJ{ii6eT^LZI7AO}?i}n$atd{~1X60(1F+Tf{3 zRcAbf@{KJnCpXe+_(<2;CE7Mf?8vKqX2{@+->q)bQ!vaLa+BnK3t4-yIw^K-2^iF-^sIEs+-uCN=V}J_SniSf} z6HQ%Uv2Yh9eaT*3?!kg&9UkK7P&;ni#Bpo-iaB}>op(}WVWrDR&Bhr5CjX1rHeF4w ze$?f#JtSitoj=zPba?94;QWB+)Cm0#18|~T#@tbc>R)z-Y|Ji~x0*-3e!n^9+1lJ{ zI0FkGeQ$vQlR7@e%MR!V=Dg(x^snP(mHY6dUpLt^U=#78tXUb0wf)T5WzCn4vjgWO z(vR=-G=56@A@mrOtu!bQPq|BBKoUhyD$&q}|D7?9(kp`?fa?|eS}81Z*3sWCRGwRE zK!>L9fK9o7?1~Yk05`VSltM0+>o>GcKSVV?;y|WYY#b?{(v3gQp4xZX!S!4;UVpDB z6=(DQ83MrWc5wdHHFWre*W>W|ebda-7dAsd_#863sN!)ftbA94@Q9sPy#VR{wu+=1Ru{6u zHOHB3{)Y?K?%S=c*ViPevk%QibI~1jax&+q7z|D%`ER#~pS?6Zo|je_UhgCBij}SP zkIYYHq;6B$pslFh3G5H-@thpZicuWK8-#5p_hC=*laIpK#qm6)&ma(LfWYrTz9bVRHkdLB`jt8 zVSYc(($I7DIc#9q#1yfO${V>%4^8jd*u*qR)>5;xmu{)uZ6M21rP1Afyj^F3XWLO2 z9R`|r$un`S5{@0Ag7KG(#ETMX&>ex74Qcb_rbLYOE=n7}XG;fMA|I`G@p*^EB|1dK zs5L#s#7NM68tTlZW?G#48e%I2bRZ|-uT$*S?laQpj@|%-vf3L-^d>PyEEMIZiFFfb z*%2NK7z7Z;;XQ&%0v}dRdDrKh$ zszuN9jwOdJ^Sc#fmsLyO*u=$01;Wim#AtA{0_eS>pR1A(CwZY)ACBF}UB3_#un~UT zJh<8V3SkxdEi~XAZ?k3Kb`#X&TFs;;O~#Z}FDSA6ft7Krz+oW39=2mI&tUUw!AUXmN2)Ml zoDrs#(7%g1~PR70J52vNqtZ zInFOgdMZRf{?Y7lwa*bc7|hn4$z(@EG|U~mjXJb;mK{9%6?SG)_}2zCM^U;iviD`{ zx2!*in&OYN`}n=O`o$ zVlc|_?peeL8&cip42Ppl-F`AuZ%0A`(k7iqL$)T9BSOfTO^4cC@$8aFb;2gS!K`|H><)pEPT5z5DFceum!$9{GS~SPtOP%B^ zVHKrlwCn9gzrjt+aW0DP z21>S7(Y1{S#&^~Gf!4Is8|c;sVQ>0{yC|j7yKV(Ux+rmF?r$bW{c_Oa7IU;77xPSW zN?U9-H(j5KmWTh=fWTMnamBXmKcb5JZ4;9ntnCbJQ5l-Z6W{O0sk$(~U%KrFr-rbd zL(Vm=mymakm<87>bj`k*Q^1?_&XDhkXMUvW*Vt`j*x%i5!9GOO^WO$@PDb{3?of*o ze9EKd*3LxmDkHem(XWkF1}`;8vRC~QV(P=XT6PNu6iZtaFGUNT5|b{vmZ~pml_w>p zGC0Z#D$_OnHl07P6se#pDB*{dB8XJ;AaR3!m-@ec7tW8o(&}RN9lsM3;v6I5n(LBP z_rgi;cE4?E+zmW^{<{i>m@;1eV}K+CNDcodOVh~K*3ulvDEzNK z62?FqCvzuvVOwiE0|x^qTZjLXh?BDYV{~LS|AGA`v;9`Gq0mX-JkLy-@CF($vrMLd z^j6`Zxtlo5)+**Rl;<#xo>~!nGM#!qI2dlBBxIgdY-~ld;*t)z52{cmFKF}ff+*e5 zHtUXi&gJCG(?bE}2bM>w5Hb@)eJo5PH%twBP>H|-<1NMwvwG^%h^K<~{6oMpokfEX zniK}Zh#is|opzJml+axNGa$VAirlsmzK5RwLDfFo9~qC)%|^>!t?E`(EcMox-Pi49 z!}U|hXr@!qIBU>?(HTIruL3S)FWza$p`?xYg}r-@h)m;#1}$01&f_vLq}N}y4h_;ghOxe0qM#9MHR8P1pDRr@3Yu`4b?{M^ zh3nlB^)zs83VdL%%WO5o_4-SOHmXG$XOp0ZRSmlI`LG${?~R@#W;_W*o>LW^{8lO5 z1gR7U@fMnkN-xvYy`m>xfk>e?B-lrf5VboH2?{)@gJW>v>m)JF{lTR|D7E?@ZMG@3PrNRK{uHgUaq{GO({{4BtePP3R*NZkWhg9|A^& z#jg^c9*!8?+{izEa_rBLnJuAFiR_fuPOqeJfibA8kRA<`ZjrQ#<_=Rn^R5=DCjq4M z@8@JmM(flgZ0t3mt__cOzE223#0e8QJ&rOAyXi}vFgx!ubK*l{ z<@iRnIy^Xv1y6P>ZwSX#IDC`LM)Z@WLW5?AyoV&zZc-!kkVH*Lg{DRMxIG#CXn;H@?FmffIsq8EIxG z)%U%%7fBDLI(7muH+fDGsBqVeA2I;k8N|IblK@ z&(>>k&sf2U*@~^VUg5hevOCXLMQpP|>r%~l*7kTKiL>YL8OqpiLSWXOI@i;;Z+;2r z*>_3kaPFY--diq{?Cc5Mn*>`MY^bsajhsqlmx=ySi6?SEluRBXZmk6baEiV>ew$&BDuY4CtBGiz0OnHpKIf(PH&l2a4O%Xh&*F;DHNIO9 z&4Up(O2>wmV?MH*%9Q%ZeX}?Ls-*879`q*Ve^1<0%O{0#9CGF#BbOnL9-Snh5`ft# zQl*VeFAhFPZMMTaV{2{>j}P{2ond0%Y~AoTv*GOyW}%U^Qg`!nhBvia?ic2(zf%96 zwfz7=uRRD5z9215VOYHiXO)iOxzTx;OZpLBcjeWb3U?Z$=g|LPvrW9?IR|qax}Mw?~Fz z*e=YxK9_$%Dw%iGb|;L0zaU4Grr3uJHd6eF3%}R$mP|72m1)q({rMlZz2;u!2a6|6 zPtclQB`_U&i2LQo-hj`6*Ea4q7m`M(L0 zLF-H8E6!e%A=8{NtnxUt!VK^@4g^$SRk537XJHL1J1r1Yv>UwAiQ!tp+P zJ%x%aWYQ>()kP6E^;=oKRbfT!_tqL$!LWThWpdd}XE^!BSrJ=#^79@a0|GBb$pO1A zK?W5Fot%HS-(c*dL(xG|LMl+<@qa22`HQ~)q^mzZRuO1w?&##;{wJMRsapRr^xG%- z18%>f*?8n#@bGf8grDdcymereUt%GYCT?z##eIESQ1H}6`&w}R^c~`3Tg>md&$B!@ zqG@$L^Vt8I{A-YagvYYrv+8-d&428k?Re2<`YXrx55m%VqzGsj9UT>&4UxtawbL3F zGdLtU>Gi^b*J%yxK=z9A>?a`{&QDDltff2f;qViYyWbdw|O9OA|6f!WHub zV;~TJ-FEdz5@5hor->d&Q$Q3bb<*UnK+C&%6x=zqR3GGK6KGwRcxL7y$^L1n$=Zgi zEKNVn;QPJ1NfEiz_onT#y)_5Q+D++rIjF3LEYO=HYq~ zXmHycRYZ9uqgglsL7`PaM$8#W<7I%VOOFYseKY6%TrV-$V=cczDigwrO$W9GqLlD~ ze6w(sJftB9|9R;zb8h+7Opb|U^=+$-?&2yDn(!%Y3C?a~5ao`@-x=d^XqcrIbq_FM z1EZT-jDD`{com#V>4kJ$tHDtjMjXnls6NrloZwq@Bc(tF1}Qyg56nsOSz4I`qXRNn z01F07w;hXv;{h{eL(UluLfKWx6o|g&Y0Yp1$Cr>@w{wLc5S%ntoI=_!gr-E*TWS>lyb_YG`s&1AoC^^1 zkEDmZ+|_}lw0O#@w{9kt)a~OShzyENuCWvBTgfd>H)~^-JgJcAXlUZ59ZMt7m9mq1V4|V_ZT6kmcT{+}LE2b@H^6fEi z?8oLIuRHOZn=oS`{ckSqoEz)}I3@IZ?hXm`YF37*pf6nlqNwE=-!te4o8U^@RSCh!vMqCDd zj3*eO8ZLK}8`WI>_U$b?o8PHYgx8h02W!I@KC9{sG-mghe?JvbmmH(QUi;Tz6lpLg?AQF+8s#BAk~5BUF>vN_u-|YVKUNC{ z;pygDrOy%VplAusa=)R}#_U>uQ$blt?S`$+8I^LIVFwMKcRrW9V~BbWzPPU2nOzAn z_2lZV52gD4BGT9s;IH68gS9y{8wo)@2jdkea+(-`l9eM!UB_oHk;8$+iCJhIBWoQX zZx}=xkD9*#p`qmLK3}6!R8nCcpHQF(S;G%s%FCZh>C7Dqfzu|bgy^1?>osb*RR3dZ z;3{HN5R%f>{s+-ri}TW=R`9Pgr}$snBhfUQ6jK2{e(@V`n<@xTpNF9`}uzP>{Ge$8R>+uSH|lLnUep9I#zt`L~IX%JJkl|Z{54xyZukUSyi#6 z`-W+dOwR;HNX(Bg6@V+K+!&B1{~U_(vUvNc@usw0qKGkMl~y>ghkRwPalEG=F;W8LGoTUr=m0j6HCM>B=2I5i^=hl-)8x6XQNzX&BI&&!MR=b@R{sxMhU#txVitg~uKixu1c{q}|mQ?cX zPo|G7*>?_NCe2&J@&h8p_->NPX+oNIRbSF;yNYVY^QVa*Q8y!k8dB`YqD7`2c?79Z z$>VChC-#*Qg&vg$pqxHsj8diYVJqgZWYci@3PGb~7WD%rHf64sCn?yxLg+Cza7=QQ zY;0b}oQj3rO81am*t}v!a2SZH_dZFRrHW)?FjNrxb8_!tvSk`uA}bMNWp)?t&cCZ8 zL&lN*J}aWk-%S7f6IeNd(7-=${=LX)3dcH_{5;}?D#}A=wF8c&%OSI#<=Xg-&GpQD zhqbEmg~O~yNXn8n*vtO@Dva1m>p)wl@7ykxEj0tq%prPG zx{EkRsCRPk>UqNz^${TBB8qnXPO3?p`x+MDBzI*->DER0h;QuSSMb@{FOt7+0c8<3 z#yNn~Gr$zVyeHS}Vp7v{IN0gX0M#fX^NhRy^u#^=$t0bZBx#h+^NHXUzozRZy4j0& zUTni&xhIBcOpI*}AF%K;e3e>kfK-8^qF0Q4InM_v<0i*~Z&_!~s!}JdWuMdvbyYp- z9^G7^vf=#iv>OF#LXTuGy^RG?ZU-n0{^ft84@Drzq%}8kGPku+HMg}g_~YfPn-S3N zkC#7LB`a=B64WCZ@*bf6~RdwAQEhLw;S)t+bnS=?V5Uu%-9{{dQIeZ z1?KgMu$L35g0e4=BrLS7y!z?v?Dy%)w`0r?mZ`KPk<^&Q@Tt`L$xLVy;^Dc(1?t$| zsvE?!0(m_aPFBIqpODE|T-~pJJ^Hx%ijLaSRGnkwm2|EW8CM($p+KEUP85*wrOcIa zA-1wAatath4m@pIn^;Q?1x>Ywr)QhN_mFobH(Aec9%)L{q!$}uZ-_r7KbTXY!l<2Z z|E~WT0cX&u-Gjo}4-Cp~L8`iGm!c4|Hv<5b8QNDxpY z@3IJKRX1;bD>WfE>P2aFB~4B)?WC*|P$9}R9t9YlPqSAg(s4q0{P=feak#46pa42; z3y{K~{NGOd4>#s7_eD`guKx>?cjOnauUF3p#U&2*2W`WV*R*|hM0@)C2K1I1mDUYg zD83oczJ8y_xvW2lB3pc)|4st@uDn{%xeiVWU;1X$#>lS9`pw{ezXlDD6Y9MbbeJFR%bwH%!M)K*>ay zbe>;=9XsZTBLySgcsZL9{F^I5=sY{X3+kurW0`q<&h3b$nu$IY5&w6RpS%$tw#XA< zn_%b_y8~#xLM~D@M6e4DV+;LIpW*t%20KQ}dD0b5AA#M>YJ3d;_fv6(#gQ5Uod!JU zWd4U!`M;(Q%b%wbEY~4B@C7N;`9=H#rTM(DDUR%{BGlIiG*~oRY9VKB+2+8X4bzyQ zHb}x=G2&$#ZN>8#HeO!d=UdMwsH-rIpbQxMAZlWpCav`H`KAqP&Y-XL++&Q?k{YHY z&dsF_$Itu*lkJ>FI#Mw456)sb%=e0olIx^>atQn>=?y9#Dv9Y#QoOde8Yd;8(}{Mb zRusmhD%D>vv`LjeV+6)B5nlB8WHvekZM-iFf%hHYTnCco91wlL5*Fc)Vcvs1KDgNX z{f{Q?fa?*z8xRs_ASC`%llI@-Y(Xaw85ue|0mTh$jIDrwB}uAc`DG!Pk%D)~)~sNB zPuo7A=_@D{3Wep6Pz_0(WY1@T<*YE{O23I$8ETf-xEjV7XrL5& z|0N@ZAJuS+HWIWQPn+NvHv3#POSnIMvzpPrxbV< z6H3!ta9OE6aMSqj3P{dJI)Vdj|1M0nam;&v41%WvMJqV}`xXBqv1IM|SHkI!n=b#y zKZJk=fAM${sHzE75XqW$KTno^JK zVt810QG%YgT=@|cZ$92U;>?*lIDQooVvJXL$MGZJEO z%4iDgLjU2%+s@c|HB$ggr{71*lWA~`P#ak~c3pOa z_YzmB(X@6OwK;q|Nr04@TTx)-1ZF$yYi19V9@6%Nz~m;m2||l~C{oE-^9jD^MLav| z61&tg|Dk7+?Wfx4?=x}db@#ODf`W@e(gOYj&sE&~d#M;g7dgcBuw5iSAXum+h zDhayxZ2vDpD{Ej4bo`SJbz)~gt{@;Z3(OK5K=Bbe#!ioPIaiQt|U7 zoodRB3xV$gV(=&&iZ4U*=hJ76=AF;FJ0EtTWcj&nc2oQ8P)tblf;1?S?Z;&$mnGsXG$W(E2R)<&0N9x&4UawyH&hY?CL}o#b|lFHnM_!P7M5h7Ne|4e zVTm5qR7+%-%r>Q;Tn?~5j-%nRkH@>2R~^;gwu%O3B)`isW4%@;@l{D^LaBA==SJ5{ zmh(KOGwJ4$M=!^lYoGtFS!nmP!11jh-;0pW0SnB2mPBMQK>US|@3iQ8{%W|R0u`$B z`Jeh7s!DFZ1cc!^hyea4XAl&FviZ9o9X}zv@dYb*rqCP@9Id~qh`wu1kpZ&eteS4b znluh;4J^-Qb~g%7X#_QTB+T?I0D3pC{4E^Hxo|!h=)2=PtxdM``sNJ3%Zh;CX@9lc z#~TvdKorqo@ZOILAPGr2p!J#B%ETkUB(l{s5Nrb%p9izA`TPVb{VWK z=wH~$(%;#5IBxtjvuw^i6-H9!j1}|vWlDM~8}(P$ULO=a5w{w$Mul!Gsj6;%3x6jB zRH>`dAnP?`3R!=i5y|tW_BGizOE+Cjrg44>q9ommY0PdNv-+{F5c0hzGBibvWDcP9 zPMaR#NUxkW7LX^f=P@{oHO`!sPGRKZII1+xCK;z}fg^k9CjC5@(wb&0nYfUyOr~j~ zT$WipP$}Jt!-0imamObFpHX_i?9F*nQ`Sdpqsb?>S?`wZ*0=w4pAv%+nNe$#LbuK> z$!%)idH+*n4$9{SH`FGc0mse{C$A`4#@M=3CN=-eDf+g*>Jx)by9e};{`Xt+@9`3y z*sed~CBfgM%zwid(fdMpw(B#qHWLx6$Scf&QJ_{Xl84id2wqtL{9xU#E;GIqm94ZCYaf!E~12_L26&Mbo~O)shjstf8xtcUHz}sC{=dcSzDYu0xF>#MZoXC|Am>x5#X( zt)OwOj-JB)vG;fyLPMiBTFT@T4>T`iJr>$Z9@pvgGQbLXFPO;uC4@;4zd}PCIxTXh zMm{5jKaj@3jERd}MW(0Gu@`rCBRM#`<1@K+t0~&t*DEjF*6xf0=Iqycwsr;J%P`|^ zp0hvy?J?ts_#ETC^SQUzJ`9tGEu z{uAndxI{_uGPVm}kV1CM1pvfFglyj%irR8H1l5a1l#}Fhsi=rVHa1%>;^|g9;<^}K z2z|gx#PJ@%UN8siI0w&%Bjhy^v`t(_F8OXf&Ix;ZyrF9&O+KOX*%=H3PklgyutH8| zO`}0ZG)|{3kn@;W^b22$+ z%0)WoPHp!8hoa zf15}1hxaUhe_j3?bo1&#GV-66)7*n%4ALEbT=gtfJ^lYybtUrqJFv3-&tGd1MB ztg1rA^8QG$oP@vK?U!;RXd|Q(rXXL@*=(_K0x$#Zw*`UM2SUlr`&kclCZ@E>7rmp< zr{8anAMGL+U`CMJk!4|i4hFLvh*_u3x{u%PE-hY7300x(S)o*}aQB=PC13Pv9w)6# zGr*=4M1)7zH>g_&J@x^KP0yZQW9)Qi3d=L=FuR%*DZ;K*_&M3Mqa{}$;WwrAMuhvn z^79%nmUaf6zXwKT&8@ZH^bmfE53>6yNerJf#hvY`_ww4b2AkJ`|IDXjcLdv|30gP* z`=gMsA@q`Y?vrOrEo~d+miSHf$+InyC@wTtCCxB0&p*Pxdj8`BuOI{)K?we*m74#C zpt6|*(7;&9z~uizI3o-4N{4#{Ya@KW*b)FNNW^ewvcvkrMTBrs56r1ab?VPKfKvSx z_Mk8W)j=Y}U}1R1D-j#1+951rqk#~utfT6JxcAL~ zHV8utZS@+=m*Nx;!&cL(+i^?Nl%NfRu^`bkjZzn($NhN2zUAAya66r;L7E&8h;@p^ zNXH!()wZox=rybutpq*aeg|b(xJ>HGmj#`_1x97gjkVwT68y>!v%7R4W=$%@E8aHs z4cVd(p51=%&YI-D4w5Ze@=?+O&!e#SyIGXq+5Utlc`<5B^dx?FfQVEOj+&XwIE+2* z`)_ji$0$LM>2h5h5Q4k^yBvx+8#ovPjfMWoOa{lv{o&dV?da!4M3idtVZ8?-b)^7t zm!x7)No2`$aXgb-0vXmUMvS%L90#64++ho3ht)AbWqd_6*w-fpOOx{b8lTx!XC8GJ z@0j+Jnd?+kM@$4;*CUiH`#_Y#V`St`c6$6Y`Gwr5IbZMiEGw)btj}T^9KC}Pzek3u z%LQtNADiJ;$Gh1uUV<;$H@1aqevby;_fAXjOX)~>tRuSYYKA)Rj!RXGP^pcf=KytR zvR&Nsyot&u@H`?@v9}PsfWUld$i%nuNS#w>g@?kFU`GLY{qqAN+R##CwJ1{MeBNkbP5`Lb9RYq(5(f9>QhwLNpuoRaH^h)qxZn~ zQn#$S-up-3BBi!+gVC=5Npj-{LgT|I+2x}5*ILZ4L6#}-X=P;JneGLix9&|DgO*+H z7L2FEDV=DxnkRmsz_5qoCzgc#e%R`*K8$&RihmH<@r7hg=jt_hGQ`_^{71NBxtp;! z8wBMh==o1`FOB|XoieI80v(iXErB+FbMJLzx)8twu`<6MAO#Pz=GM)%E0U;b=l4QX z-hcP8a$!6p&6e2%<4d%Q-Y-s_B)h$RqX#hngqg-*3gNklYR(A@S;z@D{v8q;e{@p=}0C~$#j za{YNY`CO#wAV}glUyjIW){jI8Q=wq?*&h1!AJ)T9iG4h6ki1NTtOpj*$J#oWG8)(! z7?}YX&77R<82|cEL1!nkKMFpL{-r4X_O)G#Q?lU9NZH`^0lAN^vy{Dqtsc5YhGFAi zhTzxxRTnWG4Jl&OlfNf{vs{IRq;!j%xmRXdS8kD0g6!9KeC z#AuN%R})_``%{Zlxiz&Un(xbj4pNV10E+hprImQ1=oB|(g#j1ga9N+t@^^Ec8Gn8# z#wk6jC&KK-nQqzWZ!1Fcw)k&&iyUhHB98HMCU$%|lPLj)TSc~5Y$k@bgiwnpP$g2Q zPHkR`CSigC-A`fk)akUTNs|K;04y{jts#^^lEe=u+JP+Y_&3QgdZBO{kfkW~U`@8- zub}vpIO8_pBB&Nk8}Yw*pnnRbHPFe-*4U9z9CUt)KznCUKpeE{`7ec( zrTXNEdW`KOp!G}h#?Z(=%Z_aLOK-B(ax8AY0UnI3m9UrmIhoAz4GH6DhhdY$XjWn& zx5TL6e4zyIsnhyt^KH*Ngg&JY zrZBXuS!!4w6lMOZ-xyY?si7Oj%-FA#dT-qjOk*aaR3VxWbM;SLWyJWxs3tfD2Z`c2 z%dzIYRseghdRO4+_dz+9u=qGaxT7^)I&J0fqCZEq^0 zfJc?gGqNT0a((Jxc_J;!{z&)V)?$8Mk`7E)AHc==xUMUj5j5gka((6nx*HuShfvz5 z15IKwvgx0%nD%E@xr4XI)gZ=)E5FaQE^r^f%m^(*Kk?*57t{MvwW_FZtf^c{4Li*b zmtL;Prjyu5;N5EP$h4&$B&U#i2Gb|f`&LSeN9EJtSJrwUdyIXT>4a|!i-Y^ALL~L^ zqYZ~KPR($Gi!9%cJ#Hf#g}-eUu<*>0xG#D7`wJPArMm{Qb#PXeYcc^iIQNL=T^M#HintLd>7g?Y|F zYd5wBk%B>`Mkhyzj<3~l!{n*LZDEsGsK0LTG{|ra8)`BIHT=!m6E>f^;a-|OHi1$u z*)a@cPJBV3&U^u4&gW1+-SkGF-P8v{c?A01IiLf-B{rltIdHRDW3x@lCLQL-{#GBL zsVv*~3GNW8k}(mL_ApmkjsCc&gwMa4M;dN}pOdFHaLm&k`;;HU2z-+q zS->(7Zf|H`zQ*;c+QXfDqqlS>Z-|MF`_8}D_EZ^TdyQXFqRnRMab&gUZ`7wq+=imP zqa0%({zSs3%%ybF_UXq#w$9|tvn?^9afMSCN(N>Yp|M3zp=}<$4LvW=B>Jdfjpze8 zb38$-*`~Kjx_mIAx{3}bDje=KiAmJs(6D=J{VeUZZAr_IqE|_Sq|0B?zYqav>%_b^ z?xDsF6Y8?-Ik0e9IAT1s9cD=hTFh~xi$0M(25-W^fU>6X3~>1zS2*x3*Lls;pxn%e zjd9}+4aDy|mbu+dJ(JeK$s@LbX}Qw#iWJhZaG}a&6d|V1aPD-n%}d zxHY63}t5uYWfMJ*9>zlBXjg^3g; zV{M#1zuuMS+Y%y%UlvG5r>2QBe9?*!q~qe3m~^^Q1m)|!Bvb=v2zY}AI#DYxpCUwT z82Pcm%x`UEy>VDwtqh7PC)&BAjqR&;ywxAu|EV?pfLqzA?S~-Tr0D9b`l%~{n z0b{tQ?32TH_4%iBvv!`fp4xT-`N+LRjjMw$wn?_S1SM(s`kJ_irgxA5o7mSJ#M{5$ zQGC-5D#+e&!h0n4Z54juh4;veIg-6i!wnXNB%}J)t4n=W;^}v<&vc7h@gk}B!?4+a z;%-DpswZTy@u;RuD;le4%}CztEx)KxrzDBnbKe&m(_F1Iodo*CE>YepM6t>pp7O3d{c9-LAinrEQors)Q0S0}AJt zn~0P{A^F4&Y#JuV>wpQE42IdXj$CrW6EH|4EJJa<-9OuA#VP;B5#`EwsImKfl zJ>}@_>2-(Vn`U`njsZ^qvVovXfO!;Dj9DLPVQ+E}`5FtXNndj?C&V@E8I5Z<@@64p zKX*`1xG@%MNBKdh4h^j0Pj)lOwsq10I^05{L>w&xv^My+_=E#^e4M^r?F4^b>+?eo zvRV;lH93aUUyVfPnbQoRL4DOT0x>i={Z;t3iuG4=_=d}43A96ua4Rog1Gx=kT|OJ5 zP}|1bp%R43>SWme;6#cXtPD*EU4B$dIS?5kj?>(#oKmmlt4ztMLiu*=W{svuvu80A zW<6r|g@3Lg)*7?^zyr8^vbuGn7?~6ZNQv}=Xf>?}&Dc2(q8 z=w|jAa^7*Cl`f|(8U8Y8?j}`xrM6#gx=^L=2`^J+I)onM^zf6pxq517uxo$z#Kf4) z9?vZ-L79YsVoz+Sae49gZT7wypIiAJw2-=yDIYfm?80~7e8hy^FOkkn2tm`=kOf8y z#XL(=Emli@2bNyB=GlVU99H5gX=98yWtLyn76VeOn0t!foaDy9$U!H=Gv~&Hoa9=&` z?GHU>rA%>2wMZP(@-ieXU%*dT@I9O5ER&s&ZVU<59F@uh4+R-c;W?%bK+M>_FO!Q}6;kCtSrV zs4`%+i^wyS9X0lIm2iL*bUsSI@`$Vdrz|!iI3>Y6M+5A_w5&VsT|zrWF}sDb1MCZ^ z{~mk%ZXc0=S1DNnUy6T+{`by|e-;RTy-xD~&|3UC@<>oyQCks1U-M^Ru7&S`z@-5B zNbz-Cl}=K!j|>4zdDTF;#urp_SRbiTqgH0mF17uh;%Paly^w*3)v_Q;x6Egn(A=Fg zOuM-tERdCJxz04pYd`$uzRRZ_;sRoXIyYHiItYrobFdGY9786Mv4Z$p3bGPlY#R^4 zfm%f-AZD;>Vy}&HKL&;*ERYfNvcr!If-}gLJRwUvd1*|}LPq#kQC3j=#MYm34*x+)l&3)J= zgo7oxC*!4JCipS+)7S|Yw;bA=+W0p29@YS|+tBDei<-hoMJs?hZau4{nKXNVrqHiZ z=_4jn-7jnG*jjR(`iByk6Ft5hNdaTcyYQ#O>vgDKNq*#;tNCCKK*k3A8djFVH6)Iu8NzbHqRa`8A{8sI4z0!oUjh93s3qXl2z)6M21s|pV zw)Kls*<9|2vXFUzl{};H{QwN-OFpkR;#V!$w9!)GDiuT-3tnTPgW+ZV4c_ASinyuE zStnP4*)#{S;q6-M+3=%xbzcYUd7TDptp4(?@6GmBE(n~qLC4Zg(zqBgjhVP?{`qoR zYO3~Fy;Ckyp!}W{roh+N8;3JKZeSYXa$VL0@wB4TT59xAb2d~b%QG02tf7i;&lFNg zt)|yYI~7Tkm_lq(8yu#N9$@W2VT7qaTz{s++(5M}vre4=wF~;zri}5D4L~@qcZTNke!|LJpeQZfJZiR+Q%IOI0WmK-Y#k_9;P}wY1EgM zu#f>V$SkFHfk;VE(urRS_i0aqqTJsQeXl|~hHdX}WPG&`F@2~2zkA*&zkdDw+MKi^qRS1^4)jX`Dv{W~c zz}lC);By?Mp%&P{3&RuUWSrsO%z^WJX(ZQ`9l0kEyBtm&5*)68J+B4!AC}JGyGmS0 z>g2`z{IJDk-JG9scm-_&bEK~toL%*fks7I?wV8JeUz0y2BN1Fu=X;l&uE^o8gE9w0 zV7j~$&ieUFp9|zE&$Nkz#vS(8-Dq~9`cwK-SyG;5K?e0NbMIHtw1+{7k;X^dV^^5g zXrYC*M4_eVEYI&ky9l6`)0+hG{LgtgUaTBM*O0%K33``&z0Badh(-OO`-=JZH^ku( zKx?|q7%@G$b05Tn@N*FNI2?!k@N>4N_j@$1`LuBE>6D99dsy((`b#HkGe@K)oKq z_raM&WAxmWis@l)W+_F0>QMpA$B?hz-(pq1j@(Zvpm71PD>`KOb%KVI2x{LE&teA| zc+YWTP(oK7USC7s&fD@7!`<|vz~28>EysH^S-b)Bzdf+K@o#k+|Fc?FwKxAW_y0Mr zRrp71;|&s1?E)z<4e8Uz`pc7HFflX)7$aU+no*-B@V15(Hoi0{!tP+^32R~zq*Ix$ ztl@<}n%{wn;ez3D*hK+)gIh7edgai0m%D(9jrwJ?v=Y&aL6e1mN{QGTUMCg%caI_F($ zWaq1Jl7QK;`RLEw@5nhM5wzfT%J)-fjbG}N?jbn4bK0TB?VD_0!2UH4OoMB(fB|XM z?+xL9yIt}>k5k0h%J`oKy%DjxHmHK=K^p+7sZ&tDiZ8ciw&U?n|Y@-H>-%Wfhyy7iM&NLPjgq%E;`! zW96Jylr%t{k}g{j=Y^0Dt(MZgX1kgG7fYPR=bW-#o%%Hg90}b0A5Odj38XIA7V41O zVTNr7C0k=yp3IwBqHBI32yktb#+UuQ$~+|Uc;j*o5)29*jSTS53sZLk3ku-B@R4pt zA%tRN;eXazst_tWq&ByWbc|ve27BkhzpBBz#E3>-ah)nj%nsd{q0^PN=Z;ZU&3pTK zMUlFNk{fTbg=}OC(9mG!e8)qX6QKgBmndl{gq*wc{^TS`)t&7s5Z~nZ3J__!6?_w>8 z5m0KzAK*3ul(#{>{DoKi9tXIXIFrHx@l*rE6Yc+vrYkr({&6q=1x!K^+FhkT)xJ`S zaSX!An#ocZP{HVf#cQ+Z0?{-EL=*WN&40n^86}?zYn7x4EI1j;I%|P{xIb!4nq4yzyV=8>Y9ffz#*QBzVwS`WV9WLRTtr7u; zg5XsI2w`h^>XHp+?D;h!;l`~9%C_Z1DaXE}lnbbrF$|P3hi2~G&hQCy<}wOq{o+Pm zAFFGJGnoM1>8Dj^9jf@oA|d&9MSVi$GtxPgP951)C83p~4mI9V^MFnxO5IL;- zV(_&!wq+8?c=m{Uy(!MoC2Q1FN7xfY(ed;s*V@~2=`Xgdf5yJ6BC=9ydXiS=ti+0L z%Y674D$unfcykS!W3I7l=j|NtQY#Tzi(mjW^M?*<_MhO4UB0}TIc zjs7>P9)CIK07I0j<*_*Bpmn&5LFso&dQ9K}4Y4o!Ibw1mU>m84bW_R+~ zr5*Aw=_?MEg?AFqO0szH1&KwV;|T=UZj{(p4EeE7$vwsRkvO$LQ}Kfe!%tKTzu?ILSU_%XTlrU^lleRD=1 zW}ijd^alT;*T#}L2ewp2)Jp(2fjUW15m<>B8)VyAnSw(B)M^;G@Lh_EQkOpCNCJp( z;TaND+s{}j$K2r<=%<$-XiQ@+EgYM^;gjafc|*)b#q}@xyBCIMk%25fpBC+Ssp6f< zg`!>ohbPKsQ%G#{42ea$R5=+gYbJE`79zezlt${Aa8mSMynGyiye;GBh(CTL!Z`_*kAc3mBpBppL{Hrx z&>xSAx}v`=k*w}83okN_pmUeibk~Jfj}gw?hRsNf&WReJH*cW4jwjA*)_Fy|V~bg= zPeu1lBJ~agJuNSRwrq|TkVRMSCZIB`zyT{o>H-|`7ku#l$))IA;z>fJn_8-@)K0Ty zFcT1R8^0uzau@#yHVArx*V`zup4qp|QZBpb>9>~MMs=t7Yo5}$b4-7U^wxQzAVw(B zmQ%B83c9G(vir+_bEw}rZ=ZK$`4t$S3jgn@k^ssH{!P-O$FO=W{ZjGeTMgb0Swtuu z25L8W^gmJ_Se=tgW0$neoa+`6u=zCU{rxw8GMX4m;h5w+SbKauIAHiNdATTp2=oK6 zUFpYPrQ6gaxW8UB{>`5%T*L|8Bdp_cRv-H(n^|@&w&Cj;=JO}?3EioubdFH60{w_w zJ|)aC;Dk##^+)sG492gWNRYX2y@)&rdo!Z}Gv1?BzZXu%tdW|$aks*$>s$X;(P{>) zY3Xgr&P2Y#ata*p?V#^$u8#Bt(>#M}Y-Rk^#ItuB0meVr)8rRMD|LEAzA|i+(t1#L5zBNpS4_NF9sAb>_CZ4i!MSiUjFk}ASqjeW{ zMbB0!S=}_!biHqolqnyXiPk5(tC-y^R|H>sQ)5P}MK{u({e!Y{zSG|;f{eX_Tr^v5 z8w}kXnCGmvrGR-(3S{WdJog#|%yTG^6;+#CqW+-UgaURRie)6lb=nKdTb1ZP^Tj+hVcBq|VNVRQbFQKc- z{H%7yRNR#tpa@Ho_qD|2QHTo#QLD`Pm&x7r0Eq9Dpw?QZim<%n7Zon3ZKK79Ju3ss z&}uRdsFlB5#@`|S0?s)1H(AL2zY|b#?ehlwIGV6IJJ-QRxf9e6Tgo@pFAxG=^DnY&QmEaE|) z!R$o_7G4e5ug0S;zfPH}$jZ{(kO^;HLG-ylTD*&O@}Rb2_~QH=;SQ5o2I^s;a_ME` zS*ifC2-c~OWd zi~=!p8%L9~@)oTbdH|H4qW+{CWJtk0GS9*N??nt9XL0U(%Wil5K(GajCk^Gqai1rio1O;G8 zS~Ur{X0ghwZh>x-b78s@-JYi|=$g<^p>*Opn34KKwHo;oNIEtCm300N>iS}rDs?~{ ztpMr5e^5~TarYc-ZLA%P|A+W;R1S{`J;>)kO865pEFH`O0<7W>XeXS2fEhJ>bfQnu z9XRFvj5T>U^8I}`D6N9IrE?HSQ(hbX_}u+C6hac+ei2^uUHtE+JnsZ=>!6E+T8LEf z{GUP!XcOx+0Ld_-pISCh1SuQ1oVu8+y@E;#)pE8*9GeM4ZYK9%b#JfUv4TFT%A&L1 zwtOlWjb4h8O4SVt3|(p66h3>xU`^-<)N1ttB0C5w6>@$S$< z4w8}(OSzCjILKWZN;ep(c>zssGTC?MQHgTrCHp}m0PKpQQ`gNv|A&izdjQi#AP8;& zjm&e(k&$tn)xk0sEYGFxpi81&;7>zr;m6NPVt0XMLCo>HcJfrRJW5jucnqFbMU6Xm z&oLoG7P~P+76hC!%ya83I9?$fyRvQgkjrNB&y$E_>Uthf<+F7~ha3{w0j$jWKbEaP z2NsheaF)_@ol|ya zA8J|PYT`fs{(q6r1V&B&7Fmj(&$ZU5T8h34L_f`BN2z(D6bR)Q+(Q|_w^0#k&ZxYw z6TW=lmrAd!M=CS;)WWhg(K0r4u)_V#;Unw`+zoVx(xa&$DxdMRK;yan39drID$X7EgwHw%$Z}~h%bGiJR?RT4CLaI~LnpFyNKE@-UbC4iwY2aI+HBRkYr?rK%`FA5O&^bPMtLvWpY`us0*F(!78NkN^9@xeI zc4$WC_Qr;e|8i&*z_l{qp?wUF_WwWx9UvmvP-;LC=N6BGtw6Ek*XW~vc)pR^I`a{`r`R7b zlP@>65YKVrtBX&fXA;zYYXWF#wA$iOoxF&g;P8(Xf2JSk<;>GjP!?U6F3Ma(<83~K z#K~|cY+mM~KJjib9*nxtA*qB48(kiYG40qan#x3KCXmU`tL`f9ctm_k%g@PH5O%Y0 z3M88YDH%0`&V<%VY9)TK7#2XY#BkBI9$x(Lcezl|w{O?rz@W4ROmP2pVdamGo$x=t z6zpvrZ47O!{zqt9u|@+*fPFY9Qmv5c!KJhhVH6~Vn=n3sh{h;S2L)u z$I7^ zb;UQ&1HHZc(XRkSXS!g(9;Kq#orrM8hz9C$h|;oc zS+=irh6f>E`yF0~o`H@B5}B*B=0$ZN9(>#TeK~xXHFe4BU|Gyb{cG=S51uLo&(0z( z`{Crs^!Z?QIIyq6Fl7!q8*loPu7V}LMLI@tOm(80k9(N5LNrt$q2Gm3oZIG8FAqHz zrEDswMSlIVr||*2@;mBXvB?GsJ(3Dl&l#rOFdIWodM%=|+Hh*giKp=X>6cAf<_R0j zduVSTa4lRR>G+mdp(Le46mzSB19-L4onoXP`@*GeyT!*~uhGA@O6x3K5^ZGZxqD&N z)+pf@zWq%Mo$}+v;P<+L2oP25|9|c&Y;JGpWCgHSGXCTG{tsM9%cBb-54{iYi6x1u z*`j=b#;T(ay0n`D7bY$vw`3=9J(U7)Xt*Xn?^_HFv>gwBoOYM3%Mq;W4%7a)fWo9%(3GCz!tCZfiox|LOFfK?s znFKa^p&Zm+?H%0RZ$w6|xHAzI$PBuuIHM)Np zh=DfDf!eBMlR{!&0~aK&lY#dfr2@(E5?$g?>By0cTC>em4H3y?Dse0DQ&^F8KpT!4 z;WgP?Hs{b`+v<_4tdKHNCoGX++RFG@0$e(fc4ck=x3j}{jw5aAG{>(i=C0e6iMx35 zaS=DuRc0m|z#VyU_z>`1OswzYFVE5(f;z+lv%)#iZXka`bINoWoV047ZHQ#WNZQXM zej5UfuzIK9Eef(|P%x{ua%?!JL*Blh%8^;ks)zxOMYkPkGcii3p-WKN&u3P}kC*3& z(>N}QkEEUPgI-?{#9?tPE>z3EM%W{wqaxji@rv zIP{-V`KaGEDZs7?O-@dM0Gv&CJiW&En(u+V=q19WknB4eCDQ2PXAqZGf2{p*Nf#ix zUcKILysy2!czc0056}*XhEf6YN3W)zT})vXG|z+!9LvU+OW7RCWUNZoYm&Q=2NU)> zS5fKf+&ZTTwVX~Y658k(NNa6X^IG6g-CXpW--9nd!JsK?<7e_|V_8WykK8dQrW(hn z_u8?dMAkN|ZMEs2)RhHtoz}tL^{k(W?KOf~oLco+Oj0?ZC?6Lpl{H1M@sJ`zE_RTj zprbxQ;|fnJD?)vaaSyPYlV?JSu*Kv)@cCOB-0g`w2OiTS@VNd%r|F;fNZAo+wrXtp zr!u}Hn(22Q6nzhf4kZ1AizooWsxe1}@*NZ={(I1bt9V;vVnYoDrH=oOg6#*yz>7+a z0MvqknZ?wUd)|$=7x(uMwvn&!dGLn4i-S}L?A0g}Fpt9#aDg*=X|bwQ+w)G#g)i-! zlyL~-hr0M~9moD?raJQmSe2N1BfB@gjO{wOcLs5^eFOlNYDacxn|FN!1eSz7tvNL5 zqpQW<`yOYOxV;o@@ILiMgI<8CGr1rfR+Xv4Yc9+Xp0(}(3{-@GYAgNR=}x=X-E`^0 z+0f&;DFV=6CUyQ8$RXq(okvm~qN#D5-_(>&*lB|{{`KVKN#VqQzm_cs%yIvr+xzDh z@oxpQf{m5A;h#qrq4=j?z$Xkv^sX)&$j^(|QMhO#WTf8*QO~5;;>k`E>y=S`5-HG- ze)oCpLd7K7T^HT1O~{}3cXqB%@w=%aVUzph2-@6EV2rre?lJ^gc;gMtpJ!Yfg=5&) zTe_7*KGz!?v`q2cq^n9%FV=p-w~h*b1BfV{H!raXk{XjoZ(6Dz9b}j zOl#B@j8<4)bVC`Xgi(20N$ehR%(;Kfv8U3_yi<`m+BYiK^@Yf5kFF;ja2dYP{6_2i zu-835#{7e&$>ag_x)=V7kzV?i^oj9|36A<_`$0-tfJ%^&AQSl_lUnzq#$M9~Mc zww!XzsnLtkwJRz{q=mK2>2Ujl6l7QxVB;GXErQ1d8m*lj*wNyn_BbEuYYGJC zW7abWOCabIk|w5^&uuOyS=^7$S^V#ywpdlcOrZ@#;hWlU18|gE+;AJ(lx173fe3I> z^R~0SXmHw?jdUj(t`-0nizCM+B*eP*iPO>_Y6az{$2^-2VvQ0z zvb7%Z57c06>Oye3lzIJ%?NoVJG%^-_K<`Oy(d(y@t8yW1jxqvfk3Kt(B+&h1)EUnG*+twJ=RwFF5LrjQaI|r=+3VW2d}<7 zzn4}kWxBQKE)FR4JAXgQwPOKsj4HFg2n|*Jh!{ z6prku`thQWCd^M=L_k$ejNU?OXdKgX0bxVwFyBI&ErwPJ4nM+P$A)b*qyKxs*_C7H zDsxRxV}@|lsENQ9+^}&5Y89;95PNZyq3enbWTS?IiliY0mxG>hcF=cP^{Yr!8IF~N zn5LvUyw24UNR=LTe&iN(dPCa4vgBMwvW%vc%E>vQl}YjOlp0kMQ=VVDg6$FjB>t%m zC$;DCOu-P^+|>8>w$NN`wxY_8dVoglgT=Z%Mp=8JJs| zp8U8upK}}_OB|H{{4OG<%^_H5{D#uuj=s!~hU+0SsoI;AW{&5O@1P~k@_yuP((E-- zkiKE_h8lTYg=oM=9FIAV$*Guo{~ zwod})KyR=vMjpc{GPVV75|c~@=0Ko1-fmcY?Uysb%(Q?o)~1+|g_leP<`9=GKk<1I z!oQ6kH@#YTtO#>kI4A@4JyL1tF+^awL+HAs`$<_eP=}jlOBQ!cRFb%t{ z7^*CefL4pZmG+Zjq~ZEAO?KWsSmCTQ9<1k9I#zX#FnE?ay6{$gek<#!tBMqyLT+gN zLzjS+PmVQcIlL_xy1}c(N-^Oo@`rMbzY>cE8WId*H$z2GIy}h&2RMkeqYdtwpaF}Ff4g$Qm zCH$fbOLr^g=ECI%6x3i)Re+eli@P5bKiW3eScUvk7BmNo`Zg>gJmv4iLoM zJIynyunBk=Y?@$nsIB?6k4y# z%|6gXQI?W$hAL349=Jitm4D5px%Fz9T_+NGF<|aycrhGgZR+J@vL%Cat-Ds#r~wRA ztlz!@*hLVuc75X$o^5q3?_+0CSsR^Ti#=*+4$bS`5s=o*G`!Gb^_>a%ALk&h^h;-XOAb*{bt4m1dvA7T>^Uc(|(k*nPIXRsUe&pw>Ko z=%Bqym0WdF!la1M#X`a5Zr%hiyw;?eXMZ{UoLh%> zIU74jsbr|Es{+|{^cUZJ%;9N#zGUq6Gb>&^&Hn+j+Lvozu#ciNN6_wpRG|8BS;AUehXnWJFmvto%9ozbF`=L4Ut=Kq_+GAF|z%e4i1C29UL`k+a99X8z8E*#xt{e8H# zSoENV!i>ujZ_mt{2-u`klrC#xU$o0kKl{t^Z`T+%q!Q>fl>i2{;ODH9a)dNy6CV#1 zC$LCvWl=qQJ;;(a^PWbO$~M1y>DA}6#I?G{c#j>~k(@1V4y$Wq2nLkh73EqHpU|I)lI1N`O(S@o~18V3{U z#M{%xiP`e^)b8B8TA?}_mlRr21Ol|hCy!3t6VznOP(}LBv$^8g1PLi}!MSOeMhB7=m!O!n0iKZ`d~L>Z$2Brcmx-(3q`XeeIllvz=;)Hf)1|sF(p)n5con^=%DK@ zIHJmW)6(}#-)bnWC@Vel6O_@V>S|diXOo$li(MJph_{-O9@Ru51H%fUQH?LV5z2!N zBMh|knu;1YCI?$UWvD8y4AzHAX6+YRyOj1aJ zu5`f$pN#U)FjCE%h81x#kd4Z2oe@&>OYwH!<+nc-deaOhJjKIW<{OPLRKLyxy&jKa zB`vdI)7P~yjzg5yq`o46ABVIz$La+uatVLyMt{?~RtPq4_H{%vhc$^djVWSifo#(E z9NS{^FLm?$3FxthTj#Mo8!KmSyP(;nFlxX|5|^*?sC#%Yi6oZNhN9ippT zck%w)CI7?_yweZN1zf;Ygn#Qn_eVqfzlqr&lx#qCRH{z^y<;b@=JwSajvhR0^YCXV zo+33RMnyBhPITd5O0rBoUe6cXqrn2gtzM*yy#BP3^YCbkFip}v-h*|A^KlqAcW+nl zZ>q_4`XhekpqjyDUYOO_(cQfU-s+Hu#(Z@&>1y#;B~?sX{SFywpBVi()0CjCY1Bh` z$&>rQKxu*;@ys1(9acDfwL}QPk^J5!tSWzgH+7ojVcJi&qD5c^=cscf>)w?QI z^M@zi+Naf@w8x3jiU{0Ey0ndx!rf&)*E=eS?qqA-O*w%pD}D+BYIjMt`&KbI8{&$vDK-32>F<;{|Uavo5v@#=R@X-zJj5!n3aYh ziI|m&p`n=7$9aky^Q*s%j(!vMfzQ0S??52@*7^NALykX5fs>=Tl>>u(VPJ{YyGwR%+Zer+uh@rkL* zMG}#G0ePd`Nw*dyHo7%7nb7G>v6*;zcs%9)hOkI3qe4@x?*>YvF>kjbf})CV$z?(A zXk2+*%!2NQ4Fyk=L3zY&ec}$bfg^##t=H{El%aB^e^~F4JD{+>?>7nsALwqFB}FL8 zn*}KC3w)4xkKM^`FwGCt!Z8D`prbG>+TC-594dmZR(0$J ze*7)eFG${pXqDSv-+(|}60g~LIa9e&%zX=dU}1>z)H7h7(^sys#?Z&tIX(fG)q@jC zk5qd6tA1R&3yi0~*fQ~y$xo}yE80+c1@bkVd0OLkHl4{`gw?U3Hi*Bo1|wRVC1sco zAN0`wVyYKR zIp?uXP=bR?6*w6O+rk&YNv5U?Xl5uEDKE&nIN-H8wl@4UFFU1I5Ghe#O`ly$KYOz| zDQY=?&inu(ltaju6?ffv;AOpW@XRyG*?!%XME>EV-)FTMhGMN5>x<(KpXYGI^X0ct zs5RT|{7{CroNXcwiwj$rPCs#}3rYf-9(B8sK;i9sOR~k#s%hQa-LrXs^R!;4KpfkgQ>lD2O}QrVB~fvs59*iN#sHYX2~9?GSQQ= zNbi=hy9ww_gpAhFzpO?a1e6{Gu3ua0bZcTT6uSZk>x-yo&_Ud-+^t&v zuspIuHz2&ovpdrQZ3)af)9w(XxM-L41w$2byopTc=fi?XY8Yn6TTn5LyMXi~?TQ6_Hgu}cs;C`_P7L%tIn&nXvb zm~!B&Wu>T9cTmNILf+&P9I=3UB__a&7!;y3f#QsY7l1*nr21V#-`)|RTe_df{3>DH zv!p(8-3}U1&Ut#AR$bN>Qp_Hf(MG2q%a?%|HI=5e#p#7K##SDYFf~Z@n|Yj>*uf!T z%hw-2aSe)9W;#tLsWa8H1$1mgD>VHwDfK2yz-&sJZnid7inr<7Pfj!dcnq`)H(jEJ zYZtP`BasE8usmT{B{P^Dc8F2`C>m2>$8ukftLxa5%!xgs=dUeRVvGH*-EK?|f0XNY zs}3h1D5)l7B=qARCP+c=NLiur8*gZfKLxSa46`N(H9fUi3we4XP~@Ir%)r#zde;YQ zL9<^-Y`R7;y<^v!5sXUPvHcMLHSr?GGw=+VA~_i3Ia=FfqB-yS^jXlB9XFG^ZvC=92GrWmH=0rJVTgZBvDtPmS;)Yc>JfU?$7?1wpPn zfTP;QfbkZE!`UdXoVT}dvM2M7ptWLUJIOwV3u%n5nG4JCV(fWa97ED$_-=%fcs^1E zHQP0weHb$4Y~Qw1mUKvBP9Phkpr zfB_Rp=@72cbhw%8EVKjW{b9%6tmk|*XijNrm`YcS+_54_^%wcH7)^LoyEn;!EVdU2 zLQ0Lc4|!}a7=)z{5)Tg?5F9!Q3Am5}SW_1+FT&s>#j81Ym_A=mE7#sKsNYKTN0p#S z)1Q+TORm<|k%T1kk3sq)qcY>1#=}eh&Hauh07Qco= ztY&-6S|l=pq6S%1QEYjb%Mkb1gj!@Qo;j-~SZ|Q4aeR;Na8vFy!}6lBRa9+MOu`*v zai_%(9YmwbQ^c}ybwk-rr_Cx;BcBaol*Ua;NR}FXE1Os&Ym}yoV@Jehs@sTbyLcaPr)24mngXq(Zr9g2&6X2LryWz4s3iEt zOC!^d4pX|go0w~HH6c&3#L^*O;r!!W#wy3C`U=79`=_NeD-$C-RSXm&AyE!#LE6mo z9~4#ks<^U|^*mAAy0PFYVHHLu^R(@MmNi$7YbNy!cue5w9k-xuGCUKctL7=t50iMf z(#57VEW%Br?SY*16+LK=fy{O*3%Oz=zJbgqDNgCI`Sn)&pyIud*rtmueNNZSqqMk- z5I((%>K#xX=nR`N1sLE*e2`U{k|_YVFZ(Ti`#byXy1(jGby+s@VeG8o~*&$`G|dZwOne z+1@<^tw{063G?S1hFjQRHkH{OrdwhJzzyV}UOhu3L><2m76PzAYJKS@C2qUM*r@Kt zlT@QbncOc{rQEwY-aDm2r+>i};-B?dWpdM&o2OmuFilY8EGO7&EH`BS^I+Bfq#>L^ ze{R-eBw7;qrGyf>zwk%RHSFD4v}(oxs7GH0+qN_&^_=wLpb{H~M3ewK zNi!WDu_&*3nw`8ZjPJ;{VI*&B7BkvUmMJ6ShKkGzWo zeH<2WnHz3H*g>c-GeoIZ9@bZ>~qv*>|O zrN*eKO%f-W{#Of2vaA^7D zXPTN4%i~l?IDf>&3W-4h-P7`rnN>EXq=XDa$s zq^~E6gsDRLgT^&`n+OwwM=7Q|)^7+l^Tt3^6!r^^hGKQ8fkJL@w0caxvgD&m4+kvJ zxh3e7tFXJ@2q5p1W7&A7Qg6!bQHZiTb4u>}5Zfm0S*p)zQlq%S)ZyecRA^kiiQC#y z;*t2m$LyT)47!7onX7GqJvotGxyXAh zo;F}sHUU={4}RRL&g4(I(E#l|-#8v>&43$p^h^=A{4|;b3WjnKTk*{2l}pYzLu6j& z2x@RPk$KYUFNP&Abt(PY@$o>OB6V^%M4caf5AJ46I}&3zMW4Atvctbr)UtKc9YeP2 zSZsc-R8V07euX(hz!E(1G%mcZndl3d?(kRFpcbp)_!2{PHFB)xtM)B$weftIk*^qEw;uc4)ml$ z2Y6;)ya48Lxqh3jCj=gh)wYLk$$O1H>Y|Y5=2J;xsI`K{+;JsV^7NC^P<1u=#Bq?} z*+NS1Dn3h;WZi?H{)r)+;^r^SXtg0tGf@sin5 zM09Hq1Y$_RN}HN;WD9%)M?Ncb77_=~?xNQIXlx%*?9W{!NSeC$>RekiY2}XO^V>JJ zUVc^ca1og2a8>La%!G{OLMapq+u`RN$%gMRPij0K3lWOh2JWZ|(0+=v*0N0DpQh6@ zT^DTcb>9D=8(7kl>>Bm}(z@U+>T`(@e{}=mo_5IHo{}TZ+f7jw#+s29hBUN6^a$(K zciALC&?#8Tv&jF^#$YqrPMG=36AkN7A~CJm^*iD&B*U*MeLY-a2GImttI zGB@!=2k;~P3|$ICq`LxBM+@hots+1Ms;fod0vMLE`&SXOfDbDV5j>rN{@pD&MLugi z8ra6I0e=4N151B&c>cKUl16{}z#E72!UEsv3pf}^CZvhdCtH4~Zo~rbAqKwCSZBpf z+eXys`WX&sGp9el);1J?-I;QK@CaHho2DoXy$y$@-wQtlzzFtl{jUG|o1}~^|LbQZsOqW%ALLu(fRRZSGY6|@ zYfv)lA@!6go0G~x!OboVQo#!yt(9OGqi1;otRVXifARrLTyh_lY0%qWFdu ztK^m3a-Ugu&gqf$e7msG^@g|!Nl=D8SV79C#`JA~lo{5VMk6+Fk4UE<9|8g*s~EdC z6c(cl_N>$ujs`ZA`e${ezVHAiXJ?TCcosEfNi#!_A?iztUu}2vIiEdk-x?bP^Ip-D zgKnKuQDwt8MWgw{a^cbcW9%KcG~u=^-HgnzZQFLdVcWKC+qP}n%&_eY+qRWAzEjn= zt7>%jIR9bqz4n@Ou4l1rY?Ao*8Bf%I1n>be*HOig2Pg)NCm5*s9Y?HePq|oWP8%#w zo?5nA4qj%-U$kR{YBNM;TRyrTZDAS@HERZdkq~@`szF5x6S*S9UtlJX(kOplc9gU_ z73y2SY}=ZLSf|I-f^-{uu6=Hr7HdO%4_$u_LHRqz&kAck1Ky@x?>8)Q>~Xjubf(e5 zRAb%UAX!058s)it)*a3y+jSCqjRw^ktQ%|AD?{`>bd=I=K!yo>t3}p7Meg!#Yi1gf zvW|=B$ZSt9J5EInNi_W%m(}N-cm*u}z^sL|>x+Lw%Z4&i+5IKqevPzdkPc|(3_VoF z?7!*brg(RhUcA+7Qn6(E<1xzYYHOj2jm6!^oX(^!QBo+~_^lrbnS)H{5U2GYzYqpP z)5cFzaEa)3mewdcFvKEI0ECniR?qsMHZ`?{a4!JH(I@~3eJou_E%$|=kdq&=Boez+ zi*9RQ*$!%thCu8|JN%m?8IoE?QZ=mTUg;XYqyS(eLc`-g&|;X=5AXOqwYJW?kcnb|;*4 zyABc@1_at;uVEULwo@UQYf&r)Qs?hbf*aVLrj6syOT&x%**fpoLf(A*>6SQNKd})X z{?4piaWcdn{XA5>K0Rl=@fk6n(A%k0Im^a(b|z21#md6D_1zOu5k(B!0JtD4mTnmP zD5fiBn%+<`J^!58liz}iG*SlfW$SG*#u|`q0!le$;?r3>ydtp|+QzXLb}))o$|C8* z?@DopqTLh_u!Rd^kUT`gpvESUYuJ8n8j9aL^e zJk$d6Uic+weTFClWK4+$wFa#?9HJ-^`9@<3(K$HLvIuHr`8IMTY1XrbHbToFJBpwQ z0%|Hbw18+LMbSfcYaO?L31v9}qi5dN26HGy@iK(EhxvykA=2S(MBQr~{ z8M7E+tp@FsF0Lul+~*fDu$(Za2Uf0O-j)L}ICoyQ#P)PB29Bj<8zq{Zpc`#TwPwvqQxy zX|*A60;G{r1hRz(7?#wuIjY5-X0=Cppt6l{!VWr9ty$`0T^-qYi{S_Vqt_u|nLJ-Xm zw(Rv66QE-r<&ligJZnzj)`RTRs;sms@~f4>z9U*LNsmvlViOPU3QwD(L%)oP8nV6b zxwp#p$1Dm94@XSky9(%N7BXQB9f6s`zWXX`TXk^d0ZN#K>N_2oDBW?m47e%{%~M zAG=?FLGA>xUT_XV*=~pqMA>c(4_s|_M#iq!dP_%nr|>p0|5Cqht~5?RzOCS+?o zt_z--ENqVW++$kOOqR-wiy*%Q0375IlQdJ9Gs5iK)+O`-GOi@y4$*|Ta+7?J_vgg8B;O;|_&5PVQ%{-AAe%cfOGbdWg{AT!fGr8{YHbOR&%?i=43YCT5 zP!js8(UpV}^u}#Mci@0Qyb#d6l4((%@Ro=uZ>@CrGZTA?+Lk}T%rkmB~l)XW8q7qu`Wl*$MgXS64K z`WCpnsGf_%+Vl7iSlz0IJyDxJftTKsmmZ;;+#oLRv-g?8Jo3j3LQ_!CwCV`Js@wJt zPUv^@ym{no#lZV2Umd8ZcV{1PmixA1sgivv)~7dhV;DyU9&8W~j@5!S`IshAuj>v!=y_?SKYQ&5nFX6VJ~vC_jkoKdfe6oy58h z4itYV0T5OHe9#CjHnjv4)mSuQU5kZ$3|E_iA&Nu1s^kM4|jngw_ExcA8zT#+wy(UY(e&7 zBaGeFWs4x2hOGf?qZ&Jrbo9Zd5jFv)s>goR@Q*(cSj_YtOyji>B+2t*pgz)R=W?T6 z<%hw_c0g(Oh!$@oEj~eMzal_+`ba)-CSJ&VBmNGfe1O%uYkx9y+kxBb1&Z-b zOu_5@7pkB zGNz|bcIwx+v?x^H_OTq(KAXmsX$)zjQE1o`{>meVy zD}*-@x~t@39=dM)2Q=gNRNxJ=3Eh$sp9x%8Ux#&$DFXvT-^SFpRjH>((!jRK(XL%O1*|ZyD$6p4VI7uyhQzs=Ms6a2MOSE5V3mB2OKV* zcFf^&rfC zC?)1~tQPN_iDQIkLvGPPbTzJrC7j#HTCW-qBTEd6Yh*x*az+7zyiKnoFz%VKqe>@; zp|2zDHYHBi0G8s8)C!o&Mw~0qE*PiW>_GY;D;n+g*%NWfh1Bx(E*jPsk+MORxgKBo zk(*+KsJj7%A!}uEc+n&sk76$lhRw#87t}yi<41HT%4&A=AyJJ3iS!BeqpTu^(`p3Q zIIgn4bDFf%GwIR6i7*TDGO7YX%IyUp=P{jDT4xD|%X?2^GP#yxz#n0+70Sd4F>I)^ zZSKr8S1vSKl_mSlgjFz7ptv|t<$PpS;uabB}m-*Gc50@Cm#({C4VD1x)>+6Cgf zn2bY+`bZbVPE4d2rR8VLN5IWDF<2gP(t!_hKJ1)?u#?lA>|63su^p*erLmofreLg- zsT1I~+Ngv|c`zd>S*{`$k^R@HUh=+wH=ZY!O zf(!K<{aF;$n_~_wU4qT|huZG*#nOh=JxC5CB7x%rtfPw&2@KuUl4kHs7QTynke}e2CTILAmj&n7xGC=?=-Vd_eOq&xSl=`uOWwfcr!6 z8dD&3Q%`_MO-i*J9aoZI5)ukdA?h>0^z_#rrfi#qLB48Y>-z;d_y6PByx?$srA_Xc z-}%P!+L_|i^X|!Fenqh{e}MS@xncX*3}j>JB$@cziv@`^Jj_49JGA5r@zUz|cV){v zGoP2=cr3-0`9l@BCz1#k`w#LBCbvUp)geT&8a7#S@(6iH#U~>Xv3t$PzDN>G`BRa{ zsz(NP?(gJCY?Jl`B8S?2M$(&7nrKtlPG zk;eL-)$oK~Gag%2{XUoHq8_9uR}@ZV%ZjSp%viGY>Y)bc>4-B)Y)8VaSlYOX(Z1>B zmQ5j9vE)29(Yy>66=#m!9U$E{1x=i5ul{9)uef#Mo-7&r9Una<*}8`I)_x;8d~;Q!?Z}(*Jd44*|0tq@2ZFy%H5| zMm9Vb06SO*LRwEX8(e9JL7O*-(z0tdEHS~rmKU7XP^a8UH`2dkH>Iy{8O}jdm7Es& zrX@*38<6SYhaD`ewJ+DG9qBn5pJ?qbILiCOI*`{F9wc_Y2dJYVt_R{nx+wKiFl|s) z-kc6l0f)OcB$&2Qt27PKh5;sB!ngx%Z-54AmV>9dZ>436p>9&i_iWV^Wi>G5l-N)w z-IjzXlJq<_OG-L^eZ*nkt3`RO@EIel=zdA@PR~K_{6mox_&Vq}T)Qg8AJ>P+^6Ae} zC@E(b=)8k~-Y^{?31FMz2b~TBy3RHjBs(sdA)GW3FVrg$k|%xGC(RC-7TYE**eh5* zQ}m?6#iu4kw35d=;0hc;cIN}eR^!FbLST1B5zl#%*cinyb^XkawBN?QHhCW4ilK6x zDkuK(0*?Ac!AFe6DjI9z5$Xj=k$am5k46GKQ;@Ip&KbfmdT-;Hz8Fv?%iT^QzA)Lp z!EjCSvSLEawVq~*Xr~g$8_%+}$den=b*?GxIV7|cSPp-&XKS8ilS~s#;>x!J1qX$e zjVelol%_+^Hp4YnLOf5j*mwd~#qh^GbBYS3i>jPBvSvh??`N!sID&?Unqv%PN)|MUp`o|_ zRE%#bFvyjd3=3)YCzt^istBWaVTnS!oQvKYXp^@(&Ffq{g#rn?GMt>{8{|^<|#n{2|pFP!f4z_m24o>FAj>ZbowrhOwUH4QX zMWI3kCE*?xe*z`>>N7AQxW0#cZ=toa7-()`pGmIGk{oAlK2(%n_3;#J&@1z@x#X% zsg|~cg)kg>xwVhS48*zA!Q3b|e+<0Iym4z36;7{gHw_TWr!Z2ZvWQ0qoSFYHZAWZL z>geA)GBa*|HsYLGx-<^7cAt%}mQmWEch!Q-7F~#8q(zPunLI%|0&aJmBkK_PMZa)s z?{VJ@MrBWI4QP&;X$;r>Mg_D5q*z!`I3e^FHax@Nzc5bIYtqfqdf*tjq*@by96;I? zbUja{zIL8>h<)P{3@cy7H$D(zN>=ZPUY`8Ov>P4iPi3iSvNmzWne!CGXvCHZpz0q_ zAMy=-O?Mk>On&@Uyh>fo>f*EyJeUA&BN3oqU@s)joPTDQY{1|qdmVVAC_Q#-3||1K zn>20B8f0&gxi`q%HW#pa1Ko%wY|Vu2BBS0~8V3HHP3jY+rK3F=#K#_mkNIYaajElV z)M&^vC?EBHC$!C0C{G2lV4)?of)cuUiNFqgJL*EgNkbeXlPEONL$=vG_<*74=X~56 zwHoah%H-1+7-zd*=LJ+Oe&#I zK*CH>vOJ8$8ivRWir7zpS5W}+vmf;%O-3i7Jn@^`qEImsZHel!;TDvQ6+7VpxGYE6 zsJ{_)?_^4BN-i4OKl&W)dTxK_dG2<-`*s|9e%K+z7M=`0qTB2Yp!4)kq1)&)hcHBm z()*8*m&744Twb*bAAoFx6NUs>(i82~_<71V72+GH^k(_Jl5^T=4+i|E>zMc?-+NK( zP4F`YfHY%Ma2jxG>Kj=Xs8+OFi1M!7*H%BiibknOAP<|S8s8J~Au#q(;dDv<4lO^_wm;v^pr-;Uz_CQ<#bH75pKJ1i+27?VN!Al7Lpf1?&Vn&s%c*{^WkVhHrTR5M{aFDw84k4cyEf>W)t2WphXRD;y| z?oc=9-&6V6FR9!hN^A(0q|KdW4BEdlU}JV(yaDOS$~6j!*}N4vjz+e>Z!?jNLQSjE z9c+&H6u3oK7c6P)OZk;;Ep|@6E_k#gY_sDs5YKEtR^6OSH6+muY~;SyOSZT(G2K3Y znAKeJgPf5BP`fdm5(h6DP`)lMV@s!Y=}Tt#2e)@2xbVe&s%Crrh*)K~KQgq5rKGL? zEH`OPu^I5HE;&guCQSBiep&T5&Pb>RbL=AB>aD&?rLR&$Ws875RcdTK`f&zxCBrvj zWt3oM+!59qS2RNegZZWcYE~-FZ%Malw1#n+-)CJXE$bDsRQJ43|7Mo#1@e$`(JCAZZ$O_u$Of4s3uE6p3KE9pi_AkQo*Nl7YYr zglxiYkQ)V(xQGk9!3)YR4S2elmXt^mgU{&wzYl1n$;~;#4^A;?5kMS+pJoX<8FgthL*Z?UiUe?@5K zNrf;3uxdSxqvrWisG4=187C9W_%PH0{a3s9AGgH9B2z{6+bv;({qckE-+IOVwax0A zRpaLV-#3!|$3s@FYUzlijPAoK-MDVFE-+5hBu3u)fO%^ZAx2SM9ip(VKu*}OT6MZK zWwWxGgeoK_k9-@z*8844XUnb;H3`nSPruK`%Q4OV@xwQ0-;-5*A%S^uA~Y*&;ya4# zPWL^O>HB*Ao&T`vN7D(Koyo}7f!^7rv>L>ZB>)=GQ8GyNLjd~iy%H!7~Rv~o`vL5>{&#MqV1MgfQFEz%?DQ;>`qN~p0Wd}Da}n-b(6h1e~K zIoaJ%zX<#!= zZJA8g%j>GjBWywyOZ2fC@`J&W1I>pTz@!WvDwwQe=L#q#TP#k5{K?X))QZZ;l#0qz zGqRjS=k}s}Le3bSG}D{7(lBxugd?a(Ls5nxvr)cfHz@}*I*rsY9XHyMD{*!4!?6r? zMr@VjLkTes$*_TtU<5MXgs|!S;+0(HTxHuLYZ4j64#Hz}cQg5w{IQB)j5_8J(hf}1AVg(bIvkrYO@XDm;;`Bp1UB>hlbw<}P zGvUmzQoE=Tdj#2i41_5&0-_aCeQ83xC-aPixF#8smDflDOxI8FEQg!R3++)ewty|pf6Ia*a6Ja-cu%yf%Px@tgXB1cmgiqDrT!;DM~Yqd`q;XpXXXik|A zPf=cX7h_Bo2#9b4yKXq*M)1-DBXpAn^|O`-<2)lh>F7QP9DyR8)L=uq>J{5UtwFrP=JDt%EWzT*L9I2eVv3)bh|?M|lEc_dkivQn%W#q5c`>Oj zw!%y+ycmh?W+I%4NgW-uvAKt1vFYYaHwmXW9^0#eC96b%UzzR-06$9cXL3z_xo-`T z3r!cCh1w8OG~J6UcV*)@f7a?01#|Qv8xizaRW}3%qGE*zne3L#gV(Hz z(HeKiLs*YPaR`U&1UZHz`T%RA_oNS*zClb6!%)6d?9Y!2o|4qI$Tufd#dVoN>uGeg zMK1^~$9rb2?fgB>HagBx`ev50yeFTwdW`0k2u*Wh6XO$QU#w3>pnjYnQtz6_pvnRY z-3b;YU6I4GZAXZY}(r ze@=kM==5Xy?lUie2ANkC{xz?f(fegB4*Le#vk&*SG1~Efj9Gq3s`?n`=}E!tZN`@r z&>Yy$!Z(dQ$c-+57E+Hx)YOpa;aLauegb92i+EuP3Z}ef;NHjhGgo>IIM*gC&IH7_d2x?$HN z^f64+^o2vcQ3Xsgrr15NrK6@Jj;-SB%mh0-p&J{XSJ$00+|O5df3;pQdOf_5dO4xP z^y&So(JT5ipe0w1Mht=6>NjKKHKGheXM!^|Jjwyh$2S zf-rhKhz2`8*zkYHG>Y=X+0`Q9a}c4lZ%Rne1NxHUAo4JN`CuddRrxWY7nx4f(ufEv z$xgUSF4$~CiqRifk{m^eAuN4cW>Kv}2MA5(-MqiH%+Hlza7URYCy%;{ zIyQQtCM>*QUEs}0&I)3L7@KT9EWKH2t1^5ITy@mF)Gj99+%rYYe9IPl$ts$%Vx3-U zM0n*ephU`6^nKy9iYS7fv=HW!F=v&JTV~XZpURy9sn19ISX}tJC1I6Woxz%8 zV7_~9&U)8NV!-@RElnwC#C>J-tOMS=!EcPrkyfOi0%K+$1ftZqGmI(e>E&itPS#7Q zUy@O0myxB z4DE8XiSuZ6OM+XvE2M%Xv3`q9R$WQkY=u>~jZ7@8l`+YSR#!#Q4=#8iIiep~vVzRx zrSjmA zUBG|;yUu@>I98i1E7xp!omAcxWvf2#nz2ZC4T`Eqt@%)HAWqOvzl=zY3+NSKZUwb; zNM8$Su!UPSlb;W{Oy4tsbSG8N{Rrk3(1(qxw5u@6Be}`5 zVoW;Mxj>Xx*pBS@2WbMmpY7N<*cehWE)HCNW_K(jyL9)^?!ds{K9LvPt7@?5#@ZG`9Pn!cU^M?E*b=h;FO)LNkcS$nm(| zzM-W*g1vu;QV_Frrb;=zxnP$o&Vq-;WgPp$V9y?89|*4>UNF0iMCf9L0Qo$}*sUk- zdTn-YPxs#+H;9*%<87*a9w5(_ZhkOunYg$fZi8|@g-s2%7!lPjx%o}DKgaBkF}aD{R*vf(wi6A;VwC#p)|qZ+j}g#_PGhU0y$~qNtMP`0G4%!h3i(JF z8$1Wa#dFX0153bhLE};cNrl<3WcXvtr!l*jfR>t-uCc9+L#^9$-RyM+>w6Q#ClT5j zuDr1U1Rr<$FZnRC-P|sAA(CaJF`?YcvIs04+1~b|avejC#4D;XCu(?9!wG*qH?gLe zHo05wEa>-e;g&ABZF(4$ow) zFRtdTiZ@3{Q%;T+#20Mk@}!pJtL9T>)8%zM-cT+iS+xq)rX#q@6swMZYV|3P8*!+J z9cFCji^0ixE|D`wY|ByZA<7p+Unuxw>$5tX0`G6t6vpRUstv&?Y)_KX?x5Nzb|%JO z8CW-Dk=|ha?{CaMkqphcX)gKiNb)xPj~{@4>lpn9y!gBMHZ*p06#N%yZtm7uK+#t2|~yv8}+vAuzEUL$@FR+R(^^J0Wb{!PLDrV)N7+V-Q)1J4O4dp#_BtRb&9t zfJ>Hwz4{jqRp_x2fX-J}?xw)VLwrcp5WRQ#V9yvuSM??$z?S@_gim#j0=&o}MsxJU zcr%TaDMXQy!-ZrZJHQx?k)xEJ5`NQiHKjE+%;~Sa|4mvV8_3cT^*VNKN{Q5S8jmtD zNxUO;mz$O=O?wT}z*dtoLR+c&RMT-XlgJ#SHJzdh&J07A!I4j+LW#|N**UsNG2;*ru%M!^<+KweE=J!x7;SDh@Pcs`l2l`pMQ3xB0fBm z*E!il2|=BA`T3WPiB(Q`oEy#_2W|9Py;j>WY8&|c&)FbL2JN5T*(@MdLBZ6v@_WB} zq`7T7)MuQW$5U;vJN43xXAJO>y(IfuK|IE3P7&P7rDZe@tNK5cSGzLeMmItxp?>~O zMRFIS%W>IANQcwA#~tT{Qv3txPGlm>|qNdB)u z-C3u8!%%_xY~=f4M@^T&3Yb&!rD(uFsEM|0YE(Qsv^3_BYgy$c~2NRmdHto zGvUAIC5s+vKl&h!e3Y!|Un>O}N6fT#hLm-6ij+C0~ zM!Pw+)>?}deO9PTH97NpH?3(j+u;^^_DeKx9fw8LTK+t+;=AOVcQIaxhWaHY={wrD zZF$*<)0HN#5gKnMFUe9buc|KByr;7Efsnvz=99B)B1madX)KO;wvtIpq55Vwfw_3}Bu9ZusVcR$%{1TaR z-cjWy!>5Un(@yQhti1EPTDvb9YvAn15~CY+eRH?b$Kn-p5!{jaA~R|{GL=O;wmQJ( z?f(N5#QtjuW|AKodXn&gwPQ=*h0>g0W1v11To11w{jha>=|>>KeDAA{SJXxl zS;@<0QQXtp&byi;OgvKf33k%CV07|XE#fB2pg?EF{{FzV;bm_oI9U_sQjnc9`JCdm zCKsYi2-fd&%-`qdzr*9j80S|u;V)oGS(oe-!HMSljhxsq#6ynj%-cdTL39R=`Ua1R ze?$4?43fqeKHTfPd4=6}z-iu46s_Wi(#u?vj=J)~?xFL9&)i(I|CQcScHusSWDR-eZo0V_{bhbf3|^MibM7)`wUFf1QsC_90MP)vqbZj6|r0q z$VgDKXp?s6!Z+0fG4yy3<4jT-OFt7}%u=#kK@EVmgXv0Wl%1~Ce-G!l5PO(1FjdGN z!U<GH)Ysv5$T5tXVe(;`+$$W^CcLvh+9AvtLGtRe_p79Me%k6NvuK$*3fyZB$n{%MJs;l z*#+`Sc{R#8vfKrw8GUQE8>sb*<4ttGzBA>g$*RijI6KWfgvB_?5sxi1?<@PmCQ=Eosy=fdGlkHX&IpfO>)apWDv_^p3b9nd?3-`~*-&+Fea&s~7`?ND4G`dk&yI*84^#`QqkofL+ZFiqnN zdAm$7_m#>W7rLw7Y#lEoSBjSuu=g8l5LeH7yzTf~ZIJEzJzOu{zVF+uXx9?}W&|kP z@e9Dk=z@fWd`kj@%a_&r;u{{o{q{uyLky6i@mV%O;X*{3{cbFAWcVuRdYF8+_8%*f6+`GL$uw=$U!hdkjQh8K zevr#+Yfw^s^hTOeX$gTMd_T=o!9j?az>Uq@C&@9*>Y{>d(jodv24)9D0_c6(YOTU< z%rHZ_HL`|^82WjLlLldkI>Ym=jx|EjtW5|%~g?1vem@Y&eQar)AR$_ ztSy4%*n z+YU`;ZZBeqO05AlL5v*p<|0G{OA@Dgw9*}7Xs)H{disOmr6tOmsbO~62qrN#9+=YP zIA?6Z^q((JB}4|QNMxQ;t>`R@I$G2Ajw3q&wc_>HlQmjGUKcx#>$Axur$2F%>YNH4 zG^RTDv5^4wA&}_Op0xzVFWP-PuSF{t5`pg&yqw-eoeqO4IDyp0EJ`aOLm@#ocS2NM zgY#{h`6+9u7Df4U;4heZwN#P|%>n}^p7Ooim(-|Or4OvX^7gPA5DTCcT?CjcYma8y z1zl9b;;XfE`txfkDUUv-dLInlsky>0>(IWPRG27Lywf}E#f-?J!@kPc`@IO|#PN}; zkLMW~D$A{-2Pzb?ckOb)PmT*QmEsW!apEfC`3zNux91^(X8k!yn3nUFB20@?k zZM>Si=>ST1b+o9N1j<=UP>b5TAjw!JgPqyjUHT5ltfH0^r?H%6{pE|7c!?D4Sp%Xs z5Q7XR+qBX@`p|XM4pn2w)-psQ?JXN|Yst$zDnl=xh)3YO5&W zYh5jX2_|u(@vLaO*;)mR3)i1!iB7bwusAU>wi;v%FCbv~{~C%2X=#9?)BudHH-qw6Q9 zfPYDvW4Kmr;3yOyHVG6joD^a6xGI^kRyB2kQ2v=4m!dKHp3oOA_Rttw5PNDr-Jrhz zdtDz10!@6<(E`N|#jz>c{)EN8fJ8_$DFiDDIapOC9!g~Zin(N*O{rD=D6b9eQH+wT zu9)mS%GN(Q7rMye5mxMIJ7y{pZBV7H#tKCPt0I*RwHXmUHt#AAT!!Mr+)dz_WwYZEP)7@V95=qPNz7!c7^eTV2!IahXX z1Ydy)JB&)oRm;`Q56Up%@%S2&8)2H0W7S}}g45~?+8X~QfrBJoBeShia1ZMZ5r&L= zsxj_jp5qOKC^PeTG=+O5dy-C6v~*wrUAP!22^T7AMz;4kt}@I?Q*#qQV-k>j2{+di zk&iHr(7-SWxW~>(+y4uHIui3a0|MJ~?{nmVvtm?6c0gYCRob3`y=ZM3t26((iKtkQ zkjclM`PXZ&_$S{UOOL_JJH=LY(=(sFGF@3k1=u`)8*Hz4ZnQhCFVybdwYps)gz$== z?7fWi!VVy7rUw|uI%?|UZcCVF9BBFOtWYY$0+h<@gnSKJ4nq5DNM!8d_(vZB|WlItHc z!8gJsM?-Gu0Ln=KV{A=|2y37qhcH5MGx!IlN9|eP!IG}z<%2t|g5u+}k2$mK4Zg&+ zu;`6JN>(3~fZYV|?47=m#c+*UfX*axpMzf(k5ZGVYGp{VF>W5fB1>0+_vg+NxaFn1 z{4vGTmOqiSls4;(`;=em*2RuD0|s^Z%NF7> zJkd+Fx)qMB)gs5CmbC<|6r8qMU`Tk9Q&w@F=cI2BKlF4_CAte9d55G=p#FMjUQZq` z<<`KxPK`=RAB0M|Ai2mc!*0o@zJkc*W;{>!-W_2FjE+c`eek-8&X1H zv)ncr`}&zIJeB6>u%3CdN=AQrzz0nJ zrwE8Do+^RzE_6+kv{taGqi~A@i|sIRh>{)m^f9NFAG}I$FQrux08A4o+dSun(D9B5 z2_0_M(k+gsABX~vTLHs^gcnw#bf2Ic1WxjJAK?@cK&u^1RDjK1T_MUU`2lnE?M|~GFQoAY=imJ%) z1zL14+Oe3;9$|z84oxD~(TeuSXN_ia$o~DO(bM`Nb}1}5v1BCndO$+v{!(AdpScn2 zF3FwZPbY5LA=Q-@WbOVX?dd0;f!kL~OI@*0igAuOK7=k2g6SYC`AMFwkjk_cVAaM3 zpEhjETK;fBcWd6mW-mCe?E1UakaIh3U)ws{NG$tAZ;3XH7rg(j0d7dGWnzAZQWw9a zwqImc?8R80 znx%?83lplgbT=wq=*IkqtUOfbk|6>F*dKWQ`@8awMvu?m7lzLOxd2Mn%4FOclx9lbQJL$FGEE~! zr0#+ILwIGH*^j;BBNKyK=ano} zSVyHEv)&jFMz>?RY=S1ob>3HnA&1GZEisCO8PWM z*FmrGw^`iK+WkA-rk;0rke7dTQlUS>Z^NuGG2-qf_U_-kkNuCMh6mcV5&oZ1uYa$H z{SUeMAH!Tw-_Yzo@tw)4@Bet^U##Md1loBacV&J-q0G`qgo|XtO*FBnmVGTs8gU)c zsZI=IAyc6I!t!1LFDk&gW#l|BLm1}OlYYF(7^WYo*gc+!B?J@{GNrE9U7onxuhZ?v zHeX-QGv_~8_f&Z^>Q$kC`2eE5@8O5@A_(xF-sJ;-@q8cIZ{vJl9i;<~h;ol7s)GI_ z2~b41JEFZp1w1?$b2^}pmve7OkkBkfKm$4v8igmS_a290i_LIgVUIjh$$lgX@9E7> zn?v_9!(bSt(0lM@HlSqwF2yE030K(N3ph+eyc^ZQHhO z+qP}n>Dac}v8|4ixB8rY@7ZU+yU)At!}{|7w(3`NR?ShP#()6nvzd??e2BuuV>Ga* zOm3K8L*FKvbRt-`Ce}c4YfUp>Sd)mBTk!iX8_H}!rnYKb5}l)EFI~q&< z>>64b{p5hIdQs!H#Y{EKSnv^14LI1T-rQia=Hi*>RGLeSLdG3o<^9ugbK?LsC`Rz^goW%A&fZ zcg$1*(&)bXpAgDErCC?c@gry%vr2%%olG4YC zO8J}5)fsh!N_M@y^76qt)-sD!)n7ouB2Dc^FS(#$@b(B%@GJpCa*Jo^V>O@os)!lp ztPzl$8EcaOC(GyWx)#rnx|Po&1Hu4>D0BD6mgC8TGX487aIsBr7K>-p?fQAFz3gNxwC2ht)8~lVOX?>PQrW$oYma z*F&oW;^bB_Ox9|(m8%48y!*BIMPXkZWD5%R35P_{m)~cu6R_Me*H#KTm}jW1@{(o! z5(}GXi+gg$cnHUyTiFj*AeddP>`GW;;VCUXES6h`7xUFAUBb2Ys9h4w{+8W#YV)is zAsJVBjx`yAMB5Y8xbT9#yT;r}u1l)(_UE=^iuK3K$=CLg)*`Yd9f;-=EF4buy3^zL za{hebg1DE=O9T1P+%)jbhFFDEmWeiLRU78XV$HwTSxw48F97( zNw`Z^recQCWA35I%>UW{<=1BSymhvlm5MMDt<^j*gdvBPLdJdM(95ztoU0@Pkb(EeFvpP$y}n^W}NcJnQ1>>KcaXd7*3G^r}?Ho z%83wo2l_jb*TPf}X?-f|>`xcL5nOB$+6MGq@lOi2NN=_gRy6Zd#6SCxusam6dmO|d z$zAU4kV+A218zd|o`FHFh{9p7nrYuyiDejpVi6=cWp?bFZdHfHYc^i4i%X>kR{@mw zvdiKl7X#;TsOGo}MlJVeD|6hDc~U~fS%O5hlAZ4so2qQqaKr)=z*~F$;q?x1!2xb zXH4d{*vA>zY%R|0st+?6O%T?JEvborM5R5wT=tv9i;#uuuSf2^a;~|bHea^CKA)j_ zfjh^4hK`t5VLX!rl5?dCqRPT3(dyxBW3JNOO!m5hvMjjeh)_qPL7Q(lqWIRLUoCO3 zu~2U}aBg0Z9cFevB$;}~{1WWaFj6&6(?+Y2!-A#^9@A2RMK7_`iDcBOf=cayZI`Bb zsxA>EgmRu~$pV&)sKbMksf;ZGOVxYj<+5}e9@-XD$EoFKalYA9gsg!?NJBLgSM@26_o!zQ(zgEg>>PEc5 z>`+|d-~*vDT7gZkWMJ}vAofrk8@(ndp2&7CsxIqy&O?k)q$H@L*P#AAS)b3g`y*F3 z^nEEqC+Q3RKxy!+=J_ZCFfn$cOnCVCOg`#Baie2X+`^;lMegFx9md9E*|nO@h7}q4 zri*jnJ0ooy?JsNc-v()xVJ(*BKV?3C_{nWHvjS09PrwPu%AH8BJSG(f9sdz<4_8Da zkJ`BTZhmi#V9`Z?gsmiC3b5(%(|$4&3fr^ue=MC^$yvV^vuVa$#;-M@kEIUT2rtnd z*|nZnOS;UHglze}u?dGGzA*jysl(H)bEkdICOhkuc)ZnXFEHzZie?|KPadTnE~5|| zBlmL9TYSq9iF1*Y7UaCWps6i0eemw*%3iaZ>3tuOjV!}n-Z(V)6ZEy+ZJFn;JAYh4 z;m-S>y0v_D{5XKV2vVP(3N>Vc2wOMjpu%`GL{uPNlTi*>m`$uQH(A1Kv%ppu)+uln zpvR5&S~RKPnd9M9R`$fG@E{MA5`ot%@%8hGw8Te4%)9qtduZM^?IrlA#4bHYl}>Ob zG;Mf=?j{U8)*i)DZP77<|GLu8Cb%B`BgKw7rU^xK9L0BNvft{|wMbFy27~)0>4z#< z+%F!6&>1^zLff7DcrimJhrqa*VFFu=1GLdtq3LK59#SJ~=sQ#!%L5#$SRoS;!d8eE z2k1MREAsyAj1vCKeORBEIcx8*^M=#+>##>`88aSsf z$`3EG4ZLWc@1Or7RIxbpooEwqkZ%CYi2ont8X;p7eP=5tF=KrrzyjYtjcwqO~LM<_CM(hY<;8T#=!Zjf_ zhb5qYv8mXLiZ2^H)A`5N)$H#7a5_M0i-cjevS=%%g2ar#vFmk;;5!*z!$e(37Ccd0=#+8pX`IHzOx95Kl7nIL zsTm~XCOKN7s2b%lO>M&(rG1*=64-Rh|1dZ(%-kL zJ0^tr(0*TBx+z&*R8V{X81a%ryOo4R8YCUIYOCOGfUbU_xp)mx_^p18o;=8T!-eQ+ zHh^sxxLMw+xm`gxd%+$E4Wp*BpinHHwXOMzrJb5XG!Pu)H-!?#M!JtT1NA49pkI2$ z=&JUF0-rIACP&L{c?b?pN10swo#x@4NO!?5BaY{@)(D!L*pMm$_3}p(YKD&v<{xLT z@LDmP!J?Sj+vpHmXD_gt!U#+h!Za-$XLxVc&y^)Tbl;(2l}7}3QVv=6NMR108iTQX zLtYFcx1d2s+?G@Hp=!c;sI^_-a+<|KavO|cOo2bF6{d7r)6=lYA7T77-)>gF3V%H3 z`3g@Xsq_+K@RAcE@Y_`?TCK)V-gOe{B+hhS>t%Ly3RCIAfVW(pc2apW-myK_ia@{O z*ThvkP5$^rn_WerKQifaoL}gac0Cr8gq!nMBlYqopD{Lry@H1IEmZNZ@YP%=Sx{PK^mRmPy)M5iQ%*rzz~ zUDXrHU8QGMs-k}GEPb``A_DFxdM-hY>S=}s4BhbQ(gN;Ignl^RuW|zHTY{`Ybbn3c zUF)bh&h%lN&Ol}6&@QK0WnUC@xohdc&uSg8obp^lIkuLKl#J}LlDZMwA9 zjA7+*x@bg`8=4obVZ??sVqZsZfWkhP_-{h-q$;#JvBKT@Hlf;%1O~5f@BiXaRRF0c zeG7QorojD=mdO9KjQOAQ*?)c7TDoGXqHGaLX4G@JAQGE%TH_`j5pO6cY2>(I+2NGZ zm&McvmRtVBh$XrEAzm0GGAvFWEC7|%#HC3l2R?Hb;^ZGaws%Xee$%Y`40OY)cHt?S zV65)rWPUpBo#i>*ZJO&iGuiX`l-dL22KHO(XCV8W7nOhPKGK%henEsJX4Maav=u$e z(Wl-%V-ye=C~iA@;Q>-*WacEMBxN^D3W^*FJ-m0^hrEou1U^CO0RuRmVB-4YiU>;x z?B1-9(xLkW%p8Q7nXn^ulw9eb;WOb!zihCFj*+aoX9Km}bWXhanRFL#h*5M^ZVMu6 z$xbLdKtN2U@1fKy4c4G!mj6f)*J@2U6z`-!t!1gAtHArNQg`&3Ux_^eG~r@A+(r9p zvNT;tEQ;Mndi18y*b^t+Ta>fvy;P&l6X*2$swv#+mL>(@l^R^;oX7cF0J5;AoqSt~<*u`anGT@?T7c5`!*A z13dK^IOY-H&^~5@eSOsUa`Uj4WNh_}T#)V6!{#WPJ?Wtu-1C-iGTj`fo{=`cZ&8X^ zH<%>Yc>w0+NvKkwDY|xHH@{;{*^kEpAA%GX{88Aff##2h@cUrsacxg2vMF;Py~FE? zH_Cs|?28XMqtKGCW_ZZgRKYAQlTbub z5eI!nv78%6r7jtE+F=yL#Fp96F~-t`fY_xR(<5y!HiKeju(I1U_40T zE8OWW$7D^)y1gsVbWV37JZuns=)cc4n6scU9g!^7_<84~cC7s;FDCU?8AW&D*4ax? z6crBscs+h3Qn*wPMf6>uS}01|2L+qWb+^AreFnMFNSoQbKg)H}p;`^OqGv6x{8+3b zD!}{u2Kg@VnvoTd+EA-gXMpPI+GRrE8r5sbCaJP&cCXWfNw5A^MTG77GpvrHo!L}I z*U_z1ak?5XCmaq)Aa$dO;0OtN8pym$njzXiSchpNww&Dx=DeL`3Fh#6es!Ski0dsk z&i2ST2kyv^Ys+#m6SJ+ePv#{Dj>W1yFbWXuDL^t7JcidI0z58{ zBR$f-6sI*x=MEj0cdv;m5eL!86f##&#Sd!L=&S>4JFjyI@8t&b>=nDL2#10e-lX-s zV&6B19^U5L45Zfm`fOxo5RU5DADb+?07>6)nRSQDX&fCqR2;wCqe;&DW&y13R~P?U zRdk#3D^bM{&a!Lm23(#7VT|?gl!1+~ppz0!i?MEJWY$XYTupAm7~jaFHtFu&D(ZvgUZ~P%OuoT>Abg$VLh%_mq5}JruK4h^NJT@!Un3~c z&fl23?JFGB9>eQ&B>|jD7Kb?%VOD)3@5_to8n{qH&5C3$3AQB`z%z1f#iH65RP=c| z$3H;vomBYr;FurnjjGZLZ|_82XcgE6u8I!>T}|X*=HwNrfR!Dsk8|yhJ=hI+0s3C# zn|Ws>A@^pW>pr&^g&XZ}QS&p!-+NEO0ghjQoBX$C*c8b=rQBDJO%xNmgi9>}LLcue zWBji`ZD3y^rt;bX)_}}YBVBLn6}iPQ78O@km8V73M8SF|2V3ttTQZwL8^hVK?O_PQ z?x+t}J2JyT6?@emf))9{OE3vd%0xE9YId%i?s7^nnX{QPEST^)%*+I$m*omWEZU=O z%0-ylVWcb69p{bF=hNk*=JgGo{;2x^MH2?YB?Q3DNwagDa;fr6rjvX9SV-HQ85WiA zLvn*{Y>Od%iC_#}v5#jyLR8V)%}pE8OnPO#dxnFYe?jqy)*iu~r-x3i)UVZK>yimO z;g%XlUgL~@LNbQ*9Ri~hU>LCr6cY_!D%oPh9w}=S8H~l3nHXRjVNd=ed;24npU}XL zxZm+$wZSg5ukgWMWq-Z-f#S+|c<6_6Wt(iDlx-~?Mk z)EjYOtDRHIzTrEbZ=y_W^$yWk6y~jR5$5LQSW)VnEI|tW4d|;8A9M)NoRJ=1h<3Ug zmyfc_Ng@S_dj-2srA|L$k8`cVqj}4W7_Ch3XeMq4$(p>8>@y>QXCjwx>P0Z_!cJlz zxrbE(YN@?HZrXtU9lKzx@MuZz{f{+dT*Xnc&wmog`1^$RPF6~t1SlFT0u&8+{_jan z|NA9v^LH%aU~BciBn<%C$-;n5;2$hR6QY8Z#j7mUAK-Ff8qLk+&2a+a;y}Q~!7BER z=^;Z?G+S2iRT{KiH@_EtmwkX`6)v8zpJ~tBin@E5AVClVd3KH(9c>+ZAAMxF9@X~v ze&FvpCnj+k9 z7a=@CL;PHC6y(Op7HmEEg8L!7f3 zgZ#+7_2q39VvS{g zUaM6Kvevr!Uc+%U2{fR;PgQdUkdCn_<5A$o3l56C=&9>o?zKh=_$l~9$l^`7e)lr= z<(FhyJ-1EK$S25Ed5WI~ovA1zV{fI#ixpOs>`YF~G3p8LQgga<$ZBi$=@s550S1rb zIir%U!Mo`((w8c#t+LxiFMP}U zhtDo^E!MU-P&k-yhK9`SSk}6SFV{d$WLqoayS%5%{q6u68x74-my#W~&Yt{r*KY~Z z;JUN6q`On=r@YSsTrC@xPc|6BMYGik?RuOwVyDFQp}dise#9*J51o3$(=2^VvHj(# zr&^Fq8oYb*)W@!Qj8j&l7c}(kPF7zJb)|>GHCn5O%F^2Fq?Kfv)}Fx940WdamSk5) zVju`Mbi1>PEF511l8PoNN8?<67vk=^!#6JDhLxEvr`zbQ=PzE)F-V;YZ@ntP-wGO< zZ;yBb_QT{|0`9*teIl=cvgZ^SE$RYq0ng+?LtEkBKxz1h*e!_W9<7F>#-t()f;ipo z+J#V7R|GbO-REjU01G9`Vi31SH%-v#XpcPop>N`Hce1_;VYYNEu#^M=>f_(t9wZ73FEZBFA5TB@u zyMLqiumlMUcJl)#OQPjB>T}sL9{jz$ERjTIVH0M>)h^61Bzk^m;e$&Q0+y*)jbT`9 zp1x7s!Nf5|34J(5jmgXUui&?|5#Y^XL8?8?%^}yeP`r8ot3rbLd-KWY+Ry111W$=+ z{VR_j+duoNn&LYASf4qd67ETU_q+29{U&LME_1!18G-7~M!U_SHAeRH{r2BywrNg< z?VW)8(HO7-^gkGM3OX1Y0fb)6^{pHM8yha>09@ifZ^q#0E`V4*e2{50=2!g#0wNUL zxze62G1-rcnTZ4;SfuGlIMZF=XSuy0z(_nY-N2;Vo6FYqnZ^bn*hYVmK#GDf6@#TDK4oTjaH1Ltv1rddoES1LHrvPd+RuQ2FCzg-^OTSskjGRV zEhUj#k`<{mFQ%uGwj+qlHE|tc{a0xf4p-@0Lw@uXto%cEl#c!IWNRdFn8jn}ElVb# zW9p{utHOOmffaM$u76pCI#QeuUj#%!9Dvtw{QHU_V{4;eY-gz}fqrri+YS znHH=~JigcVYTiKF)eqt$s?u~K05;qk;*jij6qL2^S5%jnKklEN7{AHRR^*4X`d=n_^R?t|p7BIip z&z1X4Kax;4tE_Q#JQgnnHzYrqepnNZPKiE!OrYK(n2ey~B+UUnBqk(?0b7j|EMHaB zvfDk!&7RQD;V$blYHaevBkW-0-olb!zs8MFN)Q!{8wAFp>@n-2_#=Fhf&RvN1_Hbq zYW#B?8&>gt&iSEAg6J+vJQ@lmXx)EoL9AQ;O2G0f>$#o~{_4TOTA6_JRmnKQxwe@_ zg$;z(aDS6w@WF9aJYlnN!&!@eE8W->GwArMQBc6Kauo=j|+3`8p0Rt#I$zbq^*HbJUgzVNcu^j(BN(ejvv=NSo() zo7XcRdW%HI>?WNyoelzpL=zFYrmokPzhJby$HAj4*A)lS%Q(L-L|2TJBpZ zx$~twcaV(L=_xYqlTyBtzJeQs>JZ}RbOV7+PdZy&PZ#X2kH_0RA3!^Z%Z=#4>_LQW zY*J7Av+dX#_v#wCCv6FROH@u?n6J<}1CLawb?6$bPE@DQQhIKO{KX2Nd$%IajZ{A? zz7tk4T#Li z2*0#7CyxKbSEWShEyA;T&{S3 zzP&j}6i1Md_nNaoF;XyF)*Pty`Z1`OxVDPMH%XD<%hM+0-A|nz0%9{*y zF%ZB2u?t|+cr-S=M+fR!a}{k>7HU^^GZ#&+BV3^GrEh$9A6G9ak4)Du6x(s`wdd{n zsSPm^{xkI@g%&$3SLkKCHNC~IDq49r+uZhdSoJ+oBmwV-f0Wu<;jYBE=sQ4Uv3XcJ zxn)Xa=@7t)poXM%;?z3AP<;+d#acJN&9QH3v1OO{7bX+XZa@Egd}hypmH}U=SgHe1 z0zYD+JeHfeXrIY^AtADQJ=w-{Ac0EXEcuY#*bg@DAI6LzEH(qdza>C#NH!{dSgcymR;y{ zC>{gJ{c-1e_@MS@;C|#@_oWlT=LOiYIH(y0Zjd=J37CoW2t!-*#z8&Pfi?`NWt`tV zz>4Vb`?96Dhv%;p<2Ja)n%sDrc$>6@?uM#14a%}HaA#)CdeG&3+^-axgzfsAop`O2fydZ})OpnXuX+X(C*>H_Xr0b$&B`x`GESbD);v_utpjXJ`P|eoq&A%#`gbt zUX>lJ{$b52V(Vc2&j&VJNmCYyANe!nyXG>anhk*tAD;wm)z6=5MDhyICH#W^gCqGk z>+~C2Q!XhJjl9FAy1iz5g>V3~*K}+D)s!DwM#q!vi~zHQbVn1PkIz5Ay<9pOY5h&% z=sD5@7v{EvLk9q+v)l3drQzq)3;%O6AP6_t&m_35u)jUzof z)euyK$!93m71+iLbSo4sXIdR);=iY|X!m45MXQ4@?DNz@5_?6=ZBNGHQ#2vbFPu(N%y{O!VY@Ud|b4fByy5N%8IX)W2Bj$kdHPDDDNotvQCw{ z28LbExaJkZuD3tn-8Sc{+X45Gu@p~xG;Pt(oVGz5asrhr*B@e!WfTc|9J1By>9;_S zq0;&Mva;^5Xx)R-p|}x@XRd=EV5Azh!;*w1oD%PbEz(Bp*3mH$v!7`+uE#wga`f`b zVkfdl?`;*%Hh1Y%+x3wyOQq);fh=xPc1u-PSJ#-D9I1;yqc^HA8F*CC{_2`SC&|nP z>@|7Ox=06yi><6XTKFIT>P9TqD*T|q3dHw@0oHEIXl_lkgEDaqP-lF7DE`e23NT%z(f*!KYe_DGg7kRHCjm@W)IV6jm@3jSR?V+1pWKWhF0cZyRyX4gmT zhF|PfKX4Cr3^rlTh{7goptGM<@C_BZ6Q(}&{Eeo*5axn%@Ps)m_5=^k6v<7hA#<6; zpZCAc$KUN;9GJ1@T|nm-0t7jv}^_Kzej}Iwc}#F8S;);06`v4 zgUBijQ<@KVFd{8FLoee5paR+?xV|>!a<<~jFh`0c{nroq_R6JEC69}3%xlYYI-Sw= zXyy8v7szU_1q^M1rpB|bj=?^Q6^&J^3Ng#NIC%}fYB7M!{EYxrU-T8n(BKOL4#qm^ zrA^ru@nWlt7$VVs{%$DGl`L^(uWlm#^e3cnD>-8F)jhxnqC(cqPJ;P@qCA?GQQ!AE zF(P-A?rbRh;wjnY7_6ZYKR}Lq23^0LdWskFjRQ9gI8khluxcfM@K)xT;!bo=^O3Re z$CEd~o>~jIqanP`z95-lA;FfbeIo75$hF9N-9`v(Au85Ss^zm%Y00?+2ui6B;~xsG z(xGw{OSz2?{7hm3FG|Ial`-X$A{LbaP-I9cjFG^F?L2`WU9E4mB9kR~nn7=0kKcl( z?%hotOd>M6sK$lV(CT}LftPv(%!Zo!>)@M;A)EY3D-rO`YFX>;k*9+zACr_A|#v$`ri zjspX5mO{XU^MAlu{&y#2Vx{k7{O^-TS<(?p82(d4Ez-@aG2n^1Rx*+Pbz%VUQeh(ayJ4BH>^PU@*29zA^^1O32dwEt^I3 z`LTEA{r3FKmTqC*gtIbZJ=89C4|60^4Hg)EmB(0V(HYGCw5F{Ld=efC2hp?^Verck zOEy?q`oX80a*ixo$-v%sbiq~gMaoDK`}AEkEW5rHYcLVYO)`7dnd*izx*&;Yjp%3a zjAcZ@nV1WQl#yb|HZe}#3VyUXMjOq5)vUS*mvC?cwk)Yjk|ijSo7J8$UjeYha`HP) zL)J}#Tqfgi&nKGSd6QHTKVTJ|aqET69e5(3+D7f_rnX*O{seU!jD?&bx!?gwco+1W z%ek7FY8BAZW1mR{YGsG-G0=$EgEY!_5)O4JAk(1@aiOBexDH>5-F9h*;BfF`cGC_i zhb(-xo@0c3LahTOo^0;1U$086mpLcg8n}C$f zU%4+Ri1lK?ya66szoErLlr@{#g}=V|rl%jf;53*UL*^S~LeLsb3FtM>&e5KPky zamjkf&xW+l*ET!Y9hD)6lU*wW@TJizbK@dS5u$~`&4W7nJzbWyQ&d7@Lu-5JH%7D?lK+!&TfH8-7lVk(FK zy9NPx!VQ;Y{yDA+3w^*@YL^5jQ?|};#ttR7Fp73(M^fVjvehoL=)nU`cOSQVfE70q zYE{%rr=QUc6| z0&3O#aQB{i_UFIIN@%x3h&BQmwrjwT>EAbQVopwW|9Fcl7&|$*i|N}KS^Y1F!Z}77 z7Kk4{SoZ}BFqXjfi;igy4vvc#Bf7ZIsHB@e#jO@;%LoQ$OPVzxzC}&;C|`W~z3<)u zjGh<&Q(TU;>APeb&5PQNIC$aJd)|zOvCb4~V&twC!*xjGFF4L(N9#XTe+Yh!2cp?_ zDxYpdcOi9;*0qu_sdj?{q{xj0NOgK{jG-l(H#A^B&;?Gu6r!hA!(Z=(&3wnzFI_C^ z+3Vsl{pAFZmBEdb_Cn4)Q{+5>{(E({>?$rB1pIj&K!qmx_d)m3;W&)f4urXAJf&$svo0{o<`Tb$v1%zIRl!uZhO=cQQ zJPCTv6q_{N6s|tBY+^y&ATi@|_gDypRryPdtddYRM6*wbD(w_*@`g5a=h#b%R_f&A zySUa6QI%sjPFp!{iLi~^FWG~p%P=TcR*sWMk_TRKlC}G>iq_a!XPIPx`z}idFxEK| zIC0ytJ0h?wW_a*d1yDt;hM+cfP>31C6zPADLwMVFl3_p$hyh&+&A*R>g0YdggR!C0 zzo0VF@e?*k^vEH;tf~3<#VbLaOjt=JWeXLQ8X6GH!jKX05|V$e7A2xgE=Kpssoo^2 z2-It1lKSfzBjn~mG951%zg|8ahk0#%H@~e476u^Z{JPl;Mg!6TT{Th@502-$ zf=ZOA=5bwzz5pJW4_7Ha9RK5<6+SP2z4Dm*XU;a{PP4p-+k}j%^%Z&u$(tNKr9&Y+ zR^0)AlD#LaykU6>L7%xhofBC{^SCuR37iN&@vKTX)(=NNJapdU{w)dqR29w5x>qM0P-m4|6M1t`97yc!IjE%8<8` zwwh!9@a+4x@6c>ovJb=hnb?RT3?|w9IWV-WoCx0w?iOlY*!S$Qj6FoS|P8BjjYJC-<^Q-JTX-P7gHrBEAbdqj@++aC!xn&LkoYjT{nG1i| zD+LEo$iS*ER}7H>t8^9|k9AYwR*+B*X`D;9v#}2V34xN2Y>Y0^lC9V}~+UHJ&c-)bYws-rbCk4FIx;vH~K+|&x4 zcrGPkvLE^D2FQzyo_bywqY>PTlyvOn^X2`!U7%rT_yAC!{Yd%)!6Lr?Cxk0+G)lp zTzP|+sqfd7!hn47FP)GlzP42!tjfXWS8CQ>qr)fVvSXh=?M(4@Jjmuqfp+aW;(K_e zSj|7-MbuEDAvn>ot}#(cQ0+uKMF=ncdDs7MV^17k6}JEg3>hFml>esy+1l993fr0} z8rwM9I;iS90ahda5g+528H?}y(8E2?ww0B|azWjGdbBtmSLI4j!2zM%$eq!Ki^-Qp z>CyA1=Y3!|`4Xno;MTzSFWp23uJ5m0-`Ylu1DN=8;~J_+sxFdJgEDW=m#?bFhB}`e zO4C7GuHjl81#Dk;+ZMIFlJ)pbR7=+s@s+*PKSxJ`e?(8DUj*zQck1=qQB+2G6Yow{ zY}mC``ofDKE)S=$XqXggV3PjM?3oCvL{iZISf51Q5R+)*QJkJu!}*@?&yhhbfK*+! zA-~j&lzouvc)Jn47VQn(W&2;($=}x1@qrgWus2u3^i%gCGJ=xIXFokaeXsIo@}MtsEOsS5uZo z(-*p7z0O&)fZu6i0Xk;TtU6-I zcL+T^E8uhbiv3ZFC=9c{IasING>e#Xbk&b=w%`2AegK3jF)dbn&DqhLL0X%uRQM9p<*+P|dxb?u;WQB){tXkWpo9oH~3mA*om@<~%)ownPs~ zltMB-vE>}vl8%~OxSps~xR{E`%#^tauL(!9yI7y$*Y``dOnTAvyR(~nca9C-Qhb&( zn%S|8s>`r3340y4#$_*m%J8bpMo7mL(@OU?^h8M)bwOW4(a`FKY^bi_C^7A4LKw|V zdQzw9Sd~n)&Gn{7q`<)>&yGfKeJ7W&TWT;+DI$extR6=3H2=aGA(+T4y3r@j{5eCyV8%1<` zY&A_@QZQJ+BOnuFrWK|q>OB{03X|n1%0|iA4x76wMr*kua*rmM%kBj~s~XBk zqSPCXv}KoT27^1?gv4Y}j&qC#!pdgj)0HX63t?o#Gce>ZsoRW=QQ=Am#|h&FS&)>C zdGqxq+fZmpW7a8Tw?Aok7QeJ!pBUj-xID%Uq)zaAYP5#n-|hG zqb7)%@R-6czO*<)&igATq%S0IiA)O2JnQkoOK0uc+~-&^b zTd<)-37uFajm>~#>tFFTrinCC5j>MH+X7n_TR6>nq$5$SOgk_xtzz?G_D^~tJTiX164jT~ z1#*^ugXuB?@@<0ZIF*dn$uo}RT^Gz!srjRCJ8J0H)nH-%!F;!LRXh3IW>SZ>XHh}K zrbr4mU$`#G734$m2&S?n3MT1|F>LEBR%#iCh_4Wl%aL0`d`>p_ouV`+9_ z#kbGgi97!sz=)FU^fTegZ)*KPyQ&gxJm?GzSAIX@33VCt{dE-uVO@p-vH?j~zEF4J zh$GxfF!EgKy_FeARuo8f9|n2_WQyviN?`_hNdHZP-O&!U^SkPaec=iuUYeYq0i3~loEFVE1oiKKDvE1JaKz!C-X zK771=>!=_QSM+~XBrHtMt0=Y^&KjS*w#apr6u6alxF7uG2Y=S6f}%UtTYudQfi=n__{xCWx&M?F9oCh$TAr=ICTac0iWq zkmnFqM0I0y`%p8x)fqQlBgwRsvFNLcA!7-Y-b2L|w<+jWpvl_=m;O65zr3Wq#%i*lUt40N+&s&F5XZYBt*6A=ej6o97B1mi)Vimo4s zGXqzKJ=bBm-=~npYx6gaB#mK7#wqS7Zx*{3{+YahisHWa47+s8C!uN|ou{D^;`F?; zUeUDSKVO`9_J_+ndQH+If>UjXhDMQOWq;s1J73#EJJlvNg2uV2eQ~YrT*b9Pu&kIjdfdpsfsUBj z!#D4K@R|^M(E{#G=N;Pr>Ky)dRjbuO(X?8j`n?XZ^7mL zRgIBm0(rG*=e4cx(?`wcL#-??m>n~ZL>^p5fREHT8JQW1DLt$=5x z8_CHN1)aP?9c&7;W}ih83U=rMC|XVcqzw3ps5M)-`t5Ul)LYQz2ZIm6J`e^Rg<9+w z8!qDUz}~GGc5K9bDWnHI@qRrUE*cFt!bqaPgQ-0i@xJ*kWY^oTC{v;;9y>d?o8|Xa z?twr-!_ZhMu{IK0&IOF>Dio84$tZw8A;?Ap1FNdkOj^CQhK_fGKAq zFd|2Zvn_^9zw>GLm#CuAD9zaerLEd)zJ$4g>u{5JV%nQ^dd-jKos*1GunJ2pAsN$3C z2bfRp@{HGVP(A8h#I;e4PtITE$V{`%2wJN1ku{>T#*!saWQk1qhLl&!_cXLd@~ffb z`%{D$*cTQ{)qPFd73QmlhK;{s&0(KJC7R?bil$1;NSMn@t0K%_D{_l#eQurq6mA3^ zdU$CHT#qXfC2^JRxo0KZLZS2wLQ&`pN@~GG2IDwkTdOFk()Cd7H$|x4P-4_pxWV+K z+_p!s-jqaa?@(jdR=RuNiPeoKaccD7%D8eSy&iK`=n`dEZ!{RhYs~MY+2|3a!YQyw2 z0K0+x#HZ{tv6oNi*L%;j+Zjg zks*7XRIMz0s)ytFKs&;IW!Pf5Mf8k{#!cTI zfaT2)*0B}{Q~im%*U0&fwZ#*ga{oiaOC^}p4vK&+U@K_R55m*SFky8nKfrR7-0T*l zNoJDJp{9h8&UFLv#ZCH?b2K6Hjv9EeJ)*7F#Vh;|h!81<;e8w}vEkB6o7?3V;_aoQ z*g+zK0QcNVeL3kXra`r50hAm`LVV5%*Ls74uR=~>MwB++8?O5ZNY(g!?Gn=1^YQ)3 zC`m%N(fjvhEg@4V8%YwHJjrp2u|Z)c$P=ml)U@G{W5xlxu}UYjKm{zAfaiHEQt-#J*Y0bJS6u%C7(dpgMw`k zu?<|YyDnTY1UY$wi3eVgjcyqkx%pS7=7;P$a5D?zQfXcx~}1-CTR;K!|xCEWJ@>wI>twcuq&d>hTMw`EFv0&4i`>y=km zB%JSx2z_8wBO`6lxq47E4$nM?t6oQ?*4BDuDM4HMh!ruy7~?|Lt1Yjad>h|iSFtvD zL!7#8I6}!>La+PfH%}S2QRc6X+-1SP;Gh4A*88{eTAcl~cmPz~1OT4F@b4<`f01|r zogsjg^$%qDKdY{@+=PT6J#v=fc^8kyC0N)w>fleB4k}2Ap*_polF*GLO9yG$L0iXN zuxEJ#f_cEyHFGR$Z1UyZ@bg#1nc^w`jl`+mBX0`r}nLt(ngUECoF^~nr%oXbP6%71U4dv$64U! zt>i&5e9LQKX;H@$J$iQ&Gg8ba=0}9ABU}I2F!a|2ya#Wx#U+I^r*@HO|7}LQLqGnY zh+9j91l#ue(74y0cXsSvVO0y{_xxb4YL~NMm;Mb1lyGV}5*x4%h|(7Iz>j~yJwS0c zaDf02>NNoS_&-wq{`S|-PUcp$;x+(_2_vJw%%y*_&`wUW226P2N4_>vT9WqrAPMqz zfDLNpVg!K9>wj26NV}PlAr>M#G*M!k*Sd8fBJKp9pxs2NWMwaUO61lD9;aisuV&4@E0?5l)$~AG&^ul`m3QJ zFa$Ufj3Olu!iMY|U0vClYPtui%gWFS1^VUygBxw1q9WymgAiQ9lbu_kSkN^>QWiq)2&}(}QZOstaw+0{;*FWwt83i4EIC?k8%86y5 z1A|M$5*OtnAw9Kr+#`l}(UeVEALJ^bonC#7W5(wVa)BNWi>4|oL1zHfQp<=?rn@y?(lOa}wojJ2Y;h zOo(MuV3IM#RgLa`1g^k)=$aYks0{|;+MgPBa8J}izT85Jz(Sog26?#sTi^YJQ=B3t z7@<~0L_z)^%HA=&5^mWR?j#-Cwr$(CZQD+|W81cEcWm3XSJE;1%X{#hckgHKbMJkg zto&F%XVt1%bJVCYf=-_paAQXg;!!Ud!LA(SfBbC%{#Qu~Kj00F_$o)8Ur*M5`xtO` zwxL&Xa&>l5HnlNRwludhHBokP`Wl6s|JP$6IYIvGM};w*n`+lttoD-%;uHv;$YB3l zI5^Fo(5(O#PZObt+`?HMw@JFKn>pkM+jg4+z@$GP{|CIOhxY& zGP0Qw(V1W}Qk#~25`+eN)iTjj9sROZt@x7oQxeVdbuKQ;%-(ao4bu5DgIN-pj3$e7 zyuF2S#|xDl>3FMT(qY)3`EX=${FPQWP6l!@fR-~0vLrSH7$!E0`Y)t$s-$70ZN4zU zvhm5_Y{6d$59sj<8=VRrH!?QL_>ozy2Q-SWjbo_4dc<^rZNrH zv0w@V$d7|B$~ohyPtY{-UHD!*yWXj{QtJK;%TUAf7Iu@Ph308gudXu2A?#Q<-a4Ms zJCOm5-hX3`{S{pmn)ddUujpoc6-?HDcj5dM-LG(38oT@jN!S_MnyQ&PS(;h?HQ1dB1~n^{GBXXv?&wHK^&Ltmp}psQh39S=mM*9GSlUYBh#J{SwQ)rvauOYSo5(Z z*4R&|Lda9QkWY-aV+$rVDm!6S6l`~)@Zwzq_Z0s)jQ0MS za|?OIXZJc-Bq3rzUxlUq6J>3QmIU%=lQiVx&!^SM6=KUNIn4kcTv|8g5LUP2c$8E7 zJ?JGeD(NDn+TAg;2MRXyH?=b0nnYrCNYm6ErIlix8=ITUj$FkKf?EoZ8A!LdL>IK) zfI+p&wiM)*@P_ol3ys3@KCUIYDZAN2GCNc$cj#idg_q~dY4l&GE)m31ZP_E-s}v5I zz4HGE3MBlF;GX=-5X`S^`Iob(zpmB)sRRG;>$jDgkOC1zsG7E(Iw0^9S&Xa(hbI%4 zloNsj5O`scUzznYbx!_{cwvS2`+-k7O=f^%W6=RsQF(rU@d46{Du(%r2^Q27`IH|+ zMcsdRd1hy<2Pd;|IE%>tcdUwnQ^t4~4n10O~4B(_^<;azv!G{c7%S@KiJu?n5 z+@ENGQ#-Cf=pUmaE?@BP>9&A@IAA)op)y|Sw_b{{J>wnGxW#APdq!yU*bYux0-}#?Y`D@71#^FZh`BfS1zM74HIl%viQ~A$9 zi_>5Hps(Wi-;=s(W$XW-lr5l1P2AS;%~ojO3s_2{3l;`sq*a91?}xGtj-ZDc+JIl! zHFA^Qs=uRVNWl2rf`83c+(VQyB#OxVR?|BhPjZ}Z(mVP6eg6RMlBFz2?0G>Ew;86U zD;65zAq|m8NzyPlAiELW3q2R)3x(*oO4~%@&ZrMZ7Nk6#Kkj#X;Tt6z zPMR*tP()c>G96Y5uHK4hkCxnJ3^(y&KKD%2JP}j6KKDE}dyd}n_$N%~72RLJgiFt6OKsVpCN`Mx$k$RQyD#c15&$+vb%?D~Z8C`%?WtyVDr^Q3Sl<8IpG=TwI)N zk0WNwRCT!1q1PobCRa99L#}F}Ba905+nct0ehEZI`WZIXYQ=9rm5(G-=naM$#E~H? zEXH!#eWRw89|Ln<-mS1ZIrnrf3w9*%mNCCQV7R?e0$vLFU(ET;Ya{ReB2 zwIF%Yd3lSOS?oiFO*jYHu47bybChA$0ZvJR#vx0gG|ocWep>*J@XOBiPs6)1Kq|Jj z6jsRDFL~rByNjNq?7)soaP(_bGoR3P%xXv&_W7)!7PGjFfUcs5~ zWM>zY5qii(cJfd=y--8EgHXl1gTH3uWt%P8@+GXpDbT|Tq83~dxFrTKvp{uBOw%H( z6rzkeh}WwpvGQqi(0ho~Xs(D6N8&?hku``yj`-}h555d6pkp$LBO`-)4zUc|WM*9C zk95^ZrKub%nNUW^qwuZZo~&cI4{_ITke;M+?OIARug=V3=ZZZqGCU&PBBiv8eMvC+ zo?JqX^ya#x=I&Jp>%RTXsqL?;AmXg3y!y-DWQOY7H~RneDo~dB7jWN{hPRK- zkn3km(N?FoE9sio+QrH1&+1fKZ3Cc&dg6)NW>f8ypeS3MMlIb5>sq&=>+u!|;G*Ju zNZ2@q^|+#@xF3lETHM18;mT1z(~@|wB!qS>m6W9BS(;=sTOeUM&9Aq<6cBX@6W@RO z@lQ=>b2?AE<2fHscHK_U^MM+W7PI(>gYTK*+bsgrpRFxF#YT4aZ;GCt=HCNjZpH`Q z0kQKw@)~b-YJ#<7C??F zsZ5hgI`6@9XH{f*xG>jW5ufWXnOd?a9`c@YrV*}xx=Pk z=|bEUYw6;2XabA%^t2HSo7HOGB3*0(a;Q6-H)wo%V6SlRSc8Labu&>;>>#ib(iAt` zg9`U+AF#h3F;UXni_4co;MSqEc1V0GXzeOHo7)|K_pvu6o41>Lz15y)wzZzSiL$9a zb9OUb0*);-bRb^X>^|SctBo@uY$Q{;T;m?LQcb6mG+t_CK-erdJqj->Lo%h^!}n== zix;~S-sN(0dubb6Yo~oK8Zeo&eXSr~yP={n9#d*{QZI;{V)bpcB*fZjGHZL8IJfyCwrl*cmMOuF<6?BdDVsD@YI@)2YB&FefK!ahVAlq8P?=6iWsWv!U(~ek*70 zNuMzoqIsP{r1|N5|8kP9cTz7wc5Nnmtm}IK2;W88J!JY;_ zKwR%)hci>2$)XoAc@UJ5_05gsw#Z$U1y8n?l;^kH8Vo3sm@mHl_3wNN>`p)fQ$SB2 zj$8&J8?Lx*o;OKOjMH>@NkNzW`T;LiyX_CBMSz;IHT3!m$d7Zuu@;$vLsjIC`t4$RuCc8Z0tlPL0hqT zOqSLqS%Pu6p#AiWVqXIS%Mzu;nQq=@w8Pj3CphV-B4_6IVY3vfm_(iYeOaq-e9#ev z;g{hEvMix)G2DnsE$a(gGKnmqNR|s~Bb+x|$)>HtMJkSE(lkEBM*7+yd_KyMr8g6c zK{DJ~5_b^#KBrVUVI@_Y!eu$9(XZW?Qq%!l1BGVuIHPUX8A3G^TQB+Dc5L*BG)gql zMaa1IA`zksdOtHGOxx6pCtds$khRFx#z0#Im-4VHlM_{cfzpAn%S#>s{3bkTezP4k zE@$IvOM?oeIu(mQ6lbMjSMi@ombmb zT!UWL<`ihm;6jXfZNQmB3+$9Runw+JP~OA`>Q8BLQ2T%?TVNV61KgUa5`>LHnY%{+rXd3FNLvenQtlh#5Fj8q`jRSOh%(hop&cM)Cdt_ zw0|PwK*fTsx%c@oW8jAmeHS6GY%XqXr7k0ymvLUpmLFc}>6%@%=oI9(C>7Lgn#i*| zXDmytVK0-<*m4OJ^fpnR@sIp1W#Qzi5$dWSLy{Z+>tH!n#294%Maf&1AsYg#Db_B@ zjQ{3GUeKdJ+zZBUyYLne7v$0`wsd&xMR~+Puv4-?87>QG2T6d@gEQ-zLG zmtZIW;zrZDPlTARiQHsXssg0%b1rXjTktKzWERnA+>(t{UW7#0>1Q`VVtj!3RT7J0 zZ&kl}h=-R1@o-w6VYD3^h2#Jj-oODE-9F}Y7%01(H%$u1u&kdH3jz>`HEo-NFOwsU zDH%7C@PJ_|&ZZ8BcAYsfvKj^l#a)Rhv4Qy?LpKjW2({{D&bCGMZQ{|0avGdpB6^uI zEeI)5wdU?5USdR{>&#DoAUTbN_A=E}gr|&QM;_-mj~=?BTTJOI8h$wRtua z9$RX}I`$+nW5WuA&|7#GcRQ5FvT}W<@tKcK&RIofjquppa=MR0WjfvUZkfcC%5vJ% z2+AGS07A{ar5df&>XVDd3iI?&3m*b+&uP5EbS{Ok3Qaq-Xk~!BtXEhq5>#vBv_$k9 zO*wQp!cB3f|x1!%YE<}lBEX%zWK`+zTNMZSs z>P$V0cGDK1JTrKJ;mPMbglA>q4@zwrTiwy%T7h3xnctn&$)C&KTBtpSP%gZPmVLwR zZE@Xnd#RXrHC;ikaT~_F2dQpR`i^C^bf3C4^iRc4ehg{*6WgIxCvFx;6xD?smuW}Ekr1=bpHxKmvX9A-P z{3&@d>GyjwnUhBtsa^yJIMin%?I)h|ThgjtWs4zDozT+ybn8w#o&h!glg%;-SXk)$t2)hotJh^#cs=Zx!x1h2wJW_pcx0DPQ^~JZe>5 zH$!h2k-zPRY;PHIHZ+Fc)*@Uli+B3%na5Et$GC5~Zy6hOpTAw3{Mn3gdZ*(Xo#z{@ zbUk)N_k+hbm1W`#9Z^cIvohv^w-lWz}rAd2l8 zw0locS}>pr6z2|>?|>c)0mUC2GiyYwfSc-$L4D*QVa`Le9FrL+np5_v}W@iI0K+Kia^>)zez$- zP)9LyTbh|{F_?$@ERYZmfsK#@S0@Qt47h?7q_v#|CLR9cudZ92MBb}qRP8f?6F=9e zuTdk;e&lM_A~Z22efn711BKiswsxRPz`JExa;Ts4 z0akBvL~V4M@oZvec999lNDCg%uLV?-IgTD|K;R;d^Au-qV_ zjl(lj`y$~EU3QJL5i4+oM3cAaQ9)y%0~eEXx^%R)U7R58x_13!um8zjm-;^K=?i^a zzX#{E3w!q&?erUeSSP}e6E;71UqcwOs+2@1NeCTx$c`%n#0C2Gj04O9#eMeemaqf$ zY2MZ&$!SVaoI*r`LG^W9M900GX) z_SVzwO*-mH9$-#Utdzq&_8^3bNM5e|9Dowc!i#LP$sJ9uvMY09WFr&&=#q7$SAQ-^ zkz&BP0(+7+9f;5qK;GVV*T75XH0Gv)tM2Z!*OGg*JpsCq>M=P3F%oC=m!lMu z<3as?iI86gFdpicSG*g!+IYOQyyMq2LHUcOlh#u(SlhhHxu0qlHPdZ=Rp?R9WKe(3 z8%obydIs}`ggskm;FT@~BjJ&wYiN3=x8buC^U@OGh=s)|m@_W?;L0}3zACVDh~UdO zLa_u_VU-L!#;X1fs=lq}XVY>Y*C6VL^y(&ubjJ}6V5D19MIy$JmGaycx6Lp?*N;9C zoU8))AZ_of&3M{uAXAj@Wr~<*4qu+=?;FMYXXFe%5;eWZ_3pU9agziy#29H`PH>_Ocgm-UBNrA4OVb3J8;d~`&bb};dfkOhFu>; zT|PZ8_nS6-v3O7BCb}==i5>8KH#dB-*t3L={j7A&uFVzk4^9)D-b6v)Nqbf`hSC4H1FX{#XKQY0q zFfb~x5h04CaOEoE(u5B6lw%H)q$?knxNg7kpb)tWQ(DDiRIUCxZ9JgQ)sKY#*7nmF z=nWX|I}XvHvYQu&_^HnCVJ9leRIB+!(i)5NyXi>eqO+%iQVu5ukeRv4*c9X>abU8f|g=TdTB zrDGAMmnU`fh9MxL5vFeWryvLesuhOvBl;<=8oGtttmd^y3EhOZfBc<@{@3w(|I`_) z_!4^z!TUb|OM*tuU$C*U%RfyQG@*U8mpuRYdu>_2ydrYC6_EUp6)Jc}BqxN-;|SU9 zf{8&YFfc|UV84r|C6X3P5EjD64z5r660uLB1=7lkN=kM^;UcxP_)!CGyYc{vi|so* zS5tVUwcL%5`}>dXSOYF~?;|ni$)9z8H?`5ufYrbg7@xVpT=2cV z)PQH`tmVBIf}W__)AP!uXKA0QUVYHHz*_9*km;SpJ#8+Zjb3QM#RE=Uy~RCluurbA zHIL~*X|6xf;lGw%jtFRyX9I53_Pn3J*gJXXdga6L0C%n~t6gz2zp6!l`+ahe)T3BAj6F5@^pQXzixl2WKT zOnL1vD$_)q0)p>+o{kxKv0{cxn(?@fa(~GrvuO_{v<&A+lCv!5Op=+hmJH`kQg8_k z(-E*0WhAVok%WkX|H~^Z`)`|0u9h#qH(2U7wn0$X&R4r8_|JiUaQ(nCXW%}8rY;5$2=S;o$*fM*<*pccCl$h6;Gy)&B^Uru^ zr*@N&_Y>7Ts+{=AT!#ivtxcsn(`m^l^TZe7?2etafuCw$Kt>@Hu1@zmQeS~o(#ov` zm#f1;Ul4OjX<8PcG-&qCBq!-qiXJrrm`R^Z0+T*e`=O1@ju)#*M-GZbU<#z>ChEEe*!r84I|+$mUOE@Mq|{W}8mc7X3S zQJG?FDjyHz@C?6MCm|G^DOf^zeo!_pQLc3|{MroNIYW+o377Z1-14+JN@8R9ya^G1 z++^(F$dlb@)Vd_=LT-yV?zF739V7E2x3KS6R{wCPyU)(|x)Ln67Y@-*3wG5$jJ3Fp zvTXS%idM^V7YiGrY$DX8i;S}-y9V3pvN_GL0XHGWJRvWpgEODU*&=f+>Or|V6b!dJ z-ug`vvu!X!)W}Ho+%@?V>s};F)wtktl*=m;Nsw~32*W>O*s-O#0=bS+1(ZNDGBS-d$dmC3aF|DN~sfQ?SqRgd3BSR7NH%?Ehyre2c zx+;ru1;0)SC1t>MYJ}6&&lwq1$qzn!2QPF2V zVV)XtFXc?PmP?FVDPD?WlU^?#NHCqZ9Ceab8U*(=XMuhL=<(s2 z)Ayk9g~V!mpomLSqu1}NjO$C&rAX6+Llv@|(0mQAGKtzf^~p9iO(j#S3J*FmLz(30 zx=D5>!8vBImN${D$$$#Ccp#ajJC2m3=@O-3HSNca=8ea|)-1-z)@(-6B1PS+N!3S_ zg8=DL2){%feJ2x-ZMmPjz0*mxNyU7cv>IUHHQMw;6LCyBz3!>j@`X1Ovb`K&Mrhl$ zo>}P9M+3~PGSZ9|6`UlZ9WG3p+9TT+wl*8&h90$7BZCM*Qgny|(Jo%;gxwkkvzxsX zYTcqATvK#v-L?o)l^$|1)~*y~ZB!j=zqViI(OuU5&|ZK_?>OEVkMc=}EL{+zIiHCM zKWZl=C<2=svrRBWqo&Kdg8F8rL=hU(pe!`5C1i^9F<|EF0sF6xY0(I?P|_cS;M5(O zMGpw_x3xr!d?7Y!NfXoO6%;|n|ubkhB4=OO*r%2yf0!ow5*N@Vok*y z|2=Oa4L3%+@U3BLC^he(3U01yO$q8HNLgck{A06)(ym$~=vCTMF+bG!XpY^eAW|Da z^Ht3^tA|{9UA%#IZsw92o64q9&xtvmif`jG%ey(|WX;2-enGe$(VM^ZBuAFdQ|aAp zWK0eS?P9xsbl!bdt>(j@UHhD`me=LDP(Ld3?G$@#b9k=)op6QB7WlHBk$egoH@N_y zrYq0KtVnMrZiduRYskjQ^)r{KteLuCCn~yr%C{Rpd!*+#Uf#3xBBOt7_BuTs@8M`d zCM)V`*087m5KNmroup%6aZWvtT5V5rR?uE(;+ADEJLDDBnbGzf%*B?r1nDSBAg5*{ zCF9Lqh)HrZVCSidq*4RujMdA=Z*NDGu!&LKfa?ay)w$SVwJNtW3vvGpZe=i$8eBGS zd?akBdn8+MdsNu0Z(QFIBkDA3s3HiGWHxW8HFnhCE2gxeC3oP&yac>Uko&99-eieM zXQTaM9v|PX&7xq1d0UoQcHBXBY0(x&Km2C<7aI@N++aV-&bL%E!wpt3zH(FJL` z#?hy5_BH|GnPWS#!bQL1Khah`-IH;i5tZIJXYAc&!(CBe0dolJI#=F2$G)4WOrcU5 zq=m=rbZ@#9kA91LAQt`EJRiJ4j;WO{>@(2`PQ?VpE@e4sPyQdt!bdG z2kjLp;b$(+p1vnHl$}=Vyo~oW=%{Y1s|X6HbOj(DzPWpZCMLfJ($Ot8!C* zm#5FLd`Y!&SF=eZaL*u|2?H)@ntb7>0|5@r^urb?4Nh8{_!4lw=(Qw+iy%;B;nLo@ z^3PBQzqa*Grlz7FqlPTOfg|<58LTj4mWdgB#P{xN>anT^+GNL|Ebe&&s9nyEZ2u?# zUCm`U)VZh$h&Qp>1X)X6bnJVr8BRj21Y7GOlI9#MF5a_@TDUvt`+8!;i|rWFz+jKe zl}EVnaRjd`odc(!Q-X-UoJ8xhWr!mvqRVl33RO`eZ3IB!6!y8TxVEMxx zOuvWe6CLk?IP*mqCGrcPKXCOCJ>I`?g6knf^iihkZ#5zAp;g}lmE02@+vUEq>mqUA zHTC?qlPV5~@PXn_n%PxV>%f-;;!n9rNGy04EojGPt->^3WEYyUCyxO+?In7LYi82W z*Gj8%ufsajz}=DW_(ob|b{CT$;*`n|Jfa|db`KD&IWK#wO`kU|^2=V|5%pVIeA0X8 z;~VU1)5ijUKQk0F;*&K*Rw}tU7IPjss<(Em6<2n8u8eTq12aG1nQi0F%j<7R%ig3= z--dQs?-n*-`ipQSYJ8WDEG4;sFh#ZBW-rdLoW5IeF3-3s#i@R`t)_E%TbBdH6Oql- z7d-jH@!m6%J6OTCGk&1q>kr8so2w|~Cxz-aZ1v=V{NS$Fwq+7+X=euK%3NaCy zngr~!1TC8cFWUsoAW0CAW$yzMb_+}LP6`Ctt{~R&08|e?JR+fAkD4I6*Lh5kHff*5 zr4inyu=B-BiC+LakH|P0Pk+^INBlf{i-arwZ~rTvaUM#p6H+IrgnhWqeh^aj33fHdp}_Po*`up{*VNgF8J<%$;4OYUfoRd-;UABCTFzf{WU7Lv91 zYG$>tS^GntzSsPAD6IWatp$X}Gy0p&r+6#HLRGx}63_SkET$lmTZ-<{y^Nw`wU%z# zuP`0{_YtYOzBGye-4DXZbUMUEN$g_@?&C2gD+fZfaVH%DYKQu&elE@Etp+gfGy`KN zMz~SZHrawU!FWRW4Jqu5!}yNK z9`Ks>9aFFa_3t&DSd!h1F`;Kl!@FcxCx3r3*z*YRWjMTE@~T1Q)Z{oIH^x}1Br%4L zJ!^t3O55QZHSiA+SYl{%qMQ20G(;fri9?FMzK#F(FA0z87Xhp5_1H9Lou%?A$fYn> z?WG0(P+qSSkgTNo2(6yK!YOa2wg9+!uFw)-t>cL)Q|ni0d^CU~w&@4gcn~d;GStw1T7?xIL>15Y)F2_nF0y7y$># z&jtt*ySVzy!aR(CUx4HT#fY6^#h6*d8LOUZ#q`b6#VqA-;Z)DM;#dobCRez2w}O7yfVmrEt9}Gj-cqJq7mUvU|N19ck7#1Ekee?=rSpqg$n(GPNras2 z-JMOHgug^24gW2}aKrA4VaSVpat;P%+1|XVMUwPAVy~E>SV*dXfCw`p0g1#z0K1Vy zd#E$M8}g`j7nB6=`5PY;jwjnqEtO_&nCUZmNS+N*27F@9)}ke zhX6|W;eKpv<_F;FB=iyFTGTW6`mlAbET?;w5ix)Qj0nk&G$QNiYx@vBCYGvy?o2!T zp4H)lEXh(C;P{tN_$6a=3sJ18Wt~9$a56F#X3GAWgZ}ptPR2n;Zt$OJSmmlZy}CW~`r>s!UnS-0&CpZDD6#g}X6z~dQSQ2yge|1kp_BK#zx`;?r^ zE~O!o(61Z(>sZ5+#haH7wqCH0V$RhWpvHj&+W9f!nbDJT{XRbA(L52h-0wF7q}qB2 zbs`WP^3q(=G}`Y81)6uoui2g2)95BKSZ#+tZ3}p1G8WMXo5DpWI8s^Y6epzm=?sJ8 z0#*Ap$93~2MRlZ-?TV$cBks@Ov!i!hxXw^9b>uEltBSi^cmJk>@fXL=O^JptKX2N6a(d|eOn-Eelg9u>2ut1aB z90-$hm?+O`60Xl-K9g&{qx<#xBzaUWL|_Eh=U=~Nak-xP&&0}qbF0@AMVvsbN~+fy z#u7eDogcqu_IMBr&G#_)lnBApxAaUB)5XjUx06WDum?tT6RFbql00dtMGE2F8RVt2 z6PZozvcH0bk6PxD6xxOozE7RZm#jjIzZ})jrNm@EJN_AIkD1~6M+@dVeDF0u8Jl{` zTJK$*QhAfys?@$n+8&B!Ec5OA7(;x~mNn+8nuTSF(Nx_4gTB2Fn?5hACC=#1IIqt~ zrTY~J$-uc}MRkOUHoQuKg*t6@G^!dVxrMWd{(kk;hGLRd>-I6pu!0eW{mUodg;XAdUkU8`v%Z+>Ga0$vCKkR9n^3G`= z*w4%jDDq_!=@$4Ok=QK#YR!hPS^@eM?te=*|F50sugw=}Q&R^)8%sCSe z319@Blxk(+@cbY~pIDQG2oNcduVeGCn7_2M+PJlX!|N3euMPwW|HJT-m~MXz=-L8` znu|aXwLt2C?1XL1HnSYNCrYJb&8WMym(78&Eaqz9;4&W?pU9b6K4q5o)5*j(Pa4~< zPoImSw`0;kUazNg8j55w7MkJCP;CZaMrY$69dhxEDItmNOpO!o!;7D)6pa?i4PHCk z)yD~x<#RznxPD&>4OzkJ|69WU1(;He&E-vhoq+pS!gKz+%IRR}WN7P5|JPys&w(iX z^`(%h;s3AH{MRZAO7KjRD|9g$4Sf$vvQ$CwtX)`N+F~35WQQba2-3SxXVeDal?^jD z={qeFQX&3dVuc6Uv}__igmw*`?q<_jZRMjE{u9#JxAsczcDhwYfh zQEHuGK61lR`V$uAbTBcNbyY56aFOAOVg1oKK(}(8lCN4X=iQxBq#ebs22r*BLvGiO z=!%=`LZhK@5U7W6p?K7B5g7az!}C=I#CND{4-=vGQk=&-{J0Eq7b)Q6!1xv`0ubK{ zq#^u{Lf6R_8q?)hR<#V8x3_xUOH9cT+*b;1(L+X65y(d1nXD#ExeT`S=J=wzXubuu z0!BN$*hmdQ)!i5WAgVe*Dv5hXI4jCJ_1!st;K@L5JGfVuw3g*}z2$W}s$cS4cihVq zxXl60*zY1LFKJ#5>R|mt12dL*w)Y$Z_O(5qko!Ek^tX+1(`=rpv6wX}!&E4a+^ztf zj!Qy@Q0`Pc@6nP9r4QhArD39~t6I!uX%tV#$iU^1y-!z7s2if+8yKRx~sM zh1giwQLyn6z*38|=T0`)F*uzjHw>Q?*wo0-{y+G~Iov~|Gbo0xC%e+0%-l{s4jy0y zKsrH-6r1ZT4JZ29gs&Hs^*n~-YL>uZ(o^sxLy~Js507k^l8)72e1)iVZK3@MqR@w& z8IU{ldcTDjiBno?^*{hiN)%Q`4-BcS)*RYl$hB7|?<*S)lI+snc?&^`XZS^3vS+}M ziihw5R+o>pW`vaZt3GOsD6S|&^%0O#*)DKHsw6f3kTIgz{hnNHxDvH5|4cjgpuzk8 zG1T7Ri;K`sF039)bBPi!8uOF>St7)VWXxAEPzQEo9?o+6m&ohgg>Veoc_?p@#aa{T zx=#e$luW!R9{B{(;V$SUr`*Ze^w2JD$|)TgD1`y$T!6GjdZPTOm{a-X0{gM=WEI&O zC-dF7MzIf8GKbax*BT7lK3!gi2>hu3KNYx=zLypDuc#q@MeW~?n*K%9v%P zHhxZOfB_{$zNE&LD|-QQ2s$?pTF+b6EVyqurW)>MSQA!PJTrv_HFpXczc&;~{|n@SYwhyr8R zaL5_kWY)|Evjy@Tz@g`9=RtC`7J;B{U) z#L6z#-iLn0fci_}{NG+Q{~`|mSYMNqZsfk|>(I>Xd`bhFh$tc|;*gR>sy~%RAW?{h zQk+0wZt27-ouzr^S<M+UeN#L|Ng!1#}Y|` z2j35%BIGno5wJ$c$#m9Cf$1tYoO?$ahQaN&`6X+Zg|mWEDmHtEQmV^}W!TO>)LQnd zSd^PrL%-u6gdKahP$BsR)I)NWdT14c6RhJ=D7>L7aBWUgJ&9506&ofRmjT>4N#=0a zDE7`{)Iu*}H=wDW71bXZhF>CP7zwiZ(ssHYw6Z;Cw8}O*xumOT0~C}kFOp*mU1mJN zg}U-=(H#bhjsBWyl)ulJ>QJCz?=CsD@*oh1_%t5vq z^c_dhnoKG^zrXE`M~8}P0RHtoJ(0a3LP{{=CXq1lR60`QLyK*Vv#=wLmPS7lrn~j2 zUc>rkMlia^7y}}mB}B4ni8^#`T|a~WR=#oC+e}e)YNnQ>IQU_rIvm*mgRcKvBC0=q zlL8F&S#Q;)_0hPLpLIzoB|eds;5oHH@U{V3A8?Tn1ZCWd3SR-@^i#kv$;S(cA)g`x z^S9q!f}O0t=g`AU&3*;?7MH?isDE2(+qMV5~u)$I-ej|1PRa#rM7@zVktpXq)@?1 zw}&;qX0vZLusCyGcECT2`2n)8ksaSRU6y+p#Xk<%oFcPX69s18^nNcxgHo{pq3U4|-My&+u|tc2k=d8OBu zimS8q64s`8mKcT*t*~QPoU|E2+PR}s z@t3oiqHUT_{NB8hCVeJ5hEJojHq6%8Ii5L&OsZ>+rHWg7yt+HH`I!C-gTDt4nMoDE;i|z4pn6-1JVFd_C2%dGK zA%(9#ufjn_MQT{N9@6tfm)UKB)g5n0eRPN&QhK#hw~QgFOSe^pS-O&(q}yuFDg?Z% z`1cjAa0A)H75f$&3{Vy^x#cBrBH=Cz!WOE5xQjvalVr4Ar@4CJ)8fXtM%<-CA|Ys2 z<0)l3R&bNhG3D=n_99Y!%UnVZrd>p@NNG`7JE4IymqHW|sZ9xQ;-otm^T9$KwHYJ) zcJtFQ(W}-!Icrp?V3l2F3ZhOJ`Wzla3?fPZ2^FMKN`?Kd%nx~^*8O3IMy$Cr!L*N? zhhn@siu|yNGq-f`7qGI(J=aR2pjt90hCTB|xQ!3TBX3N6y}Thm$uk9qLn#<|wpwgT zYE8)xh|8GV6iw1x;w=_s$s;ikU77O=rdHY*?I8$9S3ykOT9o%jJK*1EmDNyR-7Z<&FNze-$W3bH@SjJuW zA*K5 zU1J-quP6F0Vp1;TgYrB)oWy6-FZy23(z-mTvN;One<;2J=dtCd{=vw}_Yax)jdk76 zF-4-NCEX;BD{GvVv(J(^_%(?D_O`&yE&?PpxBsYQ8J_}4>t~tj_P}Gl)f@(Aai7D9 z~i(pSN!yo8R?R9xR~x8)Sv5>z39|F4vx4_Z-)_>&&P39tX+mf0C=k{9EfJY zIijo@VZI_5$I5GjbyBNToFNpVK3wR%+u%N{Rbz-EiL4U$j=YC=Sf`3CW^N_=Dv=@3 z1!c9sF%#laVfdQs$B2zVy;pp~9_%}ko*fALf+H{u`_H$+5scgRpZxiA1iN5B_+P^k zDAXzJXVoq<5No#ECg|0g0RygAj8FYjWa8KR@=Jd)96oY5{rP+Kq>tM05q3Dbg{w>O z&(MjBHa|V294vmZSz^eY`Da|j=bVJw0Zd7c0i_1?+f0q7pZ0T;JU>IlX!brcWLYW5mU^k zV0Bj6$3^<_PmU#F{Rm^LUr!~(|Lx)UxBL8WPo}@OR`L^a{Q@Yw=<&c-Mw=MFdR4g_ z+*DzakiQ$vwOP|8jw#H`{dJYw(mj9s>V!>!u+iC{9Pv1xPM*Bs4T2`V1%4CmztVnB zCY$hhi7#4MS>wnaypac($h4)~c}x(Q7mDuNnEfW+J8x%cHa_6vv{%7L4>D9@Q>#d& zQ4ivd&1^|>(X>Qkrc=Rf77GfUx9UTkXhyO=p(5m!SFvd?8}jGsf1vF5X$D~I^(2ek zNkl_!2lFLZpEJxvL9uS2S}zeJaD8gxLz$nx`dzoed4*~_q`mq7<0E_RdO}_3tM{S* z-*y)Nc2-LEt}dnu|J3;;+S()iC6X}VOsP~2v^;CtY-gTiVZjy`9*yTHo!0BMySEt}y z#BuG8EtCxq3w1-Ywo7!MZg+aWF<>s(h#9%{f!TH`?uu~`cr4#o=?_{%*GZvl>?0gF zh6z{X%bC3qiG1RI6=}!kXRL}=KiuJP;Srv1^f;|3sgvD<2?p3ii$uiWdKxA$){qIQz}ijugcEAIzoFdB z0yYwGQ@ak$$LqY-Q`gTTD!QC_LcKKN=a+Q2Yl^(?i~-NLt6Xjmn%aeZ4v|kO*k^vo8*6&(tQCo`G%PxDJ2(qai80Ki$S6k%UT!^$<h_>&uyGvq`_d%#log8b( zUoa5tnH&gwaw6C!F?o|9T3cbw2Ab%e4Dhcv5ffZ@7@ir^uk=4YCOtj;d_cQ+W?(Wf z9Oevw_g>AANEbZu8G0DAJ+!A$=_;rki{|nOaHRGID;Bj^$O*MH2f=a%r)&mS9{R_` z!63>&M}(o8n_|Z%oo6x~CJfRB)pQ89Vgmi^|T z|JDX(tKBI7Web@Sw}}X+L@X*4SW;~ykx>L%-!h9YAy`1zCs3|vo<+==aG`U(e^gU& z!0`#r^*UDTnguA-VpTI+l;=9YKYG4Cf*|B4K{IoEdUV@4@!WoD-+KR;sr?1i7U2G# zHs}f@O_cJ1(GQErF76233R)GlOQZK7+)I_)PHZIG%a!Xh1rJ$C#P+>Nw2N*ZwufrK zoE_8z^zge4BgERKH+K6KZpc}bVT!VoES*}C%20z6{FMF^Z|J zI-Ba}r5v0#YV&m5DXe;;Y`d0x_57;fhEonCvw~U`_jr$u^>T_Oj?bt=WoCV6rn4*i z^hc_fzBBK@5D*cghqdgkK=+pnpkUc=^ZrB*xz}ab#M_usp4`+cIRU(_+f@Mdm#Ez$=;)%EDtav zs9_VLQ_XGu8+DYL7gSHzBBAt&Jir6(UDeuFs*)G*iycyQ`g5*d@cq(k_=F8QYWo9M za*dj8q(6o~0~EK%wjs}dI3zHp zaZcD<-SsK8K)^?phADg3ERHz_TTByVMwn0JS#bhI^u;P&k~;PBnX5Udpta$>gH z8QAETDvN}CLUbZ#=}VaQ4dS&pHjgwaY$#=!n}3w9`j4(c-o)Ww zMf`uJ)zyEL8x>?8DPRbOq!hgry!FN4ezdL)PSFZ+ z9q=6-`_wVfu)B!97cqS(#2nkC#PM_PpQLtNZEbo^yFYEWWb%E!zMy@%BEl2oI|3FD zo(*Y}y*J<$zDs9>vdo0ty~FPYCkT^B_QdPktHWR$V5ahg`p*txJT_kR1b~)*{mvcMmp=ZLIb-Y2AeM8m4^!1VT}dq zjsE4euP`7m$6R9s9==GI3@qoTL*J@sO#0*9V${>uBa4HrQ`?4 zvZ~3aHgo#TH80rI%5n$%6>Ulm?k5qaj(2nw6*AsFiX4~DB^>uXvIeZLzWe1!pcl6l zHVo=QdQMdYRYxj`J(@06omneO?yqt)KZC|+>CdCJ*Ehd2I2D>1N4(C2YtJPZnyH+R zm(vrXFWDVL@+lF!QISQDgb#k3lYN0A|%s`ix_p+mnQ>IL7h;0E8o zG&5^+Djm_|8c{?0&(SwCU<^a^?$Nxksp$<2A+4T2=1gvM(jSZH&lH$#RA}O@s?{Ex z(~hLp)~BrWtJ#mTtbxlT(K|0Qxh#u((N$$_1m&N&&b#y`el{J&QdIX^QS7fWxb|Eg znZao44;APPpU{S@kb?-BEhP2--Lm<<(ON6&e#WL~dkzQG-E zMeXMM#(^2$}el^P^FE!lJ3e;LjRRb=<`axkBjT=pPC863OF*nGoX7JB>~H4m2fLDPDQ^3eU{sUVipF%=-zCO= zk-Nu8v&-z9D~o2m`U%pmOVGNu;2tzvTfdhIo<+OLNPt_X&w*-v!2&Af2HG)ZyG-6q z7W^s%AN#q-(3y`eDvQwV$H#k%)E|1{;U>sE==cSzXlD>~yI+@kADr7%B8UoKF|Y;RS5P7cepxJ-9zI5a1zG$+Zt0Xg+IJerTt)Bf`ncnseDe!~$!)%09Gv?zw~i8ItL zwg&@-v%ncgL2d-T5b1ez$v$*p`RMdHMXtqeanHBr1kQd;X|53V%25JL{rfIgf~NMG zb>YDHRZq#SE5{&a$?1h^∾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 GdcW$j+wR5eCsK|=U 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 z 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

UEH4Htjgd-gl3aM}e`iFw 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 C&j5%3 diff --git a/lib/httpmime-4.1.2.jar b/lib/httpmime-4.1.2.jar deleted file mode 100644 index eea3b3ff1761f4683a527726887a193ffbf7af49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26890 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 diff --git a/lib/json_simple-1.1.jar b/lib/json_simple-1.1.jar deleted file mode 100644 index f395f41471a796876278a6c5667ded4632546893..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16046 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
JN5nX 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
Tj1!v+$xtx*dI+GHLe_`0!x-QO(~;6cfZ-l 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%0AKWfE$qTJhrf;L-7QRzS1a7;?kZ7Am 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

y3E z293 ztf@XDj!67oeWb{A{xj#CC``F#BA9MS=ZQP`IvDO)4qXoWVoNNSFz<(Dl6K^O{vYv% BkMRHi diff --git a/pom.xml b/pom.xml index d2236b46b3e..d968a4b83b9 100644 --- a/pom.xml +++ b/pom.xml @@ -23,9 +23,9 @@ 1.6 - org.json - json - 20090211 + com.googlecode.json-simple + json-simple + 1.1 log4j @@ -58,6 +58,24 @@ UTF-8 + + maven-assembly-plugin + 2.3 + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF deleted file mode 100644 index 87a3423ac9d..00000000000 --- a/src/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Main-Class: net.kencochrane.sentry.SentryAppender - diff --git a/src/net/kencochrane/sentry/RavenClient.java b/src/main/java/net/kencochrane/sentry/RavenClient.java similarity index 69% rename from src/net/kencochrane/sentry/RavenClient.java rename to src/main/java/net/kencochrane/sentry/RavenClient.java index 0819ced448f..7f746802ce2 100644 --- a/src/net/kencochrane/sentry/RavenClient.java +++ b/src/main/java/net/kencochrane/sentry/RavenClient.java @@ -1,5 +1,6 @@ package net.kencochrane.sentry; +import org.json.simple.JSONArray; import org.json.simple.JSONObject; import java.io.IOException; @@ -58,11 +59,17 @@ public void setSentryDSN(String sentryDSN) { * @param culprit Who we think caused the problem. * @return JSON String of message body */ - private String buildJSON(String message, String timestamp, String loggerClass, int logLevel, String culprit) { + private String buildJSON(String message, String timestamp, String loggerClass, int logLevel, String culprit, Throwable exception) { JSONObject obj = new JSONObject(); obj.put("event_id", RavenUtils.getRandomUUID()); //Hexadecimal string representing a uuid4 value. obj.put("checksum", RavenUtils.calculateChecksum(message)); - obj.put("culprit", culprit); + if (exception == null) { + obj.put("culprit", culprit); + } else { + obj.put("culprit", determineCulprit(exception)); + obj.put("sentry.interfaces.Exception", buildException(exception)); + obj.put("sentry.interfaces.Stacktrace", buildStacktrace(exception)); + } obj.put("timestamp", timestamp); obj.put("message", message); obj.put("project", getConfig().getProjectId()); @@ -72,6 +79,63 @@ private String buildJSON(String message, String timestamp, String loggerClass, i return obj.toJSONString(); } + /** + * Determines the class and method name where the root cause exception occurred. + * + * @param exception exception + * @return the culprit + */ + private String determineCulprit(Throwable exception) { + Throwable cause = exception; + String culprit = null; + while (cause != null) { + StackTraceElement[] elements = cause.getStackTrace(); + if (elements.length > 0) { + StackTraceElement trace = elements[0]; + culprit = trace.getClassName() + "." + trace.getMethodName(); + } + cause = cause.getCause(); + } + return culprit; + } + + private JSONObject buildException(Throwable exception) { + JSONObject json = new JSONObject(); + json.put("type", exception.getClass().getSimpleName()); + json.put("value", exception.getMessage()); + json.put("module", exception.getClass().getPackage().getName()); + return json; + } + + private JSONObject buildStacktrace(Throwable exception) { + JSONArray array = new JSONArray(); + Throwable cause = exception; + while (cause != null) { + StackTraceElement[] elements = cause.getStackTrace(); + for (int index = 0; index < elements.length; ++index) { + if (index == 0) { + JSONObject causedByFrame = new JSONObject(); + String msg = "Caused by: " + cause.getClass().getName(); + if (cause.getMessage() != null) { + msg += " (\"" + cause.getMessage() + "\")"; + } + causedByFrame.put("filename", msg); + causedByFrame.put("lineno", -1); + array.add(causedByFrame); + } + StackTraceElement element = elements[index]; + JSONObject frame = new JSONObject(); + frame.put("filename", element.getClassName()); + frame.put("function", element.getMethodName()); + frame.put("lineno", element.getLineNumber()); + array.add(frame); + } + cause = cause.getCause(); + } + JSONObject stacktrace = new JSONObject(); + stacktrace.put("frames", array); + return stacktrace; + } /** * Take the raw message body and get it ready for sending. Encode and compress it. @@ -98,11 +162,12 @@ private String buildMessageBody(String jsonMessage) { * @param loggerClass The class associated with the log message * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.) * @param culprit Who we think caused the problem. + * @param exception exception causing the problem * @return Encode and compressed version of the JSON Message body */ - private String buildMessage(String message, String timestamp, String loggerClass, int logLevel, String culprit) { + private String buildMessage(String message, String timestamp, String loggerClass, int logLevel, String culprit, Throwable exception) { // get the json version of the body - String jsonMessage = buildJSON(message, timestamp, loggerClass, logLevel, culprit); + String jsonMessage = buildJSON(message, timestamp, loggerClass, logLevel, culprit, exception); // compress and encode the json message. return buildMessageBody(jsonMessage); @@ -183,11 +248,13 @@ private void sendMessage(String messageBody, long timestamp) { * @param loggerClass The class associated with the log message * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.) * @param culprit Who we think caused the problem. + * @param exception exception that occurred */ - public void logMessage(String theLogMessage, long timestamp, String loggerClass, int logLevel, String culprit) { + public void logMessage(String theLogMessage, long timestamp, String loggerClass, int logLevel, String culprit, Throwable exception) { String timestampDate = RavenUtils.getTimestampString(timestamp); - String message = buildMessage(theLogMessage, timestampDate, loggerClass, logLevel, culprit); + String message = buildMessage(theLogMessage, timestampDate, loggerClass, logLevel, culprit, exception); sendMessage(message, timestamp); } + } \ No newline at end of file diff --git a/src/net/kencochrane/sentry/RavenConfig.java b/src/main/java/net/kencochrane/sentry/RavenConfig.java similarity index 100% rename from src/net/kencochrane/sentry/RavenConfig.java rename to src/main/java/net/kencochrane/sentry/RavenConfig.java diff --git a/src/net/kencochrane/sentry/RavenUtils.java b/src/main/java/net/kencochrane/sentry/RavenUtils.java similarity index 94% rename from src/net/kencochrane/sentry/RavenUtils.java rename to src/main/java/net/kencochrane/sentry/RavenUtils.java index dc06b48515d..049fdb5a971 100644 --- a/src/net/kencochrane/sentry/RavenUtils.java +++ b/src/main/java/net/kencochrane/sentry/RavenUtils.java @@ -1,5 +1,7 @@ package net.kencochrane.sentry; +import org.apache.commons.lang.time.DateFormatUtils; + import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.io.ByteArrayOutputStream; @@ -7,7 +9,6 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.security.SignatureException; -import java.text.SimpleDateFormat; import java.util.Date; import java.util.UUID; import java.util.zip.CRC32; @@ -25,7 +26,6 @@ public class RavenUtils { private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; - private static final SimpleDateFormat ISO8601FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); /** * Computes RFC 2104-compliant HMAC signature. @@ -83,10 +83,7 @@ public static String getHostname() { * @return ISO8601 formatted date as a String */ public static String getDateAsISO8601String(Date date) { - String result = ISO8601FORMAT.format(date); - result = result.substring(0, result.length() - 2) - + ":" + result.substring(result.length() - 2); - return result; + return DateFormatUtils.ISO_DATETIME_FORMAT.format(date); } /** @@ -95,8 +92,7 @@ public static String getDateAsISO8601String(Date date) { * @return timestamp for now as long */ public static long getTimestampLong() { - java.util.Date date = new java.util.Date(); - return date.getTime(); + return System.currentTimeMillis(); } /** diff --git a/src/net/kencochrane/sentry/SentryAppender.java b/src/main/java/net/kencochrane/sentry/SentryAppender.java similarity index 86% rename from src/net/kencochrane/sentry/SentryAppender.java rename to src/main/java/net/kencochrane/sentry/SentryAppender.java index 10dd3efc4aa..a6c9184a44e 100644 --- a/src/net/kencochrane/sentry/SentryAppender.java +++ b/src/main/java/net/kencochrane/sentry/SentryAppender.java @@ -2,6 +2,7 @@ import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.spi.LoggingEvent; +import org.apache.log4j.spi.ThrowableInformation; /** * User: ken cochrane @@ -52,7 +53,9 @@ protected void append(LoggingEvent loggingEvent) { RavenClient client = new RavenClient(getSentry_dsn(), getProxy()); // send the message to the sentry server - client.logMessage(logMessage, timestamp, loggingClass, logLevel, culprit); + ThrowableInformation throwableInformation = loggingEvent.getThrowableInformation(); + Throwable throwable = (throwableInformation == null ? null : throwableInformation.getThrowable()); + client.logMessage(logMessage, timestamp, loggingClass, logLevel, culprit, throwable); } catch (Exception e) { System.err.println(e); diff --git a/src/test/java/net/kencochrane/sentry/SentryExample.java b/src/test/java/net/kencochrane/sentry/SentryExample.java new file mode 100644 index 00000000000..52839ad56bc --- /dev/null +++ b/src/test/java/net/kencochrane/sentry/SentryExample.java @@ -0,0 +1,55 @@ +package net.kencochrane.sentry; + +/** + * User: ken cochrane + * Date: 2/6/12 + * Time: 11:35 AM + */ + +// Import log4j classes. + +import org.apache.log4j.Logger; +import org.apache.log4j.PropertyConfigurator; +import org.junit.Test; + +/** + * Simple example used to test out the sentry logger. + */ +public class SentryExample { + + // Define a static logger variable so that it references the + // Logger instance named "MyApp". + static final Logger logger = Logger.getLogger(SentryExample.class); + + public void triggerRuntimeException() { + try { + triggerNullPointer(); + } catch (Exception e) { + throw new RuntimeException("Error triggering null pointer", e); + } + } + + public String triggerNullPointer() { + String c = null; + return c.toLowerCase(); + } + + @Test + public void test_simple() { + + // PropertyConfigurator. + PropertyConfigurator.configure(getClass().getResource("/log4j_configuration.txt")); + + logger.debug("Debug example"); + logger.error("Error example"); + logger.trace("Trace Example"); + logger.fatal("Fatal Example"); + logger.info("info Example"); + logger.warn("Warn Example"); + try { + triggerRuntimeException(); + } catch (RuntimeException e) { + logger.error("Error example with stacktrace", e); + } + } +} diff --git a/example/log4j_configuration.txt b/src/test/resources/log4j_configuration.txt similarity index 99% rename from example/log4j_configuration.txt rename to src/test/resources/log4j_configuration.txt index ab760c25870..7bba8f0e6cc 100644 --- a/example/log4j_configuration.txt +++ b/src/test/resources/log4j_configuration.txt @@ -20,3 +20,4 @@ log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n log4j.appender.sentry=net.kencochrane.sentry.SentryAppender log4j.appender.sentry.sentry_dsn=http://b4935bdd78624092ac2bc70fdcdb6f5a:7a37d9ad4765428180316bfec91a27ef@localhost:8000/1 + From c934bda86d3323d05a63a288122aba8621721013 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Sun, 12 Feb 2012 20:57:53 +0100 Subject: [PATCH 0016/2152] Update README --- README.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 99fdbb678eb..35bfacfc7fc 100644 --- a/README.rst +++ b/README.rst @@ -26,13 +26,12 @@ you'll find in the target directory of the project. **Option 3**: add the self contained jar file to your classpath +Log4J configuration +------------------- Check out src/test/java/resources/log4j_configuration.txt where you can see an example log4j config file. You will need to add the SentryAppender and the sentry_dsn properties. -Log4J configuration -------------------- - sentry_dsn ~~~~~~~~~~ You will get this value from the "Projects / default / Manage / Member: " page. It will be under "Client DSN". From 3dcd0e39cf3b9afefc00565bfd9ecf4c799dedb0 Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Sun, 12 Feb 2012 14:58:32 -0500 Subject: [PATCH 0017/2152] Updated README.rst to change the Sentry version support --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 0521d506369..f76c41d5938 100644 --- a/README.rst +++ b/README.rst @@ -27,7 +27,7 @@ Log4j Config example:: Sentry Versions Supported ------------------------- -This client has been tested with Sentry 2.7 and 2.8, and only very briefly. +This client supports Sentry protocol version 2.0 (which is Sentry >= 2.0) TODO ---- From 504f6ffce2670a6d8c264b198f523bfaa69d4ec1 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Sun, 12 Feb 2012 20:59:19 +0100 Subject: [PATCH 0018/2152] Ignore .idea directory --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9dbdbbfd785..bfe9da4a1dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ *.pyc *.DS_Store -out/* \ No newline at end of file +out/* +target/* +.idea/* \ No newline at end of file From ab97ab92173a01b51939e17450171e836440c770 Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Sun, 12 Feb 2012 15:48:40 -0500 Subject: [PATCH 0019/2152] Updated README, removed the duplicate todo item about documentation --- README.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/README.rst b/README.rst index 546da935cf0..74e213d9c49 100644 --- a/README.rst +++ b/README.rst @@ -62,7 +62,6 @@ TODO - Create better documentation - Add unit tests - Add more examples -- Add more documentation - Get compression to work on message body, it isn't working now,not sure if it is sentry server or raven-java. Might be incompatible versions of zlib Java->python. From 5a99dc84776d771162d8734340f38fbb63b31803 Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Sun, 12 Feb 2012 20:44:37 -0500 Subject: [PATCH 0020/2152] Added a contributors section. --- README.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 74e213d9c49..175edbace3c 100644 --- a/README.rst +++ b/README.rst @@ -65,9 +65,13 @@ TODO - Get compression to work on message body, it isn't working now,not sure if it is sentry server or raven-java. Might be incompatible versions of zlib Java->python. - History ------- 0.2 - code refactor and cleanup -0.1 - initial version \ No newline at end of file +0.1 - initial version + +Contributors +------------ +Ken Cochrane (@KenCochrane) +Kevin Wetzels (@roambe) \ No newline at end of file From b1f853ee3dabab2269723476bf9e5b58dfbdbc2e Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Sun, 12 Feb 2012 20:45:05 -0500 Subject: [PATCH 0021/2152] added formatting for the contributors section --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 175edbace3c..64994fa095a 100644 --- a/README.rst +++ b/README.rst @@ -73,5 +73,5 @@ History Contributors ------------ -Ken Cochrane (@KenCochrane) -Kevin Wetzels (@roambe) \ No newline at end of file +- Ken Cochrane (@KenCochrane) +- Kevin Wetzels (@roambe) \ No newline at end of file From 62929add364a1d501f9880e46b6c46a99d6f472f Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Mon, 13 Feb 2012 10:36:11 -0500 Subject: [PATCH 0022/2152] bumped version to 0.3, cleaned up some directories and files, and updated the java docs. --- .gitignore | 5 +- .idea/.name | 1 - .idea/ant.xml | 15 - .idea/artifacts/raven_java_jar.xml | 18 - .idea/compiler.xml | 21 - .idea/copyright/profiles_settings.xml | 5 - .idea/encodings.xml | 5 - .idea/misc.xml | 33 - .idea/modules.xml | 9 - .idea/uiDesigner.xml | 125 --- .idea/vcs.xml | 7 - .idea/workspace.xml | 841 ------------------ README.rst | 18 +- docs/allclasses-frame.html | 6 +- docs/allclasses-noframe.html | 6 +- docs/constant-values.html | 4 +- docs/deprecated-list.html | 4 +- docs/help-doc.html | 4 +- docs/index-files/index-1.html | 8 +- docs/index-files/index-2.html | 8 +- docs/index-files/index-3.html | 14 +- docs/index-files/index-4.html | 8 +- docs/index-files/index-5.html | 21 +- docs/index-files/index-6.html | 35 +- docs/index-files/index-7.html | 56 +- docs/index-files/index-8.html | 51 +- docs/index.html | 2 +- docs/net/kencochrane/sentry/RavenConfig.html | 43 +- docs/net/kencochrane/sentry/RavenUtils.html | 4 +- .../kencochrane/sentry/SentryAppender.html | 46 +- .../net/kencochrane/sentry/package-frame.html | 8 +- .../kencochrane/sentry/package-summary.html | 8 +- docs/net/kencochrane/sentry/package-tree.html | 6 +- docs/overview-tree.html | 6 +- pom.xml | 3 +- raven-java-0.2.jar | Bin 1171046 -> 0 bytes raven-java.iml | 14 - raven-java.xml | 198 ----- 38 files changed, 230 insertions(+), 1436 deletions(-) delete mode 100644 .idea/.name delete mode 100644 .idea/ant.xml delete mode 100644 .idea/artifacts/raven_java_jar.xml delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/copyright/profiles_settings.xml delete mode 100644 .idea/encodings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/uiDesigner.xml delete mode 100644 .idea/vcs.xml delete mode 100644 .idea/workspace.xml delete mode 100644 raven-java-0.2.jar delete mode 100644 raven-java.iml delete mode 100644 raven-java.xml diff --git a/.gitignore b/.gitignore index bfe9da4a1dd..e3ff706586e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -*.pyc *.DS_Store out/* target/* -.idea/* \ No newline at end of file +.idea/* +*.iml +example.log \ No newline at end of file diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index f6e2de0e085..00000000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -raven-java \ No newline at end of file diff --git a/.idea/ant.xml b/.idea/ant.xml deleted file mode 100644 index ea91b4abaea..00000000000 --- a/.idea/ant.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/.idea/artifacts/raven_java_jar.xml b/.idea/artifacts/raven_java_jar.xml deleted file mode 100644 index b3940b12c01..00000000000 --- a/.idea/artifacts/raven_java_jar.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - $PROJECT_DIR$/out/artifacts/raven_java_jar - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index a1b41c52c72..00000000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml deleted file mode 100644 index 3572571ad83..00000000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index e206d70d859..00000000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 3f368af3c49..00000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - http://www.w3.org/1999/xhtml - - - - - - diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 9c0090beddf..00000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml deleted file mode 100644 index 3b000203088..00000000000 --- a/.idea/uiDesigner.xml +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 275077f8255..00000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index cdc2f19647e..00000000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,841 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - localhost - 5050 - - - - - - - - 1328543448831 - 1328543448831 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - raven-java:jar - - - - - - - - Android - - - - - - - - - - - - - - - 1.6 - - - - - - - - raven-java - - - - - - - - 1.6 - - - - - - - - common - - - - - - - - - diff --git a/README.rst b/README.rst index 64994fa095a..5fec356a555 100644 --- a/README.rst +++ b/README.rst @@ -19,7 +19,7 @@ you'll find in the target directory of the project. net.kencochrane raven-java - 1.0-SNAPSHOT + 0.3-SNAPSHOT **Option 2**: add the plain jar and the jar files of all dependencies to your classpath @@ -60,16 +60,24 @@ docs from the target directory of the project. TODO ---- - Create better documentation -- Add unit tests +- Add more unit tests - Add more examples -- Get compression to work on message body, it isn't working now,not sure if it is sentry server or raven-java. Might be incompatible versions of zlib Java->python. +- Get compression to work on message body, it isn't working now, not sure if it is sentry server or raven-java. Might be incompatible versions of zlib Java->python. History ------- -0.2 - code refactor and cleanup +- 0.3 + - Added Maven support + - Merged with log4sentry project by Kevin Wetzels + - Added Proxy support + - Added full stack trace to logs -0.1 - initial version +- 0.2 + - code refactor and cleanup + +- 0.1 + - initial version Contributors ------------ diff --git a/docs/allclasses-frame.html b/docs/allclasses-frame.html index 6204da27456..e7b4c4f89a4 100644 --- a/docs/allclasses-frame.html +++ b/docs/allclasses-frame.html @@ -2,12 +2,12 @@ - + All Classes - + @@ -27,8 +27,6 @@
SentryAppender
-SentryExample -
diff --git a/docs/allclasses-noframe.html b/docs/allclasses-noframe.html index a2ae2a9449d..2752d537041 100644 --- a/docs/allclasses-noframe.html +++ b/docs/allclasses-noframe.html @@ -2,12 +2,12 @@ - + All Classes - + @@ -27,8 +27,6 @@
SentryAppender
-SentryExample -
diff --git a/docs/constant-values.html b/docs/constant-values.html index 175fa398984..537ac550158 100644 --- a/docs/constant-values.html +++ b/docs/constant-values.html @@ -2,12 +2,12 @@ - + Constant Field Values - + diff --git a/docs/deprecated-list.html b/docs/deprecated-list.html index cf2ee185dc8..a018fbe56d6 100644 --- a/docs/deprecated-list.html +++ b/docs/deprecated-list.html @@ -2,12 +2,12 @@ - + Deprecated List - + diff --git a/docs/help-doc.html b/docs/help-doc.html index a50abe552f8..f2ea01c3dc4 100644 --- a/docs/help-doc.html +++ b/docs/help-doc.html @@ -2,12 +2,12 @@ - + API Help - + diff --git a/docs/index-files/index-1.html b/docs/index-files/index-1.html index f1959f7c122..cfde7705385 100644 --- a/docs/index-files/index-1.html +++ b/docs/index-files/index-1.html @@ -2,12 +2,12 @@ - + A-Index - + @@ -76,7 +76,7 @@ -A C G H M N R S T


+A C G H N R S T

A

@@ -135,7 +135,7 @@ -A C G H M N R S T
+A C G H N R S T
diff --git a/docs/index-files/index-2.html b/docs/index-files/index-2.html index f880431d12c..35d190bea13 100644 --- a/docs/index-files/index-2.html +++ b/docs/index-files/index-2.html @@ -2,12 +2,12 @@ - + C-Index - + @@ -76,7 +76,7 @@ -A C G H M N R S T
+A C G H N R S T

C

@@ -144,7 +144,7 @@ -A C G H M N R S T
+A C G H N R S T
diff --git a/docs/index-files/index-3.html b/docs/index-files/index-3.html index 084b065f0b9..f7a73534998 100644 --- a/docs/index-files/index-3.html +++ b/docs/index-files/index-3.html @@ -2,12 +2,12 @@ - + G-Index - + @@ -76,7 +76,7 @@ -A C G H M N R S T
+A C G H N R S T

G

@@ -102,6 +102,12 @@
getProtocol() - Method in class net.kencochrane.sentry.RavenConfig
Sentry server protocol http https? +
getProxy() - +Method in class net.kencochrane.sentry.RavenConfig +
  +
getProxy() - +Method in class net.kencochrane.sentry.SentryAppender +
 
getPublicKey() - Method in class net.kencochrane.sentry.RavenConfig
The Sentry public key @@ -184,7 +190,7 @@ -A C G H M N R S T
+A C G H N R S T
diff --git a/docs/index-files/index-4.html b/docs/index-files/index-4.html index 89678b717eb..356aa1c1dde 100644 --- a/docs/index-files/index-4.html +++ b/docs/index-files/index-4.html @@ -2,12 +2,12 @@ - + H-Index - + @@ -76,7 +76,7 @@ -A C G H M N R S T
+A C G H N R S T

H

@@ -136,7 +136,7 @@ -A C G H M N R S T
+A C G H N R S T
diff --git a/docs/index-files/index-5.html b/docs/index-files/index-5.html index 14122f84cdb..7e13001cf05 100644 --- a/docs/index-files/index-5.html +++ b/docs/index-files/index-5.html @@ -2,12 +2,12 @@ - + -M-Index +N-Index - + @@ -15,7 +15,7 @@ function windowTitle() { if (location.href.indexOf('is-external=true') == -1) { - parent.document.title="M-Index"; + parent.document.title="N-Index"; } } @@ -76,14 +76,11 @@ -A C G H M N R S T
-

-M

+A C G H N R S T
+

+N

-
main(String[]) - -Static method in class net.kencochrane.sentry.SentryExample -
  -
+
net.kencochrane.sentry - package net.kencochrane.sentry
 

@@ -135,7 +132,7 @@ -A C G H M N R S T
+A C G H N R S T
diff --git a/docs/index-files/index-6.html b/docs/index-files/index-6.html index 4999257eb84..9c15e42b7a3 100644 --- a/docs/index-files/index-6.html +++ b/docs/index-files/index-6.html @@ -2,12 +2,12 @@ - + -N-Index +R-Index - + @@ -15,7 +15,7 @@ function windowTitle() { if (location.href.indexOf('is-external=true') == -1) { - parent.document.title="N-Index"; + parent.document.title="R-Index"; } } @@ -76,11 +76,28 @@ -A C G H M N R S T
-

-N

+A C G H N R S T
+

+R

-
net.kencochrane.sentry - package net.kencochrane.sentry
 
+
RavenConfig - Class in net.kencochrane.sentry
User: ken cochrane + Date: 2/8/12 + Time: 1:16 PM
RavenConfig(String) - +Constructor for class net.kencochrane.sentry.RavenConfig +
Takes in a sentryDSN and builds up the configuration +
RavenConfig(String, String) - +Constructor for class net.kencochrane.sentry.RavenConfig +
Takes in a sentryDSN and builds up the configuration +
RavenUtils - Class in net.kencochrane.sentry
User: ken Cochrane + Date: 2/10/12 + Time: 10:43 AM + Utility class for the Raven client.
RavenUtils() - +Constructor for class net.kencochrane.sentry.RavenUtils +
  +
requiresLayout() - +Method in class net.kencochrane.sentry.SentryAppender +
  +

@@ -132,7 +149,7 @@ -A C G H M N R S T
+A C G H N R S T
diff --git a/docs/index-files/index-7.html b/docs/index-files/index-7.html index 3c84ab2df94..1ea2f3449b7 100644 --- a/docs/index-files/index-7.html +++ b/docs/index-files/index-7.html @@ -2,12 +2,12 @@ - + -R-Index +S-Index - + @@ -15,7 +15,7 @@ function windowTitle() { if (location.href.indexOf('is-external=true') == -1) { - parent.document.title="R-Index"; + parent.document.title="S-Index"; } } @@ -76,22 +76,40 @@ -A C G H M N R S T
-

-R

+A C G H N R S T
+

+S

-
RavenConfig - Class in net.kencochrane.sentry
User: ken cochrane - Date: 2/8/12 - Time: 1:16 PM
RavenConfig(String) - -Constructor for class net.kencochrane.sentry.RavenConfig -
Takes in a sentryDSN and builds up the configuration -
RavenUtils - Class in net.kencochrane.sentry
User: ken Cochrane - Date: 2/10/12 - Time: 10:43 AM - Utility class for the Raven client.
RavenUtils() - -Constructor for class net.kencochrane.sentry.RavenUtils +
SentryAppender - Class in net.kencochrane.sentry
User: ken cochrane + Date: 2/6/12 + Time: 10:54 AM
SentryAppender() - +Constructor for class net.kencochrane.sentry.SentryAppender
  -
requiresLayout() - +
setHost(String) - +Method in class net.kencochrane.sentry.RavenConfig +
  +
setPath(String) - +Method in class net.kencochrane.sentry.RavenConfig +
  +
setPort(int) - +Method in class net.kencochrane.sentry.RavenConfig +
  +
setProjectId(String) - +Method in class net.kencochrane.sentry.RavenConfig +
  +
setProtocol(String) - +Method in class net.kencochrane.sentry.RavenConfig +
  +
setProxy(String) - +Method in class net.kencochrane.sentry.SentryAppender +
  +
setPublicKey(String) - +Method in class net.kencochrane.sentry.RavenConfig +
  +
setSecretKey(String) - +Method in class net.kencochrane.sentry.RavenConfig +
  +
setSentry_dsn(String) - Method in class net.kencochrane.sentry.SentryAppender
 
@@ -146,7 +164,7 @@ -A C G H M N R S T
+A C G H N R S T
diff --git a/docs/index-files/index-8.html b/docs/index-files/index-8.html index cb97bd58c67..52856f4706b 100644 --- a/docs/index-files/index-8.html +++ b/docs/index-files/index-8.html @@ -2,12 +2,12 @@ - + -S-Index +T-Index - + @@ -15,7 +15,7 @@ function windowTitle() { if (location.href.indexOf('is-external=true') == -1) { - parent.document.title="S-Index"; + parent.document.title="T-Index"; } } @@ -54,7 +54,7 @@  PREV LETTER  - NEXT LETTER + NEXT LETTER FRAMES    NO FRAMES   @@ -76,42 +76,13 @@ -A C G H M N R S T
-

-S

+A C G H N R S T
+

+T

-
SentryAppender - Class in net.kencochrane.sentry
User: ken cochrane - Date: 2/6/12 - Time: 10:54 AM
SentryAppender() - -Constructor for class net.kencochrane.sentry.SentryAppender -
  -
SentryExample - Class in net.kencochrane.sentry
Simple example used to test out the sentry logger.
SentryExample() - -Constructor for class net.kencochrane.sentry.SentryExample -
  -
setHost(String) - -Method in class net.kencochrane.sentry.RavenConfig -
  -
setPath(String) - -Method in class net.kencochrane.sentry.RavenConfig -
  -
setPort(int) - -Method in class net.kencochrane.sentry.RavenConfig -
  -
setProjectId(String) - +
toString() - Method in class net.kencochrane.sentry.RavenConfig
  -
setProtocol(String) - -Method in class net.kencochrane.sentry.RavenConfig -
  -
setPublicKey(String) - -Method in class net.kencochrane.sentry.RavenConfig -
  -
setSecretKey(String) - -Method in class net.kencochrane.sentry.RavenConfig -
  -
setSentry_dsn(String) - -Method in class net.kencochrane.sentry.SentryAppender -
 

@@ -142,7 +113,7 @@  PREV LETTER  - NEXT LETTER + NEXT LETTER
FRAMES    NO FRAMES   @@ -164,7 +135,7 @@ -A C G H M N R S T
+A C G H N R S T
diff --git a/docs/index.html b/docs/index.html index 2923f414395..7105375e378 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2,7 +2,7 @@ - + Generated Documentation (Untitled) diff --git a/docs/net/kencochrane/sentry/RavenConfig.html b/docs/net/kencochrane/sentry/RavenConfig.html index 6b5a73a30d2..ebaf0884315 100644 --- a/docs/net/kencochrane/sentry/RavenConfig.html +++ b/docs/net/kencochrane/sentry/RavenConfig.html @@ -2,12 +2,12 @@ - + RavenConfig - + @@ -120,6 +120,13 @@

RavenConfig(java.lang.String sentryDSN) +
+          Takes in a sentryDSN and builds up the configuration + + +RavenConfig(java.lang.String sentryDSN, + java.lang.String proxy) +
          Takes in a sentryDSN and builds up the configuration @@ -175,6 +182,14 @@

+ java.net.Proxy +getProxy() + +
+            + + +  java.lang.String getPublicKey() @@ -294,6 +309,19 @@

Parameters:
sentryDSN - '{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}{PROJECT_ID}'

+
+ +

+RavenConfig

+
+public RavenConfig(java.lang.String sentryDSN,
+                   java.lang.String proxy)
+
+
Takes in a sentryDSN and builds up the configuration +

+

+
Parameters:
sentryDSN - '{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}{PROJECT_ID}'
proxy - proxy to use for the HTTP connections; blank or null when no proxy is to be used
+
@@ -319,6 +347,17 @@


+

+getProxy

+
+public java.net.Proxy getProxy()
+
+
+
+
+
+
+

getHost

diff --git a/docs/net/kencochrane/sentry/RavenUtils.html b/docs/net/kencochrane/sentry/RavenUtils.html
index 52e1d4e3e38..086068cd946 100644
--- a/docs/net/kencochrane/sentry/RavenUtils.html
+++ b/docs/net/kencochrane/sentry/RavenUtils.html
@@ -2,12 +2,12 @@
 
 
 
-
+
 
 RavenUtils
 
 
-
+
 
 
 
diff --git a/docs/net/kencochrane/sentry/SentryAppender.html b/docs/net/kencochrane/sentry/SentryAppender.html
index 640c4899a34..a3237462969 100644
--- a/docs/net/kencochrane/sentry/SentryAppender.html
+++ b/docs/net/kencochrane/sentry/SentryAppender.html
@@ -2,12 +2,12 @@
 
 
 
-
+
 
 SentryAppender
 
 
-
+
 
 
 
@@ -54,7 +54,7 @@
 
 
  PREV CLASS 
- NEXT CLASS
+ NEXT CLASS
 
   FRAMES   
  NO FRAMES   
@@ -168,6 +168,14 @@ 

 void close() +
+            + + + + java.lang.String +getProxy() +
            @@ -184,6 +192,14 @@

 boolean requiresLayout() +
+            + + + + void +setProxy(java.lang.String proxy) +
            @@ -266,6 +282,28 @@

ZMuR+Y{k2`PK7em9YD~rgRW%u)|4)T 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


+

+getProxy

+
+public java.lang.String getProxy()
+
+
+
+
+
+
+ +

+setProxy

+
+public void setProxy(java.lang.String proxy)
+
+
+
+
+
+
+

append

@@ -330,7 +368,7 @@ 

 PREV CLASS  - NEXT CLASS + NEXT CLASS FRAMES    NO FRAMES   diff --git a/docs/net/kencochrane/sentry/package-frame.html b/docs/net/kencochrane/sentry/package-frame.html index 969123a68f2..22a070b2216 100644 --- a/docs/net/kencochrane/sentry/package-frame.html +++ b/docs/net/kencochrane/sentry/package-frame.html @@ -2,12 +2,12 @@ - + net.kencochrane.sentry - + @@ -27,9 +27,7 @@
RavenUtils
-SentryAppender -
-SentryExample
+SentryAppender diff --git a/docs/net/kencochrane/sentry/package-summary.html b/docs/net/kencochrane/sentry/package-summary.html index a1ffcb0e7b5..8b3e221fe43 100644 --- a/docs/net/kencochrane/sentry/package-summary.html +++ b/docs/net/kencochrane/sentry/package-summary.html @@ -2,12 +2,12 @@ - + net.kencochrane.sentry - + @@ -105,10 +105,6 @@

Date: 2/6/12 Time: 10:54 AM - -SentryExample -Simple example used to test out the sentry logger. -   diff --git a/docs/net/kencochrane/sentry/package-tree.html b/docs/net/kencochrane/sentry/package-tree.html index fda61d39a88..05116620130 100644 --- a/docs/net/kencochrane/sentry/package-tree.html +++ b/docs/net/kencochrane/sentry/package-tree.html @@ -2,12 +2,12 @@ - + net.kencochrane.sentry Class Hierarchy - + @@ -90,7 +90,7 @@

  • org.apache.log4j.AppenderSkeleton (implements org.apache.log4j.Appender, org.apache.log4j.spi.OptionHandler) -
  • net.kencochrane.sentry.RavenConfig
  • net.kencochrane.sentry.RavenUtils
  • net.kencochrane.sentry.SentryExample +
  • net.kencochrane.sentry.RavenConfig
  • net.kencochrane.sentry.RavenUtils
    diff --git a/docs/overview-tree.html b/docs/overview-tree.html index 11bb8ac39c2..0d8261f2a30 100644 --- a/docs/overview-tree.html +++ b/docs/overview-tree.html @@ -2,12 +2,12 @@ - + Class Hierarchy - + @@ -92,7 +92,7 @@

  • org.apache.log4j.AppenderSkeleton (implements org.apache.log4j.Appender, org.apache.log4j.spi.OptionHandler) -
  • net.kencochrane.sentry.RavenConfig
  • net.kencochrane.sentry.RavenUtils
  • net.kencochrane.sentry.SentryExample +
  • net.kencochrane.sentry.RavenConfig
  • net.kencochrane.sentry.RavenUtils
    diff --git a/pom.xml b/pom.xml index d968a4b83b9..b277042a8ec 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ net.kencochrane raven-java - 1.0-SNAPSHOT + 0.3-SNAPSHOT jar raven-java Java Raven client and log4j appender. @@ -56,6 +56,7 @@ maven-resources-plugin UTF-8 + diff --git a/raven-java-0.2.jar b/raven-java-0.2.jar deleted file mode 100644 index 53f63eff201e27b0d4484afad3c68991971bb911..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1171046 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ℜ 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

    hC%Mm-=Xc~K(-${^q66LUQa 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%0A8w(9FaT?(Jzdj_X&pkC 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%0A2T!H=W9L(KN2;X4zEs#6VlOdY>?#x0<(EQow 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%0DatEzDh|AyO41r9y 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

    {D6;Qb> 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

    *

    - * Any of this schemes can be prefixed with async+ in which case the transport built using the original + * Any of these schemes can be prefixed with async+ in which case the transport built using the original * scheme will be wrapped in the transport class registered for {@link #VARIANT_ASYNC}, which is {@link AsyncTransport} * by default. *

    @@ -70,11 +71,7 @@ public class Client { protected Transport transport; static { - TRANSPORT_REGISTRY.put("http", Transport.Http.class); - TRANSPORT_REGISTRY.put("https", Transport.Http.class); - TRANSPORT_REGISTRY.put("naive+https", Transport.NaiveHttps.class); - TRANSPORT_REGISTRY.put("udp", Transport.Udp.class); - TRANSPORT_REGISTRY.put(VARIANT_ASYNC, AsyncTransport.class); + registerDefaults(); } /** @@ -292,8 +289,13 @@ public static Transport newAsyncTransport(Transport transport) { if (transportClass == null) { throw new InvalidConfig("No async transport registered"); } + final String invalidSignature = "The async transport handler should contain a public static \"build\" method " + // + "with a single parameter of type " + Transport.class + " and returning a new " + Transport.class + " instance."; try { Method method = transportClass.getMethod("build", Transport.class); + if (!Modifier.isStatic(method.getModifiers())) { + throw new InvalidConfig(invalidSignature); + } Object result = method.invoke(null, transport); if (!(result instanceof Transport)) { throw new InvalidConfig("The build method of the async transport layer should return an instance of " + Transport.class); @@ -302,13 +304,9 @@ public static Transport newAsyncTransport(Transport transport) { } catch (InvocationTargetException e) { throw new InvalidConfig("Could not invoke the static build method of " + transportClass.getName(), e); } catch (NoSuchMethodException e) { - String msg = "The async transport handler should contain a publci static \"build\" method with a single " + // - "parameter of type " + Transport.class + " and returning a new " + Transport.class + " instance."; - throw new InvalidConfig(msg, e); + throw new InvalidConfig(invalidSignature, e); } catch (IllegalAccessException e) { - String msg = "The async transport handler should contain a publci static \"build\" method with a single " + // - "parameter of type " + Transport.class + " and returning a new " + Transport.class + " instance."; - throw new InvalidConfig(msg, e); + throw new InvalidConfig(invalidSignature, e); } } @@ -323,6 +321,17 @@ public static Class register(String scheme, Class diff --git a/raven/src/test/java/net/kencochrane/raven/ClientTest.java b/raven/src/test/java/net/kencochrane/raven/ClientTest.java index b44afff613a..0bc3f8f38fa 100644 --- a/raven/src/test/java/net/kencochrane/raven/ClientTest.java +++ b/raven/src/test/java/net/kencochrane/raven/ClientTest.java @@ -3,7 +3,7 @@ import org.junit.After; import org.junit.Test; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; /** * Test cases for {@link Client}. @@ -58,9 +58,99 @@ public void constructor_specifyDsn() { verifyClient(new Client(dsn, false), Transport.Http.class, false, false); } + @Test + public void newTransport() { + // HTTP + String dsn = "http://public:private@localhost/1"; + Transport transport = Client.newTransport(SentryDsn.build(dsn)); + assertNotNull(transport); + assertTrue(transport instanceof Transport.Http); + + // HTTP with custom transport + Client.register("http", DummyTransport.class); + transport = Client.newTransport(SentryDsn.build(dsn)); + assertNotNull(transport); + assertTrue(transport instanceof DummyTransport); + + // HTTPS + dsn = "https://public:private@localhost/1"; + transport = Client.newTransport(SentryDsn.build(dsn)); + assertNotNull(transport); + assertTrue(transport instanceof Transport.Http); + + // HTTPS with custom transport + Client.register("https", DummyTransport.class); + transport = Client.newTransport(SentryDsn.build(dsn)); + assertNotNull(transport); + assertTrue(transport instanceof DummyTransport); + + // Naive HTTPS + dsn = "naive+https://public:private@localhost/1"; + transport = Client.newTransport(SentryDsn.build(dsn)); + assertNotNull(transport); + assertTrue(transport instanceof Transport.NaiveHttps); + + // Naive HTTPS with custom transport + Client.register("naive+https", DummyTransport.class); + transport = Client.newTransport(SentryDsn.build(dsn)); + assertNotNull(transport); + assertTrue(transport instanceof DummyTransport); + + // UDP + dsn = "udp://public:private@localhost:9000/1"; + transport = Client.newTransport(SentryDsn.build(dsn)); + assertNotNull(transport); + assertTrue(transport instanceof Transport.Udp); + + // UDP with custom transport + Client.register("udp", DummyTransport.class); + transport = Client.newTransport(SentryDsn.build(dsn)); + assertNotNull(transport); + assertTrue(transport instanceof DummyTransport); + + // Custom + dsn = "custom://public:private@localhost:9000/1"; + try { + Client.newTransport(SentryDsn.build(dsn)); + fail("Expected an exception"); + } catch (Client.InvalidConfig e) { + // Ok + } + + Client.register("custom", DummyTransport.class); + transport = Client.newTransport(SentryDsn.build(dsn)); + assertNotNull(transport); + assertTrue(transport instanceof DummyTransport); + + // Async + Client.register("udp", Transport.Udp.class); + dsn = "async+udp://public:private@localhost:9000/1"; + transport = Client.newTransport(SentryDsn.build(dsn)); + assertNotNull(transport); + assertTrue(transport instanceof AsyncTransport); + assertTrue(((AsyncTransport) transport).transport instanceof Transport.Udp); + + // Async with custom transport + Client.register("async", DummyTransport.class); + try { + Client.newTransport(SentryDsn.build(dsn)); + fail("Expected an exception because " + DummyTransport.class + " does not have the right signature"); + } catch (Client.InvalidConfig e) { + // Ok + } + + // Async with valid custom transport + Client.register("async", DummyAsyncTransport.class); + transport = Client.newTransport(SentryDsn.build(dsn)); + assertNotNull(transport); + assertTrue(transport instanceof DummyAsyncTransport); + assertTrue(((DummyAsyncTransport) transport).transport instanceof Transport.Udp); + } + @After public void tearDown() { System.setProperty(Utils.SENTRY_DSN, ""); + Client.registerDefaults(); } protected void verifyClient(Client client, Class transportClass, boolean async) { @@ -80,4 +170,27 @@ protected void verifyClient(Client client, Class transportC } } + protected static class DummyTransport extends Transport { + + public DummyTransport(SentryDsn dsn) { + super(dsn); + } + + } + + protected static class DummyAsyncTransport extends Transport { + + public final Transport transport; + + public DummyAsyncTransport(Transport transport) { + super(transport.dsn); + this.transport = transport; + } + + public static Transport build(Transport transport) { + return new DummyAsyncTransport(transport); + } + + } + } From 5c80b75746277957ae43ab248719cfba1925bc04 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Sun, 1 Jul 2012 20:48:52 +0200 Subject: [PATCH 0047/2152] Add integration test skeleton Introduced the new module raven-integration-tests to test the actual sending of messages to Sentry. --- .gitignore | 3 +- pom.xml | 1 + raven-integration-tests/README.md | 23 +++ raven-integration-tests/pom.xml | 75 ++++++++++ .../java/net/kencochrane/raven/SentryApi.java | 136 ++++++++++++++++++ .../src/main/resources/Procfile | 2 + .../src/main/resources/default_config.py | 49 +++++++ .../src/main/resources/run_sentry.sh | 4 + .../net/kencochrane/raven/ClientTest.java | 107 ++++++++++++++ .../kencochrane/raven/IntegrationContext.java | 32 +++++ .../java/net/kencochrane/raven/Client.java | 12 +- 11 files changed, 440 insertions(+), 4 deletions(-) create mode 100644 raven-integration-tests/README.md create mode 100644 raven-integration-tests/pom.xml create mode 100644 raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java create mode 100644 raven-integration-tests/src/main/resources/Procfile create mode 100644 raven-integration-tests/src/main/resources/default_config.py create mode 100755 raven-integration-tests/src/main/resources/run_sentry.sh create mode 100644 raven-integration-tests/src/test/java/net/kencochrane/raven/ClientTest.java create mode 100644 raven-integration-tests/src/test/java/net/kencochrane/raven/IntegrationContext.java diff --git a/.gitignore b/.gitignore index edd5763ef10..42a0f43729f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ target/* **/target/* .idea/* *.iml -example.log \ No newline at end of file +example.log +raven-integration-tests/src/main/resources/sentry.db \ No newline at end of file diff --git a/pom.xml b/pom.xml index b7a06f93ccc..d699ce98b0f 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,7 @@ raven raven-log4j + raven-integration-tests diff --git a/raven-integration-tests/README.md b/raven-integration-tests/README.md new file mode 100644 index 00000000000..9550237dd63 --- /dev/null +++ b/raven-integration-tests/README.md @@ -0,0 +1,23 @@ +# Raven Integration Tests + +This module provides integration tests for Raven-Java with Sentry. Running these tests is a bit cumbersome, but worth +it. + +## 1. Install Sentry +Make sure you have Sentry installed. Follow the instructions in +[Sentry's documentation](http://sentry.readthedocs.org/en/latest/quickstart/index.html#install-sentry) if you haven't. + +## 2. Install foreman +[Foreman](http://ddollar.github.com/foreman/) allows you to use a Procfile which we can use to run the HTTP and UDP +services of Sentry in the same terminal. + +## 3. Run `src/main/resources/run_sentry.sh` before running the tests +This script will remove any existing `sentry.db` at the *same* location so we can start fresh and then perform a Sentry +upgrade using `default_config.py` as the Sentry configuration. During this process, you will be asked to create a +superuser. Use username and password `test`. + +Once the Sentry installation is finished, the `Procfile` at the same location will be used to kickstart Sentry HTTP and +UDP services at ports 9500 and 9501 respectively. + +## 4. Run the tests +Run the tests like you normally would. \ No newline at end of file diff --git a/raven-integration-tests/pom.xml b/raven-integration-tests/pom.xml new file mode 100644 index 00000000000..b7933d10b6b --- /dev/null +++ b/raven-integration-tests/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + + net.kencochrane + raven-all + 1.0-SNAPSHOT + + raven-integration-tests + jar + raven-integration-tests + Raven-Java/Sentry integration tests + + + + ${project.groupId} + raven + ${project.version} + + + org.apache.httpcomponents + httpclient + 4.1.2 + + + org.jsoup + jsoup + 1.6.3 + + + com.googlecode.json-simple + json-simple + 1.1 + + + com.googlecode.jmockit + jmockit + 0.999.12 + test + + + junit + junit + 4.8.1 + test + + + + + + + maven-assembly-plugin + 2.3 + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + + \ No newline at end of file diff --git a/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java b/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java new file mode 100644 index 00000000000..0affe11b363 --- /dev/null +++ b/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java @@ -0,0 +1,136 @@ +package net.kencochrane.raven; + +import org.apache.commons.lang.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.HttpContext; +import org.apache.http.util.EntityUtils; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +/** + * Sentry tools. + */ +public class SentryApi { + + public final String host; + public final HttpClient client = new DefaultHttpClient(); + public final HttpContext context = new BasicHttpContext(); + private boolean loggedIn; + + public SentryApi() throws MalformedURLException { + this("http://localhost:9500"); + } + + public SentryApi(String host) { + this.host = host; + } + + public boolean login(String username, String password) throws IOException { + if (loggedIn) { + return true; + } + String url = host + "/login/"; + HttpResponse response = client.execute(new HttpGet(url), context); + String html = EntityUtils.toString(response.getEntity()); + Document doc = Jsoup.parse(html); + Elements inputs = doc.select("input[name=csrfmiddlewaretoken]"); + String token = inputs.get(0).val(); + List formparams = new ArrayList(); + formparams.add(new BasicNameValuePair("username", username)); + formparams.add(new BasicNameValuePair("password", password)); + formparams.add(new BasicNameValuePair("csrfmiddlewaretoken", token)); + HttpPost post = new HttpPost(host + "/login/"); + UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, "UTF-8"); + post.setEntity(entity); + response = client.execute(post, context); + if (response.getStatusLine().getStatusCode() == 302) { + EntityUtils.toString(response.getEntity()); + response = client.execute(new HttpGet(url), context); + html = EntityUtils.toString(response.getEntity()); + doc = Jsoup.parse(html); + html = doc.getElementById("header").select("li.dropdown").get(1).html(); + loggedIn = html.contains(">Logout<"); + } + return loggedIn; + } + + public String getDsn(String projectSlug) throws IOException { + HttpResponse response = client.execute(new HttpGet(host + "/account/projects/" + projectSlug + "/docs/")); + String html = EntityUtils.toString(response.getEntity()); + Document doc = Jsoup.parse(html); + Element wrapper = doc.select("#content code.clippy").get(0); + return StringUtils.trim(wrapper.html()); + } + + public boolean clear(String projectId) throws IOException { + HttpResponse response = client.execute(new HttpPost(host + "/api/" + projectId + "/clear/")); + boolean ok = (response.getStatusLine().getStatusCode() == 200); + EntityUtils.toString(response.getEntity()); + return ok; + } + + public List getEvents(String projectSlug) throws IOException { + HttpResponse response = client.execute(new HttpGet(host + "/" + projectSlug)); + Document doc = Jsoup.parse(EntityUtils.toString(response.getEntity())); + Elements items = doc.select("ul#event_list li.event"); + List events = new LinkedList(); + for (Element item : items) { + int count = Integer.parseInt(item.attr("data-count")); + int level = extractLevel(item.classNames()); + Element anchor = item.select("h3 a").get(0); + String link = anchor.attr("href"); + String title = StringUtils.trim(anchor.text()); + Element messageElement = item.select("p.message").get(0); + String message = StringUtils.trim(messageElement.attr("title")); + String logger = StringUtils.trim(messageElement.select("span.tag-logger").text()); + events.add(new Event(count, level, link, title, message, logger)); + } + return events; + } + + protected static int extractLevel(Collection classNames) { + for (String name : classNames) { + if (name.startsWith("level-")) { + return Integer.parseInt(name.replace("level-", "")); + } + } + return 0; + } + + public static class Event { + public final int count; + public final int level; + public final String url; + public final String title; + public final String message; + public final String logger; + + public Event(int count, int level, String url, String title, String message, String logger) { + this.count = count; + this.level = level; + this.url = url; + this.title = title; + this.message = message; + this.logger = logger; + } + + } + +} diff --git a/raven-integration-tests/src/main/resources/Procfile b/raven-integration-tests/src/main/resources/Procfile new file mode 100644 index 00000000000..e999972c151 --- /dev/null +++ b/raven-integration-tests/src/main/resources/Procfile @@ -0,0 +1,2 @@ +web: sentry --config=default_config.py start http +udp: sentry --config=default_config.py start udp \ No newline at end of file diff --git a/raven-integration-tests/src/main/resources/default_config.py b/raven-integration-tests/src/main/resources/default_config.py new file mode 100644 index 00000000000..b6b974479d7 --- /dev/null +++ b/raven-integration-tests/src/main/resources/default_config.py @@ -0,0 +1,49 @@ +import os.path + +CONF_ROOT = os.path.dirname(__file__) + +DATABASES = { + 'default': { + # You can swap out the engine for MySQL easily by changing this value + # to ``django.db.backends.mysql`` or to PostgreSQL with + # ``django.db.backends.postgresql_psycopg2`` + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(CONF_ROOT, 'sentry.db'), + 'USER': 'postgres', + 'PASSWORD': '', + 'HOST': '', + 'PORT': '', + } +} + +SENTRY_KEY = 'dhrlPFFtABltJoCdbQ9b327uY6vufXGOymBZnC348p3Hoeot0K8CTw==' + +# Set this to false to require authentication +SENTRY_PUBLIC = True + +# You should configure the absolute URI to Sentry. It will attempt to guess it if you don't +# but proxies may interfere with this. +# SENTRY_URL_PREFIX = 'http://sentry.example.com' # No trailing slash! + +SENTRY_WEB_HOST = '0.0.0.0' +SENTRY_WEB_PORT = 9500 +SENTRY_WEB_OPTIONS = { + 'workers': 3, # the number of gunicorn workers + # 'worker_class': 'gevent', +} + +# Mail server configuration + +# For more information check Django's documentation: +# https://docs.djangoproject.com/en/1.3/topics/email/?from=olddocs#e-mail-backends + +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' + +EMAIL_HOST = 'localhost' +EMAIL_HOST_PASSWORD = '' +EMAIL_HOST_USER = '' +EMAIL_PORT = 25 +EMAIL_USE_TLS = False + +SENTRY_UDP_HOST = '0.0.0.0' # bind to all addresses +SENTRY_UDP_PORT = 9501 \ No newline at end of file diff --git a/raven-integration-tests/src/main/resources/run_sentry.sh b/raven-integration-tests/src/main/resources/run_sentry.sh new file mode 100755 index 00000000000..0af88b0b56e --- /dev/null +++ b/raven-integration-tests/src/main/resources/run_sentry.sh @@ -0,0 +1,4 @@ +#!/bin/sh +rm -f sentry.db +sentry --config=default_config.py upgrade +foreman start diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/ClientTest.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/ClientTest.java new file mode 100644 index 00000000000..9b0dac1531c --- /dev/null +++ b/raven-integration-tests/src/test/java/net/kencochrane/raven/ClientTest.java @@ -0,0 +1,107 @@ +package net.kencochrane.raven; + +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Integration tests for {@link Client}. + */ +public class ClientTest { + + private SentryApi api; + + @Test + public void captureMessage_http() throws IOException, InterruptedException { + Client client = new Client(IntegrationContext.httpDsn); + String message = ClientTest.class.getName() + ".captureMessage_http says hi!"; + captureMessage(client, message, false); + } + + @Test + public void captureMessage_udp() throws IOException, InterruptedException { + Client client = new Client(IntegrationContext.udpDsn); + String message = ClientTest.class.getName() + ".captureMessage_udp says hi!"; + captureMessage(client, message, true); + } + + @Test + public void captureMessage_complex_http() throws IOException { + Client client = new Client(IntegrationContext.httpDsn); + final String message = ClientTest.class.getName() + ".captureMessage_complex_http says hi!"; + final String logger = "some.custom.logger.Name"; + final String culprit = "Damn you!"; + client.captureMessage(message, null, logger, null, culprit); + + List events = api.getEvents(IntegrationContext.projectSlug); + assertEquals(1, events.size()); + SentryApi.Event event = events.get(0); + assertTrue(event.count > 0); + assertEquals(Client.Default.LOG_LEVEL, event.level); + assertEquals(message, event.message); + assertEquals(culprit, event.title); + assertEquals(logger, event.logger); + } + + @Test + public void captureMessage_complex_udp() throws IOException, InterruptedException { + Client client = new Client(IntegrationContext.udpDsn); + final String message = ClientTest.class.getName() + ".captureMessage_complex_udp says hi!"; + final String logger = "some.custom.logger.Name"; + final String culprit = "Damn you!"; + client.captureMessage(message, null, logger, null, culprit); + Thread.sleep(1000); + + List events = api.getEvents(IntegrationContext.projectSlug); + assertEquals(1, events.size()); + SentryApi.Event event = events.get(0); + assertTrue(event.count > 0); + assertEquals(Client.Default.LOG_LEVEL, event.level); + assertEquals(message, event.message); + assertEquals(culprit, event.title); + assertEquals(logger, event.logger); + } + + protected void captureMessage(Client client, String message, boolean wait) throws IOException, InterruptedException { + client.captureMessage(message); + if (wait) { + // Wait a bit in case of UDP transport + Thread.sleep(1000); + } + List events = api.getEvents(IntegrationContext.projectSlug); + assertEquals(1, events.size()); + SentryApi.Event event = events.get(0); + assertTrue(event.count > 0); + assertEquals(Client.Default.LOG_LEVEL, event.level); + assertEquals(message, event.message); + assertEquals(message, event.title); + assertEquals(Client.Default.LOGGER, event.logger); + + // Log the same message; the count should be incremented + client.captureMessage(message); + if (wait) { + Thread.sleep(1000); + } + events = api.getEvents(IntegrationContext.projectSlug); + assertEquals(1, events.size()); + SentryApi.Event newEvent = events.get(0); + assertEquals(event.count + 1, newEvent.count); + assertEquals(Client.Default.LOG_LEVEL, event.level); + assertEquals(message, event.message); + assertEquals(message, event.title); + assertEquals(Client.Default.LOGGER, event.logger); + } + + @Before + public void setUp() throws IOException { + IntegrationContext.init(); + api = IntegrationContext.api; + api.clear(IntegrationContext.httpDsn.projectId); + } + +} diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/IntegrationContext.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/IntegrationContext.java new file mode 100644 index 00000000000..6df2bb9fc9e --- /dev/null +++ b/raven-integration-tests/src/test/java/net/kencochrane/raven/IntegrationContext.java @@ -0,0 +1,32 @@ +package net.kencochrane.raven; + +import java.io.IOException; + +/** + * Context for integration tests. + */ +public class IntegrationContext { + + public static String host = "http://localhost"; + public static int port = 9500; + public static int udpPort = 9501; + public static SentryApi api; + public static SentryDsn httpDsn; + public static SentryDsn udpDsn; + public static String projectSlug = "default"; + protected static boolean initialized = false; + + public static void init() throws IOException { + api = new SentryApi(host + ":" + port); + boolean loggedOn = api.login("test", "test"); + if (!loggedOn) { + throw new RuntimeException("Could not log on"); + } + String dsn = api.getDsn(projectSlug); + httpDsn = SentryDsn.build(dsn, null, null); + udpDsn = SentryDsn.build(dsn.replace("http://", "udp://").replace(":" + port, ":" + udpPort), null, null); + System.out.println("UDP:" + udpDsn); + initialized = true; + } + +} diff --git a/raven/src/main/java/net/kencochrane/raven/Client.java b/raven/src/main/java/net/kencochrane/raven/Client.java index cd5be9684c1..afe8df71f11 100644 --- a/raven/src/main/java/net/kencochrane/raven/Client.java +++ b/raven/src/main/java/net/kencochrane/raven/Client.java @@ -45,6 +45,12 @@ */ public class Client { + public interface Default { + String LOGGER = "root"; + int LOG_LEVEL = Events.LogLevel.ERROR.intValue; + String EMPTY_MESSAGE = "(empty)"; + } + /** * Async transport layers require some extra work when instantiating. */ @@ -200,15 +206,15 @@ protected Message buildMessage(String message, String timestamp, String loggerCl } if (message == null) { message = (exception == null ? null : exception.getMessage()); - message = (message == null ? "(empty)" : message); + message = (message == null ? Default.EMPTY_MESSAGE : message); } obj.put("event_id", eventId); obj.put("checksum", calculateChecksum(message)); obj.put("timestamp", timestamp); obj.put("message", message); obj.put("project", dsn.projectId); - obj.put("level", logLevel == null ? Events.LogLevel.ERROR.intValue : logLevel); - obj.put("logger", loggerClass == null ? "root" : loggerClass); + obj.put("level", logLevel == null ? Default.LOG_LEVEL : logLevel); + obj.put("logger", loggerClass == null ? Default.LOGGER : loggerClass); obj.put("server_name", Utils.hostname()); return new Message(obj, eventId); } From 60594ba03b218c983c09184bab5cbca39fd41a2a Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Thu, 5 Jul 2012 21:42:37 +0200 Subject: [PATCH 0048/2152] Add unit test for SentryAppender for log4j --- .../raven/{ => integration}/ClientTest.java | 7 +++++-- .../{ => integration}/IntegrationContext.java | 5 ++++- .../integration/log4j/SentryAppenderTest.java | 10 ++++++++++ .../resources/sentryappender.log4j.properties | 20 +++++++++++++++++++ .../resources/sentryappender.log4j.properties | 0 5 files changed, 39 insertions(+), 3 deletions(-) rename raven-integration-tests/src/test/java/net/kencochrane/raven/{ => integration}/ClientTest.java (94%) rename raven-integration-tests/src/test/java/net/kencochrane/raven/{ => integration}/IntegrationContext.java (88%) create mode 100644 raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java create mode 100644 raven-integration-tests/src/test/resources/sentryappender.log4j.properties create mode 100644 raven-log4j/src/test/resources/sentryappender.log4j.properties diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/ClientTest.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java similarity index 94% rename from raven-integration-tests/src/test/java/net/kencochrane/raven/ClientTest.java rename to raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java index 9b0dac1531c..e66111bccb9 100644 --- a/raven-integration-tests/src/test/java/net/kencochrane/raven/ClientTest.java +++ b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java @@ -1,5 +1,8 @@ -package net.kencochrane.raven; +package net.kencochrane.raven.integration; +import net.kencochrane.raven.Client; +import net.kencochrane.raven.IntegrationContext; +import net.kencochrane.raven.SentryApi; import org.junit.Before; import org.junit.Test; @@ -10,7 +13,7 @@ import static org.junit.Assert.assertTrue; /** - * Integration tests for {@link Client}. + * Integration tests for {@link net.kencochrane.raven.Client}. */ public class ClientTest { diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/IntegrationContext.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/IntegrationContext.java similarity index 88% rename from raven-integration-tests/src/test/java/net/kencochrane/raven/IntegrationContext.java rename to raven-integration-tests/src/test/java/net/kencochrane/raven/integration/IntegrationContext.java index 6df2bb9fc9e..7905fa5eb3b 100644 --- a/raven-integration-tests/src/test/java/net/kencochrane/raven/IntegrationContext.java +++ b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/IntegrationContext.java @@ -1,4 +1,7 @@ -package net.kencochrane.raven; +package net.kencochrane.raven.integration; + +import net.kencochrane.raven.SentryApi; +import net.kencochrane.raven.SentryDsn; import java.io.IOException; diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java new file mode 100644 index 00000000000..80f3f922eae --- /dev/null +++ b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java @@ -0,0 +1,10 @@ +package net.kencochrane.raven.integration.log4j; + +/** + * Integration tests for {@link net.kencochrane.raven.log4j.SentryAppender}. + */ +public class SentryAppenderTest { + + // TODO Integration test + +} diff --git a/raven-integration-tests/src/test/resources/sentryappender.log4j.properties b/raven-integration-tests/src/test/resources/sentryappender.log4j.properties new file mode 100644 index 00000000000..b5dc9cb270a --- /dev/null +++ b/raven-integration-tests/src/test/resources/sentryappender.log4j.properties @@ -0,0 +1,20 @@ +# Basic setup +log4j.rootLogger=trace, stdout, sentry + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + +# Pattern to output the caller's file name and line number. +log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n + +# Configure the Sentry appender. Note: when a SENTRY_DSN environment variable +# or system property is available, that value will be used instead of the value +# of log4j.appender.sentry.sentryDsn +log4j.appender.sentry=net.kencochrane.raven.log4j.SentryAppender +log4j.appender.sentry.sentryDsn=SENTRY_DSN + +# Do not let the Raven logging go to Sentry or you could end up in an infinite loop when +# something goes wrong with your configuration. Sentry uses the java.util.logging package +# so this should only happen when you add a bridge from JUL to Log4J. +log4j.logger.raven=DEBUG, stdout +log4j.additivity.raven=false diff --git a/raven-log4j/src/test/resources/sentryappender.log4j.properties b/raven-log4j/src/test/resources/sentryappender.log4j.properties new file mode 100644 index 00000000000..e69de29bb2d From 3772daed861b64d2ed8e7638a57741f090658a57 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Thu, 5 Jul 2012 21:42:57 +0200 Subject: [PATCH 0049/2152] Add unit test for SentryAppender for log4j --- raven-integration-tests/pom.xml | 5 + .../raven/integration/ClientTest.java | 1 - raven-log4j/pom.xml | 12 ++ .../raven/log4j/SentryAppenderTest.java | 151 +++++++++++++++++- .../resources/sentryappender.log4j.properties | 20 +++ 5 files changed, 187 insertions(+), 2 deletions(-) diff --git a/raven-integration-tests/pom.xml b/raven-integration-tests/pom.xml index b7933d10b6b..84527058232 100644 --- a/raven-integration-tests/pom.xml +++ b/raven-integration-tests/pom.xml @@ -20,6 +20,11 @@ raven ${project.version} + + ${project.groupId} + raven-log4j + ${project.version} + org.apache.httpcomponents httpclient diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java index e66111bccb9..09c7ece3882 100644 --- a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java +++ b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java @@ -1,7 +1,6 @@ package net.kencochrane.raven.integration; import net.kencochrane.raven.Client; -import net.kencochrane.raven.IntegrationContext; import net.kencochrane.raven.SentryApi; import org.junit.Before; import org.junit.Test; diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 749dfa741ee..0e87cc12029 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -25,6 +25,18 @@ log4j 1.2.16 + + com.googlecode.jmockit + jmockit + 0.999.12 + test + + + junit + junit + 4.8.1 + test + diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index ffb9ca7a262..7cc7824ea75 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -1,10 +1,159 @@ package net.kencochrane.raven.log4j; +import net.kencochrane.raven.Utils; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.PropertyConfigurator; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; +import java.net.SocketException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + /** * Test cases for {@link SentryAppender}. */ public class SentryAppenderTest { - // TODO Test duh + private static DatagramSocket serverSocket; + private static String host = "localhost"; + private static int port = 9505; + + @BeforeClass + public static void beforeClass() throws SocketException { + serverSocket = new DatagramSocket(new InetSocketAddress(host, port)); + } + + @AfterClass + public static void afterClass() throws SocketException { + System.setProperty(Utils.SENTRY_DSN, ""); + serverSocket.close(); + } + + @Test + public void debugLevel() throws IOException, ParseException { + final String loggerName = "omg.logger"; + final long logLevel = (long) Level.DEBUG_INT / 1000; + final String projectId = "1"; + final String message = "hi there!"; + + // Log + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", host, port, projectId)); + PropertyConfigurator.configure(getClass().getResource("/sentryappender.log4j.properties")); + Logger.getLogger(loggerName).debug(message); + + // Verify log message + verifyMessage(loggerName, logLevel, projectId, message); + } + + @Test + public void infoLevel() throws IOException, ParseException { + final String loggerName = "dude.wheres.my.ride"; + final long logLevel = (long) Level.INFO_INT / 1000; + final String projectId = "2"; + final String message = "This message will self-destruct in 5...4...3..."; + + // Log + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", host, port, projectId)); + PropertyConfigurator.configure(getClass().getResource("/sentryappender.log4j.properties")); + Logger.getLogger(loggerName).info(message); + + // And verify + verifyMessage(loggerName, logLevel, projectId, message); + } + + @Test + public void warnLevel() throws IOException, ParseException { + final String loggerName = "org.apache.commons.httpclient.andStuff"; + final long logLevel = (long) Level.WARN_INT / 1000; + final String projectId = "20"; + final String message = "Warning! Warning! WARNING! Oh, come on!"; + + // Log + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", host, port, projectId)); + PropertyConfigurator.configure(getClass().getResource("/sentryappender.log4j.properties")); + Logger.getLogger(loggerName).warn(message); + + // And verify + verifyMessage(loggerName, logLevel, projectId, message); + } + + @Test + public void errorLevel() throws IOException, ParseException { + final String loggerName = "org.apache.commons.httpclient.andStuff"; + final long logLevel = (long) Level.ERROR_INT / 1000; + final String projectId = "5"; + final String message = "D'oh!"; + + // Log + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", host, port, projectId)); + PropertyConfigurator.configure(getClass().getResource("/sentryappender.log4j.properties")); + Logger.getLogger(loggerName).error(message); + + // Verify + verifyMessage(loggerName, logLevel, projectId, message); + } + + @Test + public void errorLevel_withException() throws IOException, ParseException { + final String loggerName = "org.apache.commons.httpclient.andStuff"; + // When an exception is logged, the culprit should be the class+method where the exception occurred + final String culprit = this.getClass().getName() + ".errorLevel_withException"; + final long logLevel = (long) Level.ERROR_INT / 1000; + final String projectId = "5"; + final String message = "D'oh!"; + + // Log + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", host, port, projectId)); + PropertyConfigurator.configure(getClass().getResource("/sentryappender.log4j.properties")); + NullPointerException npe = new NullPointerException("Damn you!"); + Logger.getLogger(loggerName).error(message, npe); + + // Verify + JSONObject json = verifyMessage(culprit, logLevel, projectId, message); + JSONObject stacktrace = (JSONObject) json.get("sentry.interfaces.Stacktrace"); + assertNotNull(stacktrace); + assertNotNull(stacktrace.get("frames")); + JSONArray frames = (JSONArray) stacktrace.get("frames"); + assertTrue(frames.size() > 0); + JSONObject exception = (JSONObject) json.get("sentry.interfaces.Exception"); + assertNotNull(exception); + assertEquals(NullPointerException.class.getSimpleName(), exception.get("type")); + assertEquals(npe.getMessage(), exception.get("value")); + assertEquals(NullPointerException.class.getPackage().getName(), exception.get("module")); + } + + protected JSONObject verifyMessage(String culprit, long logLevel, String projectId, String message) throws IOException, ParseException { + String payload = Utils.fromUtf8(fetchMessage()); + String[] payloadParts = StringUtils.split(payload, "\n\n"); + assertEquals(2, payloadParts.length); + String raw = Utils.fromUtf8(Base64.decodeBase64(payloadParts[1])); + JSONObject json = (JSONObject) new JSONParser().parse(raw); + assertEquals(message, json.get("message")); + assertEquals(culprit, json.get("culprit")); + assertEquals(projectId, json.get("project")); + assertEquals(logLevel, json.get("level")); + return json; + } + + protected static byte[] fetchMessage() throws IOException { + DatagramPacket packet = new DatagramPacket(new byte[5048], 5048); + serverSocket.receive(packet); + return packet.getData(); + } } diff --git a/raven-log4j/src/test/resources/sentryappender.log4j.properties b/raven-log4j/src/test/resources/sentryappender.log4j.properties index e69de29bb2d..b5dc9cb270a 100644 --- a/raven-log4j/src/test/resources/sentryappender.log4j.properties +++ b/raven-log4j/src/test/resources/sentryappender.log4j.properties @@ -0,0 +1,20 @@ +# Basic setup +log4j.rootLogger=trace, stdout, sentry + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + +# Pattern to output the caller's file name and line number. +log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n + +# Configure the Sentry appender. Note: when a SENTRY_DSN environment variable +# or system property is available, that value will be used instead of the value +# of log4j.appender.sentry.sentryDsn +log4j.appender.sentry=net.kencochrane.raven.log4j.SentryAppender +log4j.appender.sentry.sentryDsn=SENTRY_DSN + +# Do not let the Raven logging go to Sentry or you could end up in an infinite loop when +# something goes wrong with your configuration. Sentry uses the java.util.logging package +# so this should only happen when you add a bridge from JUL to Log4J. +log4j.logger.raven=DEBUG, stdout +log4j.additivity.raven=false From 1cc664450a1f911fa6d4c68d602989798a464ad0 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Thu, 5 Jul 2012 21:48:19 +0200 Subject: [PATCH 0050/2152] Fix SentryAppenderTest: bigger buffer for the mocked server UDP socket --- .../java/net/kencochrane/raven/log4j/SentryAppenderTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 7cc7824ea75..fccb885d50e 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -151,7 +151,7 @@ protected JSONObject verifyMessage(String culprit, long logLevel, String project } protected static byte[] fetchMessage() throws IOException { - DatagramPacket packet = new DatagramPacket(new byte[5048], 5048); + DatagramPacket packet = new DatagramPacket(new byte[10000], 10000); serverSocket.receive(packet); return packet.getData(); } From 155bae8287fc8b9f987f7d3e16bd7030cbfeb771 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Sat, 7 Jul 2012 12:21:18 +0200 Subject: [PATCH 0051/2152] Remove raven-integration-tests as a module Otherwise the raven-integration-tests tests would be run on each build at the project level. Because those tests require a particular Sentry setup, this is something that would become a serious PITA eventually. --- pom.xml | 1 - raven-integration-tests/pom.xml | 69 ++++++++++++++----- .../raven/log4j/AsyncSentryAppenderTest.java | 11 +++ .../asyncsentryappender.log4j.properties | 20 ++++++ 4 files changed, 81 insertions(+), 20 deletions(-) create mode 100644 raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java create mode 100644 raven-log4j/src/test/resources/asyncsentryappender.log4j.properties diff --git a/pom.xml b/pom.xml index d699ce98b0f..b7a06f93ccc 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,6 @@ raven raven-log4j - raven-integration-tests diff --git a/raven-integration-tests/pom.xml b/raven-integration-tests/pom.xml index 84527058232..c9a5d16e9fc 100644 --- a/raven-integration-tests/pom.xml +++ b/raven-integration-tests/pom.xml @@ -4,12 +4,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - net.kencochrane - raven-all - 1.0-SNAPSHOT - + net.kencochrane raven-integration-tests + 1.0-SNAPSHOT jar raven-integration-tests Raven-Java/Sentry integration tests @@ -57,24 +54,58 @@ - maven-assembly-plugin - 2.3 + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 - - jar-with-dependencies - + 1.6 + 1.6 + UTF-8 + + + + org.apache.maven.plugins + maven-resources-plugin + 2.5 + + UTF-8 + - - - make-assembly - package - - single - - - + + + + ${basedir}/src/main/java + + **/*.json + **/*.yml + + + + ${basedir}/src/main/resources + + **/*.* + + + + + + + ${basedir}/src/test/java + + **/*.json + **/*.yml + **/*.txt + + + + ${basedir}/src/test/resources + + **/*.* + + + \ No newline at end of file diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java new file mode 100644 index 00000000000..f140b47127c --- /dev/null +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java @@ -0,0 +1,11 @@ +package net.kencochrane.raven.log4j; + +/** + * Created by IntelliJ IDEA. + * User: kevin + * Date: 07/07/12 + * Time: 11:39 + * To change this template use File | Settings | File Templates. + */ +public class AsyncSentryAppenderTest { +} diff --git a/raven-log4j/src/test/resources/asyncsentryappender.log4j.properties b/raven-log4j/src/test/resources/asyncsentryappender.log4j.properties new file mode 100644 index 00000000000..b5dc9cb270a --- /dev/null +++ b/raven-log4j/src/test/resources/asyncsentryappender.log4j.properties @@ -0,0 +1,20 @@ +# Basic setup +log4j.rootLogger=trace, stdout, sentry + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + +# Pattern to output the caller's file name and line number. +log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n + +# Configure the Sentry appender. Note: when a SENTRY_DSN environment variable +# or system property is available, that value will be used instead of the value +# of log4j.appender.sentry.sentryDsn +log4j.appender.sentry=net.kencochrane.raven.log4j.SentryAppender +log4j.appender.sentry.sentryDsn=SENTRY_DSN + +# Do not let the Raven logging go to Sentry or you could end up in an infinite loop when +# something goes wrong with your configuration. Sentry uses the java.util.logging package +# so this should only happen when you add a bridge from JUL to Log4J. +log4j.logger.raven=DEBUG, stdout +log4j.additivity.raven=false From ab9cf224e03860d93a13d9bbaa14c939353956a2 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Sat, 7 Jul 2012 12:22:16 +0200 Subject: [PATCH 0052/2152] Add tests for raven-log4j's AsyncSentryAppender And make sure the Log4J appenders are initialized properly, even when no sentryDsn property is set. --- .../integration/log4j/SentryAppenderTest.java | 6 + .../raven/log4j/AsyncSentryAppender.java | 43 ++++-- .../raven/log4j/SentryAppender.java | 22 ++- .../raven/log4j/AsyncSentryAppenderTest.java | 143 +++++++++++++++++- .../raven/log4j/SentryAppenderTest.java | 72 ++++++--- .../asyncsentryappender.log4j.properties | 2 +- 6 files changed, 246 insertions(+), 42 deletions(-) diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java index 80f3f922eae..1058eb167f4 100644 --- a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java +++ b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java @@ -1,10 +1,16 @@ package net.kencochrane.raven.integration.log4j; +import org.junit.Test; + /** * Integration tests for {@link net.kencochrane.raven.log4j.SentryAppender}. */ public class SentryAppenderTest { // TODO Integration test + @Test + public void test() { + System.out.println("Hi"); + } } diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java index 62ca4e68cd5..d8638af79d6 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java @@ -1,9 +1,17 @@ package net.kencochrane.raven.log4j; +import net.kencochrane.raven.SentryDsn; import org.apache.log4j.AsyncAppender; +import org.apache.log4j.spi.LoggingEvent; /** - * TODO Check if this works + * A Log4J appender that sends events asynchronously to Sentry. + *

    + * This appender extends Log4J's {@link AsyncAppender}. If you use a log4j.xml file to configure Log4J, you can use that + * class directly to wrap a {@link SentryAppender}. If you use a log4j.properties file -- which doesn't provide a way to + * configure the {@link AsyncAppender} -- to configure Log4J and you need to send events asynchronously, you can use + * this class instead. + *

    */ public class AsyncSentryAppender extends AsyncAppender { @@ -15,20 +23,29 @@ public String getSentryDsn() { } public void setSentryDsn(String sentryDsn) { - synchronized (this) { - this.sentryDsn = sentryDsn; - if (appender != null) { - removeAppender(appender); + System.out.println("Oh come on!"); + this.sentryDsn = sentryDsn; + if (appender != null) { + removeAppender(appender); + } + SentryAppender appender = new SentryAppender(); + appender.setSentryDsn(sentryDsn); + appender.setErrorHandler(this.getErrorHandler()); + appender.setLayout(this.getLayout()); + appender.setName(this.getName()); + appender.setThreshold(this.getThreshold()); + this.appender = appender; + addAppender(appender); + } + + @Override + public void append(LoggingEvent event) { + if (appender == null) { + synchronized (this) { + setSentryDsn(SentryDsn.build().toString()); } - SentryAppender appender = new SentryAppender(); - appender.setSentryDsn(sentryDsn); - appender.setErrorHandler(this.getErrorHandler()); - appender.setLayout(this.getLayout()); - appender.setName(this.getName()); - appender.setThreshold(this.getThreshold()); - this.appender = appender; - addAppender(appender); } + super.append(event); } } diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 7a8418e24c7..7a692d5f238 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -24,13 +24,16 @@ public void setSentryDsn(String sentryDsn) { if (client != null) { client.stop(); } + // Create a client that start automatically client = new Client(SentryDsn.build(sentryDsn)); } } @Override public void close() { - client.stop(); + if (client != null) { + client.stop(); + } } @Override @@ -40,6 +43,7 @@ public boolean requiresLayout() { @Override protected void append(LoggingEvent event) { + Client client = fetchClient(); // get timestamp and timestamp in correct string format. long timestamp = event.getTimeStamp(); @@ -59,4 +63,20 @@ protected void append(LoggingEvent event) { client.captureException(message, timestamp, logger, level, culprit, info.getThrowable()); } } + + /** + * Use this to refer to the client instead of using {@link #client} directly. + *

    + * This makes sure a client is available. + *

    + * + * @return the client + */ + protected synchronized Client fetchClient() { + if (client == null) { + client = new Client(); + } + return client; + } + } diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java index f140b47127c..7f3f525f03d 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java @@ -1,11 +1,144 @@ package net.kencochrane.raven.log4j; +import net.kencochrane.raven.Utils; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.PropertyConfigurator; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.ParseException; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.net.SocketException; + +import static org.junit.Assert.*; + /** - * Created by IntelliJ IDEA. - * User: kevin - * Date: 07/07/12 - * Time: 11:39 - * To change this template use File | Settings | File Templates. + * Test cases for {@link AsyncSentryAppender}. */ public class AsyncSentryAppenderTest { + + protected static SentryAppenderTest.SentryMock sentry; + + @BeforeClass + public static void beforeClass() throws SocketException { + sentry = new SentryAppenderTest.SentryMock(); + } + + @AfterClass + public static void afterClass() throws SocketException { + System.setProperty(Utils.SENTRY_DSN, ""); + sentry.stop(); + } + + @Test + public void debugLevel() throws IOException, ParseException { + final String loggerName = "omg.logger"; + final long logLevel = (long) Level.DEBUG_INT / 1000; + final String projectId = "1"; + final String message = "hi there!"; + + // Log + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + configureLog4J(); + Logger.getLogger(loggerName).debug(message); + + // Verify log message + verifyMessage(loggerName, logLevel, projectId, message); + } + + @Test + public void infoLevel() throws IOException, ParseException { + final String loggerName = "dude.wheres.my.ride"; + final long logLevel = (long) Level.INFO_INT / 1000; + final String projectId = "2"; + final String message = "This message will self-destruct in 5...4...3..."; + + // Log + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + configureLog4J(); + Logger.getLogger(loggerName).info(message); + + // And verify + verifyMessage(loggerName, logLevel, projectId, message); + } + + @Test + public void warnLevel() throws IOException, ParseException { + final String loggerName = "org.apache.commons.httpclient.andStuff"; + final long logLevel = (long) Level.WARN_INT / 1000; + final String projectId = "20"; + final String message = "Warning! Warning! WARNING! Oh, come on!"; + + // Log + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + configureLog4J(); + Logger.getLogger(loggerName).warn(message); + + // And verify + verifyMessage(loggerName, logLevel, projectId, message); + } + + @Test + public void errorLevel() throws IOException, ParseException { + final String loggerName = "org.apache.commons.httpclient.andStuff"; + final long logLevel = (long) Level.ERROR_INT / 1000; + final String projectId = "5"; + final String message = "D'oh!"; + + // Log + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + configureLog4J(); + Logger.getLogger(loggerName).error(message); + + // Verify + verifyMessage(loggerName, logLevel, projectId, message); + } + + @Test + public void errorLevel_withException() throws IOException, ParseException { + final String loggerName = "org.apache.commons.httpclient.andStuff"; + // When an exception is logged, the culprit should be the class+method where the exception occurred + final String culprit = getClass().getName() + ".errorLevel_withException"; + final long logLevel = (long) Level.ERROR_INT / 1000; + final String projectId = "5"; + final String message = "D'oh!"; + + // Log + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + configureLog4J(); + NullPointerException npe = new NullPointerException("Damn you!"); + Logger.getLogger(loggerName).error(message, npe); + + // Verify + JSONObject json = verifyMessage(culprit, logLevel, projectId, message); + JSONObject stacktrace = (JSONObject) json.get("sentry.interfaces.Stacktrace"); + assertNotNull(stacktrace); + assertNotNull(stacktrace.get("frames")); + JSONArray frames = (JSONArray) stacktrace.get("frames"); + assertTrue(frames.size() > 0); + JSONObject exception = (JSONObject) json.get("sentry.interfaces.Exception"); + assertNotNull(exception); + assertEquals(NullPointerException.class.getSimpleName(), exception.get("type")); + assertEquals(npe.getMessage(), exception.get("value")); + assertEquals(NullPointerException.class.getPackage().getName(), exception.get("module")); + } + + protected void configureLog4J() { + PropertyConfigurator.configure(getClass().getResource("/asyncsentryappender.log4j.properties")); + } + + + protected JSONObject verifyMessage(String loggerName, long logLevel, String projectId, String message) throws IOException, ParseException { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return SentryAppenderTest.verifyMessage(sentry, loggerName, logLevel, projectId, message); + } + } diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index fccb885d50e..963a4359a50 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -29,19 +29,17 @@ */ public class SentryAppenderTest { - private static DatagramSocket serverSocket; - private static String host = "localhost"; - private static int port = 9505; + protected static SentryMock sentry; @BeforeClass public static void beforeClass() throws SocketException { - serverSocket = new DatagramSocket(new InetSocketAddress(host, port)); + sentry = new SentryMock(); } @AfterClass public static void afterClass() throws SocketException { System.setProperty(Utils.SENTRY_DSN, ""); - serverSocket.close(); + sentry.stop(); } @Test @@ -52,8 +50,8 @@ public void debugLevel() throws IOException, ParseException { final String message = "hi there!"; // Log - System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", host, port, projectId)); - PropertyConfigurator.configure(getClass().getResource("/sentryappender.log4j.properties")); + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + configureLog4J(); Logger.getLogger(loggerName).debug(message); // Verify log message @@ -68,8 +66,8 @@ public void infoLevel() throws IOException, ParseException { final String message = "This message will self-destruct in 5...4...3..."; // Log - System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", host, port, projectId)); - PropertyConfigurator.configure(getClass().getResource("/sentryappender.log4j.properties")); + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + configureLog4J(); Logger.getLogger(loggerName).info(message); // And verify @@ -84,8 +82,8 @@ public void warnLevel() throws IOException, ParseException { final String message = "Warning! Warning! WARNING! Oh, come on!"; // Log - System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", host, port, projectId)); - PropertyConfigurator.configure(getClass().getResource("/sentryappender.log4j.properties")); + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + configureLog4J(); Logger.getLogger(loggerName).warn(message); // And verify @@ -100,8 +98,8 @@ public void errorLevel() throws IOException, ParseException { final String message = "D'oh!"; // Log - System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", host, port, projectId)); - PropertyConfigurator.configure(getClass().getResource("/sentryappender.log4j.properties")); + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + configureLog4J(); Logger.getLogger(loggerName).error(message); // Verify @@ -112,14 +110,14 @@ public void errorLevel() throws IOException, ParseException { public void errorLevel_withException() throws IOException, ParseException { final String loggerName = "org.apache.commons.httpclient.andStuff"; // When an exception is logged, the culprit should be the class+method where the exception occurred - final String culprit = this.getClass().getName() + ".errorLevel_withException"; + final String culprit = getClass().getName() + ".errorLevel_withException"; final long logLevel = (long) Level.ERROR_INT / 1000; final String projectId = "5"; final String message = "D'oh!"; // Log - System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", host, port, projectId)); - PropertyConfigurator.configure(getClass().getResource("/sentryappender.log4j.properties")); + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + configureLog4J(); NullPointerException npe = new NullPointerException("Damn you!"); Logger.getLogger(loggerName).error(message, npe); @@ -137,8 +135,17 @@ public void errorLevel_withException() throws IOException, ParseException { assertEquals(NullPointerException.class.getPackage().getName(), exception.get("module")); } - protected JSONObject verifyMessage(String culprit, long logLevel, String projectId, String message) throws IOException, ParseException { - String payload = Utils.fromUtf8(fetchMessage()); + protected void configureLog4J() { + PropertyConfigurator.configure(getClass().getResource("/sentryappender.log4j.properties")); + } + + protected + JSONObject verifyMessage(String culprit, long logLevel, String projectId, String message) throws IOException, ParseException { + return verifyMessage(sentry, culprit, logLevel, projectId, message); + } + + protected static JSONObject verifyMessage(SentryMock sentry, String culprit, long logLevel, String projectId, String message) throws IOException, ParseException { + String payload = Utils.fromUtf8(sentry.fetchMessage()); String[] payloadParts = StringUtils.split(payload, "\n\n"); assertEquals(2, payloadParts.length); String raw = Utils.fromUtf8(Base64.decodeBase64(payloadParts[1])); @@ -150,10 +157,31 @@ protected JSONObject verifyMessage(String culprit, long logLevel, String project return json; } - protected static byte[] fetchMessage() throws IOException { - DatagramPacket packet = new DatagramPacket(new byte[10000], 10000); - serverSocket.receive(packet); - return packet.getData(); + public static class SentryMock { + public final DatagramSocket serverSocket; + public final String host; + public final int port; + + public SentryMock() throws SocketException { + this("localhost", 9505); + } + + public SentryMock(String host, int port) throws SocketException { + this.host = host; + this.port = port; + serverSocket = new DatagramSocket(new InetSocketAddress(host, port)); + } + + public void stop() { + serverSocket.close(); + } + + public byte[] fetchMessage() throws IOException { + DatagramPacket packet = new DatagramPacket(new byte[10000], 10000); + serverSocket.receive(packet); + return packet.getData(); + } + } } diff --git a/raven-log4j/src/test/resources/asyncsentryappender.log4j.properties b/raven-log4j/src/test/resources/asyncsentryappender.log4j.properties index b5dc9cb270a..e05a6e53abf 100644 --- a/raven-log4j/src/test/resources/asyncsentryappender.log4j.properties +++ b/raven-log4j/src/test/resources/asyncsentryappender.log4j.properties @@ -10,7 +10,7 @@ log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n # Configure the Sentry appender. Note: when a SENTRY_DSN environment variable # or system property is available, that value will be used instead of the value # of log4j.appender.sentry.sentryDsn -log4j.appender.sentry=net.kencochrane.raven.log4j.SentryAppender +log4j.appender.sentry=net.kencochrane.raven.log4j.AsyncSentryAppender log4j.appender.sentry.sentryDsn=SENTRY_DSN # Do not let the Raven logging go to Sentry or you could end up in an infinite loop when From 36dc62c6cc5b667dbee61c5bc645c7251d9d6082 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Sat, 7 Jul 2012 12:56:07 +0200 Subject: [PATCH 0053/2152] Add support for tags --- .../java/net/kencochrane/raven/SentryApi.java | 3 ++ .../raven/integration/ClientTest.java | 22 +++++++++++++-- .../java/net/kencochrane/raven/Client.java | 28 +++++++++++++++++-- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java b/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java index 0affe11b363..34a99ec18b9 100644 --- a/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java +++ b/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java @@ -92,6 +92,9 @@ public List getEvents(String projectSlug) throws IOException { Elements items = doc.select("ul#event_list li.event"); List events = new LinkedList(); for (Element item : items) { + if (item.hasClass("resolved")) { + continue; + } int count = Integer.parseInt(item.attr("data-count")); int level = extractLevel(item.classNames()); Element anchor = item.select("h3 a").get(0); diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java index 09c7ece3882..77738b5f120 100644 --- a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java +++ b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java @@ -6,6 +6,8 @@ import org.junit.Test; import java.io.IOException; +import java.util.Map; +import java.util.HashMap; import java.util.List; import static org.junit.Assert.assertEquals; @@ -25,6 +27,16 @@ public void captureMessage_http() throws IOException, InterruptedException { captureMessage(client, message, false); } + @Test + public void captureMessage_withTags() throws IOException, InterruptedException { + Client client = new Client(IntegrationContext.httpDsn); + String message = ClientTest.class.getName() + ".captureMessage_withTags says hi!"; + Map tags = new HashMap(); + tags.put("release", "1.2.0"); + tags.put("uptime", 60000L); + captureMessage(client, message, false, tags); + } + @Test public void captureMessage_udp() throws IOException, InterruptedException { Client client = new Client(IntegrationContext.udpDsn); @@ -70,7 +82,11 @@ public void captureMessage_complex_udp() throws IOException, InterruptedExceptio } protected void captureMessage(Client client, String message, boolean wait) throws IOException, InterruptedException { - client.captureMessage(message); + captureMessage(client, message, wait, null); + } + + protected void captureMessage(Client client, String message, boolean wait, Map tags) throws IOException, InterruptedException { + client.captureMessage(message, tags); if (wait) { // Wait a bit in case of UDP transport Thread.sleep(1000); @@ -85,7 +101,7 @@ protected void captureMessage(Client client, String message, boolean wait) throw assertEquals(Client.Default.LOGGER, event.logger); // Log the same message; the count should be incremented - client.captureMessage(message); + client.captureMessage(message, tags); if (wait) { Thread.sleep(1000); } @@ -97,6 +113,8 @@ protected void captureMessage(Client client, String message, boolean wait) throw assertEquals(message, event.message); assertEquals(message, event.title); assertEquals(Client.Default.LOGGER, event.logger); + + // TODO Verify tags when it's a bit more stable on the Sentry side } @Before diff --git a/raven/src/main/java/net/kencochrane/raven/Client.java b/raven/src/main/java/net/kencochrane/raven/Client.java index afe8df71f11..f1e8ce08b0e 100644 --- a/raven/src/main/java/net/kencochrane/raven/Client.java +++ b/raven/src/main/java/net/kencochrane/raven/Client.java @@ -157,9 +157,17 @@ public String captureMessage(String msg) { return captureMessage(msg, null, null, null, null); } + public String captureMessage(String msg, Map tags) { + return captureMessage(msg, null, null, null, null, tags); + } + public String captureMessage(String message, Long timestamp, String loggerClass, Integer logLevel, String culprit) { + return captureMessage(message, timestamp, loggerClass, logLevel, culprit, null); + } + + public String captureMessage(String message, Long timestamp, String loggerClass, Integer logLevel, String culprit, Map tags) { timestamp = (timestamp == null ? Utils.now() : timestamp); - Message msg = buildMessage(message, formatTimestamp(timestamp), loggerClass, logLevel, culprit, null); + Message msg = buildMessage(message, formatTimestamp(timestamp), loggerClass, logLevel, culprit, null, tags); send(msg, timestamp); return msg.eventId; } @@ -169,8 +177,17 @@ public String captureException(Throwable exception) { return captureException(exception.getMessage(), timestamp, null, null, null, exception); } + public String captureException(Throwable exception, Map tags) { + long timestamp = Utils.now(); + return captureException(exception.getMessage(), timestamp, null, null, null, exception, tags); + } + public String captureException(String logMessage, long timestamp, String loggerName, Integer logLevel, String culprit, Throwable exception) { - Message message = buildMessage(logMessage, formatTimestamp(timestamp), loggerName, logLevel, culprit, exception); + return captureException(logMessage, timestamp, loggerName, logLevel, culprit, exception, null); + } + + public String captureException(String logMessage, long timestamp, String loggerName, Integer logLevel, String culprit, Throwable exception, Map tags) { + Message message = buildMessage(logMessage, formatTimestamp(timestamp), loggerName, logLevel, culprit, exception, tags); send(message, timestamp); return message.eventId; } @@ -195,7 +212,7 @@ public void stop() { } @SuppressWarnings("unchecked") - protected Message buildMessage(String message, String timestamp, String loggerClass, Integer logLevel, String culprit, Throwable exception) { + protected Message buildMessage(String message, String timestamp, String loggerClass, Integer logLevel, String culprit, Throwable exception, Map tags) { String eventId = generateEventId(); JSONObject obj = new JSONObject(); if (exception == null) { @@ -216,6 +233,11 @@ protected Message buildMessage(String message, String timestamp, String loggerCl obj.put("level", logLevel == null ? Default.LOG_LEVEL : logLevel); obj.put("logger", loggerClass == null ? Default.LOGGER : loggerClass); obj.put("server_name", Utils.hostname()); + if (tags != null) { + JSONObject jsonTags = new JSONObject(); + jsonTags.putAll(tags); + obj.put("tags", jsonTags); + } return new Message(obj, eventId); } From 3282272648384f63cd8b31a6d415721b692205ce Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Sun, 8 Jul 2012 12:41:32 +0200 Subject: [PATCH 0054/2152] Update the project README --- README.md | 176 +++++++++++++++++++++++++++++++++++ README.rst => old/README.rst | 0 2 files changed, 176 insertions(+) create mode 100644 README.md rename README.rst => old/README.rst (100%) diff --git a/README.md b/README.md new file mode 100644 index 00000000000..69b98c15487 --- /dev/null +++ b/README.md @@ -0,0 +1,176 @@ +# Raven-Java + +Raven-Java is a Java client for [Sentry](https://www.getsentry.com/). Besides a regular client you can use within your application code, Raven-Java also provides the `raven-log4j` package you can use to send logging to Sentry via [log4j](http://logging.apache.org/log4j/). + +Raven-Java supports both HTTP(S) and UDP transport of messages. + +## Sentry Versions Supported +This client supports Sentry protocol version 2.0 (which is Sentry >= 2.0). + +Since version 4.6.0 of Sentry, signed messages have been deprecated. If you still use an earlier version, have a look at the client options below to find out how to enable signed messages. + +* * * + +## Using the client + +````java +import net.kencochrane.raven.Client +import net.kencochrane.raven.SentryDsn + +public class Example { + + public static void main(String[] args) { + // The DSN from Sentry: "http://public:private@host:port/1" + String rawDsn = args[0]; + SentryDsn dsn = SentryDsn.build(rawDsn); + Client = new Client(dsn); + client.captureMessage("Hello from Raven-Java!"); + } + +} + +```` + +A full example is available in `raven/src/main/test/net/kencochrane/raven/ClientExample`. + +Note that `SentryDsn` will first examine the environment variables and system properties for a variable called `SENTRY_DSN`. If such a variable is available, *that* value will be used instead of the DSN you supply (the `rawDsn` variable in the example above). + +This allows flexible usage of the library on PaaS providers such as Heroku. This also means the above example can be simplified to the following *if* you specify `SENTRY_DSN` in your: + +- environment variables — e.g. `export SENTRY_DSN=yoursentrydsn` or `setenv SENTRYDSN yoursentrydsn`; or +- system properties — `-DSENTRY_DSN=yoursentrydsn` + +````java +import net.kencochrane.raven.Client +import net.kencochrane.raven.SentryDsn + +public class Example { + + public static void main(String[] args) { + // DSN is determined by the client from system properties or env + Client = new Client(); + client.captureMessage("Hello from Raven-Java!"); + } + +} + +```` + +* * * + +## Using the log4j appender +You can either utilize the `net.kencochrane.raven.log4j.SentryAppender` or `net.kencochrane.raven.log4j.AsyncSentryAppender` as an appender in your log4j configuration just like you would use any other appender. + + log4j.appender.sentry=net.kencochrane.raven.log4j.SentryAppender + log4j.appender.sentry.sentryDsn=http://b4935bdd7862409:7a37d9ad47654281@localhost:8000/1 + +Like the client, these appenders will examine the system properties and environment variables for a better `SENTRY_DSN` candidate. Which log messages are ultimately sent to your Sentry instance, depends on the configuration of your Sentry appender. If you only want to send error messages, use the following: + + log4j.appender.sentry.Threshold=ERROR + +### Asynchronous logging +If you use log4j's XML configuration, you can use its [AsyncAppender](http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/AsyncAppender.html) to wrap Raven-Java's `SentryAppender`. + +But even if you use a properties file to configure log4j, you can log asynchronously by using the `net.kencochrane.raven.log4j.AsyncSentryAppender` instead. + +* * * + +## Client configuration +Client configuration is completely driven through the Sentry DSN. The DSN you can copy-paste from your Sentry instance looks like this: + + http://public:private@host:port/1 + +Changing the behavior of the client is done through the *scheme* and *querystring* of the DSN. + +### Transport protocols + +#### HTTPS +If you're using https, your DSN should look like this: + + https://public:private@host:port/1 + +#### Naive HTTPS +If you're using https with a self-signed certificate and you're too lazy to add the certificate to your truststore, you can tell the client to be naive and allow it to ignore the certificate (which you really shouldn't do in a production environment): + + naive+https://public:private@host:port/1 + +#### UDP +Prefer udp? Change your DSN to this: + + udp://public:private@host:port/1 + +#### Asynchronous +The client can use a separate thread to actually send the messages to Sentry. To enable this feature, add `async+` to the scheme in your DSN: + + async+http://public:private@host:port/1 + # Or + async+https://public:private@host:port/1 + # Or + async+naive+https://public:private@host:port/1 + # Or + async+udp://public:private@host:port/1 + +#### Adding other schemes +You can add your own custom transport and scheme through the `register` method of the client. + +### Client options +More client configuration can be specified through the querystring of the DSN like this: + + http://public:private@host:port/1?optionA=true&optionB=20 + +#### Enabling signed messages +Signed messages have been deprecated in Sentry 4.6.0. If you're using an earlier version, you'll have to tell the client to sign messages through the option `raven.includeSignature`: + + http://public:private@host:port/1?raven.includeSignature=true + +#### HTTP/HTTPS timeout +The default timeout for HTTP/HTTPS transport is set to 10 seconds. If you want to change this value, use the `raven.timeout` option to specify the timeout in milliseconds. + + http://public:private@host:port/1?raven.timeout=10000 + +#### Async queue configuration +When using the async transport (this is not the same as using the `AsyncSentryAppender`), you can configure the behavior of the underlying `java.util.concurrent.BlockingQueue` through the `raven.waitWhenFull` and `raven.capacity` options. + +By default the client will **not** block when the queue is full and will use a queue at maximum capacity. If instead you want to use a blocking queue with a capacity of 20 messages, change your DSN to something like this: + + async+http://public:private@host:port/1?raven.waitWhenFull=true&raven.capacity=20 + +* * * + +## Installation +TODO + +* * * + +## History +- 1.0 + - Rewrite + - Support tags +- 0.6 + - Added support for sending messages through UDP +- 0.5 + - Added async support + - Fixed issue with parsing of path and port in DSN +- 0.4 + - Added the ability to get the SENTRY_DSN from the ENV + - Added RavenClient.captureMessage + - Added RavenClient.captureException +- 0.3 + - Added Maven support + - Merged with log4sentry project by Kevin Wetzels + - Added Proxy support + - Added full stack trace to logs + +- 0.2 + - code refactor and cleanup + +- 0.1 + - initial version + + +## Contributors + +- Ken Cochrane (@KenCochrane) +- Kevin Wetzels (@roambe) +- David Cramer (@zeeg) +- Mark Philpot (@griphiam) \ No newline at end of file diff --git a/README.rst b/old/README.rst similarity index 100% rename from README.rst rename to old/README.rst From 0bc54709946e53c1e27a3c8f99cdf078fa7c5ee0 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Mon, 9 Jul 2012 20:09:31 +0200 Subject: [PATCH 0055/2152] Add integration tests for log4j SentryAppender and AsyncSentryAppender --- .../java/net/kencochrane/raven/SentryApi.java | 22 ++- .../log4j/AsyncSentryAppenderTest.java | 153 ++++++++++++++++++ .../integration/log4j/SentryAppenderTest.java | 138 +++++++++++++++- .../asyncsentryappender.log4j.properties | 15 ++ .../resources/sentryappender.log4j.properties | 3 + .../resources/sentryappender.log4j.properties | 2 +- 6 files changed, 323 insertions(+), 10 deletions(-) create mode 100644 raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/AsyncSentryAppenderTest.java create mode 100644 raven-integration-tests/src/test/resources/asyncsentryappender.log4j.properties diff --git a/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java b/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java index 34a99ec18b9..47a41b9408e 100644 --- a/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java +++ b/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java @@ -96,38 +96,48 @@ public List getEvents(String projectSlug) throws IOException { continue; } int count = Integer.parseInt(item.attr("data-count")); - int level = extractLevel(item.classNames()); + String levelName = extractLevel(item.classNames()); + int level = -1; + if (levelName != null) { + try { + level = Integer.parseInt(levelName); + } catch (NumberFormatException e) { + // Ignore + } + } Element anchor = item.select("h3 a").get(0); String link = anchor.attr("href"); String title = StringUtils.trim(anchor.text()); Element messageElement = item.select("p.message").get(0); String message = StringUtils.trim(messageElement.attr("title")); String logger = StringUtils.trim(messageElement.select("span.tag-logger").text()); - events.add(new Event(count, level, link, title, message, logger)); + events.add(new Event(count, level, levelName, link, title, message, logger)); } return events; } - protected static int extractLevel(Collection classNames) { + protected static String extractLevel(Collection classNames) { for (String name : classNames) { if (name.startsWith("level-")) { - return Integer.parseInt(name.replace("level-", "")); + return name.replace("level-", ""); } } - return 0; + return null; } public static class Event { public final int count; public final int level; + public final String levelName; public final String url; public final String title; public final String message; public final String logger; - public Event(int count, int level, String url, String title, String message, String logger) { + public Event(int count, int level, String levelName, String url, String title, String message, String logger) { this.count = count; this.level = level; + this.levelName = levelName; this.url = url; this.title = title; this.message = message; diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/AsyncSentryAppenderTest.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/AsyncSentryAppenderTest.java new file mode 100644 index 00000000000..6135ca5f183 --- /dev/null +++ b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/AsyncSentryAppenderTest.java @@ -0,0 +1,153 @@ +package net.kencochrane.raven.integration.log4j; + +import net.kencochrane.raven.SentryApi; +import net.kencochrane.raven.Utils; +import net.kencochrane.raven.integration.IntegrationContext; +import org.apache.log4j.Logger; +import org.apache.log4j.PropertyConfigurator; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Integration tests for {@link net.kencochrane.raven.log4j.AsyncSentryAppender}. + */ +public class AsyncSentryAppenderTest { + + private SentryApi api; + + @Test + public void debug() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "This is a debug message"; + Logger logger = Logger.getLogger(loggerName); + logger.debug(message); + + verify(loggerName, message, "debug", loggerName); + } + + @Test + public void debug_withThrowable() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "This is a debug message"; + Logger logger = Logger.getLogger(loggerName); + logger.debug(message, new NullPointerException("omg!")); + + final String levelName = "debug"; + final String title = this.getClass().getName() + ".debug_withThrowable"; + verify(loggerName, message, levelName, title); + } + + @Test + public void info() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "This is an info message"; + Logger logger = Logger.getLogger(loggerName); + logger.info(message); + + verify(loggerName, message, "info", loggerName); + } + + @Test + public void info_withThrowable() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "This is an info message"; + Logger logger = Logger.getLogger(loggerName); + logger.info(message, new NullPointerException("omg!")); + + verify(loggerName, message, "info", this.getClass().getName() + ".info_withThrowable"); + } + + @Test + public void warn() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "This is a warning!"; + Logger logger = Logger.getLogger(loggerName); + logger.warn(message); + + verify(loggerName, message, "warning", loggerName); + } + + @Test + public void warn_withThrowable() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "This is another warning"; + Logger logger = Logger.getLogger(loggerName); + logger.warn(message, new NullPointerException("no no no!")); + + verify(loggerName, message, "warning", this.getClass().getName() + ".warn_withThrowable"); + } + + @Test + public void error() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "This is an error!"; + Logger logger = Logger.getLogger(loggerName); + logger.error(message); + verify(loggerName, message, "error", loggerName); + } + + @Test + public void error_withThrowable() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "This is an... err.. or"; + Logger logger = Logger.getLogger(loggerName); + logger.error(message, new NullPointerException("no no no!")); + verify(loggerName, message, "error", this.getClass().getName() + ".error_withThrowable"); + } + + @Test + public void fatal() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "Head for the lifeboat!"; + Logger logger = Logger.getLogger(loggerName); + logger.fatal(message); + verify(loggerName, message, "fatal", loggerName); + } + + @Test + public void fatal_withThrowable() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "What lifeboat?"; + Logger logger = Logger.getLogger(loggerName); + logger.fatal(message, new NullPointerException("no no no!")); + verify(loggerName, message, "fatal", this.getClass().getName() + ".fatal_withThrowable"); + } + + @Before + public void setUp() throws IOException { + IntegrationContext.init(); + api = IntegrationContext.api; + api.clear(IntegrationContext.httpDsn.projectId); + System.setProperty(Utils.SENTRY_DSN, IntegrationContext.httpDsn.toString()); + PropertyConfigurator.configure(getClass().getResource("/asyncsentryappender.log4j.properties")); + } + + @After + public void tearDown() throws IOException { + System.setProperty(Utils.SENTRY_DSN, ""); + } + + private void verify(String loggerName, String message, String levelName, String title) throws IOException { + try { + Thread.sleep(600); + } catch (InterruptedException e) { + // Ignore + } + List events = api.getEvents(IntegrationContext.projectSlug); + assertEquals(1, events.size()); + SentryApi.Event event = events.get(0); + assertTrue(event.count > 0); + assertEquals(levelName, event.levelName); + assertEquals(message, event.message); + assertEquals(title, event.title); + assertEquals(loggerName, event.logger); + } + +} diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java index 1058eb167f4..49a22b7d909 100644 --- a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java +++ b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java @@ -1,16 +1,148 @@ package net.kencochrane.raven.integration.log4j; +import net.kencochrane.raven.SentryApi; +import net.kencochrane.raven.Utils; +import net.kencochrane.raven.integration.IntegrationContext; +import org.apache.log4j.Logger; +import org.apache.log4j.PropertyConfigurator; +import org.junit.After; +import org.junit.Before; import org.junit.Test; +import java.io.IOException; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + /** * Integration tests for {@link net.kencochrane.raven.log4j.SentryAppender}. */ public class SentryAppenderTest { - // TODO Integration test + private SentryApi api; + + @Test + public void debug() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "This is a debug message"; + Logger logger = Logger.getLogger(loggerName); + logger.debug(message); + + verify(loggerName, message, "debug", loggerName); + } + + @Test + public void debug_withThrowable() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "This is a debug message"; + Logger logger = Logger.getLogger(loggerName); + logger.debug(message, new NullPointerException("omg!")); + + final String levelName = "debug"; + final String title = this.getClass().getName() + ".debug_withThrowable"; + verify(loggerName, message, levelName, title); + } + + @Test + public void info() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "This is an info message"; + Logger logger = Logger.getLogger(loggerName); + logger.info(message); + + verify(loggerName, message, "info", loggerName); + } + + @Test + public void info_withThrowable() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "This is an info message"; + Logger logger = Logger.getLogger(loggerName); + logger.info(message, new NullPointerException("omg!")); + + verify(loggerName, message, "info", this.getClass().getName() + ".info_withThrowable"); + } + + @Test + public void warn() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "This is a warning!"; + Logger logger = Logger.getLogger(loggerName); + logger.warn(message); + + verify(loggerName, message, "warning", loggerName); + } + + @Test + public void warn_withThrowable() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "This is another warning"; + Logger logger = Logger.getLogger(loggerName); + logger.warn(message, new NullPointerException("no no no!")); + + verify(loggerName, message, "warning", this.getClass().getName() + ".warn_withThrowable"); + } + @Test - public void test() { - System.out.println("Hi"); + public void error() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "This is an error!"; + Logger logger = Logger.getLogger(loggerName); + logger.error(message); + verify(loggerName, message, "error", loggerName); + } + + @Test + public void error_withThrowable() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "This is an... err.. or"; + Logger logger = Logger.getLogger(loggerName); + logger.error(message, new NullPointerException("no no no!")); + verify(loggerName, message, "error", this.getClass().getName() + ".error_withThrowable"); + } + + @Test + public void fatal() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "Head for the lifeboat!"; + Logger logger = Logger.getLogger(loggerName); + logger.fatal(message); + verify(loggerName, message, "fatal", loggerName); + } + + @Test + public void fatal_withThrowable() throws IOException { + final String loggerName = this.getClass().getName(); + final String message = "What lifeboat?"; + Logger logger = Logger.getLogger(loggerName); + logger.fatal(message, new NullPointerException("no no no!")); + verify(loggerName, message, "fatal", this.getClass().getName() + ".fatal_withThrowable"); + } + + @Before + public void setUp() throws IOException { + IntegrationContext.init(); + api = IntegrationContext.api; + api.clear(IntegrationContext.httpDsn.projectId); + System.setProperty(Utils.SENTRY_DSN, IntegrationContext.httpDsn.toString()); + PropertyConfigurator.configure(getClass().getResource("/sentryappender.log4j.properties")); + } + + @After + public void tearDown() throws IOException { + System.setProperty(Utils.SENTRY_DSN, ""); + } + + private void verify(String loggerName, String message, String levelName, String title) throws IOException { + List events = api.getEvents(IntegrationContext.projectSlug); + assertEquals(1, events.size()); + SentryApi.Event event = events.get(0); + assertTrue(event.count > 0); + assertEquals(levelName, event.levelName); + assertEquals(message, event.message); + assertEquals(title, event.title); + assertEquals(loggerName, event.logger); } } diff --git a/raven-integration-tests/src/test/resources/asyncsentryappender.log4j.properties b/raven-integration-tests/src/test/resources/asyncsentryappender.log4j.properties new file mode 100644 index 00000000000..20fb2e2394a --- /dev/null +++ b/raven-integration-tests/src/test/resources/asyncsentryappender.log4j.properties @@ -0,0 +1,15 @@ +log4j.rootLogger=trace, stdout, sentry + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + +log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n + +log4j.appender.sentry=net.kencochrane.raven.log4j.AsyncSentryAppender +log4j.appender.sentry.sentryDsn=SENTRY_DSN + +log4j.logger.raven=DEBUG, stdout +log4j.additivity.raven=false + +log4j.logger.org.apache.http=ERROR +log4j.additivity.org.apache.http=false \ No newline at end of file diff --git a/raven-integration-tests/src/test/resources/sentryappender.log4j.properties b/raven-integration-tests/src/test/resources/sentryappender.log4j.properties index b5dc9cb270a..82a82396918 100644 --- a/raven-integration-tests/src/test/resources/sentryappender.log4j.properties +++ b/raven-integration-tests/src/test/resources/sentryappender.log4j.properties @@ -18,3 +18,6 @@ log4j.appender.sentry.sentryDsn=SENTRY_DSN # so this should only happen when you add a bridge from JUL to Log4J. log4j.logger.raven=DEBUG, stdout log4j.additivity.raven=false + +log4j.logger.org.apache.http=ERROR +log4j.additivity.org.apache.http=false \ No newline at end of file diff --git a/raven-log4j/src/test/resources/sentryappender.log4j.properties b/raven-log4j/src/test/resources/sentryappender.log4j.properties index b5dc9cb270a..f06ec40b4d4 100644 --- a/raven-log4j/src/test/resources/sentryappender.log4j.properties +++ b/raven-log4j/src/test/resources/sentryappender.log4j.properties @@ -17,4 +17,4 @@ log4j.appender.sentry.sentryDsn=SENTRY_DSN # something goes wrong with your configuration. Sentry uses the java.util.logging package # so this should only happen when you add a bridge from JUL to Log4J. log4j.logger.raven=DEBUG, stdout -log4j.additivity.raven=false +log4j.additivity.raven=false \ No newline at end of file From a48d958907bc5752e25ba1419ddec4487ebb40db Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Tue, 7 Aug 2012 14:19:41 +0200 Subject: [PATCH 0056/2152] Update Client to disable itself when the DSN is not specified In accordance with the new guidelines from "Writing a Client" in the Sentry developer docs http://sentry.readthedocs.org/en/latest/developer/client/index.html --- .../java/net/kencochrane/raven/Client.java | 32 ++++++++++++++++++- .../net/kencochrane/raven/ClientTest.java | 10 ++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Client.java b/raven/src/main/java/net/kencochrane/raven/Client.java index f1e8ce08b0e..d25de87ce43 100644 --- a/raven/src/main/java/net/kencochrane/raven/Client.java +++ b/raven/src/main/java/net/kencochrane/raven/Client.java @@ -97,7 +97,7 @@ public Client() { * @param autoStart whether to start the underlying transport automatically or not */ public Client(boolean autoStart) { - this(SentryDsn.build(), autoStart); + this(fetchOptionalDsn(), autoStart); } /** @@ -193,6 +193,9 @@ public String captureException(String logMessage, long timestamp, String loggerN } public void start() { + if (isDisabled()) { + return; + } if (transport == null) { transport = newTransport(dsn); } @@ -211,8 +214,15 @@ public void stop() { transport = null; } + public boolean isDisabled() { + return dsn == null; + } + @SuppressWarnings("unchecked") protected Message buildMessage(String message, String timestamp, String loggerClass, Integer logLevel, String culprit, Throwable exception, Map tags) { + if (isDisabled()) { + return Message.NONE; + } String eventId = generateEventId(); JSONObject obj = new JSONObject(); if (exception == null) { @@ -242,6 +252,9 @@ protected Message buildMessage(String message, String timestamp, String loggerCl } protected void send(Message message, long timestamp) { + if (isDisabled()) { + return; + } try { transport.send(message.encoded(), timestamp); } catch (ConnectException e) { @@ -404,6 +417,21 @@ public static String calculateChecksum(String message) { return String.valueOf(checksum.getValue()); } + /** + * Wrapper for {@link net.kencochrane.raven.SentryDsn#build()} that turns an {@link SentryDsn.InvalidDsnException} + * into a null DSN, effectively disabling the client. + * + * @return the Sentry DSN as determined from the environment or null when no DSN was found + */ + protected static SentryDsn fetchOptionalDsn() { + try { + return SentryDsn.build(); + } catch (SentryDsn.InvalidDsnException e) { + LOG.log(Level.WARNING, "Could not automatically determine a valid DSN; client will be disabled", e); + return null; + } + } + public static class InvalidConfig extends RuntimeException { public InvalidConfig(String msg) { @@ -418,6 +446,8 @@ public InvalidConfig(String msg, Throwable t) { public static class Message { + public static final Message NONE = new Message(null, "-1"); + public final JSONObject json; public final String eventId; diff --git a/raven/src/test/java/net/kencochrane/raven/ClientTest.java b/raven/src/test/java/net/kencochrane/raven/ClientTest.java index 0bc3f8f38fa..1dcae01128d 100644 --- a/raven/src/test/java/net/kencochrane/raven/ClientTest.java +++ b/raven/src/test/java/net/kencochrane/raven/ClientTest.java @@ -58,6 +58,15 @@ public void constructor_specifyDsn() { verifyClient(new Client(dsn, false), Transport.Http.class, false, false); } + @Test + public void constructor_noDsn() { + Client client = new Client(); + assertTrue(client.isDisabled()); + client.start(); + assertFalse(client.isStarted()); + assertEquals("-1", client.captureMessage("Hi")); + } + @Test public void newTransport() { // HTTP @@ -159,6 +168,7 @@ protected void verifyClient(Client client, Class transportC protected void verifyClient(Client client, Class transportClass, boolean async, boolean autostart) { assertTrue(!autostart || client.isStarted()); + assertFalse(client.isDisabled()); if (async) { assertTrue(client.transport instanceof AsyncTransport); assertTrue(((AsyncTransport) client.transport).transport.getClass().isAssignableFrom(transportClass)); From c39e74dd324236890a430b1028f7e957a4305208 Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Fri, 24 Aug 2012 16:47:27 -0400 Subject: [PATCH 0057/2152] added travis-ci config file --- .travis.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000000..dff5f3a5d02 --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: java From 52e4d079b7c25087d4148b42d08a786e829e231f Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Fri, 24 Aug 2012 16:51:45 -0400 Subject: [PATCH 0058/2152] added jdk versions --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index dff5f3a5d02..4db492a319c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1 +1,4 @@ language: java +jdk: + - openjdk7 + - openjdk6 From 5265f3bea0dcab880305375746375c2ba77c2e38 Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Fri, 24 Aug 2012 16:56:50 -0400 Subject: [PATCH 0059/2152] added travis-ci current status icon to the readme page --- README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.rst b/README.rst index 952894fffcf..f7ae10fb0d0 100644 --- a/README.rst +++ b/README.rst @@ -6,6 +6,12 @@ It is officially recognized as production-ready by Sentry: http://sentry.readthe The log4j appender is asyncronous by design so there is no need to put it in a AsyncAppender. +Current Status +-------------- +.. image:: https://secure.travis-ci.org/kencochrane/raven-java.png + :target: http://travis-ci.org/kencochrane/raven-java + + Installation ------------ From 3a6726331ccda0337e2622e46ffe521db2ad0729 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Sat, 25 Aug 2012 09:57:53 +0200 Subject: [PATCH 0060/2152] Improve integration test set up and use Sentry message interface The integration test set up script will now copy and existing, bare-bones database instead of expecting you to set it up yourself. The integration tests have been changed to include verification of tags and whether the Sentry message interface is actually being used by the Client -- thanks to Brad Chen (@vvasabi). Building the JSON message should probably be extracted from the client to make testing this part a lot easier. --- raven-integration-tests/README.md | 5 +- .../java/net/kencochrane/raven/SentryApi.java | 44 +++++++++++++++++- .../src/main/resources/boot_sentry.db | Bin 0 -> 581632 bytes .../src/main/resources/run_sentry.sh | 1 + .../raven/integration/ClientTest.java | 26 ++++++++--- .../raven/integration/IntegrationContext.java | 3 +- .../log4j/AsyncSentryAppenderTest.java | 2 +- .../java/net/kencochrane/raven/Client.java | 2 + .../java/net/kencochrane/raven/Events.java | 8 +++- .../kencochrane/raven/AsyncTransportTest.java | 1 - .../{ClientTest.java => ClientSetupTest.java} | 2 +- 11 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 raven-integration-tests/src/main/resources/boot_sentry.db rename raven/src/test/java/net/kencochrane/raven/{ClientTest.java => ClientSetupTest.java} (99%) diff --git a/raven-integration-tests/README.md b/raven-integration-tests/README.md index 9550237dd63..31f3fe5b9b6 100644 --- a/raven-integration-tests/README.md +++ b/raven-integration-tests/README.md @@ -12,9 +12,8 @@ Make sure you have Sentry installed. Follow the instructions in services of Sentry in the same terminal. ## 3. Run `src/main/resources/run_sentry.sh` before running the tests -This script will remove any existing `sentry.db` at the *same* location so we can start fresh and then perform a Sentry -upgrade using `default_config.py` as the Sentry configuration. During this process, you will be asked to create a -superuser. Use username and password `test`. +This script will remove any existing `sentry.db` at the *same* location so we can start fresh, replace it with `boot_sentry.db` and then perform a Sentry +upgrade using `default_config.py` as the Sentry configuration. The database is set up with a user with username and password `test`. Once the Sentry installation is finished, the `Procfile` at the same location will be used to kickstart Sentry HTTP and UDP services at ports 9500 and 9501 respectively. diff --git a/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java b/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java index 47a41b9408e..3e7ad737d39 100644 --- a/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java +++ b/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java @@ -7,11 +7,16 @@ import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.params.ClientPNames; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -23,6 +28,8 @@ import java.util.Collection; import java.util.LinkedList; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Sentry tools. @@ -40,6 +47,8 @@ public SentryApi() throws MalformedURLException { public SentryApi(String host) { this.host = host; + // Otherwise HttpClient gets confused when hitting the dashboard without an existing project + client.getParams().setParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true); } public boolean login(String username, String password) throws IOException { @@ -86,6 +95,19 @@ public boolean clear(String projectId) throws IOException { return ok; } + public JSONArray getRawJson(String projectSlug, int group) throws IOException { + HttpResponse response = client.execute(new HttpGet(host + "/" + projectSlug + "/group/" + group + "/events/json/")); + String raw = EntityUtils.toString(response.getEntity()); + if (StringUtils.isBlank(raw)) { + return null; + } + try { + return (JSONArray) new JSONParser().parse(raw); + } catch (ParseException e) { + throw new IOException(e); + } + } + public List getEvents(String projectSlug) throws IOException { HttpResponse response = client.execute(new HttpGet(host + "/" + projectSlug)); Document doc = Jsoup.parse(EntityUtils.toString(response.getEntity())); @@ -95,6 +117,7 @@ public List getEvents(String projectSlug) throws IOException { if (item.hasClass("resolved")) { continue; } + int group = Integer.parseInt(item.attr("data-group")); int count = Integer.parseInt(item.attr("data-count")); String levelName = extractLevel(item.classNames()); int level = -1; @@ -111,11 +134,26 @@ public List getEvents(String projectSlug) throws IOException { Element messageElement = item.select("p.message").get(0); String message = StringUtils.trim(messageElement.attr("title")); String logger = StringUtils.trim(messageElement.select("span.tag-logger").text()); - events.add(new Event(count, level, levelName, link, title, message, logger)); + events.add(new Event(group, count, level, levelName, link, title, message, logger)); } return events; } + public List getAvailableTags(String projectSlug) throws IOException { + HttpResponse response = client.execute(new HttpGet(host + "/account/projects/" + projectSlug + "/tags/")); + Document doc = Jsoup.parse(EntityUtils.toString(response.getEntity())); + Elements items = doc.select("#div_id_filters label.checkbox"); + Pattern pattern = Pattern.compile(".*\\((\\w+)\\)"); + List tagNames = new LinkedList(); + for (Element item : items) { + Matcher matcher = pattern.matcher(item.text()); + if (matcher.matches()) { + tagNames.add(matcher.group(1)); + } + } + return tagNames; + } + protected static String extractLevel(Collection classNames) { for (String name : classNames) { if (name.startsWith("level-")) { @@ -126,6 +164,7 @@ protected static String extractLevel(Collection classNames) { } public static class Event { + public final int group; public final int count; public final int level; public final String levelName; @@ -134,7 +173,8 @@ public static class Event { public final String message; public final String logger; - public Event(int count, int level, String levelName, String url, String title, String message, String logger) { + public Event(int group, int count, int level, String levelName, String url, String title, String message, String logger) { + this.group = group; this.count = count; this.level = level; this.levelName = levelName; diff --git a/raven-integration-tests/src/main/resources/boot_sentry.db b/raven-integration-tests/src/main/resources/boot_sentry.db new file mode 100644 index 0000000000000000000000000000000000000000..d719cb45ae444df708c635621c15f70e515c011d GIT binary patch literal 581632 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 literal 0 HcmV?d00001 diff --git a/raven-integration-tests/src/main/resources/run_sentry.sh b/raven-integration-tests/src/main/resources/run_sentry.sh index 0af88b0b56e..be2442b5e8c 100755 --- a/raven-integration-tests/src/main/resources/run_sentry.sh +++ b/raven-integration-tests/src/main/resources/run_sentry.sh @@ -1,4 +1,5 @@ #!/bin/sh rm -f sentry.db +cp boot_sentry.db sentry.db sentry --config=default_config.py upgrade foreman start diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java index 77738b5f120..121b4194db6 100644 --- a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java +++ b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java @@ -2,16 +2,15 @@ import net.kencochrane.raven.Client; import net.kencochrane.raven.SentryApi; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; import org.junit.Before; import org.junit.Test; import java.io.IOException; -import java.util.Map; -import java.util.HashMap; -import java.util.List; +import java.util.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; /** * Integration tests for {@link net.kencochrane.raven.Client}. @@ -34,6 +33,7 @@ public void captureMessage_withTags() throws IOException, InterruptedException { Map tags = new HashMap(); tags.put("release", "1.2.0"); tags.put("uptime", 60000L); + tags.put("client", "Raven-Java"); captureMessage(client, message, false, tags); } @@ -114,7 +114,21 @@ protected void captureMessage(Client client, String message, boolean wait, Map 0); + JSONObject json = (JSONObject) jsonArray.get(0); + JSONObject messageJson = (JSONObject) json.get("sentry.interfaces.Message"); + assertNotNull(messageJson); + assertEquals(message, messageJson.get("message")); + assertTrue(messageJson.get("params") instanceof JSONArray); + + if (tags != null && !tags.isEmpty()) { + List tagNames = api.getAvailableTags(IntegrationContext.projectSlug); + Set nonMatches = new HashSet(tags.keySet()); + nonMatches.removeAll(tagNames); + assertTrue(nonMatches.isEmpty()); + } } @Before diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/IntegrationContext.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/IntegrationContext.java index 7905fa5eb3b..bb40b202ea1 100644 --- a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/IntegrationContext.java +++ b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/IntegrationContext.java @@ -16,7 +16,7 @@ public class IntegrationContext { public static SentryApi api; public static SentryDsn httpDsn; public static SentryDsn udpDsn; - public static String projectSlug = "default"; + public static String projectSlug = "ravenjava"; protected static boolean initialized = false; public static void init() throws IOException { @@ -28,7 +28,6 @@ public static void init() throws IOException { String dsn = api.getDsn(projectSlug); httpDsn = SentryDsn.build(dsn, null, null); udpDsn = SentryDsn.build(dsn.replace("http://", "udp://").replace(":" + port, ":" + udpPort), null, null); - System.out.println("UDP:" + udpDsn); initialized = true; } diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/AsyncSentryAppenderTest.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/AsyncSentryAppenderTest.java index 6135ca5f183..f2cffdfd048 100644 --- a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/AsyncSentryAppenderTest.java +++ b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/AsyncSentryAppenderTest.java @@ -136,7 +136,7 @@ public void tearDown() throws IOException { private void verify(String loggerName, String message, String levelName, String title) throws IOException { try { - Thread.sleep(600); + Thread.sleep(1000); } catch (InterruptedException e) { // Ignore } diff --git a/raven/src/main/java/net/kencochrane/raven/Client.java b/raven/src/main/java/net/kencochrane/raven/Client.java index d25de87ce43..5c86e8c187a 100644 --- a/raven/src/main/java/net/kencochrane/raven/Client.java +++ b/raven/src/main/java/net/kencochrane/raven/Client.java @@ -227,6 +227,8 @@ protected Message buildMessage(String message, String timestamp, String loggerCl JSONObject obj = new JSONObject(); if (exception == null) { obj.put("culprit", culprit); + JSONObject messageJson = Events.message(message); + obj.putAll(messageJson); } else { JSONObject exceptionJson = Events.exception(exception); obj.putAll(exceptionJson); diff --git a/raven/src/main/java/net/kencochrane/raven/Events.java b/raven/src/main/java/net/kencochrane/raven/Events.java index 5737c51297c..05d0d5e0ad3 100644 --- a/raven/src/main/java/net/kencochrane/raven/Events.java +++ b/raven/src/main/java/net/kencochrane/raven/Events.java @@ -3,6 +3,8 @@ import org.json.simple.JSONArray; import org.json.simple.JSONObject; +import java.util.Arrays; + /** * Collection of builtin Raven/Sentry events. */ @@ -24,7 +26,11 @@ public static JSONObject message(String message, Object... params) { JSONObject json = new JSONObject(); JSONObject messageJson = new JSONObject(); messageJson.put("message", message); - messageJson.put("params", params); + JSONArray paramArray = new JSONArray(); + if (params != null) { + paramArray.addAll(Arrays.asList(params)); + } + messageJson.put("params", paramArray); json.put("sentry.interfaces.Message", messageJson); return json; } diff --git a/raven/src/test/java/net/kencochrane/raven/AsyncTransportTest.java b/raven/src/test/java/net/kencochrane/raven/AsyncTransportTest.java index 207f9b3ce4b..df799b1e0aa 100644 --- a/raven/src/test/java/net/kencochrane/raven/AsyncTransportTest.java +++ b/raven/src/test/java/net/kencochrane/raven/AsyncTransportTest.java @@ -2,7 +2,6 @@ import mockit.Expectations; import mockit.Mocked; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; diff --git a/raven/src/test/java/net/kencochrane/raven/ClientTest.java b/raven/src/test/java/net/kencochrane/raven/ClientSetupTest.java similarity index 99% rename from raven/src/test/java/net/kencochrane/raven/ClientTest.java rename to raven/src/test/java/net/kencochrane/raven/ClientSetupTest.java index 1dcae01128d..c30424fe59d 100644 --- a/raven/src/test/java/net/kencochrane/raven/ClientTest.java +++ b/raven/src/test/java/net/kencochrane/raven/ClientSetupTest.java @@ -8,7 +8,7 @@ /** * Test cases for {@link Client}. */ -public class ClientTest { +public class ClientSetupTest { @Test public void defaultConstructor() { From 01bbc9683ae79ceefbaf0493672aded77d2fba99 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Sat, 25 Aug 2012 12:49:16 +0200 Subject: [PATCH 0061/2152] Add methods to Events that accept an existing JSONObject --- .../java/net/kencochrane/raven/Client.java | 6 ++---- .../java/net/kencochrane/raven/Events.java | 21 +++++++++++++------ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Client.java b/raven/src/main/java/net/kencochrane/raven/Client.java index 5c86e8c187a..ef94ff4d0fc 100644 --- a/raven/src/main/java/net/kencochrane/raven/Client.java +++ b/raven/src/main/java/net/kencochrane/raven/Client.java @@ -227,11 +227,9 @@ protected Message buildMessage(String message, String timestamp, String loggerCl JSONObject obj = new JSONObject(); if (exception == null) { obj.put("culprit", culprit); - JSONObject messageJson = Events.message(message); - obj.putAll(messageJson); + Events.message(obj, message); } else { - JSONObject exceptionJson = Events.exception(exception); - obj.putAll(exceptionJson); + Events.exception(obj, exception); } if (message == null) { message = (exception == null ? null : exception.getMessage()); diff --git a/raven/src/main/java/net/kencochrane/raven/Events.java b/raven/src/main/java/net/kencochrane/raven/Events.java index 05d0d5e0ad3..f4cc85f5833 100644 --- a/raven/src/main/java/net/kencochrane/raven/Events.java +++ b/raven/src/main/java/net/kencochrane/raven/Events.java @@ -21,9 +21,12 @@ public enum LogLevel { } - @SuppressWarnings("unchecked") public static JSONObject message(String message, Object... params) { - JSONObject json = new JSONObject(); + return message(new JSONObject(), message, params); + } + + @SuppressWarnings("unchecked") + public static JSONObject message(JSONObject json, String message, Object... params) { JSONObject messageJson = new JSONObject(); messageJson.put("message", message); JSONArray paramArray = new JSONArray(); @@ -35,9 +38,12 @@ public static JSONObject message(String message, Object... params) { return json; } - @SuppressWarnings("unchecked") public static JSONObject query(String query, String engine) { - JSONObject json = new JSONObject(); + return query(new JSONObject(), query, engine); + } + + @SuppressWarnings("unchecked") + public static JSONObject query(JSONObject json, String query, String engine) { JSONObject content = new JSONObject(); content.put("query", query); content.put("engine", engine); @@ -45,9 +51,12 @@ public static JSONObject query(String query, String engine) { return json; } - @SuppressWarnings("unchecked") public static JSONObject exception(Throwable exception) { - JSONObject json = new JSONObject(); + return exception(new JSONObject(), exception); + } + + @SuppressWarnings("unchecked") + public static JSONObject exception(JSONObject json, Throwable exception) { json.put("level", LogLevel.ERROR.intValue); json.put("culprit", determineCulprit(exception)); json.put("sentry.interfaces.Exception", buildException(exception)); From 1ad708d0579976cbce510d542c6f4d1845e0bef4 Mon Sep 17 00:00:00 2001 From: Brad Chen Date: Sun, 26 Aug 2012 00:39:43 -0400 Subject: [PATCH 0062/2152] Add RequestProcessor and MDC SPIs. --- .../net/kencochrane/raven/log4j/Log4jMDC.java | 39 +++++++++++++++++++ .../raven/log4j/SentryAppender.java | 5 +++ .../net.kencochrane.raven.spi.RavenMDC | 1 + .../java/net/kencochrane/raven/Client.java | 23 +++++++++++ .../net/kencochrane/raven/spi/RavenMDC.java | 24 ++++++++++++ .../raven/spi/RequestProcessor.java | 9 +++++ 6 files changed, 101 insertions(+) create mode 100644 raven-log4j/src/main/java/net/kencochrane/raven/log4j/Log4jMDC.java create mode 100644 raven-log4j/src/main/resources/META-INF/services/net.kencochrane.raven.spi.RavenMDC create mode 100644 raven/src/main/java/net/kencochrane/raven/spi/RavenMDC.java create mode 100644 raven/src/main/java/net/kencochrane/raven/spi/RequestProcessor.java diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/Log4jMDC.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/Log4jMDC.java new file mode 100644 index 00000000000..f9e4d27585d --- /dev/null +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/Log4jMDC.java @@ -0,0 +1,39 @@ +package net.kencochrane.raven.log4j; + +import org.apache.log4j.MDC; +import org.apache.log4j.spi.LoggingEvent; + +import net.kencochrane.raven.spi.RavenMDC; + +public class Log4jMDC extends RavenMDC { + + private static final ThreadLocal THREAD_LOGGING_EVENT + = new ThreadLocal(); + + public void setThreadLoggingEvent(LoggingEvent event) { + THREAD_LOGGING_EVENT.set(event); + } + + public void removeThreadLoggingEvent() { + THREAD_LOGGING_EVENT.remove(); + } + + @Override + public Object get(String key) { + if (THREAD_LOGGING_EVENT.get() != null) { + return THREAD_LOGGING_EVENT.get().getMDC(key); + } + return MDC.get(key); + } + + @Override + public void put(String key, Object value) { + MDC.put(key, value); + } + + @Override + public void remove(String key) { + MDC.remove(key); + } + +} diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 7a692d5f238..61880a41484 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -2,6 +2,8 @@ import net.kencochrane.raven.Client; import net.kencochrane.raven.SentryDsn; +import net.kencochrane.raven.spi.RavenMDC; + import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.spi.LoggingEvent; import org.apache.log4j.spi.ThrowableInformation; @@ -43,6 +45,8 @@ public boolean requiresLayout() { @Override protected void append(LoggingEvent event) { + ((Log4jMDC)RavenMDC.getInstance()).setThreadLoggingEvent(event); + Client client = fetchClient(); // get timestamp and timestamp in correct string format. long timestamp = event.getTimeStamp(); @@ -62,6 +66,7 @@ protected void append(LoggingEvent event) { } else { client.captureException(message, timestamp, logger, level, culprit, info.getThrowable()); } + ((Log4jMDC)RavenMDC.getInstance()).removeThreadLoggingEvent(); } /** diff --git a/raven-log4j/src/main/resources/META-INF/services/net.kencochrane.raven.spi.RavenMDC b/raven-log4j/src/main/resources/META-INF/services/net.kencochrane.raven.spi.RavenMDC new file mode 100644 index 00000000000..b768ea0ac17 --- /dev/null +++ b/raven-log4j/src/main/resources/META-INF/services/net.kencochrane.raven.spi.RavenMDC @@ -0,0 +1 @@ +net.kencochrane.raven.log4j.Log4jMDC \ No newline at end of file diff --git a/raven/src/main/java/net/kencochrane/raven/Client.java b/raven/src/main/java/net/kencochrane/raven/Client.java index ef94ff4d0fc..22522e77ffd 100644 --- a/raven/src/main/java/net/kencochrane/raven/Client.java +++ b/raven/src/main/java/net/kencochrane/raven/Client.java @@ -1,5 +1,7 @@ package net.kencochrane.raven; +import net.kencochrane.raven.spi.RequestProcessor; + import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang.time.DateFormatUtils; import org.json.simple.JSONObject; @@ -15,8 +17,13 @@ import java.net.ConnectException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.ServiceLoader; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; @@ -66,6 +73,8 @@ public interface Default { */ private static final Logger LOG = Logger.getLogger("raven.client"); + private static final List PROCESSORS; + /** * The dsn used by this client. */ @@ -78,6 +87,16 @@ public interface Default { static { registerDefaults(); + PROCESSORS = Collections.unmodifiableList(loadProcessors()); + } + + private static List loadProcessors() { + List processors = new ArrayList(); + Iterator iterator = ServiceLoader.load(RequestProcessor.class).iterator(); + while (iterator.hasNext()) { + processors.add(iterator.next()); + } + return processors; } /** @@ -248,6 +267,10 @@ protected Message buildMessage(String message, String timestamp, String loggerCl jsonTags.putAll(tags); obj.put("tags", jsonTags); } + + for (RequestProcessor processor : PROCESSORS) { + processor.process(obj); + } return new Message(obj, eventId); } diff --git a/raven/src/main/java/net/kencochrane/raven/spi/RavenMDC.java b/raven/src/main/java/net/kencochrane/raven/spi/RavenMDC.java new file mode 100644 index 00000000000..ed350738846 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/spi/RavenMDC.java @@ -0,0 +1,24 @@ +package net.kencochrane.raven.spi; + +import java.util.ServiceLoader; + +public abstract class RavenMDC { + + private static RavenMDC instance; + + public static RavenMDC getInstance() { + synchronized (RavenMDC.class) { + if (instance == null) { + instance = ServiceLoader.load(RavenMDC.class).iterator().next(); + } + } + return instance; + } + + public abstract Object get(String key); + + public abstract void put(String key, Object value); + + public abstract void remove(String key); + +} diff --git a/raven/src/main/java/net/kencochrane/raven/spi/RequestProcessor.java b/raven/src/main/java/net/kencochrane/raven/spi/RequestProcessor.java new file mode 100644 index 00000000000..364721a1d47 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/spi/RequestProcessor.java @@ -0,0 +1,9 @@ +package net.kencochrane.raven.spi; + +import org.json.simple.JSONObject; + +public interface RequestProcessor { + + void process(JSONObject json); + +} From 28755f98c8594f9a74edab4547dafdbfe6720e4e Mon Sep 17 00:00:00 2001 From: Brad Chen Date: Sun, 26 Aug 2012 21:05:03 -0400 Subject: [PATCH 0063/2152] Add documentation and unit tests for the processor architecture. Remove the use of ServiceLoader and instead allow users to configure the list of processors used through log4j.properties or equivalent. --- .../raven/log4j/AsyncSentryAppender.java | 16 +++ .../raven/log4j/SentryAppender.java | 98 ++++++++++++++----- .../net/kencochrane/raven/log4j/Utils.java | 25 +++++ .../net.kencochrane.raven.spi.RavenMDC | 1 - .../raven/log4j/MockJSONProcessor.java | 15 +++ .../raven/log4j/SentryAppenderTest.java | 37 +++++-- .../resources/sentryappender.log4j.properties | 3 + .../java/net/kencochrane/raven/Client.java | 34 ++++--- .../kencochrane/raven/spi/JSONProcessor.java | 25 +++++ .../net/kencochrane/raven/spi/RavenMDC.java | 60 ++++++++++-- .../raven/spi/RequestProcessor.java | 9 -- .../kencochrane/raven/JSONProcessorTest.java | 55 +++++++++++ 12 files changed, 311 insertions(+), 67 deletions(-) create mode 100644 raven-log4j/src/main/java/net/kencochrane/raven/log4j/Utils.java delete mode 100644 raven-log4j/src/main/resources/META-INF/services/net.kencochrane.raven.spi.RavenMDC create mode 100644 raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockJSONProcessor.java create mode 100644 raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java delete mode 100644 raven/src/main/java/net/kencochrane/raven/spi/RequestProcessor.java create mode 100644 raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java index d8638af79d6..580288c2615 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java @@ -16,8 +16,13 @@ public class AsyncSentryAppender extends AsyncAppender { private String sentryDsn; + private String jsonProcessors; private SentryAppender appender; + public AsyncSentryAppender() { + Utils.initMDC(); + } + public String getSentryDsn() { return sentryDsn; } @@ -30,6 +35,7 @@ public void setSentryDsn(String sentryDsn) { } SentryAppender appender = new SentryAppender(); appender.setSentryDsn(sentryDsn); + appender.setJsonProcessors(jsonProcessors); appender.setErrorHandler(this.getErrorHandler()); appender.setLayout(this.getLayout()); appender.setName(this.getName()); @@ -38,6 +44,16 @@ public void setSentryDsn(String sentryDsn) { addAppender(appender); } + /** + * See {@link SentryAppender#setJsonProcessors}. + * + * @param jsonProcessors a comma-separated list of fully qualified class + * names of JSONProcessors + */ + public void setJsonProcessors(String jsonProcessors) { + this.jsonProcessors = jsonProcessors; + } + @Override public void append(LoggingEvent event) { if (appender == null) { diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 61880a41484..d24d346223b 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -1,7 +1,12 @@ package net.kencochrane.raven.log4j; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import net.kencochrane.raven.Client; import net.kencochrane.raven.SentryDsn; +import net.kencochrane.raven.spi.JSONProcessor; import net.kencochrane.raven.spi.RavenMDC; import org.apache.log4j.AppenderSkeleton; @@ -13,8 +18,15 @@ */ public class SentryAppender extends AppenderSkeleton { + private Log4jMDC mdc; protected String sentryDsn; protected Client client; + private String jsonProcessors; + + public SentryAppender() { + Utils.initMDC(); + mdc = (Log4jMDC)RavenMDC.getInstance(); + } public String getSentryDsn() { return sentryDsn; @@ -25,12 +37,22 @@ public void setSentryDsn(String sentryDsn) { this.sentryDsn = sentryDsn; if (client != null) { client.stop(); + client = null; } - // Create a client that start automatically - client = new Client(SentryDsn.build(sentryDsn)); } } + /** + * Set a comma-separated list of fully qualified class names of + * JSONProcessors to be used. + * + * @param jsonProcessors a comma-separated list of fully qualified class + * names of JSONProcessors + */ + public void setJsonProcessors(String jsonProcessors) { + this.jsonProcessors = jsonProcessors; + } + @Override public void close() { if (client != null) { @@ -45,28 +67,30 @@ public boolean requiresLayout() { @Override protected void append(LoggingEvent event) { - ((Log4jMDC)RavenMDC.getInstance()).setThreadLoggingEvent(event); - - Client client = fetchClient(); - // get timestamp and timestamp in correct string format. - long timestamp = event.getTimeStamp(); - - // get the log and info about the log. - String message = event.getRenderedMessage(); - String logger = event.getLogger().getName(); - int level = (event.getLevel().toInt() / 1000); //Need to divide by 1000 to keep consistent with sentry - String culprit = event.getLoggerName(); - - // is it an exception? - ThrowableInformation info = event.getThrowableInformation(); - - // send the message to the sentry server - if (info == null) { - client.captureMessage(message, timestamp, logger, level, culprit); - } else { - client.captureException(message, timestamp, logger, level, culprit, info.getThrowable()); + mdc.setThreadLoggingEvent(event); + try { + Client client = fetchClient(); + // get timestamp and timestamp in correct string format. + long timestamp = event.getTimeStamp(); + + // get the log and info about the log. + String message = event.getRenderedMessage(); + String logger = event.getLogger().getName(); + int level = (event.getLevel().toInt() / 1000); //Need to divide by 1000 to keep consistent with sentry + String culprit = event.getLoggerName(); + + // is it an exception? + ThrowableInformation info = event.getThrowableInformation(); + + // send the message to the sentry server + if (info == null) { + client.captureMessage(message, timestamp, logger, level, culprit); + } else { + client.captureException(message, timestamp, logger, level, culprit, info.getThrowable()); + } + } finally { + mdc.removeThreadLoggingEvent(); } - ((Log4jMDC)RavenMDC.getInstance()).removeThreadLoggingEvent(); } /** @@ -79,9 +103,35 @@ protected void append(LoggingEvent event) { */ protected synchronized Client fetchClient() { if (client == null) { - client = new Client(); + if (sentryDsn == null) { + client = new Client(); + } else { + client = new Client(SentryDsn.build(sentryDsn)); + } + client.setJSONProcessors(loadJSONProcessors()); } return client; } + private List loadJSONProcessors() { + if (jsonProcessors == null) { + return Collections.emptyList(); + } + try { + List processors = new ArrayList(); + String[] clazzes = jsonProcessors.split(",\\s*"); + for (String clazz : clazzes) { + JSONProcessor processor = (JSONProcessor)Class.forName(clazz).newInstance(); + processors.add(processor); + } + return processors; + } catch (ClassNotFoundException exception) { + throw new RuntimeException("Processor could not be found.", exception); + } catch (InstantiationException exception) { + throw new RuntimeException("Processor could not be instantiated.", exception); + } catch (IllegalAccessException exception) { + throw new RuntimeException("Processor could not be instantiated.", exception); + } + } + } diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/Utils.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/Utils.java new file mode 100644 index 00000000000..60e1f0502ec --- /dev/null +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/Utils.java @@ -0,0 +1,25 @@ +package net.kencochrane.raven.log4j; + +import net.kencochrane.raven.spi.RavenMDC; + +final class Utils { + + static void initMDC() { + if (RavenMDC.getInstance() != null) { + if (!(RavenMDC.getInstance() instanceof Log4jMDC)) { + throw new IllegalStateException("An incompatible RavenMDC " + + "instance has been set. Please check your Raven " + + "configuration."); + } + return; + } + RavenMDC.setInstance(new Log4jMDC()); + } + + /** + * Prevent instantiation. + */ + private Utils() { + } + +} diff --git a/raven-log4j/src/main/resources/META-INF/services/net.kencochrane.raven.spi.RavenMDC b/raven-log4j/src/main/resources/META-INF/services/net.kencochrane.raven.spi.RavenMDC deleted file mode 100644 index b768ea0ac17..00000000000 --- a/raven-log4j/src/main/resources/META-INF/services/net.kencochrane.raven.spi.RavenMDC +++ /dev/null @@ -1 +0,0 @@ -net.kencochrane.raven.log4j.Log4jMDC \ No newline at end of file diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockJSONProcessor.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockJSONProcessor.java new file mode 100644 index 00000000000..a2fb330c20a --- /dev/null +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockJSONProcessor.java @@ -0,0 +1,15 @@ +package net.kencochrane.raven.log4j; + +import org.json.simple.JSONObject; + +import net.kencochrane.raven.spi.JSONProcessor; + +public class MockJSONProcessor implements JSONProcessor { + + @Override + @SuppressWarnings("unchecked") + public void process(JSONObject json) { + json.put("Test", "Value"); + } + +} diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 963a4359a50..9b87328a25b 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -50,7 +50,7 @@ public void debugLevel() throws IOException, ParseException { final String message = "hi there!"; // Log - System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + setSentryDSN(projectId); configureLog4J(); Logger.getLogger(loggerName).debug(message); @@ -66,7 +66,7 @@ public void infoLevel() throws IOException, ParseException { final String message = "This message will self-destruct in 5...4...3..."; // Log - System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + setSentryDSN(projectId); configureLog4J(); Logger.getLogger(loggerName).info(message); @@ -82,7 +82,7 @@ public void warnLevel() throws IOException, ParseException { final String message = "Warning! Warning! WARNING! Oh, come on!"; // Log - System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + setSentryDSN(projectId); configureLog4J(); Logger.getLogger(loggerName).warn(message); @@ -98,7 +98,7 @@ public void errorLevel() throws IOException, ParseException { final String message = "D'oh!"; // Log - System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + setSentryDSN(projectId); configureLog4J(); Logger.getLogger(loggerName).error(message); @@ -116,7 +116,7 @@ public void errorLevel_withException() throws IOException, ParseException { final String message = "D'oh!"; // Log - System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + setSentryDSN(projectId); configureLog4J(); NullPointerException npe = new NullPointerException("Damn you!"); Logger.getLogger(loggerName).error(message, npe); @@ -135,6 +135,19 @@ public void errorLevel_withException() throws IOException, ParseException { assertEquals(NullPointerException.class.getPackage().getName(), exception.get("module")); } + @Test + public void testJSONProcessors() throws IOException, ParseException { + setSentryDSN("6"); + configureLog4J(); + Logger.getLogger("logger").info("test"); + JSONObject json = fetchJSONObject(sentry); + assertEquals("Value", json.get("Test")); + } + + protected void setSentryDSN(String projectId) { + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + } + protected void configureLog4J() { PropertyConfigurator.configure(getClass().getResource("/sentryappender.log4j.properties")); } @@ -145,11 +158,7 @@ JSONObject verifyMessage(String culprit, long logLevel, String projectId, String } protected static JSONObject verifyMessage(SentryMock sentry, String culprit, long logLevel, String projectId, String message) throws IOException, ParseException { - String payload = Utils.fromUtf8(sentry.fetchMessage()); - String[] payloadParts = StringUtils.split(payload, "\n\n"); - assertEquals(2, payloadParts.length); - String raw = Utils.fromUtf8(Base64.decodeBase64(payloadParts[1])); - JSONObject json = (JSONObject) new JSONParser().parse(raw); + JSONObject json = fetchJSONObject(sentry); assertEquals(message, json.get("message")); assertEquals(culprit, json.get("culprit")); assertEquals(projectId, json.get("project")); @@ -157,6 +166,14 @@ protected static JSONObject verifyMessage(SentryMock sentry, String culprit, lon return json; } + protected static JSONObject fetchJSONObject(SentryMock sentry) throws IOException, ParseException { + String payload = Utils.fromUtf8(sentry.fetchMessage()); + String[] payloadParts = StringUtils.split(payload, "\n\n"); + assertEquals(2, payloadParts.length); + String raw = Utils.fromUtf8(Base64.decodeBase64(payloadParts[1])); + return (JSONObject) new JSONParser().parse(raw); + } + public static class SentryMock { public final DatagramSocket serverSocket; public final String host; diff --git a/raven-log4j/src/test/resources/sentryappender.log4j.properties b/raven-log4j/src/test/resources/sentryappender.log4j.properties index f06ec40b4d4..7c6d91d12ae 100644 --- a/raven-log4j/src/test/resources/sentryappender.log4j.properties +++ b/raven-log4j/src/test/resources/sentryappender.log4j.properties @@ -13,6 +13,9 @@ log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n log4j.appender.sentry=net.kencochrane.raven.log4j.SentryAppender log4j.appender.sentry.sentryDsn=SENTRY_DSN +# Add a JSONProcessor +log4j.appender.sentry.jsonProcessors=net.kencochrane.raven.log4j.MockJSONProcessor + # Do not let the Raven logging go to Sentry or you could end up in an infinite loop when # something goes wrong with your configuration. Sentry uses the java.util.logging package # so this should only happen when you add a bridge from JUL to Log4J. diff --git a/raven/src/main/java/net/kencochrane/raven/Client.java b/raven/src/main/java/net/kencochrane/raven/Client.java index 22522e77ffd..ca4579460f9 100644 --- a/raven/src/main/java/net/kencochrane/raven/Client.java +++ b/raven/src/main/java/net/kencochrane/raven/Client.java @@ -1,6 +1,6 @@ package net.kencochrane.raven; -import net.kencochrane.raven.spi.RequestProcessor; +import net.kencochrane.raven.spi.JSONProcessor; import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang.time.DateFormatUtils; @@ -20,10 +20,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.ServiceLoader; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; @@ -73,8 +71,6 @@ public interface Default { */ private static final Logger LOG = Logger.getLogger("raven.client"); - private static final List PROCESSORS; - /** * The dsn used by this client. */ @@ -85,18 +81,13 @@ public interface Default { */ protected Transport transport; + /** + * JSONProcessor instances. Initialized with an empty list to prevent NPE. + */ + private List jsonProcessors = Collections.emptyList(); + static { registerDefaults(); - PROCESSORS = Collections.unmodifiableList(loadProcessors()); - } - - private static List loadProcessors() { - List processors = new ArrayList(); - Iterator iterator = ServiceLoader.load(RequestProcessor.class).iterator(); - while (iterator.hasNext()) { - processors.add(iterator.next()); - } - return processors; } /** @@ -172,6 +163,17 @@ public Client(Transport transport, boolean autoStart) { } } + /** + * Set the processors to be used by this client. Instances from the list are + * copied over. + * + * @param processors a list of processors to be used by this client + */ + public synchronized void setJSONProcessors(List processors) { + this.jsonProcessors = new ArrayList(processors.size()); + this.jsonProcessors.addAll(processors); + } + public String captureMessage(String msg) { return captureMessage(msg, null, null, null, null); } @@ -268,7 +270,7 @@ protected Message buildMessage(String message, String timestamp, String loggerCl obj.put("tags", jsonTags); } - for (RequestProcessor processor : PROCESSORS) { + for (JSONProcessor processor : jsonProcessors) { processor.process(obj); } return new Message(obj, eventId); diff --git a/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java b/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java new file mode 100644 index 00000000000..e95162a6eef --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java @@ -0,0 +1,25 @@ +package net.kencochrane.raven.spi; + +import org.json.simple.JSONObject; + +/** + * A JSONProcessor is used to modify JSON requests before they are sent to + * Sentry. It is expected for a JSON processor to be singleton and be + * thread-safe. + * + * To register JSONProcessors, refer to the settings of the appender used. + * + * @author vvasabi + * @since 1.0 + */ +public interface JSONProcessor { + + /** + * Modify the JSON request object specified before it is sent to Sentry. + * This method may be called concurrently and therefore must be thread-safe. + * + * @param json request JSON object to be modified + */ + void process(JSONObject json); + +} diff --git a/raven/src/main/java/net/kencochrane/raven/spi/RavenMDC.java b/raven/src/main/java/net/kencochrane/raven/spi/RavenMDC.java index ed350738846..f738f4480c7 100644 --- a/raven/src/main/java/net/kencochrane/raven/spi/RavenMDC.java +++ b/raven/src/main/java/net/kencochrane/raven/spi/RavenMDC.java @@ -1,24 +1,70 @@ package net.kencochrane.raven.spi; -import java.util.ServiceLoader; - +/** + * Since Raven plugins may be executed on threads different from those that + * produce logs, RavenMDC provides a means for context variables to be passed + * from log spawning threads to log processing threads. + * + * This service is intended to be used by Raven plugins and not by user + * application. An implementation is expected to be singleton and should be + * thread-safe. + * + * @author vvasabi + * @since 1.0 + */ public abstract class RavenMDC { private static RavenMDC instance; + /** + * Get the current instance. + * + * @return current instance + */ public static RavenMDC getInstance() { - synchronized (RavenMDC.class) { - if (instance == null) { - instance = ServiceLoader.load(RavenMDC.class).iterator().next(); - } - } return instance; } + /** + * Set the instance of RavenMDC. Note that this method can only be called + * once. + * + * @param newInstance new instance of RavenMDC + */ + public static synchronized void setInstance(RavenMDC newInstance) { + if (newInstance == null) { + throw new NullPointerException("New instance cannot be null."); + } + if (instance != null) { + throw new IllegalStateException("A RavenMDC instance already exists."); + } + instance = newInstance; + } + + /** + * Get the context variable specified by key from MDC. If the value + * specified does not exist, null is returned. + * + * @param key key of the context variable to get + * @return context variable specified by key, or null if not found + */ public abstract Object get(String key); + /** + * Add a context variable to MDC. If an existing value with the same key + * exists, it is overridden. + * + * @param key key of the context variable + * @param value value of the context variable + */ public abstract void put(String key, Object value); + /** + * Remove a context variable from MDC. If the value specified by the key + * does not exist, this method does nothing. + * + * @param key key of the context variable to remove + */ public abstract void remove(String key); } diff --git a/raven/src/main/java/net/kencochrane/raven/spi/RequestProcessor.java b/raven/src/main/java/net/kencochrane/raven/spi/RequestProcessor.java deleted file mode 100644 index 364721a1d47..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/spi/RequestProcessor.java +++ /dev/null @@ -1,9 +0,0 @@ -package net.kencochrane.raven.spi; - -import org.json.simple.JSONObject; - -public interface RequestProcessor { - - void process(JSONObject json); - -} diff --git a/raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java b/raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java new file mode 100644 index 00000000000..ff9c5f65520 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java @@ -0,0 +1,55 @@ +package net.kencochrane.raven; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import net.kencochrane.raven.Events.LogLevel; +import net.kencochrane.raven.spi.JSONProcessor; + +import org.json.simple.JSONObject; +import org.junit.After; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * This test ensures that {@link Client} would execute the + * {@link net.kencochrane.raven.spi.JSONProcessor} passed in. + * + * @author vvasabi + */ +public class JSONProcessorTest extends Client { + + public JSONProcessorTest() { + super(SentryDsn.build("http://public:private@localhost:9000/1")); + } + + @After + public void tearDown() { + setJSONProcessors(Collections.emptyList()); + } + + @Test + public void testWithProcessor() { + List processors = new ArrayList(); + processors.add(new MockJSONProcessor()); + setJSONProcessors(processors); + Message message = buildMessage("test", + formatTimestamp(new Date().getTime()), "test", + LogLevel.ERROR.intValue, "test", null, null); + assertEquals("Value", message.json.get("Test")); + } + + private static class MockJSONProcessor implements JSONProcessor { + + @Override + @SuppressWarnings("unchecked") + public void process(JSONObject json) { + json.put("Test", "Value"); + } + + } + +} From d8ec238f65a62bef581eece37ec13573a617a4a5 Mon Sep 17 00:00:00 2001 From: Brad Chen Date: Sun, 26 Aug 2012 22:51:42 -0400 Subject: [PATCH 0064/2152] Remove Utils and move initMDC to SentryAppender. --- .../raven/log4j/AsyncSentryAppender.java | 2 +- .../raven/log4j/SentryAppender.java | 14 ++++++++++- .../net/kencochrane/raven/log4j/Utils.java | 25 ------------------- 3 files changed, 14 insertions(+), 27 deletions(-) delete mode 100644 raven-log4j/src/main/java/net/kencochrane/raven/log4j/Utils.java diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java index 580288c2615..c1cda42e115 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java @@ -20,7 +20,7 @@ public class AsyncSentryAppender extends AsyncAppender { private SentryAppender appender; public AsyncSentryAppender() { - Utils.initMDC(); + SentryAppender.initMDC(); } public String getSentryDsn() { diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index d24d346223b..a7d1e919359 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -24,7 +24,7 @@ public class SentryAppender extends AppenderSkeleton { private String jsonProcessors; public SentryAppender() { - Utils.initMDC(); + initMDC(); mdc = (Log4jMDC)RavenMDC.getInstance(); } @@ -134,4 +134,16 @@ private List loadJSONProcessors() { } } + public static void initMDC() { + if (RavenMDC.getInstance() != null) { + if (!(RavenMDC.getInstance() instanceof Log4jMDC)) { + throw new IllegalStateException("An incompatible RavenMDC " + + "instance has been set. Please check your Raven " + + "configuration."); + } + return; + } + RavenMDC.setInstance(new Log4jMDC()); + } + } diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/Utils.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/Utils.java deleted file mode 100644 index 60e1f0502ec..00000000000 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/Utils.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.kencochrane.raven.log4j; - -import net.kencochrane.raven.spi.RavenMDC; - -final class Utils { - - static void initMDC() { - if (RavenMDC.getInstance() != null) { - if (!(RavenMDC.getInstance() instanceof Log4jMDC)) { - throw new IllegalStateException("An incompatible RavenMDC " - + "instance has been set. Please check your Raven " - + "configuration."); - } - return; - } - RavenMDC.setInstance(new Log4jMDC()); - } - - /** - * Prevent instantiation. - */ - private Utils() { - } - -} From 20eed8f0a3b97b291071088d0014d415cfa9f4ec Mon Sep 17 00:00:00 2001 From: Brad Chen Date: Mon, 27 Aug 2012 10:08:15 -0400 Subject: [PATCH 0065/2152] Add unit test for async appender. Ensure async appender does not get initialized without json processors properly configured. --- .../raven/log4j/AsyncSentryAppender.java | 27 ++++++++++++------- .../raven/log4j/AsyncSentryAppenderTest.java | 23 ++++++++++++---- .../asyncsentryappender.log4j.properties | 3 +++ 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java index c1cda42e115..ffdcf789d40 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java @@ -32,16 +32,8 @@ public void setSentryDsn(String sentryDsn) { this.sentryDsn = sentryDsn; if (appender != null) { removeAppender(appender); + appender = null; } - SentryAppender appender = new SentryAppender(); - appender.setSentryDsn(sentryDsn); - appender.setJsonProcessors(jsonProcessors); - appender.setErrorHandler(this.getErrorHandler()); - appender.setLayout(this.getLayout()); - appender.setName(this.getName()); - appender.setThreshold(this.getThreshold()); - this.appender = appender; - addAppender(appender); } /** @@ -58,10 +50,25 @@ public void setJsonProcessors(String jsonProcessors) { public void append(LoggingEvent event) { if (appender == null) { synchronized (this) { - setSentryDsn(SentryDsn.build().toString()); + if (sentryDsn == null) { + setSentryDsn(SentryDsn.build().toString()); + } + createAppender(); } } super.append(event); } + private void createAppender() { + SentryAppender appender = new SentryAppender(); + appender.setSentryDsn(sentryDsn); + appender.setJsonProcessors(jsonProcessors); + appender.setErrorHandler(this.getErrorHandler()); + appender.setLayout(this.getLayout()); + appender.setName(this.getName()); + appender.setThreshold(this.getThreshold()); + this.appender = appender; + addAppender(appender); + } + } diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java index 7f3f525f03d..386cacb5e7e 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java @@ -42,7 +42,7 @@ public void debugLevel() throws IOException, ParseException { final String message = "hi there!"; // Log - System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + setSentryDSN(projectId); configureLog4J(); Logger.getLogger(loggerName).debug(message); @@ -58,7 +58,7 @@ public void infoLevel() throws IOException, ParseException { final String message = "This message will self-destruct in 5...4...3..."; // Log - System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + setSentryDSN(projectId); configureLog4J(); Logger.getLogger(loggerName).info(message); @@ -74,7 +74,7 @@ public void warnLevel() throws IOException, ParseException { final String message = "Warning! Warning! WARNING! Oh, come on!"; // Log - System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + setSentryDSN(projectId); configureLog4J(); Logger.getLogger(loggerName).warn(message); @@ -90,7 +90,7 @@ public void errorLevel() throws IOException, ParseException { final String message = "D'oh!"; // Log - System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + setSentryDSN(projectId); configureLog4J(); Logger.getLogger(loggerName).error(message); @@ -108,7 +108,7 @@ public void errorLevel_withException() throws IOException, ParseException { final String message = "D'oh!"; // Log - System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + setSentryDSN(projectId); configureLog4J(); NullPointerException npe = new NullPointerException("Damn you!"); Logger.getLogger(loggerName).error(message, npe); @@ -127,6 +127,19 @@ public void errorLevel_withException() throws IOException, ParseException { assertEquals(NullPointerException.class.getPackage().getName(), exception.get("module")); } + @Test + public void testJSONProcessors() throws IOException, ParseException { + setSentryDSN("6"); + configureLog4J(); + Logger.getLogger("logger").info("test"); + JSONObject json = SentryAppenderTest.fetchJSONObject(sentry); + assertEquals("Value", json.get("Test")); + } + + protected void setSentryDSN(String projectId) { + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + } + protected void configureLog4J() { PropertyConfigurator.configure(getClass().getResource("/asyncsentryappender.log4j.properties")); } diff --git a/raven-log4j/src/test/resources/asyncsentryappender.log4j.properties b/raven-log4j/src/test/resources/asyncsentryappender.log4j.properties index e05a6e53abf..4f4edb30f73 100644 --- a/raven-log4j/src/test/resources/asyncsentryappender.log4j.properties +++ b/raven-log4j/src/test/resources/asyncsentryappender.log4j.properties @@ -13,6 +13,9 @@ log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n log4j.appender.sentry=net.kencochrane.raven.log4j.AsyncSentryAppender log4j.appender.sentry.sentryDsn=SENTRY_DSN +# Add a JSONProcessor +log4j.appender.sentry.jsonProcessors=net.kencochrane.raven.log4j.MockJSONProcessor + # Do not let the Raven logging go to Sentry or you could end up in an infinite loop when # something goes wrong with your configuration. Sentry uses the java.util.logging package # so this should only happen when you add a bridge from JUL to Log4J. From f02d2303efc43805c55848bfcbebadd32b0162ef Mon Sep 17 00:00:00 2001 From: Brad Chen Date: Mon, 27 Aug 2012 13:32:35 -0400 Subject: [PATCH 0066/2152] Add ServletJSONProcessor. --- .../ext/RavenServletRequestListener.java | 31 +++++ .../raven/ext/ServletJSONProcessor.java | 113 ++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/ext/RavenServletRequestListener.java create mode 100644 raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java diff --git a/raven/src/main/java/net/kencochrane/raven/ext/RavenServletRequestListener.java b/raven/src/main/java/net/kencochrane/raven/ext/RavenServletRequestListener.java new file mode 100644 index 00000000000..66a73ff9eb2 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/ext/RavenServletRequestListener.java @@ -0,0 +1,31 @@ +package net.kencochrane.raven.ext; + +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.annotation.WebListener; + +import net.kencochrane.raven.spi.RavenMDC; + +/** + * Store HttpServletRequest object in RavenMDC, allowing the request to be + * accessed by {@link ServletJSONProcessor}. + * + * @author vvasabi + * @since 1.0 + */ +@WebListener +public class RavenServletRequestListener implements ServletRequestListener { + + @Override + public void requestInitialized(ServletRequestEvent sre) { + RavenMDC mdc = RavenMDC.getInstance(); + mdc.put(ServletJSONProcessor.MDC_REQUEST, sre.getServletRequest()); + } + + @Override + public void requestDestroyed(ServletRequestEvent sre) { + RavenMDC mdc = RavenMDC.getInstance(); + mdc.remove(ServletJSONProcessor.MDC_REQUEST); + } + +} diff --git a/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java b/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java new file mode 100644 index 00000000000..7ac55f8b81d --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java @@ -0,0 +1,113 @@ +package net.kencochrane.raven.ext; + +import java.util.Enumeration; +import java.util.Map; +import java.util.Map.Entry; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang.StringUtils; +import org.json.simple.JSONObject; + +import net.kencochrane.raven.spi.JSONProcessor; +import net.kencochrane.raven.spi.RavenMDC; + +/** + * Add HTTP request information to logs when logs are created on HTTP request + * threads. + * + * @author vvasabi + * @since 1.0 + */ +public class ServletJSONProcessor implements JSONProcessor { + + public static final String MDC_REQUEST + = ServletJSONProcessor.class.getName() + ".httpServletRequest"; + private static final String HTTP_INTERFACE = "sentry.interfaces.Http"; + + @Override + @SuppressWarnings("unchecked") + public void process(JSONObject json) { + RavenMDC mdc = RavenMDC.getInstance(); + HttpServletRequest request = (HttpServletRequest)mdc.get(MDC_REQUEST); + if (request == null) { + // no request available; do nothing + return; + } + + json.put(HTTP_INTERFACE, buildHttpObject(request)); + } + + @SuppressWarnings("unchecked") + private JSONObject buildHttpObject(HttpServletRequest request) { + JSONObject http = new JSONObject(); + http.put("url", getUrl(request)); + http.put("method", request.getMethod()); + http.put("data", getData(request)); + http.put("query_string", request.getQueryString()); + http.put("headers", getHeaders(request)); + http.put("env", getEnvironmentVariables(request)); + return http; + } + + private String getUrl(HttpServletRequest request) { + StringBuffer sb = request.getRequestURL(); + String query = request.getQueryString(); + if (query != null) { + sb.append("?").append(query); + } + return sb.toString(); + } + + @SuppressWarnings("unchecked") + private JSONObject getData(HttpServletRequest request) { + if ("GET".equals(request.getMethod())) { + return null; + } + + JSONObject data = new JSONObject(); + Map params = request.getParameterMap(); + for (Entry entry : params.entrySet()) { + data.put(entry.getKey(), entry.getValue()[0]); + } + return data; + } + + @SuppressWarnings("unchecked") + private JSONObject getHeaders(HttpServletRequest request) { + JSONObject headers = new JSONObject(); + Enumeration headersEnum = request.getHeaderNames(); + while (headersEnum.hasMoreElements()) { + String name = headersEnum.nextElement(); + headers.put(capitalize(name), request.getHeader(name)); + } + return headers; + } + + @SuppressWarnings("unchecked") + private JSONObject getEnvironmentVariables(HttpServletRequest request) { + JSONObject env = new JSONObject(); + env.put("REMOTE_ADDR", request.getRemoteAddr()); + env.put("SERVER_NAME", request.getServerName()); + env.put("SERVER_PORT", request.getServerPort()); + env.put("SERVER_PROTOCOL", request.getProtocol()); + return env; + } + + /** + * Capitalize the first letter of each part of a header name. This is + * necessary because Sentry currently expects header names to be formatted + * this way. + * + * @param headerName header name to capitalize + * @return capitalized header name + */ + private static String capitalize(String headerName) { + String[] tokens = headerName.split("-"); + for (int i = 0; i < tokens.length; i ++) { + tokens[i] = StringUtils.capitalize(tokens[i]); + } + return StringUtils.join(tokens, "-"); + } + +} From 7fb42074449f0fbfb786e88911cffa8ed2c2aabf Mon Sep 17 00:00:00 2001 From: Brad Chen Date: Mon, 27 Aug 2012 13:35:34 -0400 Subject: [PATCH 0067/2152] Add javax.servlet-api dependency. --- raven/pom.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/raven/pom.xml b/raven/pom.xml index 1e6df27ab09..093bb459985 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -30,6 +30,13 @@ json-simple 1.1 + + + javax.servlet + javax.servlet-api + 3.0.1 + true + com.googlecode.jmockit jmockit @@ -67,4 +74,4 @@
    - \ No newline at end of file + From 3b1ea7f8e48a14e51db117c45844452a49100cf1 Mon Sep 17 00:00:00 2001 From: Brad Chen Date: Mon, 27 Aug 2012 14:13:12 -0400 Subject: [PATCH 0068/2152] Include cookies. Append data only when request is POST. --- .../raven/ext/ServletJSONProcessor.java | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java b/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java index 7ac55f8b81d..e4d08f13b5d 100644 --- a/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java +++ b/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java @@ -4,6 +4,7 @@ import java.util.Map; import java.util.Map.Entry; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils; @@ -39,18 +40,19 @@ public void process(JSONObject json) { } @SuppressWarnings("unchecked") - private JSONObject buildHttpObject(HttpServletRequest request) { + private static JSONObject buildHttpObject(HttpServletRequest request) { JSONObject http = new JSONObject(); http.put("url", getUrl(request)); http.put("method", request.getMethod()); http.put("data", getData(request)); http.put("query_string", request.getQueryString()); + http.put("cookies", getCookies(request)); http.put("headers", getHeaders(request)); http.put("env", getEnvironmentVariables(request)); return http; } - private String getUrl(HttpServletRequest request) { + private static String getUrl(HttpServletRequest request) { StringBuffer sb = request.getRequestURL(); String query = request.getQueryString(); if (query != null) { @@ -60,8 +62,8 @@ private String getUrl(HttpServletRequest request) { } @SuppressWarnings("unchecked") - private JSONObject getData(HttpServletRequest request) { - if ("GET".equals(request.getMethod())) { + private static JSONObject getData(HttpServletRequest request) { + if (!"POST".equals(request.getMethod())) { return null; } @@ -74,7 +76,16 @@ private JSONObject getData(HttpServletRequest request) { } @SuppressWarnings("unchecked") - private JSONObject getHeaders(HttpServletRequest request) { + private static JSONObject getCookies(HttpServletRequest request) { + JSONObject cookiesMap = new JSONObject(); + for (Cookie cookie : request.getCookies()) { + cookiesMap.put(cookie.getName(), cookie.getValue()); + } + return cookiesMap; + } + + @SuppressWarnings("unchecked") + private static JSONObject getHeaders(HttpServletRequest request) { JSONObject headers = new JSONObject(); Enumeration headersEnum = request.getHeaderNames(); while (headersEnum.hasMoreElements()) { @@ -85,7 +96,7 @@ private JSONObject getHeaders(HttpServletRequest request) { } @SuppressWarnings("unchecked") - private JSONObject getEnvironmentVariables(HttpServletRequest request) { + private static JSONObject getEnvironmentVariables(HttpServletRequest request) { JSONObject env = new JSONObject(); env.put("REMOTE_ADDR", request.getRemoteAddr()); env.put("SERVER_NAME", request.getServerName()); From 572bf31885a7f215ea92d3a7089b751f8de848bb Mon Sep 17 00:00:00 2001 From: Brad Chen Date: Mon, 27 Aug 2012 14:14:57 -0400 Subject: [PATCH 0069/2152] Update README with JSONProcessor information and vvasabi as contributor. --- README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 69b98c15487..e9b08f740c8 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,31 @@ By default the client will **not** block when the queue is full and will use a q async+http://public:private@host:port/1?raven.waitWhenFull=true&raven.capacity=20 +#### Enabling ServletJSONProcessor +In a servlet environment, Raven can append request information to logs sent to Sentry when logs are created on request threads. Information sent to Sentry include: + +* Request URL +* POST parameters +* Request headers +* Cookies +* Environment variables, including: + * Remote address + * Server name + * Server port + * Server protocol + +Please be aware that sensitive information, such as user passwords or credit card numbers, may potentially be logged. Common security measures, such as protecting the Sentry installation, should be practiced. To enable this support, add the following line to Log4j configuration: + + log4j.appender.sentry.jsonProcessors=net.kencochrane.raven.ext.ServletJSONProcessor + +Then, add the following lines to web.xml: + + + + net.kencochrane.raven.ext.RavenServletRequestListener + + + * * * ## Installation @@ -146,6 +171,7 @@ TODO - 1.0 - Rewrite - Support tags + - Added support for JSON processors (see bundled `ServletJSONProcessor`) - 0.6 - Added support for sending messages through UDP - 0.5 @@ -173,4 +199,5 @@ TODO - Ken Cochrane (@KenCochrane) - Kevin Wetzels (@roambe) - David Cramer (@zeeg) -- Mark Philpot (@griphiam) \ No newline at end of file +- Mark Philpot (@griphiam) +- Brad Chen (@vvasabi) From b3c692c632de8307b4aec010822336863e6d6d99 Mon Sep 17 00:00:00 2001 From: Brad Chen Date: Mon, 27 Aug 2012 14:31:39 -0400 Subject: [PATCH 0070/2152] Change javax.servlet dependency's scope to provided. --- raven/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/pom.xml b/raven/pom.xml index 093bb459985..a05a810cf61 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -35,7 +35,7 @@ javax.servlet javax.servlet-api 3.0.1 - true + provided com.googlecode.jmockit From 4b857a8e7cae7433eade841922d2d2f486909e10 Mon Sep 17 00:00:00 2001 From: Brad Chen Date: Mon, 27 Aug 2012 16:50:05 -0400 Subject: [PATCH 0071/2152] Fix a race condition, which may occur when context objects, such as the request object, are reused in different threads. --- .../raven/log4j/AsyncSentryAppender.java | 2 + .../raven/log4j/SentryAppender.java | 44 ++++++++++++++++--- .../raven/log4j/AsyncSentryAppenderTest.java | 2 +- .../raven/log4j/MockJSONProcessor.java | 15 ------- .../raven/log4j/SentryAppenderTest.java | 22 +++++++++- .../asyncsentryappender.log4j.properties | 2 +- .../resources/sentryappender.log4j.properties | 2 +- raven/pom.xml | 2 +- .../ext/RavenServletRequestListener.java | 16 ++++--- .../raven/ext/ServletJSONProcessor.java | 21 ++++++--- .../kencochrane/raven/spi/JSONProcessor.java | 11 +++++ .../kencochrane/raven/JSONProcessorTest.java | 14 +++++- 12 files changed, 111 insertions(+), 42 deletions(-) delete mode 100644 raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockJSONProcessor.java diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java index ffdcf789d40..4aedd42c30f 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java @@ -56,11 +56,13 @@ public void append(LoggingEvent event) { createAppender(); } } + appender.notifyProcessors(event); super.append(event); } private void createAppender() { SentryAppender appender = new SentryAppender(); + appender.setAsync(true); appender.setSentryDsn(sentryDsn); appender.setJsonProcessors(jsonProcessors); appender.setErrorHandler(this.getErrorHandler()); diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index a7d1e919359..75712abb22a 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -18,16 +18,25 @@ */ public class SentryAppender extends AppenderSkeleton { + private boolean async; private Log4jMDC mdc; protected String sentryDsn; protected Client client; - private String jsonProcessors; + private List jsonProcessors = Collections.emptyList(); public SentryAppender() { initMDC(); mdc = (Log4jMDC)RavenMDC.getInstance(); } + public boolean isAsync() { + return async; + } + + public void setAsync(boolean async) { + this.async = async; + } + public String getSentryDsn() { return sentryDsn; } @@ -49,8 +58,22 @@ public void setSentryDsn(String sentryDsn) { * @param jsonProcessors a comma-separated list of fully qualified class * names of JSONProcessors */ - public void setJsonProcessors(String jsonProcessors) { - this.jsonProcessors = jsonProcessors; + public void setJsonProcessors(String setting) { + this.jsonProcessors = loadJSONProcessors(setting); + } + + /** + * Notify processors that a message has been logged. Note that this method + * is intended to be run on the same thread that creates the message. + */ + public void notifyProcessors(LoggingEvent event) { + Throwable exception = null; + if (event.getThrowableInformation() != null) { + exception = event.getThrowableInformation().getThrowable(); + } + for (JSONProcessor processor : jsonProcessors) { + processor.prepareDiagnosticContext(exception); + } } @Override @@ -70,6 +93,7 @@ protected void append(LoggingEvent event) { mdc.setThreadLoggingEvent(event); try { Client client = fetchClient(); + // get timestamp and timestamp in correct string format. long timestamp = event.getTimeStamp(); @@ -82,6 +106,12 @@ protected void append(LoggingEvent event) { // is it an exception? ThrowableInformation info = event.getThrowableInformation(); + // notify processors about the message + // (in async mode this is done by AsyncSentryAppender) + if (!async) { + notifyProcessors(event); + } + // send the message to the sentry server if (info == null) { client.captureMessage(message, timestamp, logger, level, culprit); @@ -108,18 +138,18 @@ protected synchronized Client fetchClient() { } else { client = new Client(SentryDsn.build(sentryDsn)); } - client.setJSONProcessors(loadJSONProcessors()); + client.setJSONProcessors(jsonProcessors); } return client; } - private List loadJSONProcessors() { - if (jsonProcessors == null) { + private static List loadJSONProcessors(String setting) { + if (setting == null) { return Collections.emptyList(); } try { List processors = new ArrayList(); - String[] clazzes = jsonProcessors.split(",\\s*"); + String[] clazzes = setting.split(",\\s*"); for (String clazz : clazzes) { JSONProcessor processor = (JSONProcessor)Class.forName(clazz).newInstance(); processors.add(processor); diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java index 386cacb5e7e..789beae830f 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java @@ -133,7 +133,7 @@ public void testJSONProcessors() throws IOException, ParseException { configureLog4J(); Logger.getLogger("logger").info("test"); JSONObject json = SentryAppenderTest.fetchJSONObject(sentry); - assertEquals("Value", json.get("Test")); + assertEquals(1, ((Long)json.get("Test")).longValue()); } protected void setSentryDSN(String projectId) { diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockJSONProcessor.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockJSONProcessor.java deleted file mode 100644 index a2fb330c20a..00000000000 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockJSONProcessor.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.kencochrane.raven.log4j; - -import org.json.simple.JSONObject; - -import net.kencochrane.raven.spi.JSONProcessor; - -public class MockJSONProcessor implements JSONProcessor { - - @Override - @SuppressWarnings("unchecked") - public void process(JSONObject json) { - json.put("Test", "Value"); - } - -} diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 9b87328a25b..bb24a3cc0c6 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -1,6 +1,8 @@ package net.kencochrane.raven.log4j; import net.kencochrane.raven.Utils; +import net.kencochrane.raven.spi.JSONProcessor; + import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Level; @@ -141,7 +143,7 @@ public void testJSONProcessors() throws IOException, ParseException { configureLog4J(); Logger.getLogger("logger").info("test"); JSONObject json = fetchJSONObject(sentry); - assertEquals("Value", json.get("Test")); + assertEquals(1, ((Long)json.get("Test")).longValue()); } protected void setSentryDSN(String projectId) { @@ -201,4 +203,22 @@ public byte[] fetchMessage() throws IOException { } + public static class MockJSONProcessor implements JSONProcessor { + + private Long value = 0L; + + @Override + public void prepareDiagnosticContext(Throwable exception) { + // this is done to ensure prepareDiagnosticContext is called exactly once + value++; + } + + @Override + @SuppressWarnings("unchecked") + public void process(JSONObject json) { + json.put("Test", value); // value should be 1 + } + + } + } diff --git a/raven-log4j/src/test/resources/asyncsentryappender.log4j.properties b/raven-log4j/src/test/resources/asyncsentryappender.log4j.properties index 4f4edb30f73..73d77a37ef4 100644 --- a/raven-log4j/src/test/resources/asyncsentryappender.log4j.properties +++ b/raven-log4j/src/test/resources/asyncsentryappender.log4j.properties @@ -14,7 +14,7 @@ log4j.appender.sentry=net.kencochrane.raven.log4j.AsyncSentryAppender log4j.appender.sentry.sentryDsn=SENTRY_DSN # Add a JSONProcessor -log4j.appender.sentry.jsonProcessors=net.kencochrane.raven.log4j.MockJSONProcessor +log4j.appender.sentry.jsonProcessors=net.kencochrane.raven.log4j.SentryAppenderTest$MockJSONProcessor # Do not let the Raven logging go to Sentry or you could end up in an infinite loop when # something goes wrong with your configuration. Sentry uses the java.util.logging package diff --git a/raven-log4j/src/test/resources/sentryappender.log4j.properties b/raven-log4j/src/test/resources/sentryappender.log4j.properties index 7c6d91d12ae..97dcc8563ef 100644 --- a/raven-log4j/src/test/resources/sentryappender.log4j.properties +++ b/raven-log4j/src/test/resources/sentryappender.log4j.properties @@ -14,7 +14,7 @@ log4j.appender.sentry=net.kencochrane.raven.log4j.SentryAppender log4j.appender.sentry.sentryDsn=SENTRY_DSN # Add a JSONProcessor -log4j.appender.sentry.jsonProcessors=net.kencochrane.raven.log4j.MockJSONProcessor +log4j.appender.sentry.jsonProcessors=net.kencochrane.raven.log4j.SentryAppenderTest$MockJSONProcessor # Do not let the Raven logging go to Sentry or you could end up in an infinite loop when # something goes wrong with your configuration. Sentry uses the java.util.logging package diff --git a/raven/pom.xml b/raven/pom.xml index 093bb459985..a05a810cf61 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -35,7 +35,7 @@ javax.servlet javax.servlet-api 3.0.1 - true + provided com.googlecode.jmockit diff --git a/raven/src/main/java/net/kencochrane/raven/ext/RavenServletRequestListener.java b/raven/src/main/java/net/kencochrane/raven/ext/RavenServletRequestListener.java index 66a73ff9eb2..d2ecfbef898 100644 --- a/raven/src/main/java/net/kencochrane/raven/ext/RavenServletRequestListener.java +++ b/raven/src/main/java/net/kencochrane/raven/ext/RavenServletRequestListener.java @@ -3,8 +3,7 @@ import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.annotation.WebListener; - -import net.kencochrane.raven.spi.RavenMDC; +import javax.servlet.http.HttpServletRequest; /** * Store HttpServletRequest object in RavenMDC, allowing the request to be @@ -16,16 +15,21 @@ @WebListener public class RavenServletRequestListener implements ServletRequestListener { + private static final ThreadLocal THREAD_REQUEST + = new ThreadLocal(); + @Override public void requestInitialized(ServletRequestEvent sre) { - RavenMDC mdc = RavenMDC.getInstance(); - mdc.put(ServletJSONProcessor.MDC_REQUEST, sre.getServletRequest()); + THREAD_REQUEST.set((HttpServletRequest)sre.getServletRequest()); } @Override public void requestDestroyed(ServletRequestEvent sre) { - RavenMDC mdc = RavenMDC.getInstance(); - mdc.remove(ServletJSONProcessor.MDC_REQUEST); + THREAD_REQUEST.remove(); + } + + public static HttpServletRequest getRequest() { + return THREAD_REQUEST.get(); } } diff --git a/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java b/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java index e4d08f13b5d..37b73a7cfea 100644 --- a/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java +++ b/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java @@ -22,21 +22,28 @@ */ public class ServletJSONProcessor implements JSONProcessor { - public static final String MDC_REQUEST - = ServletJSONProcessor.class.getName() + ".httpServletRequest"; private static final String HTTP_INTERFACE = "sentry.interfaces.Http"; @Override - @SuppressWarnings("unchecked") - public void process(JSONObject json) { - RavenMDC mdc = RavenMDC.getInstance(); - HttpServletRequest request = (HttpServletRequest)mdc.get(MDC_REQUEST); + public void prepareDiagnosticContext(Throwable exception) { + HttpServletRequest request = RavenServletRequestListener.getRequest(); if (request == null) { // no request available; do nothing return; } - json.put(HTTP_INTERFACE, buildHttpObject(request)); + RavenMDC.getInstance().put(HTTP_INTERFACE, buildHttpObject(request)); + } + + @Override + @SuppressWarnings("unchecked") + public void process(JSONObject json) { + JSONObject http = (JSONObject)RavenMDC.getInstance().get(HTTP_INTERFACE); + if (http == null) { + return; + } + + json.put(HTTP_INTERFACE, http); } @SuppressWarnings("unchecked") diff --git a/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java b/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java index e95162a6eef..56e917d693f 100644 --- a/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java +++ b/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java @@ -14,6 +14,17 @@ */ public interface JSONProcessor { + /** + * This is called when a message is logged. Since + * {@link #process(JSONObject)} may be executed on a different thread, this + * method should copy any data the processor needs into {@link RavenMDC}. + * + * For each message logged, this method should be called exactly once. + * + * @param exception exception attached with the message and may be null + */ + void prepareDiagnosticContext(Throwable exception); + /** * Modify the JSON request object specified before it is sent to Sentry. * This method may be called concurrently and therefore must be thread-safe. diff --git a/raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java b/raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java index ff9c5f65520..75b21db17e9 100644 --- a/raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java +++ b/raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java @@ -34,8 +34,11 @@ public void tearDown() { @Test public void testWithProcessor() { List processors = new ArrayList(); - processors.add(new MockJSONProcessor()); + JSONProcessor mockProcessor = new MockJSONProcessor(); + processors.add(mockProcessor); setJSONProcessors(processors); + + mockProcessor.prepareDiagnosticContext(null); Message message = buildMessage("test", formatTimestamp(new Date().getTime()), "test", LogLevel.ERROR.intValue, "test", null, null); @@ -44,10 +47,17 @@ public void testWithProcessor() { private static class MockJSONProcessor implements JSONProcessor { + private String testValue; + + @Override + public void prepareDiagnosticContext(Throwable exception) { + testValue = "Value"; + } + @Override @SuppressWarnings("unchecked") public void process(JSONObject json) { - json.put("Test", "Value"); + json.put("Test", testValue); } } From f710d8e565e4b99b2f9b927c269bd518c53ea9b1 Mon Sep 17 00:00:00 2001 From: Brad Chen Date: Mon, 27 Aug 2012 17:54:09 -0400 Subject: [PATCH 0072/2152] Make a minor performance improvement. Move exception information from JSONProcessor#prepareDiagnosticContext() to JSONProcessor#process(). --- .../kencochrane/raven/log4j/AsyncSentryAppender.java | 2 +- .../net/kencochrane/raven/log4j/SentryAppender.java | 10 +++------- .../kencochrane/raven/log4j/SentryAppenderTest.java | 4 ++-- raven/src/main/java/net/kencochrane/raven/Client.java | 2 +- .../kencochrane/raven/ext/ServletJSONProcessor.java | 9 +++++++-- .../java/net/kencochrane/raven/spi/JSONProcessor.java | 7 +++---- .../java/net/kencochrane/raven/JSONProcessorTest.java | 6 +++--- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java index 4aedd42c30f..e9b6bc7f0e1 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java @@ -56,7 +56,7 @@ public void append(LoggingEvent event) { createAppender(); } } - appender.notifyProcessors(event); + appender.notifyProcessors(); super.append(event); } diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 75712abb22a..822ee52aa14 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -66,13 +66,9 @@ public void setJsonProcessors(String setting) { * Notify processors that a message has been logged. Note that this method * is intended to be run on the same thread that creates the message. */ - public void notifyProcessors(LoggingEvent event) { - Throwable exception = null; - if (event.getThrowableInformation() != null) { - exception = event.getThrowableInformation().getThrowable(); - } + public void notifyProcessors() { for (JSONProcessor processor : jsonProcessors) { - processor.prepareDiagnosticContext(exception); + processor.prepareDiagnosticContext(); } } @@ -109,7 +105,7 @@ protected void append(LoggingEvent event) { // notify processors about the message // (in async mode this is done by AsyncSentryAppender) if (!async) { - notifyProcessors(event); + notifyProcessors(); } // send the message to the sentry server diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index bb24a3cc0c6..ff665276889 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -208,14 +208,14 @@ public static class MockJSONProcessor implements JSONProcessor { private Long value = 0L; @Override - public void prepareDiagnosticContext(Throwable exception) { + public void prepareDiagnosticContext() { // this is done to ensure prepareDiagnosticContext is called exactly once value++; } @Override @SuppressWarnings("unchecked") - public void process(JSONObject json) { + public void process(JSONObject json, Throwable exception) { json.put("Test", value); // value should be 1 } diff --git a/raven/src/main/java/net/kencochrane/raven/Client.java b/raven/src/main/java/net/kencochrane/raven/Client.java index ca4579460f9..ff47c1ae947 100644 --- a/raven/src/main/java/net/kencochrane/raven/Client.java +++ b/raven/src/main/java/net/kencochrane/raven/Client.java @@ -271,7 +271,7 @@ protected Message buildMessage(String message, String timestamp, String loggerCl } for (JSONProcessor processor : jsonProcessors) { - processor.process(obj); + processor.process(obj, exception); } return new Message(obj, eventId); } diff --git a/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java b/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java index 37b73a7cfea..b2dcf95386c 100644 --- a/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java +++ b/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java @@ -25,19 +25,24 @@ public class ServletJSONProcessor implements JSONProcessor { private static final String HTTP_INTERFACE = "sentry.interfaces.Http"; @Override - public void prepareDiagnosticContext(Throwable exception) { + public void prepareDiagnosticContext() { HttpServletRequest request = RavenServletRequestListener.getRequest(); if (request == null) { // no request available; do nothing return; } + if (RavenMDC.getInstance().get(HTTP_INTERFACE) != null) { + // an http object has been built; no need to build again + return; + } + RavenMDC.getInstance().put(HTTP_INTERFACE, buildHttpObject(request)); } @Override @SuppressWarnings("unchecked") - public void process(JSONObject json) { + public void process(JSONObject json, Throwable exception) { JSONObject http = (JSONObject)RavenMDC.getInstance().get(HTTP_INTERFACE); if (http == null) { return; diff --git a/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java b/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java index 56e917d693f..b6760536243 100644 --- a/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java +++ b/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java @@ -20,17 +20,16 @@ public interface JSONProcessor { * method should copy any data the processor needs into {@link RavenMDC}. * * For each message logged, this method should be called exactly once. - * - * @param exception exception attached with the message and may be null */ - void prepareDiagnosticContext(Throwable exception); + void prepareDiagnosticContext(); /** * Modify the JSON request object specified before it is sent to Sentry. * This method may be called concurrently and therefore must be thread-safe. * * @param json request JSON object to be modified + * @param exception exception attached with the message and may be null */ - void process(JSONObject json); + void process(JSONObject json, Throwable exception); } diff --git a/raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java b/raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java index 75b21db17e9..1d7126012cb 100644 --- a/raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java +++ b/raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java @@ -38,7 +38,7 @@ public void testWithProcessor() { processors.add(mockProcessor); setJSONProcessors(processors); - mockProcessor.prepareDiagnosticContext(null); + mockProcessor.prepareDiagnosticContext(); Message message = buildMessage("test", formatTimestamp(new Date().getTime()), "test", LogLevel.ERROR.intValue, "test", null, null); @@ -50,13 +50,13 @@ private static class MockJSONProcessor implements JSONProcessor { private String testValue; @Override - public void prepareDiagnosticContext(Throwable exception) { + public void prepareDiagnosticContext() { testValue = "Value"; } @Override @SuppressWarnings("unchecked") - public void process(JSONObject json) { + public void process(JSONObject json, Throwable exception) { json.put("Test", testValue); } From f6388dacbb09ad0bf72262fa5c6713fc41cef215 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Wed, 29 Aug 2012 19:14:34 +0200 Subject: [PATCH 0073/2152] Fix javadoc for JSONProcessor.prepareDiagnosticContext --- .../src/main/java/net/kencochrane/raven/spi/JSONProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java b/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java index b6760536243..4012083ac55 100644 --- a/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java +++ b/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java @@ -16,7 +16,7 @@ public interface JSONProcessor { /** * This is called when a message is logged. Since - * {@link #process(JSONObject)} may be executed on a different thread, this + * {@link #process(JSONObject, Throwable)} may be executed on a different thread, this * method should copy any data the processor needs into {@link RavenMDC}. * * For each message logged, this method should be called exactly once. From d496bede422690d04fc7c850a03c24567759f0a9 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Wed, 29 Aug 2012 19:31:43 +0200 Subject: [PATCH 0074/2152] Fix issue #14: Move setup-code to AppenderSkeleton.activateOptions() --- .../raven/log4j/AsyncSentryAppender.java | 19 ++++---- .../raven/log4j/SentryAppender.java | 47 ++++++------------- 2 files changed, 22 insertions(+), 44 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java index e9b6bc7f0e1..f432dae662c 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java @@ -28,7 +28,6 @@ public String getSentryDsn() { } public void setSentryDsn(String sentryDsn) { - System.out.println("Oh come on!"); this.sentryDsn = sentryDsn; if (appender != null) { removeAppender(appender); @@ -40,7 +39,7 @@ public void setSentryDsn(String sentryDsn) { * See {@link SentryAppender#setJsonProcessors}. * * @param jsonProcessors a comma-separated list of fully qualified class - * names of JSONProcessors + * names of JSONProcessors */ public void setJsonProcessors(String jsonProcessors) { this.jsonProcessors = jsonProcessors; @@ -48,19 +47,15 @@ public void setJsonProcessors(String jsonProcessors) { @Override public void append(LoggingEvent event) { - if (appender == null) { - synchronized (this) { - if (sentryDsn == null) { - setSentryDsn(SentryDsn.build().toString()); - } - createAppender(); - } - } appender.notifyProcessors(); super.append(event); } - private void createAppender() { + @Override + public void activateOptions() { + if (sentryDsn == null) { + setSentryDsn(SentryDsn.build().toString()); + } SentryAppender appender = new SentryAppender(); appender.setAsync(true); appender.setSentryDsn(sentryDsn); @@ -69,8 +64,10 @@ private void createAppender() { appender.setLayout(this.getLayout()); appender.setName(this.getName()); appender.setThreshold(this.getThreshold()); + appender.activateOptions(); this.appender = appender; addAppender(appender); + super.activateOptions(); } } diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 822ee52aa14..b339c499764 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -1,18 +1,17 @@ package net.kencochrane.raven.log4j; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import net.kencochrane.raven.Client; import net.kencochrane.raven.SentryDsn; import net.kencochrane.raven.spi.JSONProcessor; import net.kencochrane.raven.spi.RavenMDC; - import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.spi.LoggingEvent; import org.apache.log4j.spi.ThrowableInformation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + /** * Log4J appender that will send messages to Sentry. */ @@ -26,7 +25,7 @@ public class SentryAppender extends AppenderSkeleton { public SentryAppender() { initMDC(); - mdc = (Log4jMDC)RavenMDC.getInstance(); + mdc = (Log4jMDC) RavenMDC.getInstance(); } public boolean isAsync() { @@ -56,7 +55,7 @@ public void setSentryDsn(String sentryDsn) { * JSONProcessors to be used. * * @param jsonProcessors a comma-separated list of fully qualified class - * names of JSONProcessors + * names of JSONProcessors */ public void setJsonProcessors(String setting) { this.jsonProcessors = loadJSONProcessors(setting); @@ -84,12 +83,16 @@ public boolean requiresLayout() { return false; } + @Override + public void activateOptions() { + client = (sentryDsn == null ? new Client() : new Client(SentryDsn.build(sentryDsn))); + client.setJSONProcessors(jsonProcessors); + } + @Override protected void append(LoggingEvent event) { mdc.setThreadLoggingEvent(event); try { - Client client = fetchClient(); - // get timestamp and timestamp in correct string format. long timestamp = event.getTimeStamp(); @@ -119,26 +122,6 @@ protected void append(LoggingEvent event) { } } - /** - * Use this to refer to the client instead of using {@link #client} directly. - *

    - * This makes sure a client is available. - *

    - * - * @return the client - */ - protected synchronized Client fetchClient() { - if (client == null) { - if (sentryDsn == null) { - client = new Client(); - } else { - client = new Client(SentryDsn.build(sentryDsn)); - } - client.setJSONProcessors(jsonProcessors); - } - return client; - } - private static List loadJSONProcessors(String setting) { if (setting == null) { return Collections.emptyList(); @@ -147,7 +130,7 @@ private static List loadJSONProcessors(String setting) { List processors = new ArrayList(); String[] clazzes = setting.split(",\\s*"); for (String clazz : clazzes) { - JSONProcessor processor = (JSONProcessor)Class.forName(clazz).newInstance(); + JSONProcessor processor = (JSONProcessor) Class.forName(clazz).newInstance(); processors.add(processor); } return processors; @@ -163,9 +146,7 @@ private static List loadJSONProcessors(String setting) { public static void initMDC() { if (RavenMDC.getInstance() != null) { if (!(RavenMDC.getInstance() instanceof Log4jMDC)) { - throw new IllegalStateException("An incompatible RavenMDC " - + "instance has been set. Please check your Raven " - + "configuration."); + throw new IllegalStateException("An incompatible RavenMDC " + "instance has been set. Please check your Raven " + "configuration."); } return; } From b92e88fa2b79fdc0e8dd0315f4fc07c4c5d4d1ef Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Wed, 29 Aug 2012 19:36:03 +0200 Subject: [PATCH 0075/2152] Fix integration test ClientTest.captureMessage_withTags --- .../java/net/kencochrane/raven/integration/ClientTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java index 121b4194db6..3fac067bb55 100644 --- a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java +++ b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java @@ -125,9 +125,7 @@ protected void captureMessage(Client client, String message, boolean wait, Map tagNames = api.getAvailableTags(IntegrationContext.projectSlug); - Set nonMatches = new HashSet(tags.keySet()); - nonMatches.removeAll(tagNames); - assertTrue(nonMatches.isEmpty()); + assertTrue(new HashSet(tagNames).containsAll(tags.keySet())); } } From e1cdd74261ef82c583aa402d85e72f60b82ac92d Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Wed, 29 Aug 2012 20:19:32 +0200 Subject: [PATCH 0076/2152] Introduce SentryDsn.buildOptional Using SentryDsn.buildOptional instead of SentryDsn.build in the SentryAppender should really fix issue #20 by returning null instead of throwing an InvalidDsnException, effectively disabling the client. --- .../raven/log4j/SentryAppender.java | 4 +- .../raven/log4j/SentryAppenderTest.java | 22 ++++--- .../sentryappender-no-dsn.log4j.properties | 24 ++++++++ .../java/net/kencochrane/raven/Client.java | 17 +----- .../java/net/kencochrane/raven/SentryDsn.java | 57 +++++++++++++++++++ .../net/kencochrane/raven/SentryDsnTest.java | 16 +++++- 6 files changed, 113 insertions(+), 27 deletions(-) create mode 100644 raven-log4j/src/test/resources/sentryappender-no-dsn.log4j.properties diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index b339c499764..f03418c4722 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -85,7 +85,7 @@ public boolean requiresLayout() { @Override public void activateOptions() { - client = (sentryDsn == null ? new Client() : new Client(SentryDsn.build(sentryDsn))); + client = (sentryDsn == null ? new Client() : new Client(SentryDsn.buildOptional(sentryDsn))); client.setJSONProcessors(jsonProcessors); } @@ -146,7 +146,7 @@ private static List loadJSONProcessors(String setting) { public static void initMDC() { if (RavenMDC.getInstance() != null) { if (!(RavenMDC.getInstance() instanceof Log4jMDC)) { - throw new IllegalStateException("An incompatible RavenMDC " + "instance has been set. Please check your Raven " + "configuration."); + throw new IllegalStateException("An incompatible RavenMDC instance has been set. Please check your Raven configuration."); } return; } diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index ff665276889..458ed2201b3 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -2,7 +2,6 @@ import net.kencochrane.raven.Utils; import net.kencochrane.raven.spi.JSONProcessor; - import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Level; @@ -22,9 +21,7 @@ import java.net.InetSocketAddress; import java.net.SocketException; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; /** * Test cases for {@link SentryAppender}. @@ -44,6 +41,18 @@ public static void afterClass() throws SocketException { sentry.stop(); } + @Test + public void noSentryDsn() { + PropertyConfigurator.configure(getClass().getResource("/sentryappender-no-dsn.log4j.properties")); + Logger.getLogger(this.getClass()).debug("No Sentry DSN, no messages"); + } + + @Test + public void invalidDsn() { + configureLog4J(); + Logger.getLogger(this.getClass()).debug("No Sentry DSN, no messages"); + } + @Test public void debugLevel() throws IOException, ParseException { final String loggerName = "omg.logger"; @@ -143,7 +152,7 @@ public void testJSONProcessors() throws IOException, ParseException { configureLog4J(); Logger.getLogger("logger").info("test"); JSONObject json = fetchJSONObject(sentry); - assertEquals(1, ((Long)json.get("Test")).longValue()); + assertEquals(1, ((Long) json.get("Test")).longValue()); } protected void setSentryDSN(String projectId) { @@ -154,8 +163,7 @@ protected void configureLog4J() { PropertyConfigurator.configure(getClass().getResource("/sentryappender.log4j.properties")); } - protected - JSONObject verifyMessage(String culprit, long logLevel, String projectId, String message) throws IOException, ParseException { + protected JSONObject verifyMessage(String culprit, long logLevel, String projectId, String message) throws IOException, ParseException { return verifyMessage(sentry, culprit, logLevel, projectId, message); } diff --git a/raven-log4j/src/test/resources/sentryappender-no-dsn.log4j.properties b/raven-log4j/src/test/resources/sentryappender-no-dsn.log4j.properties new file mode 100644 index 00000000000..37972103073 --- /dev/null +++ b/raven-log4j/src/test/resources/sentryappender-no-dsn.log4j.properties @@ -0,0 +1,24 @@ +# Basic setup +log4j.rootLogger=trace, stdout, sentry + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + +# Pattern to output the caller's file name and line number. +log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n + +# Configure the Sentry appender. Note: when a SENTRY_DSN environment variable +# or system property is available, that value will be used instead of the value +# of log4j.appender.sentry.sentryDsn +log4j.appender.sentry=net.kencochrane.raven.log4j.SentryAppender +# No DSN here +#log4j.appender.sentry.sentryDsn= + +# Add a JSONProcessor +log4j.appender.sentry.jsonProcessors=net.kencochrane.raven.log4j.SentryAppenderTest$MockJSONProcessor + +# Do not let the Raven logging go to Sentry or you could end up in an infinite loop when +# something goes wrong with your configuration. Sentry uses the java.util.logging package +# so this should only happen when you add a bridge from JUL to Log4J. +log4j.logger.raven=DEBUG, stdout +log4j.additivity.raven=false \ No newline at end of file diff --git a/raven/src/main/java/net/kencochrane/raven/Client.java b/raven/src/main/java/net/kencochrane/raven/Client.java index ff47c1ae947..f6c20b3fa44 100644 --- a/raven/src/main/java/net/kencochrane/raven/Client.java +++ b/raven/src/main/java/net/kencochrane/raven/Client.java @@ -107,7 +107,7 @@ public Client() { * @param autoStart whether to start the underlying transport automatically or not */ public Client(boolean autoStart) { - this(fetchOptionalDsn(), autoStart); + this(SentryDsn.buildOptional(), autoStart); } /** @@ -442,21 +442,6 @@ public static String calculateChecksum(String message) { return String.valueOf(checksum.getValue()); } - /** - * Wrapper for {@link net.kencochrane.raven.SentryDsn#build()} that turns an {@link SentryDsn.InvalidDsnException} - * into a null DSN, effectively disabling the client. - * - * @return the Sentry DSN as determined from the environment or null when no DSN was found - */ - protected static SentryDsn fetchOptionalDsn() { - try { - return SentryDsn.build(); - } catch (SentryDsn.InvalidDsnException e) { - LOG.log(Level.WARNING, "Could not automatically determine a valid DSN; client will be disabled", e); - return null; - } - } - public static class InvalidConfig extends RuntimeException { public InvalidConfig(String msg) { diff --git a/raven/src/main/java/net/kencochrane/raven/SentryDsn.java b/raven/src/main/java/net/kencochrane/raven/SentryDsn.java index 0c1e03ab28e..5514ea42a6c 100644 --- a/raven/src/main/java/net/kencochrane/raven/SentryDsn.java +++ b/raven/src/main/java/net/kencochrane/raven/SentryDsn.java @@ -5,6 +5,8 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; import static org.apache.commons.lang.StringUtils.defaultString; @@ -27,6 +29,11 @@ */ public class SentryDsn { + /** + * Logger. + */ + private static final Logger LOG = Logger.getLogger("raven.client"); + /** * The scheme, e.g. http, https or udp. */ @@ -213,6 +220,21 @@ public static SentryDsn build() { return build(null, DefaultLookUps.values(), null); } + /** + * Performs the same logic as {@link #build()} but will catch any {@link InvalidDsnException} thrown and return null + * instead. + * + * @return the Sentry dsn when found and valid or null instead + */ + public static SentryDsn buildOptional() { + try { + return build(); + } catch (SentryDsn.InvalidDsnException e) { + LOG.log(Level.WARNING, "Could not automatically determine a valid DSN; client will be disabled", e); + return null; + } + } + /** * Builds the Sentry dsn. *

    @@ -227,6 +249,22 @@ public static SentryDsn build(String fullDsn) { return build(fullDsn, DefaultLookUps.values(), null); } + /** + * Performs the same logic as {@link #build(String)} but will catch any {@link InvalidDsnException} thrown and + * return null instead. + * + * @return the dsn found in either the environment, system properties or derived from the parameter + * fullDsn; if no valid dsn is available, this will return null + */ + public static SentryDsn buildOptional(String fullDsn) { + try { + return build(fullDsn); + } catch (SentryDsn.InvalidDsnException e) { + LOG.log(Level.WARNING, "Could not automatically determine a valid DSN; client will be disabled", e); + return null; + } + } + /** * Builds the dsn. *

    @@ -279,6 +317,25 @@ public static SentryDsn build(final String fullDsn, LookUp[] overrides, LookUp[] } } + /** + * See {@link #build(String, net.kencochrane.raven.SentryDsn.LookUp[], net.kencochrane.raven.SentryDsn.LookUp[])}. + * This method will return null when no valid DSN was found instead of throwing a + * {@link InvalidDsnException}. + * + * @param fullDsn the supplied dsn + * @param overrides places to check for a dsn value before using the supplied dsn + * @param fallbacks places to check for a dsn value when the supplied dsn is an empty or null string + * @return the built Sentry dsn or null when no such value was found + */ + public static SentryDsn buildOptional(final String fullDsn, LookUp[] overrides, LookUp[] fallbacks) { + try { + return build(fullDsn, overrides, fallbacks); + } catch (SentryDsn.InvalidDsnException e) { + LOG.log(Level.WARNING, "Could not automatically determine a valid DSN; client will be disabled", e); + return null; + } + } + /** * Parses simple query strings. *

    diff --git a/raven/src/test/java/net/kencochrane/raven/SentryDsnTest.java b/raven/src/test/java/net/kencochrane/raven/SentryDsnTest.java index 9601d2c31a0..0b52eb2b689 100644 --- a/raven/src/test/java/net/kencochrane/raven/SentryDsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/SentryDsnTest.java @@ -1,9 +1,11 @@ package net.kencochrane.raven; +import junit.framework.Assert; import mockit.*; import org.junit.After; import org.junit.Test; +import static junit.framework.Assert.assertNull; import static net.kencochrane.raven.SentryDsn.DefaultLookUps; import static net.kencochrane.raven.SentryDsn.LookUp; import static org.junit.Assert.*; @@ -63,11 +65,21 @@ public void emptyScheme() { SentryDsn.build("://public:secret@host/path/1"); } + @Test + public void emptyScheme_optional() { + assertNull(SentryDsn.buildOptional("://public:secret@host/path/1")); + } + @Test(expected = SentryDsn.InvalidDsnException.class) public void noScheme() { SentryDsn.build("public:secret@host/path/1"); } + @Test + public void noScheme_optional() { + assertNull(SentryDsn.buildOptional("public:secret@host/path/1")); + } + @Test public void noMalformedUrlErrorWithUdp() { SentryDsn dsn = SentryDsn.build("udp://public@host/path/goes/on/1"); @@ -76,7 +88,7 @@ public void noMalformedUrlErrorWithUdp() { assertEquals(0, dsn.variants.length); assertEquals("udp", dsn.scheme); assertEquals("public", dsn.publicKey); - assertNull(dsn.secretKey); + org.junit.Assert.assertNull(dsn.secretKey); assertEquals("host", dsn.host); assertEquals("/path/goes/on", dsn.path); assertEquals("1", dsn.projectId); @@ -93,7 +105,7 @@ public void withOptions() { assertEquals("1", dsn.projectId); assertEquals(2, dsn.options.size()); assertTrue(dsn.options.containsKey("raven.go")); - assertNull(dsn.options.get("raven.go")); + org.junit.Assert.assertNull(dsn.options.get("raven.go")); assertEquals("true", dsn.options.get("raven.wait")); } From 3b729059870e91eb3bbcd212efd42aa6cc17bf66 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Wed, 29 Aug 2012 20:24:19 +0200 Subject: [PATCH 0077/2152] Remove "duplicate" JUnit import from SentryDsnTest --- .../src/test/java/net/kencochrane/raven/SentryDsnTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/SentryDsnTest.java b/raven/src/test/java/net/kencochrane/raven/SentryDsnTest.java index 0b52eb2b689..131ecabbe61 100644 --- a/raven/src/test/java/net/kencochrane/raven/SentryDsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/SentryDsnTest.java @@ -1,11 +1,11 @@ package net.kencochrane.raven; -import junit.framework.Assert; -import mockit.*; +import mockit.Mock; +import mockit.MockClass; +import mockit.Mockit; import org.junit.After; import org.junit.Test; -import static junit.framework.Assert.assertNull; import static net.kencochrane.raven.SentryDsn.DefaultLookUps; import static net.kencochrane.raven.SentryDsn.LookUp; import static org.junit.Assert.*; From 5d91fd0814f1bcf4c5c830aaf9269f49e92bfa41 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Wed, 29 Aug 2012 20:30:20 +0200 Subject: [PATCH 0078/2152] Wait a bit in integration tests introducing new tags [ci skip] --- .../net/kencochrane/raven/integration/ClientTest.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java index 3fac067bb55..bd629a27cd4 100644 --- a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java +++ b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java @@ -88,8 +88,8 @@ protected void captureMessage(Client client, String message, boolean wait) throw protected void captureMessage(Client client, String message, boolean wait, Map tags) throws IOException, InterruptedException { client.captureMessage(message, tags); if (wait) { - // Wait a bit in case of UDP transport - Thread.sleep(1000); + // Wait a bit in case of UDP transport or tags + Thread.sleep(1000 + (tags == null || tags.isEmpty() ? 0 : 3000)); } List events = api.getEvents(IntegrationContext.projectSlug); assertEquals(1, events.size()); @@ -125,6 +125,12 @@ protected void captureMessage(Client client, String message, boolean wait, Map tagNames = api.getAvailableTags(IntegrationContext.projectSlug); + for (String tagName : tagNames) { + System.out.println("Found tag " + tagName); + } + for (String tagName : tags.keySet()) { + System.out.println("Expect tag " + tagName); + } assertTrue(new HashSet(tagNames).containsAll(tags.keySet())); } } From ad25cabf970b9c595e3a2beb17d3e034358aa2e1 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Wed, 29 Aug 2012 21:19:15 +0200 Subject: [PATCH 0079/2152] Make sure AsyncSentryAppender leaves everything non-async up to SentryAppender AsyncSentryAppender should not try to build its own Sentry DSN - it should let SentryAppender find out what it should do with the supplied values. [ci-skip] --- .../raven/log4j/AsyncSentryAppender.java | 7 ------ .../raven/log4j/SentryAppender.java | 8 +------ .../raven/log4j/AsyncSentryAppenderTest.java | 12 ++++++++++ ...syncsentryappender-no-dsn.log4j.properties | 24 +++++++++++++++++++ .../java/net/kencochrane/raven/Client.java | 10 ++------ .../java/net/kencochrane/raven/SentryDsn.java | 2 +- 6 files changed, 40 insertions(+), 23 deletions(-) create mode 100644 raven-log4j/src/test/resources/asyncsentryappender-no-dsn.log4j.properties diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java index f432dae662c..88a0f284b94 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java @@ -29,10 +29,6 @@ public String getSentryDsn() { public void setSentryDsn(String sentryDsn) { this.sentryDsn = sentryDsn; - if (appender != null) { - removeAppender(appender); - appender = null; - } } /** @@ -53,9 +49,6 @@ public void append(LoggingEvent event) { @Override public void activateOptions() { - if (sentryDsn == null) { - setSentryDsn(SentryDsn.build().toString()); - } SentryAppender appender = new SentryAppender(); appender.setAsync(true); appender.setSentryDsn(sentryDsn); diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index f03418c4722..1148894dd9b 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -41,13 +41,7 @@ public String getSentryDsn() { } public void setSentryDsn(String sentryDsn) { - synchronized (this) { - this.sentryDsn = sentryDsn; - if (client != null) { - client.stop(); - client = null; - } - } + this.sentryDsn = sentryDsn; } /** diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java index 789beae830f..e8752600d93 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java @@ -34,6 +34,18 @@ public static void afterClass() throws SocketException { sentry.stop(); } + @Test + public void noSentryDsn() { + PropertyConfigurator.configure(getClass().getResource("/asyncsentryappender-no-dsn.log4j.properties")); + Logger.getLogger(this.getClass()).debug("No Sentry DSN, no messages"); + } + + @Test + public void invalidDsn() { + configureLog4J(); + Logger.getLogger(this.getClass()).debug("No Sentry DSN, no messages"); + } + @Test public void debugLevel() throws IOException, ParseException { final String loggerName = "omg.logger"; diff --git a/raven-log4j/src/test/resources/asyncsentryappender-no-dsn.log4j.properties b/raven-log4j/src/test/resources/asyncsentryappender-no-dsn.log4j.properties new file mode 100644 index 00000000000..6830841073c --- /dev/null +++ b/raven-log4j/src/test/resources/asyncsentryappender-no-dsn.log4j.properties @@ -0,0 +1,24 @@ +# Basic setup +log4j.rootLogger=trace, stdout, sentry + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + +# Pattern to output the caller's file name and line number. +log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n + +# Configure the Sentry appender. Note: when a SENTRY_DSN environment variable +# or system property is available, that value will be used instead of the value +# of log4j.appender.sentry.sentryDsn +log4j.appender.sentry=net.kencochrane.raven.log4j.AsyncSentryAppender +# Don't set the DSN +#log4j.appender.sentry.sentryDsn=SENTRY_DSN + +# Add a JSONProcessor +log4j.appender.sentry.jsonProcessors=net.kencochrane.raven.log4j.SentryAppenderTest$MockJSONProcessor + +# Do not let the Raven logging go to Sentry or you could end up in an infinite loop when +# something goes wrong with your configuration. Sentry uses the java.util.logging package +# so this should only happen when you add a bridge from JUL to Log4J. +log4j.logger.raven=DEBUG, stdout +log4j.additivity.raven=false diff --git a/raven/src/main/java/net/kencochrane/raven/Client.java b/raven/src/main/java/net/kencochrane/raven/Client.java index f6c20b3fa44..4dc55bf9f9a 100644 --- a/raven/src/main/java/net/kencochrane/raven/Client.java +++ b/raven/src/main/java/net/kencochrane/raven/Client.java @@ -1,7 +1,6 @@ package net.kencochrane.raven; import net.kencochrane.raven.spi.JSONProcessor; - import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang.time.DateFormatUtils; import org.json.simple.JSONObject; @@ -17,12 +16,7 @@ import java.net.ConnectException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.CRC32; @@ -94,7 +88,7 @@ public interface Default { * Default, easy constructor. *

    * A client instance instantiated through this constructor will use the Sentry DSN returned by - * {@link net.kencochrane.raven.SentryDsn#build()} and perform an automatic start. + * {@link SentryDsn#buildOptional()} and perform an automatic start. *

    */ public Client() { diff --git a/raven/src/main/java/net/kencochrane/raven/SentryDsn.java b/raven/src/main/java/net/kencochrane/raven/SentryDsn.java index 5514ea42a6c..7a8429c4b51 100644 --- a/raven/src/main/java/net/kencochrane/raven/SentryDsn.java +++ b/raven/src/main/java/net/kencochrane/raven/SentryDsn.java @@ -82,7 +82,7 @@ public class SentryDsn { /** * Constructor for your convenience. *

    - * It's recommended to use one of the {@link #build()} methods instead. + * It's recommended to use one of the {@link #build()} or {@link #buildOptional()} methods instead. *

    * * @param scheme scheme From acf819d36b517d786c4b64a175d1505206174dad Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Wed, 29 Aug 2012 22:35:52 +0200 Subject: [PATCH 0080/2152] Add message compression The client will, by default, compress messages. Turn it off through Client.setMessageCompressionEnabled or the corresponding log4j property log4j.appender.sentry.messageCompressionEnabled=false. [ci skip] --- .../raven/integration/ClientTest.java | 2 +- .../raven/log4j/AsyncSentryAppender.java | 11 ++++++- .../raven/log4j/SentryAppender.java | 10 ++++++ .../raven/log4j/SentryAppenderTest.java | 2 +- .../java/net/kencochrane/raven/Client.java | 31 +++++++++++++++---- .../java/net/kencochrane/raven/Utils.java | 30 ++++++++++++++++++ .../kencochrane/raven/ClientSetupTest.java | 31 +++++++++++++++++++ .../java/net/kencochrane/raven/UtilsTest.java | 17 ++++++++++ 8 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 raven/src/test/java/net/kencochrane/raven/UtilsTest.java diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java index bd629a27cd4..b1ebb6efb19 100644 --- a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java +++ b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java @@ -89,7 +89,7 @@ protected void captureMessage(Client client, String message, boolean wait, Map events = api.getEvents(IntegrationContext.projectSlug); assertEquals(1, events.size()); diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java index 88a0f284b94..3ac9b125cd8 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java @@ -1,6 +1,5 @@ package net.kencochrane.raven.log4j; -import net.kencochrane.raven.SentryDsn; import org.apache.log4j.AsyncAppender; import org.apache.log4j.spi.LoggingEvent; @@ -18,6 +17,7 @@ public class AsyncSentryAppender extends AsyncAppender { private String sentryDsn; private String jsonProcessors; private SentryAppender appender; + private boolean messageCompressionEnabled = true; public AsyncSentryAppender() { SentryAppender.initMDC(); @@ -31,6 +31,14 @@ public void setSentryDsn(String sentryDsn) { this.sentryDsn = sentryDsn; } + public boolean isMessageCompressionEnabled() { + return messageCompressionEnabled; + } + + public void setMessageCompressionEnabled(boolean messageCompressionEnabled) { + this.messageCompressionEnabled = messageCompressionEnabled; + } + /** * See {@link SentryAppender#setJsonProcessors}. * @@ -51,6 +59,7 @@ public void append(LoggingEvent event) { public void activateOptions() { SentryAppender appender = new SentryAppender(); appender.setAsync(true); + appender.setMessageCompressionEnabled(messageCompressionEnabled); appender.setSentryDsn(sentryDsn); appender.setJsonProcessors(jsonProcessors); appender.setErrorHandler(this.getErrorHandler()); diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 1148894dd9b..3e1eb5186b5 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -21,6 +21,7 @@ public class SentryAppender extends AppenderSkeleton { private Log4jMDC mdc; protected String sentryDsn; protected Client client; + protected boolean messageCompressionEnabled = true; private List jsonProcessors = Collections.emptyList(); public SentryAppender() { @@ -44,6 +45,14 @@ public void setSentryDsn(String sentryDsn) { this.sentryDsn = sentryDsn; } + public boolean isMessageCompressionEnabled() { + return messageCompressionEnabled; + } + + public void setMessageCompressionEnabled(boolean messageCompressionEnabled) { + this.messageCompressionEnabled = messageCompressionEnabled; + } + /** * Set a comma-separated list of fully qualified class names of * JSONProcessors to be used. @@ -81,6 +90,7 @@ public boolean requiresLayout() { public void activateOptions() { client = (sentryDsn == null ? new Client() : new Client(SentryDsn.buildOptional(sentryDsn))); client.setJSONProcessors(jsonProcessors); + client.setMessageCompressionEnabled(messageCompressionEnabled); } @Override diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 458ed2201b3..a6eecd10cb3 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -180,7 +180,7 @@ protected static JSONObject fetchJSONObject(SentryMock sentry) throws IOExceptio String payload = Utils.fromUtf8(sentry.fetchMessage()); String[] payloadParts = StringUtils.split(payload, "\n\n"); assertEquals(2, payloadParts.length); - String raw = Utils.fromUtf8(Base64.decodeBase64(payloadParts[1])); + String raw = Utils.fromUtf8(Utils.decompress(Base64.decodeBase64(payloadParts[1]))); return (JSONObject) new JSONParser().parse(raw); } diff --git a/raven/src/main/java/net/kencochrane/raven/Client.java b/raven/src/main/java/net/kencochrane/raven/Client.java index 4dc55bf9f9a..b3bde65af9b 100644 --- a/raven/src/main/java/net/kencochrane/raven/Client.java +++ b/raven/src/main/java/net/kencochrane/raven/Client.java @@ -75,6 +75,11 @@ public interface Default { */ protected Transport transport; + /** + * Whether messages should be compressed or not - defaults to true. + */ + protected boolean messageCompressionEnabled = true; + /** * JSONProcessor instances. Initialized with an empty list to prevent NPE. */ @@ -168,6 +173,14 @@ public synchronized void setJSONProcessors(List processors) { this.jsonProcessors.addAll(processors); } + public boolean isMessageCompressionEnabled() { + return messageCompressionEnabled; + } + + public void setMessageCompressionEnabled(boolean messageCompressionEnabled) { + this.messageCompressionEnabled = messageCompressionEnabled; + } + public String captureMessage(String msg) { return captureMessage(msg, null, null, null, null); } @@ -207,7 +220,7 @@ public String captureException(String logMessage, long timestamp, String loggerN return message.eventId; } - public void start() { + public synchronized void start() { if (isDisabled()) { return; } @@ -221,7 +234,7 @@ public boolean isStarted() { return transport != null && transport.isStarted(); } - public void stop() { + public synchronized void stop() { if (transport == null) { return; } @@ -267,7 +280,7 @@ protected Message buildMessage(String message, String timestamp, String loggerCl for (JSONProcessor processor : jsonProcessors) { processor.process(obj, exception); } - return new Message(obj, eventId); + return new Message(obj, eventId, messageCompressionEnabled); } protected void send(Message message, long timestamp) { @@ -450,18 +463,24 @@ public InvalidConfig(String msg, Throwable t) { public static class Message { - public static final Message NONE = new Message(null, "-1"); + public static final Message NONE = new Message(null, "-1", false); public final JSONObject json; public final String eventId; + public final boolean compress; - public Message(JSONObject json, String eventId) { + public Message(JSONObject json, String eventId, boolean compress) { this.json = json; this.eventId = eventId; + this.compress = compress; } public String encoded() { - return encodeBase64String(Utils.toUtf8(json.toJSONString())); + byte[] raw = Utils.toUtf8(json.toJSONString()); + if (compress) { + raw = Utils.compress(raw); + } + return encodeBase64String(raw); } @Override diff --git a/raven/src/main/java/net/kencochrane/raven/Utils.java b/raven/src/main/java/net/kencochrane/raven/Utils.java index 261a80b40e4..17b257e80e6 100644 --- a/raven/src/main/java/net/kencochrane/raven/Utils.java +++ b/raven/src/main/java/net/kencochrane/raven/Utils.java @@ -1,10 +1,14 @@ package net.kencochrane.raven; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.InflaterOutputStream; /** * Utilities for the Raven client. @@ -56,6 +60,32 @@ public static String fromUtf8(byte[] b) { } } + public static byte[] compress(byte[] input) { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + DeflaterOutputStream output = new DeflaterOutputStream(bytes); + try { + output.write(input); + output.close(); + return bytes.toByteArray(); + } catch (IOException e) { + // Look, if this thing starts throwing IOExceptions, you're on your own. Sorry. + throw new RuntimeException(e); + } + } + + public static byte[] decompress(byte[] input) { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + InflaterOutputStream output = new InflaterOutputStream(bytes); + try { + output.write(input); + output.close(); + return bytes.toByteArray(); + } catch (IOException e) { + // ... Let things crash if this happens + throw new RuntimeException(e); + } + } + @SuppressWarnings("unchecked") protected static T fromCache(String key, long timeout) { CacheEntry entry = (CacheEntry) CACHE.get(key); diff --git a/raven/src/test/java/net/kencochrane/raven/ClientSetupTest.java b/raven/src/test/java/net/kencochrane/raven/ClientSetupTest.java index c30424fe59d..d7cac1f0b03 100644 --- a/raven/src/test/java/net/kencochrane/raven/ClientSetupTest.java +++ b/raven/src/test/java/net/kencochrane/raven/ClientSetupTest.java @@ -1,8 +1,15 @@ package net.kencochrane.raven; +import org.apache.commons.codec.binary.Base64; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; import org.junit.After; import org.junit.Test; +import java.io.IOException; + +import static net.kencochrane.raven.Utils.decompress; +import static net.kencochrane.raven.Utils.fromUtf8; import static org.junit.Assert.*; /** @@ -67,6 +74,22 @@ public void constructor_noDsn() { assertEquals("-1", client.captureMessage("Hi")); } + @Test + public void messageCompression() throws ParseException { + Client.register("http", DummyTransport.class); + Client client = new Client(SentryDsn.build("http://public:private@localhost:9000/1")); + client.captureMessage("hello"); + byte[] bytes = Base64.decodeBase64(DummyTransport.messageBody.getBytes()); + // This should not throw an exception + new JSONParser().parse(fromUtf8(decompress(bytes))); + + client.setMessageCompressionEnabled(false); + client.captureMessage("hello"); + bytes = Base64.decodeBase64(DummyTransport.messageBody.getBytes()); + // Neither should this + new JSONParser().parse(fromUtf8(bytes)); + } + @Test public void newTransport() { // HTTP @@ -182,10 +205,18 @@ protected void verifyClient(Client client, Class transportC protected static class DummyTransport extends Transport { + public static String messageBody; + public static long timestamp; + public DummyTransport(SentryDsn dsn) { super(dsn); } + @Override + public void send(String messageBody, long timestamp) throws IOException { + DummyTransport.messageBody = messageBody; + DummyTransport.timestamp = timestamp; + } } protected static class DummyAsyncTransport extends Transport { diff --git a/raven/src/test/java/net/kencochrane/raven/UtilsTest.java b/raven/src/test/java/net/kencochrane/raven/UtilsTest.java new file mode 100644 index 00000000000..8df8641db2a --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/UtilsTest.java @@ -0,0 +1,17 @@ +package net.kencochrane.raven; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Test cases for methods from {@link Utils}. + */ +public class UtilsTest { + + @Test + public void compress_decompress() { + final String s = "this is a string - no really!"; + Assert.assertEquals(s, Utils.fromUtf8(Utils.decompress(Utils.compress(Utils.toUtf8(s))))); + } + +} From a1e43224364727ba3935615787f504b37137b5b2 Mon Sep 17 00:00:00 2001 From: Brad Chen Date: Fri, 31 Aug 2012 16:54:49 -0400 Subject: [PATCH 0081/2152] Remove an optimization that is not thread safe. Create test for ServletJSONProcessor. Trivial javadoc fix. --- .../raven/log4j/SentryAppender.java | 3 +- .../raven/ext/ServletJSONProcessor.java | 6 +- .../raven/ext/ServletJSONProcessorTest.java | 148 ++++++++++++++++++ 3 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 raven/src/test/java/net/kencochrane/raven/ext/ServletJSONProcessorTest.java diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 3e1eb5186b5..70807497414 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -57,8 +57,7 @@ public void setMessageCompressionEnabled(boolean messageCompressionEnabled) { * Set a comma-separated list of fully qualified class names of * JSONProcessors to be used. * - * @param jsonProcessors a comma-separated list of fully qualified class - * names of JSONProcessors + * @param setting a comma-separated list of fully qualified class names of JSONProcessors */ public void setJsonProcessors(String setting) { this.jsonProcessors = loadJSONProcessors(setting); diff --git a/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java b/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java index b2dcf95386c..3bec9a92dd6 100644 --- a/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java +++ b/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java @@ -32,11 +32,6 @@ public void prepareDiagnosticContext() { return; } - if (RavenMDC.getInstance().get(HTTP_INTERFACE) != null) { - // an http object has been built; no need to build again - return; - } - RavenMDC.getInstance().put(HTTP_INTERFACE, buildHttpObject(request)); } @@ -49,6 +44,7 @@ public void process(JSONObject json, Throwable exception) { } json.put(HTTP_INTERFACE, http); + RavenMDC.getInstance().remove(HTTP_INTERFACE); } @SuppressWarnings("unchecked") diff --git a/raven/src/test/java/net/kencochrane/raven/ext/ServletJSONProcessorTest.java b/raven/src/test/java/net/kencochrane/raven/ext/ServletJSONProcessorTest.java new file mode 100644 index 00000000000..cfe749dafbd --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/ext/ServletJSONProcessorTest.java @@ -0,0 +1,148 @@ +package net.kencochrane.raven.ext; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; + +import org.json.simple.JSONObject; +import org.junit.Test; + +import mockit.Mocked; +import mockit.NonStrictExpectations; +import net.kencochrane.raven.Client; +import net.kencochrane.raven.SentryDsn; +import net.kencochrane.raven.Events.LogLevel; +import net.kencochrane.raven.spi.JSONProcessor; +import net.kencochrane.raven.spi.RavenMDC; + +/** + * Check to see if ServletJSONProcessor correctly produces the desired http + * JSON object. + * + * @author vvasabi + */ +public class ServletJSONProcessorTest extends Client { + + @Mocked + ServletContext mockServletContext; + + @Mocked + HttpServletRequest mockHttpServletRequest; + + public ServletJSONProcessorTest() { + super(SentryDsn.build("http://public:private@localhost:9000/1")); + } + + @Test + @SuppressWarnings("unchecked") + public void testConstructHttpJsonResponse() { + List processors = new ArrayList(); + JSONProcessor processor = new ServletJSONProcessor(); + processors.add(processor); + setJSONProcessors(processors); + + setHttpServletRequest(); + new MockRavenMDC(); + + processor.prepareDiagnosticContext(); + Message message = buildMessage("test", + formatTimestamp(new Date().getTime()), "test", + LogLevel.ERROR.intValue, "test", null, null); + JSONObject http = (JSONObject) message.json.get("sentry.interfaces.Http"); + assertEquals("GET", http.get("method")); + assertEquals("test=abc", http.get("query_string")); + assertEquals("http://example.com/test?test=abc", http.get("url")); + + JSONObject headers = new JSONObject(); + headers.put("Test-Header", "test value"); + assertEquals(headers, http.get("headers")); + + JSONObject env = new JSONObject(); + env.put("SERVER_PORT", 80); + env.put("REMOTE_ADDR", "127.0.0.1"); + env.put("SERVER_PROTOCOL", "http"); + env.put("SERVER_NAME", "localhost"); + assertEquals(env, http.get("env")); + + JSONObject cookies = new JSONObject(); + cookies.put("test", "cookie"); + assertEquals(cookies, http.get("cookies")); + } + + private void setHttpServletRequest() { + new NonStrictExpectations() { + { + mockHttpServletRequest.getRequestURL(); + returns(new StringBuffer("http://example.com/test")); + + mockHttpServletRequest.getQueryString(); + returns("test=abc"); + + mockHttpServletRequest.getMethod(); + returns("GET"); + + mockHttpServletRequest.getHeaderNames(); + List headerNames = new ArrayList(); + headerNames.add("test-header"); + returns(Collections.enumeration(headerNames)); + + mockHttpServletRequest.getHeader("test-header"); + returns("test value"); + + mockHttpServletRequest.getRemoteAddr(); + returns("127.0.0.1"); + + mockHttpServletRequest.getServerName(); + returns("localhost"); + + mockHttpServletRequest.getServerPort(); + returns(80); + + mockHttpServletRequest.getProtocol(); + returns("http"); + + mockHttpServletRequest.getCookies(); + returns(new Cookie[] { new Cookie("test", "cookie") }); + } + }; + ServletRequestListener listener = new RavenServletRequestListener(); + listener.requestInitialized(new ServletRequestEvent(mockServletContext, mockHttpServletRequest)); + } + + protected static class MockRavenMDC extends RavenMDC { + + private Map values = new HashMap(); + + public MockRavenMDC() { + RavenMDC.setInstance(this); + } + + @Override + public Object get(String key) { + return values.get(key); + } + + @Override + public void put(String key, Object value) { + values.put(key, value); + } + + @Override + public void remove(String key) { + values.remove(key); + } + + } + +} From b3b74fd99e60ff614f220f58dce1c40689ae208f Mon Sep 17 00:00:00 2001 From: Brad Chen Date: Mon, 10 Sep 2012 10:35:41 -0400 Subject: [PATCH 0082/2152] Allow RavenMDC content to be cleared after message has been appended or sent to the async processing queue. This prevents memory leak for ThreadLocal-based MDC when container pools threads. --- .../raven/log4j/AsyncSentryAppender.java | 3 ++- .../raven/log4j/SentryAppender.java | 18 ++++++++++++++++-- .../raven/log4j/AsyncSentryAppenderTest.java | 13 +++++++++++++ .../raven/log4j/SentryAppenderTest.java | 18 ++++++++++++++++++ .../raven/ext/ServletJSONProcessor.java | 6 +++++- .../kencochrane/raven/spi/JSONProcessor.java | 16 ++++++++++++++-- .../kencochrane/raven/JSONProcessorTest.java | 5 +++++ .../raven/ext/ServletJSONProcessorTest.java | 6 +++++- 8 files changed, 78 insertions(+), 7 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java index 3ac9b125cd8..ce743e3ca6a 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java @@ -51,8 +51,9 @@ public void setJsonProcessors(String jsonProcessors) { @Override public void append(LoggingEvent event) { - appender.notifyProcessors(); + appender.notifyProcessorsBeforeAppending(); super.append(event); + appender.notifyProcessorsAfterAppending(); } @Override diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 70807497414..a24d0517d0b 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -67,12 +67,22 @@ public void setJsonProcessors(String setting) { * Notify processors that a message has been logged. Note that this method * is intended to be run on the same thread that creates the message. */ - public void notifyProcessors() { + public void notifyProcessorsBeforeAppending() { for (JSONProcessor processor : jsonProcessors) { processor.prepareDiagnosticContext(); } } + /** + * Notify processors after a message has been logged. Note that this method + * is intended to be run on the same thread that creates the message. + */ + public void notifyProcessorsAfterAppending() { + for (JSONProcessor processor : jsonProcessors) { + processor.clearDiagnosticContext(); + } + } + @Override public void close() { if (client != null) { @@ -111,7 +121,7 @@ protected void append(LoggingEvent event) { // notify processors about the message // (in async mode this is done by AsyncSentryAppender) if (!async) { - notifyProcessors(); + notifyProcessorsBeforeAppending(); } // send the message to the sentry server @@ -120,6 +130,10 @@ protected void append(LoggingEvent event) { } else { client.captureException(message, timestamp, logger, level, culprit, info.getThrowable()); } + + if (!async) { + notifyProcessorsAfterAppending(); + } } finally { mdc.removeThreadLoggingEvent(); } diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java index e8752600d93..852108a3057 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java @@ -1,6 +1,8 @@ package net.kencochrane.raven.log4j; import net.kencochrane.raven.Utils; +import net.kencochrane.raven.spi.RavenMDC; + import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; @@ -148,6 +150,17 @@ public void testJSONProcessors() throws IOException, ParseException { assertEquals(1, ((Long)json.get("Test")).longValue()); } + @Test + public void testClearMDC() throws IOException, ParseException { + RavenMDC mdc = RavenMDC.getInstance(); + mdc.put("test", "test"); + assertNotNull(mdc.get("test")); + setSentryDSN("6"); + configureLog4J(); + Logger.getLogger("logger").info("test"); + assertNull(RavenMDC.getInstance().get("test")); + } + protected void setSentryDSN(String projectId) { System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); } diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index a6eecd10cb3..bb24eb5475e 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -2,6 +2,8 @@ import net.kencochrane.raven.Utils; import net.kencochrane.raven.spi.JSONProcessor; +import net.kencochrane.raven.spi.RavenMDC; + import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Level; @@ -155,6 +157,17 @@ public void testJSONProcessors() throws IOException, ParseException { assertEquals(1, ((Long) json.get("Test")).longValue()); } + @Test + public void testClearMDC() throws IOException, ParseException { + RavenMDC mdc = RavenMDC.getInstance(); + mdc.put("test", "test"); + assertNotNull(mdc.get("test")); + setSentryDSN("6"); + configureLog4J(); + Logger.getLogger("logger").info("test"); + assertNull(RavenMDC.getInstance().get("test")); + } + protected void setSentryDSN(String projectId) { System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); } @@ -221,6 +234,11 @@ public void prepareDiagnosticContext() { value++; } + @Override + public void clearDiagnosticContext() { + RavenMDC.getInstance().remove("test"); + } + @Override @SuppressWarnings("unchecked") public void process(JSONObject json, Throwable exception) { diff --git a/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java b/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java index 3bec9a92dd6..60a888b6202 100644 --- a/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java +++ b/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java @@ -35,6 +35,11 @@ public void prepareDiagnosticContext() { RavenMDC.getInstance().put(HTTP_INTERFACE, buildHttpObject(request)); } + @Override + public void clearDiagnosticContext() { + RavenMDC.getInstance().remove(HTTP_INTERFACE); + } + @Override @SuppressWarnings("unchecked") public void process(JSONObject json, Throwable exception) { @@ -44,7 +49,6 @@ public void process(JSONObject json, Throwable exception) { } json.put(HTTP_INTERFACE, http); - RavenMDC.getInstance().remove(HTTP_INTERFACE); } @SuppressWarnings("unchecked") diff --git a/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java b/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java index 4012083ac55..ec7e25332a0 100644 --- a/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java +++ b/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java @@ -16,13 +16,25 @@ public interface JSONProcessor { /** * This is called when a message is logged. Since - * {@link #process(JSONObject, Throwable)} may be executed on a different thread, this - * method should copy any data the processor needs into {@link RavenMDC}. + * {@link #process(JSONObject, Throwable)} may be executed on a different + * thread, this method should copy any data the processor needs into + * {@link RavenMDC}. * * For each message logged, this method should be called exactly once. */ void prepareDiagnosticContext(); + /** + * This is called after the message logged is processed (in synchronous + * mode), or after the message has been sent to the processing queue (in + * asynchronous mode). The intention of this method is to clear the data + * put in {@link RavenMDC} by {@link #prepareDiagnosticContext()} to prevent + * memory leak. + * + * For each message logged, this method should be called exactly once. + */ + void clearDiagnosticContext(); + /** * Modify the JSON request object specified before it is sent to Sentry. * This method may be called concurrently and therefore must be thread-safe. diff --git a/raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java b/raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java index 1d7126012cb..8f65210447a 100644 --- a/raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java +++ b/raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java @@ -54,6 +54,11 @@ public void prepareDiagnosticContext() { testValue = "Value"; } + @Override + public void clearDiagnosticContext() { + testValue = null; + } + @Override @SuppressWarnings("unchecked") public void process(JSONObject json, Throwable exception) { diff --git a/raven/src/test/java/net/kencochrane/raven/ext/ServletJSONProcessorTest.java b/raven/src/test/java/net/kencochrane/raven/ext/ServletJSONProcessorTest.java index cfe749dafbd..f909550cd66 100644 --- a/raven/src/test/java/net/kencochrane/raven/ext/ServletJSONProcessorTest.java +++ b/raven/src/test/java/net/kencochrane/raven/ext/ServletJSONProcessorTest.java @@ -53,9 +53,10 @@ public void testConstructHttpJsonResponse() { setJSONProcessors(processors); setHttpServletRequest(); - new MockRavenMDC(); + RavenMDC mdc = new MockRavenMDC(); processor.prepareDiagnosticContext(); + assertNotNull(mdc.get("sentry.interfaces.Http")); Message message = buildMessage("test", formatTimestamp(new Date().getTime()), "test", LogLevel.ERROR.intValue, "test", null, null); @@ -78,6 +79,9 @@ public void testConstructHttpJsonResponse() { JSONObject cookies = new JSONObject(); cookies.put("test", "cookie"); assertEquals(cookies, http.get("cookies")); + + processor.clearDiagnosticContext(); + assertNull(mdc.get("sentry.interfaces.Http")); } private void setHttpServletRequest() { From f914d599e066bc55ed0500de96cd8541e54a4337 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Mon, 5 Nov 2012 15:55:54 +0100 Subject: [PATCH 0083/2152] Check request.getCookies for null return value --- .../raven/ext/ServletJSONProcessor.java | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java b/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java index 60a888b6202..16e666a4d5a 100644 --- a/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java +++ b/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java @@ -1,17 +1,15 @@ package net.kencochrane.raven.ext; -import java.util.Enumeration; -import java.util.Map; -import java.util.Map.Entry; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; - +import net.kencochrane.raven.spi.JSONProcessor; +import net.kencochrane.raven.spi.RavenMDC; import org.apache.commons.lang.StringUtils; import org.json.simple.JSONObject; -import net.kencochrane.raven.spi.JSONProcessor; -import net.kencochrane.raven.spi.RavenMDC; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import java.util.Enumeration; +import java.util.Map; +import java.util.Map.Entry; /** * Add HTTP request information to logs when logs are created on HTTP request @@ -43,7 +41,7 @@ public void clearDiagnosticContext() { @Override @SuppressWarnings("unchecked") public void process(JSONObject json, Throwable exception) { - JSONObject http = (JSONObject)RavenMDC.getInstance().get(HTTP_INTERFACE); + JSONObject http = (JSONObject) RavenMDC.getInstance().get(HTTP_INTERFACE); if (http == null) { return; } @@ -90,10 +88,13 @@ private static JSONObject getData(HttpServletRequest request) { @SuppressWarnings("unchecked") private static JSONObject getCookies(HttpServletRequest request) { JSONObject cookiesMap = new JSONObject(); - for (Cookie cookie : request.getCookies()) { - cookiesMap.put(cookie.getName(), cookie.getValue()); - } - return cookiesMap; + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : request.getCookies()) { + cookiesMap.put(cookie.getName(), cookie.getValue()); + } + } + return cookiesMap; } @SuppressWarnings("unchecked") @@ -127,7 +128,7 @@ private static JSONObject getEnvironmentVariables(HttpServletRequest request) { */ private static String capitalize(String headerName) { String[] tokens = headerName.split("-"); - for (int i = 0; i < tokens.length; i ++) { + for (int i = 0; i < tokens.length; i++) { tokens[i] = StringUtils.capitalize(tokens[i]); } return StringUtils.join(tokens, "-"); From 82b548493a5535c231789cd41264e64eb3b9de0f Mon Sep 17 00:00:00 2001 From: Cyril Couturier Date: Mon, 17 Dec 2012 17:00:01 +0100 Subject: [PATCH 0084/2152] Add Sentry appender for Logback logger --- pom.xml | 4 +- raven-logback/pom.xml | 72 ++++++ .../kencochrane/raven/logback/LogbackMDC.java | 39 +++ .../raven/logback/SentryAppender.java | 188 ++++++++++++++ .../raven/logback/SentryAppenderTest.java | 236 ++++++++++++++++++ .../sentryappender-no-dsn.logback.xml | 20 ++ .../test/resources/sentryappender.logback.xml | 21 ++ 7 files changed, 579 insertions(+), 1 deletion(-) create mode 100644 raven-logback/pom.xml create mode 100644 raven-logback/src/main/java/net/kencochrane/raven/logback/LogbackMDC.java create mode 100644 raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java create mode 100644 raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java create mode 100644 raven-logback/src/test/resources/sentryappender-no-dsn.logback.xml create mode 100644 raven-logback/src/test/resources/sentryappender.logback.xml diff --git a/pom.xml b/pom.xml index b7a06f93ccc..920e2b48e53 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,7 @@ raven raven-log4j + raven-logback @@ -73,4 +74,5 @@ - \ No newline at end of file + + diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml new file mode 100644 index 00000000000..c3c9360e34a --- /dev/null +++ b/raven-logback/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + + net.kencochrane + raven-all + 1.0-SNAPSHOT + + raven-logback + jar + raven-logback + Logback appender for Raven/Sentry. + + + + ${project.groupId} + raven + ${project.version} + + + ch.qos.logback + logback-core + 1.0.7 + + + ch.qos.logback + logback-classic + 1.0.7 + + + + junit + junit + 4.8.1 + test + + + + org.slf4j + slf4j-api + 1.7.2 + test + + + + + + + maven-assembly-plugin + 2.3 + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + + diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/LogbackMDC.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/LogbackMDC.java new file mode 100644 index 00000000000..43b088c98bd --- /dev/null +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/LogbackMDC.java @@ -0,0 +1,39 @@ +package net.kencochrane.raven.logback; + +import net.kencochrane.raven.spi.RavenMDC; + +import org.slf4j.MDC; + +import ch.qos.logback.classic.spi.ILoggingEvent; + +public class LogbackMDC extends RavenMDC { + + private static final ThreadLocal THREAD_LOGGING_EVENT = new ThreadLocal(); + + public void setThreadLoggingEvent(ILoggingEvent event) { + THREAD_LOGGING_EVENT.set(event); + } + + public void removeThreadLoggingEvent() { + THREAD_LOGGING_EVENT.remove(); + } + + @Override + public Object get(String key) { + if (THREAD_LOGGING_EVENT.get() != null) { + return THREAD_LOGGING_EVENT.get().getMDCPropertyMap().get(key); + } + return MDC.get(key); + } + + @Override + public void put(String key, Object value) { + MDC.put(key, value.toString()); + } + + @Override + public void remove(String key) { + MDC.remove(key); + } + +} diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java new file mode 100644 index 00000000000..d4d9985cc67 --- /dev/null +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -0,0 +1,188 @@ +package net.kencochrane.raven.logback; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.kencochrane.raven.Client; +import net.kencochrane.raven.SentryDsn; +import net.kencochrane.raven.spi.JSONProcessor; +import net.kencochrane.raven.spi.RavenMDC; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.IThrowableProxy; +import ch.qos.logback.classic.spi.ThrowableProxy; +import ch.qos.logback.core.AppenderBase; +import ch.qos.logback.core.encoder.Encoder; + +/** + * Logback appender that will send messages to Sentry. + */ +public class SentryAppender extends AppenderBase { + + private boolean async; + private LogbackMDC mdc; + protected String sentryDsn; + protected Client client; + protected boolean messageCompressionEnabled = true; + private List jsonProcessors = Collections.emptyList(); + private Encoder encoder; + + public SentryAppender() { + initMDC(); + mdc = (LogbackMDC) RavenMDC.getInstance(); + } + + public boolean isAsync() { + return async; + } + + public void setAsync(boolean async) { + this.async = async; + } + + public String getSentryDsn() { + return sentryDsn; + } + + public void setSentryDsn(String sentryDsn) { + this.sentryDsn = sentryDsn; + } + + public boolean isMessageCompressionEnabled() { + return messageCompressionEnabled; + } + + public void setMessageCompressionEnabled(boolean messageCompressionEnabled) { + this.messageCompressionEnabled = messageCompressionEnabled; + } + + /** + * Set a comma-separated list of fully qualified class names of + * JSONProcessors to be used. + * + * @param setting + * a comma-separated list of fully qualified class names of + * JSONProcessors + */ + public void setJsonProcessors(String setting) { + this.jsonProcessors = loadJSONProcessors(setting); + } + + /** + * Notify processors that a message has been logged. Note that this method + * is intended to be run on the same thread that creates the message. + */ + public void notifyProcessorsBeforeAppending() { + for (JSONProcessor processor : jsonProcessors) { + processor.prepareDiagnosticContext(); + } + } + + /** + * Notify processors after a message has been logged. Note that this method + * is intended to be run on the same thread that creates the message. + */ + public void notifyProcessorsAfterAppending() { + for (JSONProcessor processor : jsonProcessors) { + processor.clearDiagnosticContext(); + } + } + + @Override + public void start() { + activateOptions(); + super.start(); + } + + @Override + public void stop() { + if (client != null) { + client.stop(); + } + super.stop(); + } + + public void activateOptions() { + client = sentryDsn == null ? new Client() : new Client(SentryDsn.buildOptional(sentryDsn)); + client.setJSONProcessors(jsonProcessors); + client.setMessageCompressionEnabled(messageCompressionEnabled); + } + + @Override + protected void append(ILoggingEvent event) { + mdc.setThreadLoggingEvent(event); + try { + // get timestamp and timestamp in correct string format. + long timestamp = event.getTimeStamp(); + + // get the log and info about the log. + String message = event.getMessage(); + String logger = event.getLoggerName(); + int level = event.getLevel().toInt() / 1000; // Need to divide by + // 1000 to keep + // consistent with + // sentry + String culprit = event.getLoggerName(); + + IThrowableProxy throwable = event.getThrowableProxy(); + + // notify processors about the message + // (in async mode this is done by AsyncSentryAppender) + if (!async) { + notifyProcessorsBeforeAppending(); + } + + // send the message to the sentry server + if (event.getThrowableProxy() == null) { + client.captureMessage(message, timestamp, logger, level, culprit); + } else { + client.captureException(message, timestamp, logger, level, culprit, ((ThrowableProxy) throwable).getThrowable()); + } + + if (!async) { + notifyProcessorsAfterAppending(); + } + } finally { + mdc.removeThreadLoggingEvent(); + } + } + + private static List loadJSONProcessors(String setting) { + if (setting == null) { + return Collections.emptyList(); + } + try { + List processors = new ArrayList(); + String[] clazzes = setting.split(",\\s*"); + for (String clazz : clazzes) { + JSONProcessor processor = (JSONProcessor) Class.forName(clazz).newInstance(); + processors.add(processor); + } + return processors; + } catch (ClassNotFoundException exception) { + throw new RuntimeException("Processor could not be found.", exception); + } catch (InstantiationException exception) { + throw new RuntimeException("Processor could not be instantiated.", exception); + } catch (IllegalAccessException exception) { + throw new RuntimeException("Processor could not be instantiated.", exception); + } + } + + public static void initMDC() { + if (RavenMDC.getInstance() != null) { + if (!(RavenMDC.getInstance() instanceof LogbackMDC)) { + throw new IllegalStateException("An incompatible RavenMDC instance has been set. Please check your Raven configuration."); + } + return; + } + RavenMDC.setInstance(new LogbackMDC()); + } + + public Encoder getEncoder() { + return encoder; + } + + public void setEncoder(Encoder encoder) { + this.encoder = encoder; + } +} diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java new file mode 100644 index 00000000000..296cc944c43 --- /dev/null +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -0,0 +1,236 @@ +package net.kencochrane.raven.logback; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; +import java.net.SocketException; + +import net.kencochrane.raven.Utils; +import net.kencochrane.raven.spi.JSONProcessor; +import net.kencochrane.raven.spi.RavenMDC; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang.StringUtils; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.joran.spi.JoranException; + +/** + * Test cases for {@link SentryAppender}. + */ +public class SentryAppenderTest { + + protected static SentryMock sentry; + + @BeforeClass + public static void beforeClass() throws SocketException { + sentry = new SentryMock(); + } + + @AfterClass + public static void afterClass() throws SocketException { + System.setProperty(Utils.SENTRY_DSN, ""); + System.setProperty("logback.configurationFile", ""); + sentry.stop(); + } + + @Test + public void noSentryDsn() { + ((LoggerContext) LoggerFactory.getILoggerFactory()).reset(); + System.setProperty(Utils.SENTRY_DSN, ""); + System.setProperty("logback.configurationFile", "sentryappender-no-dsn.logback.xml"); + LoggerFactory.getLogger(this.getClass()).debug("No Sentry DSN, no messages"); + } + + @Test + public void invalidDsn() throws JoranException { + ((LoggerContext) LoggerFactory.getILoggerFactory()).reset(); + System.setProperty(Utils.SENTRY_DSN, "INVALID"); + configureLogback(); + LoggerFactory.getLogger(this.getClass()).debug("Invalid Sentry DSN, no messages"); + } + + @Test + public void debugLevel() throws IOException, ParseException, JoranException { + final String loggerName = "omg.logger"; + final long logLevel = (long) Level.DEBUG_INT / 1000; + final String projectId = "1"; + final String message = "hi there!"; + + configureLogback(projectId); + LoggerFactory.getLogger(loggerName).debug(message); + verifyMessage(loggerName, logLevel, "1", message); + } + + @Test + public void infoLevel() throws IOException, ParseException, JoranException { + final String loggerName = "dude.wheres.my.ride"; + final long logLevel = (long) Level.INFO_INT / 1000; + final String projectId = "2"; + final String message = "This message will self-destruct in 5...4...3..."; + + configureLogback(projectId); + LoggerFactory.getLogger(loggerName).info(message); + verifyMessage(loggerName, logLevel, projectId, message); + } + + @Test + public void warnLevel() throws IOException, ParseException, JoranException { + final String loggerName = "org.apache.commons.httpclient.andStuff"; + final long logLevel = (long) Level.WARN_INT / 1000; + final String projectId = "20"; + final String message = "Warning! Warning! WARNING! Oh, come on!"; + + configureLogback(projectId); + LoggerFactory.getLogger(loggerName).warn(message); + verifyMessage(loggerName, logLevel, projectId, message); + } + + @Test + public void errorLevel() throws IOException, ParseException, JoranException { + final String loggerName = "org.apache.commons.httpclient.andStuff"; + final long logLevel = (long) Level.ERROR_INT / 1000; + final String projectId = "5"; + final String message = "D'oh!"; + + configureLogback(projectId); + LoggerFactory.getLogger(loggerName).error(message); + verifyMessage(loggerName, logLevel, projectId, message); + } + + @Test + public void errorLevel_withException() throws IOException, ParseException, JoranException { + + final String loggerName = "org.apache.commons.httpclient.andStuff"; + // When an exception is logged, the culprit should be the class+method + // where the exception occurred + final String culprit = getClass().getName() + ".errorLevel_withException"; + final long logLevel = (long) Level.ERROR_INT / 1000; + final String projectId = "5"; + final String message = "D'oh!"; + + configureLogback(projectId); + NullPointerException npe = new NullPointerException("Damn you!"); + + LoggerFactory.getLogger(loggerName).error(message, npe); + + // Verify + JSONObject json = verifyMessage(culprit, logLevel, projectId, message); + JSONObject stacktrace = (JSONObject) json.get("sentry.interfaces.Stacktrace"); + assertNotNull(stacktrace); + assertNotNull(stacktrace.get("frames")); + JSONArray frames = (JSONArray) stacktrace.get("frames"); + assertTrue(frames.size() > 0); + JSONObject exception = (JSONObject) json.get("sentry.interfaces.Exception"); + assertNotNull(exception); + assertEquals(NullPointerException.class.getSimpleName(), exception.get("type")); + assertEquals(npe.getMessage(), exception.get("value")); + assertEquals(NullPointerException.class.getPackage().getName(), exception.get("module")); + } + + protected void setSentryDSN(String projectId) { + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + } + + public void configureLogback(String projectId) throws JoranException { + setSentryDSN(projectId); + configureLogback(); + } + + public void configureLogback() throws JoranException { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + JoranConfigurator jc = new JoranConfigurator(); + jc.setContext(context); + context.reset(); + jc.doConfigure("target/test-classes/sentryappender.logback.xml"); + } + + protected JSONObject verifyMessage(String culprit, long logLevel, String projectId, String message) throws IOException, ParseException { + return verifyMessage(sentry, culprit, logLevel, projectId, message); + } + + protected static JSONObject verifyMessage(SentryMock sentry, String culprit, long logLevel, String projectId, String message) throws IOException, ParseException { + JSONObject json = fetchJSONObject(sentry); + System.out.println(json); + assertEquals(message, json.get("message")); + assertEquals(culprit, json.get("culprit")); + assertEquals(projectId, json.get("project")); + assertEquals(logLevel, json.get("level")); + return json; + } + + protected static JSONObject fetchJSONObject(SentryMock sentry) throws IOException, ParseException { + String payload = Utils.fromUtf8(sentry.fetchMessage()); + String[] payloadParts = StringUtils.split(payload, "\n\n"); + assertEquals(2, payloadParts.length); + String raw = Utils.fromUtf8(Utils.decompress(Base64.decodeBase64(payloadParts[1]))); + return (JSONObject) new JSONParser().parse(raw); + } + + public static class SentryMock { + public final DatagramSocket serverSocket; + public final String host; + public final int port; + + public SentryMock() throws SocketException { + this("localhost", 9505); + } + + public SentryMock(String host, int port) throws SocketException { + this.host = host; + this.port = port; + serverSocket = new DatagramSocket(new InetSocketAddress(host, port)); + } + + public void stop() { + serverSocket.close(); + } + + public byte[] fetchMessage() throws IOException { + DatagramPacket packet = new DatagramPacket(new byte[10000], 10000); + serverSocket.receive(packet); + return packet.getData(); + } + + } + + public static class MockJSONProcessor implements JSONProcessor { + + private Long value = 0L; + + @Override + public void prepareDiagnosticContext() { + // this is done to ensure prepareDiagnosticContext is called exactly + // once + value++; + } + + @Override + public void clearDiagnosticContext() { + RavenMDC.getInstance().remove("test"); + } + + @Override + @SuppressWarnings("unchecked") + public void process(JSONObject json, Throwable exception) { + json.put("Test", value); // value should be 1 + } + + } + +} diff --git a/raven-logback/src/test/resources/sentryappender-no-dsn.logback.xml b/raven-logback/src/test/resources/sentryappender-no-dsn.logback.xml new file mode 100644 index 00000000000..4b8ace1e590 --- /dev/null +++ b/raven-logback/src/test/resources/sentryappender-no-dsn.logback.xml @@ -0,0 +1,20 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + No Sentry DSN Test: %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/raven-logback/src/test/resources/sentryappender.logback.xml b/raven-logback/src/test/resources/sentryappender.logback.xml new file mode 100644 index 00000000000..825ccbe3fa2 --- /dev/null +++ b/raven-logback/src/test/resources/sentryappender.logback.xml @@ -0,0 +1,21 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + ${SENTRY_DSN} + + No Sentry DSN Test: %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file From 42be83222ce7b7a677c8a122a1e67c6b102c58a4 Mon Sep 17 00:00:00 2001 From: Cyril Couturier Date: Mon, 17 Dec 2012 17:12:30 +0100 Subject: [PATCH 0085/2152] Clean --- .../net/kencochrane/raven/logback/SentryAppenderTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java index 296cc944c43..950be41f999 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -157,7 +157,7 @@ public void configureLogback() throws JoranException { JoranConfigurator jc = new JoranConfigurator(); jc.setContext(context); context.reset(); - jc.doConfigure("target/test-classes/sentryappender.logback.xml"); + jc.doConfigure(this.getClass().getClassLoader().getResource("sentryappender.logback.xml")); } protected JSONObject verifyMessage(String culprit, long logLevel, String projectId, String message) throws IOException, ParseException { @@ -166,7 +166,6 @@ protected JSONObject verifyMessage(String culprit, long logLevel, String project protected static JSONObject verifyMessage(SentryMock sentry, String culprit, long logLevel, String projectId, String message) throws IOException, ParseException { JSONObject json = fetchJSONObject(sentry); - System.out.println(json); assertEquals(message, json.get("message")); assertEquals(culprit, json.get("culprit")); assertEquals(projectId, json.get("project")); @@ -188,7 +187,7 @@ public static class SentryMock { public final int port; public SentryMock() throws SocketException { - this("localhost", 9505); + this("localhost", 9506); } public SentryMock(String host, int port) throws SocketException { From 5d91bbc7c9634a462f1ac984b747428d0bc44584 Mon Sep 17 00:00:00 2001 From: Cyril Couturier Date: Mon, 17 Dec 2012 17:37:01 +0100 Subject: [PATCH 0086/2152] Add readme file --- raven-logback/README.md | 78 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 raven-logback/README.md diff --git a/raven-logback/README.md b/raven-logback/README.md new file mode 100644 index 00000000000..4eb6e5d3072 --- /dev/null +++ b/raven-logback/README.md @@ -0,0 +1,78 @@ +## Logback ## +"Logback is intended as a successor to the popular log4j project, picking up where log4j leaves off.[...] +Logback brings a very large number of improvements over log4j, big and small. [...] +Keep in mind that logback is conceptually very similar to log4j as both projects were founded by the same developer. If you are already familiar with log4j, you will quickly feel at home using logback. If you like log4j, you will probably love logback." + +Logback project: http://logback.qos.ch/ +Reasons to prefer logback over log4j: http://logback.qos.ch/reasonsToSwitch.html + +## Maven dependencies ## +````xml + + + org.slf4j + slf4j-api + 1.7.2 + + + + ch.qos.logback + logback-classic + 1.0.7 + + + + net.kencochrane + raven-logback + 1.0-SNAPSHOT + + +```` + +## Using the logback appender + +````java +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Example { +public static Logger logger = LoggerFactory.getLogger(Example.class); + +public static void main(String[] args) { +logger.info("Hello World"); +logger.trace("Hello World!"); +logger.debug("How are you today?"); +logger.info("I am fine."); +logger.warn("I love programming."); +logger.error("I am programming."); +} + +} +```` + +## Appender configuration ## +Example of configuration in src/main/resources/logback.xml + +````xml + + + + + + %d{HH:mm:ss.SSS} [%thread] %boldRed(%-5level) %logger{36} - %msg%n + + + + + http://2d0fd0e8c0d546279c7115b563bdf60f:3429a634a51b4b5b937be06d83bc6c97@localhost:9000/1 + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + +```` From 7b2ec58b05074fbb4d39a4888a93a8eb9b02d7d9 Mon Sep 17 00:00:00 2001 From: Cyril Couturier Date: Mon, 17 Dec 2012 17:39:31 +0100 Subject: [PATCH 0087/2152] Indentation --- raven-logback/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/raven-logback/README.md b/raven-logback/README.md index 4eb6e5d3072..1a02c58341f 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -36,16 +36,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Example { -public static Logger logger = LoggerFactory.getLogger(Example.class); + public static Logger logger = LoggerFactory.getLogger(Example.class); -public static void main(String[] args) { -logger.info("Hello World"); -logger.trace("Hello World!"); -logger.debug("How are you today?"); -logger.info("I am fine."); -logger.warn("I love programming."); -logger.error("I am programming."); -} + public static void main(String[] args) { + logger.info("Hello World"); + logger.trace("Hello World!"); + logger.debug("How are you today?"); + logger.info("I am fine."); + logger.warn("I love programming."); + logger.error("I am programming."); + } } ```` From 9fc879a3e2ef893d6541726f86ad8944aa294210 Mon Sep 17 00:00:00 2001 From: Cyril Couturier Date: Mon, 17 Dec 2012 17:00:01 +0100 Subject: [PATCH 0088/2152] Add Sentry appender for Logback logger --- pom.xml | 4 +- raven-logback/README.md | 78 ++++++ raven-logback/pom.xml | 72 ++++++ .../kencochrane/raven/logback/LogbackMDC.java | 39 +++ .../raven/logback/SentryAppender.java | 188 ++++++++++++++ .../raven/logback/SentryAppenderTest.java | 235 ++++++++++++++++++ .../sentryappender-no-dsn.logback.xml | 20 ++ .../test/resources/sentryappender.logback.xml | 21 ++ 8 files changed, 656 insertions(+), 1 deletion(-) create mode 100644 raven-logback/README.md create mode 100644 raven-logback/pom.xml create mode 100644 raven-logback/src/main/java/net/kencochrane/raven/logback/LogbackMDC.java create mode 100644 raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java create mode 100644 raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java create mode 100644 raven-logback/src/test/resources/sentryappender-no-dsn.logback.xml create mode 100644 raven-logback/src/test/resources/sentryappender.logback.xml diff --git a/pom.xml b/pom.xml index b7a06f93ccc..920e2b48e53 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,7 @@ raven raven-log4j + raven-logback @@ -73,4 +74,5 @@ - \ No newline at end of file + + diff --git a/raven-logback/README.md b/raven-logback/README.md new file mode 100644 index 00000000000..1a02c58341f --- /dev/null +++ b/raven-logback/README.md @@ -0,0 +1,78 @@ +## Logback ## +"Logback is intended as a successor to the popular log4j project, picking up where log4j leaves off.[...] +Logback brings a very large number of improvements over log4j, big and small. [...] +Keep in mind that logback is conceptually very similar to log4j as both projects were founded by the same developer. If you are already familiar with log4j, you will quickly feel at home using logback. If you like log4j, you will probably love logback." + +Logback project: http://logback.qos.ch/ +Reasons to prefer logback over log4j: http://logback.qos.ch/reasonsToSwitch.html + +## Maven dependencies ## +````xml + + + org.slf4j + slf4j-api + 1.7.2 + + + + ch.qos.logback + logback-classic + 1.0.7 + + + + net.kencochrane + raven-logback + 1.0-SNAPSHOT + + +```` + +## Using the logback appender + +````java +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Example { + public static Logger logger = LoggerFactory.getLogger(Example.class); + + public static void main(String[] args) { + logger.info("Hello World"); + logger.trace("Hello World!"); + logger.debug("How are you today?"); + logger.info("I am fine."); + logger.warn("I love programming."); + logger.error("I am programming."); + } + +} +```` + +## Appender configuration ## +Example of configuration in src/main/resources/logback.xml + +````xml + + + + + + %d{HH:mm:ss.SSS} [%thread] %boldRed(%-5level) %logger{36} - %msg%n + + + + + http://2d0fd0e8c0d546279c7115b563bdf60f:3429a634a51b4b5b937be06d83bc6c97@localhost:9000/1 + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + +```` diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml new file mode 100644 index 00000000000..c3c9360e34a --- /dev/null +++ b/raven-logback/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + + net.kencochrane + raven-all + 1.0-SNAPSHOT + + raven-logback + jar + raven-logback + Logback appender for Raven/Sentry. + + + + ${project.groupId} + raven + ${project.version} + + + ch.qos.logback + logback-core + 1.0.7 + + + ch.qos.logback + logback-classic + 1.0.7 + + + + junit + junit + 4.8.1 + test + + + + org.slf4j + slf4j-api + 1.7.2 + test + + + + + + + maven-assembly-plugin + 2.3 + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + + diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/LogbackMDC.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/LogbackMDC.java new file mode 100644 index 00000000000..43b088c98bd --- /dev/null +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/LogbackMDC.java @@ -0,0 +1,39 @@ +package net.kencochrane.raven.logback; + +import net.kencochrane.raven.spi.RavenMDC; + +import org.slf4j.MDC; + +import ch.qos.logback.classic.spi.ILoggingEvent; + +public class LogbackMDC extends RavenMDC { + + private static final ThreadLocal THREAD_LOGGING_EVENT = new ThreadLocal(); + + public void setThreadLoggingEvent(ILoggingEvent event) { + THREAD_LOGGING_EVENT.set(event); + } + + public void removeThreadLoggingEvent() { + THREAD_LOGGING_EVENT.remove(); + } + + @Override + public Object get(String key) { + if (THREAD_LOGGING_EVENT.get() != null) { + return THREAD_LOGGING_EVENT.get().getMDCPropertyMap().get(key); + } + return MDC.get(key); + } + + @Override + public void put(String key, Object value) { + MDC.put(key, value.toString()); + } + + @Override + public void remove(String key) { + MDC.remove(key); + } + +} diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java new file mode 100644 index 00000000000..d4d9985cc67 --- /dev/null +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -0,0 +1,188 @@ +package net.kencochrane.raven.logback; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.kencochrane.raven.Client; +import net.kencochrane.raven.SentryDsn; +import net.kencochrane.raven.spi.JSONProcessor; +import net.kencochrane.raven.spi.RavenMDC; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.IThrowableProxy; +import ch.qos.logback.classic.spi.ThrowableProxy; +import ch.qos.logback.core.AppenderBase; +import ch.qos.logback.core.encoder.Encoder; + +/** + * Logback appender that will send messages to Sentry. + */ +public class SentryAppender extends AppenderBase { + + private boolean async; + private LogbackMDC mdc; + protected String sentryDsn; + protected Client client; + protected boolean messageCompressionEnabled = true; + private List jsonProcessors = Collections.emptyList(); + private Encoder encoder; + + public SentryAppender() { + initMDC(); + mdc = (LogbackMDC) RavenMDC.getInstance(); + } + + public boolean isAsync() { + return async; + } + + public void setAsync(boolean async) { + this.async = async; + } + + public String getSentryDsn() { + return sentryDsn; + } + + public void setSentryDsn(String sentryDsn) { + this.sentryDsn = sentryDsn; + } + + public boolean isMessageCompressionEnabled() { + return messageCompressionEnabled; + } + + public void setMessageCompressionEnabled(boolean messageCompressionEnabled) { + this.messageCompressionEnabled = messageCompressionEnabled; + } + + /** + * Set a comma-separated list of fully qualified class names of + * JSONProcessors to be used. + * + * @param setting + * a comma-separated list of fully qualified class names of + * JSONProcessors + */ + public void setJsonProcessors(String setting) { + this.jsonProcessors = loadJSONProcessors(setting); + } + + /** + * Notify processors that a message has been logged. Note that this method + * is intended to be run on the same thread that creates the message. + */ + public void notifyProcessorsBeforeAppending() { + for (JSONProcessor processor : jsonProcessors) { + processor.prepareDiagnosticContext(); + } + } + + /** + * Notify processors after a message has been logged. Note that this method + * is intended to be run on the same thread that creates the message. + */ + public void notifyProcessorsAfterAppending() { + for (JSONProcessor processor : jsonProcessors) { + processor.clearDiagnosticContext(); + } + } + + @Override + public void start() { + activateOptions(); + super.start(); + } + + @Override + public void stop() { + if (client != null) { + client.stop(); + } + super.stop(); + } + + public void activateOptions() { + client = sentryDsn == null ? new Client() : new Client(SentryDsn.buildOptional(sentryDsn)); + client.setJSONProcessors(jsonProcessors); + client.setMessageCompressionEnabled(messageCompressionEnabled); + } + + @Override + protected void append(ILoggingEvent event) { + mdc.setThreadLoggingEvent(event); + try { + // get timestamp and timestamp in correct string format. + long timestamp = event.getTimeStamp(); + + // get the log and info about the log. + String message = event.getMessage(); + String logger = event.getLoggerName(); + int level = event.getLevel().toInt() / 1000; // Need to divide by + // 1000 to keep + // consistent with + // sentry + String culprit = event.getLoggerName(); + + IThrowableProxy throwable = event.getThrowableProxy(); + + // notify processors about the message + // (in async mode this is done by AsyncSentryAppender) + if (!async) { + notifyProcessorsBeforeAppending(); + } + + // send the message to the sentry server + if (event.getThrowableProxy() == null) { + client.captureMessage(message, timestamp, logger, level, culprit); + } else { + client.captureException(message, timestamp, logger, level, culprit, ((ThrowableProxy) throwable).getThrowable()); + } + + if (!async) { + notifyProcessorsAfterAppending(); + } + } finally { + mdc.removeThreadLoggingEvent(); + } + } + + private static List loadJSONProcessors(String setting) { + if (setting == null) { + return Collections.emptyList(); + } + try { + List processors = new ArrayList(); + String[] clazzes = setting.split(",\\s*"); + for (String clazz : clazzes) { + JSONProcessor processor = (JSONProcessor) Class.forName(clazz).newInstance(); + processors.add(processor); + } + return processors; + } catch (ClassNotFoundException exception) { + throw new RuntimeException("Processor could not be found.", exception); + } catch (InstantiationException exception) { + throw new RuntimeException("Processor could not be instantiated.", exception); + } catch (IllegalAccessException exception) { + throw new RuntimeException("Processor could not be instantiated.", exception); + } + } + + public static void initMDC() { + if (RavenMDC.getInstance() != null) { + if (!(RavenMDC.getInstance() instanceof LogbackMDC)) { + throw new IllegalStateException("An incompatible RavenMDC instance has been set. Please check your Raven configuration."); + } + return; + } + RavenMDC.setInstance(new LogbackMDC()); + } + + public Encoder getEncoder() { + return encoder; + } + + public void setEncoder(Encoder encoder) { + this.encoder = encoder; + } +} diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java new file mode 100644 index 00000000000..950be41f999 --- /dev/null +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -0,0 +1,235 @@ +package net.kencochrane.raven.logback; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; +import java.net.SocketException; + +import net.kencochrane.raven.Utils; +import net.kencochrane.raven.spi.JSONProcessor; +import net.kencochrane.raven.spi.RavenMDC; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang.StringUtils; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.joran.spi.JoranException; + +/** + * Test cases for {@link SentryAppender}. + */ +public class SentryAppenderTest { + + protected static SentryMock sentry; + + @BeforeClass + public static void beforeClass() throws SocketException { + sentry = new SentryMock(); + } + + @AfterClass + public static void afterClass() throws SocketException { + System.setProperty(Utils.SENTRY_DSN, ""); + System.setProperty("logback.configurationFile", ""); + sentry.stop(); + } + + @Test + public void noSentryDsn() { + ((LoggerContext) LoggerFactory.getILoggerFactory()).reset(); + System.setProperty(Utils.SENTRY_DSN, ""); + System.setProperty("logback.configurationFile", "sentryappender-no-dsn.logback.xml"); + LoggerFactory.getLogger(this.getClass()).debug("No Sentry DSN, no messages"); + } + + @Test + public void invalidDsn() throws JoranException { + ((LoggerContext) LoggerFactory.getILoggerFactory()).reset(); + System.setProperty(Utils.SENTRY_DSN, "INVALID"); + configureLogback(); + LoggerFactory.getLogger(this.getClass()).debug("Invalid Sentry DSN, no messages"); + } + + @Test + public void debugLevel() throws IOException, ParseException, JoranException { + final String loggerName = "omg.logger"; + final long logLevel = (long) Level.DEBUG_INT / 1000; + final String projectId = "1"; + final String message = "hi there!"; + + configureLogback(projectId); + LoggerFactory.getLogger(loggerName).debug(message); + verifyMessage(loggerName, logLevel, "1", message); + } + + @Test + public void infoLevel() throws IOException, ParseException, JoranException { + final String loggerName = "dude.wheres.my.ride"; + final long logLevel = (long) Level.INFO_INT / 1000; + final String projectId = "2"; + final String message = "This message will self-destruct in 5...4...3..."; + + configureLogback(projectId); + LoggerFactory.getLogger(loggerName).info(message); + verifyMessage(loggerName, logLevel, projectId, message); + } + + @Test + public void warnLevel() throws IOException, ParseException, JoranException { + final String loggerName = "org.apache.commons.httpclient.andStuff"; + final long logLevel = (long) Level.WARN_INT / 1000; + final String projectId = "20"; + final String message = "Warning! Warning! WARNING! Oh, come on!"; + + configureLogback(projectId); + LoggerFactory.getLogger(loggerName).warn(message); + verifyMessage(loggerName, logLevel, projectId, message); + } + + @Test + public void errorLevel() throws IOException, ParseException, JoranException { + final String loggerName = "org.apache.commons.httpclient.andStuff"; + final long logLevel = (long) Level.ERROR_INT / 1000; + final String projectId = "5"; + final String message = "D'oh!"; + + configureLogback(projectId); + LoggerFactory.getLogger(loggerName).error(message); + verifyMessage(loggerName, logLevel, projectId, message); + } + + @Test + public void errorLevel_withException() throws IOException, ParseException, JoranException { + + final String loggerName = "org.apache.commons.httpclient.andStuff"; + // When an exception is logged, the culprit should be the class+method + // where the exception occurred + final String culprit = getClass().getName() + ".errorLevel_withException"; + final long logLevel = (long) Level.ERROR_INT / 1000; + final String projectId = "5"; + final String message = "D'oh!"; + + configureLogback(projectId); + NullPointerException npe = new NullPointerException("Damn you!"); + + LoggerFactory.getLogger(loggerName).error(message, npe); + + // Verify + JSONObject json = verifyMessage(culprit, logLevel, projectId, message); + JSONObject stacktrace = (JSONObject) json.get("sentry.interfaces.Stacktrace"); + assertNotNull(stacktrace); + assertNotNull(stacktrace.get("frames")); + JSONArray frames = (JSONArray) stacktrace.get("frames"); + assertTrue(frames.size() > 0); + JSONObject exception = (JSONObject) json.get("sentry.interfaces.Exception"); + assertNotNull(exception); + assertEquals(NullPointerException.class.getSimpleName(), exception.get("type")); + assertEquals(npe.getMessage(), exception.get("value")); + assertEquals(NullPointerException.class.getPackage().getName(), exception.get("module")); + } + + protected void setSentryDSN(String projectId) { + System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); + } + + public void configureLogback(String projectId) throws JoranException { + setSentryDSN(projectId); + configureLogback(); + } + + public void configureLogback() throws JoranException { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + JoranConfigurator jc = new JoranConfigurator(); + jc.setContext(context); + context.reset(); + jc.doConfigure(this.getClass().getClassLoader().getResource("sentryappender.logback.xml")); + } + + protected JSONObject verifyMessage(String culprit, long logLevel, String projectId, String message) throws IOException, ParseException { + return verifyMessage(sentry, culprit, logLevel, projectId, message); + } + + protected static JSONObject verifyMessage(SentryMock sentry, String culprit, long logLevel, String projectId, String message) throws IOException, ParseException { + JSONObject json = fetchJSONObject(sentry); + assertEquals(message, json.get("message")); + assertEquals(culprit, json.get("culprit")); + assertEquals(projectId, json.get("project")); + assertEquals(logLevel, json.get("level")); + return json; + } + + protected static JSONObject fetchJSONObject(SentryMock sentry) throws IOException, ParseException { + String payload = Utils.fromUtf8(sentry.fetchMessage()); + String[] payloadParts = StringUtils.split(payload, "\n\n"); + assertEquals(2, payloadParts.length); + String raw = Utils.fromUtf8(Utils.decompress(Base64.decodeBase64(payloadParts[1]))); + return (JSONObject) new JSONParser().parse(raw); + } + + public static class SentryMock { + public final DatagramSocket serverSocket; + public final String host; + public final int port; + + public SentryMock() throws SocketException { + this("localhost", 9506); + } + + public SentryMock(String host, int port) throws SocketException { + this.host = host; + this.port = port; + serverSocket = new DatagramSocket(new InetSocketAddress(host, port)); + } + + public void stop() { + serverSocket.close(); + } + + public byte[] fetchMessage() throws IOException { + DatagramPacket packet = new DatagramPacket(new byte[10000], 10000); + serverSocket.receive(packet); + return packet.getData(); + } + + } + + public static class MockJSONProcessor implements JSONProcessor { + + private Long value = 0L; + + @Override + public void prepareDiagnosticContext() { + // this is done to ensure prepareDiagnosticContext is called exactly + // once + value++; + } + + @Override + public void clearDiagnosticContext() { + RavenMDC.getInstance().remove("test"); + } + + @Override + @SuppressWarnings("unchecked") + public void process(JSONObject json, Throwable exception) { + json.put("Test", value); // value should be 1 + } + + } + +} diff --git a/raven-logback/src/test/resources/sentryappender-no-dsn.logback.xml b/raven-logback/src/test/resources/sentryappender-no-dsn.logback.xml new file mode 100644 index 00000000000..4b8ace1e590 --- /dev/null +++ b/raven-logback/src/test/resources/sentryappender-no-dsn.logback.xml @@ -0,0 +1,20 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + No Sentry DSN Test: %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/raven-logback/src/test/resources/sentryappender.logback.xml b/raven-logback/src/test/resources/sentryappender.logback.xml new file mode 100644 index 00000000000..825ccbe3fa2 --- /dev/null +++ b/raven-logback/src/test/resources/sentryappender.logback.xml @@ -0,0 +1,21 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + ${SENTRY_DSN} + + No Sentry DSN Test: %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file From 9c8a83eeb66621c41e958c629e6b748330acf5ab Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Tue, 18 Dec 2012 10:06:19 -0800 Subject: [PATCH 0089/2152] Create LICENSE Added License file. --- LICENSE | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000000..b6257d98a76 --- /dev/null +++ b/LICENSE @@ -0,0 +1,12 @@ +Copyright (c) 2012 Ken Cochrane and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + + 3. Neither the name of the Raven nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From e2a8788be641fe58cf9bbd1c6b334d3ad560eb0b Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Tue, 18 Dec 2012 13:10:34 -0500 Subject: [PATCH 0090/2152] Updated Readme to include section about License I Updated the README to include a section about License, we are using, which is BSD. Which is the same as the Master Sentry project. --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index f7ae10fb0d0..8bcfc1b062c 100644 --- a/README.rst +++ b/README.rst @@ -206,3 +206,7 @@ Contributors - Kevin Wetzels (@roambe) - David Cramer (@zeeg) - Mark Philpot (@griphiam) + +License +------- +We are using the same license as the Sentry master project which is a BSD license. For more information see the LICENSE file, or follow this link: http://en.wikipedia.org/wiki/BSD_licenses From 3a8de04d038be2956bfbd396fbc2da0a122cba8a Mon Sep 17 00:00:00 2001 From: Alan Fachini Date: Fri, 21 Dec 2012 09:11:04 -0200 Subject: [PATCH 0091/2152] Add missing info to pom to release to maven central. --- pom.xml | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index cc3af1de8c6..0599ba43208 100644 --- a/pom.xml +++ b/pom.xml @@ -2,14 +2,58 @@ - 4.0.0 + + org.sonatype.oss + oss-parent + 7 + + + 4.0.0 net.kencochrane raven-java 0.6-SNAPSHOT jar raven-java Java Raven client and log4j appender. + http://sentry.readthedocs.org/en/latest/client/index.html + + + + BSD New + http://opensource.org/licenses/BSD-3-Clause + repo + + + + + git@github.com:kencochrane/raven-java.git + scm:git:git://github.com/kencochrane/raven-java.git + scm:git:git@github.com:kencochrane/raven-java.git + + + + + kencochrane + Ken Cochrane + contact@email.com + + + roambe + Kevin Wetzels + contact@email.com + + + zeeg + David Cramer + contact@email.com + + + griphiam + Mark Philpot + contact@email.com + + @@ -120,5 +164,4 @@
    - - \ No newline at end of file + From 42c58fcbe3a35bb7911bee2e34c9c5fe113f9e5c Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Fri, 21 Dec 2012 08:17:41 -0500 Subject: [PATCH 0092/2152] updated pom.xml to include email address updated pom.xml to include ken cochrane's email address --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0599ba43208..70e4b9835a8 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,7 @@ kencochrane Ken Cochrane - contact@email.com + kencochrane+maven@gmail.com roambe From a74630de563f3b08eddc1c560879aa103cb39715 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Wed, 16 Jan 2013 20:16:52 +0100 Subject: [PATCH 0093/2152] Fix integration tests for running under Sentry 5.1.5 --- .../java/net/kencochrane/raven/SentryApi.java | 62 +++++++++--------- .../src/main/resources/boot_sentry.db | Bin 581632 -> 581632 bytes .../raven/integration/IntegrationContext.java | 1 + .../integration/log4j/SentryAppenderTest.java | 2 +- .../java/net/kencochrane/raven/Utils.java | 2 +- 5 files changed, 33 insertions(+), 34 deletions(-) diff --git a/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java b/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java index 3e7ad737d39..d3d61ba7292 100644 --- a/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java +++ b/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java @@ -24,10 +24,7 @@ import java.io.IOException; import java.net.MalformedURLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -95,7 +92,7 @@ public boolean clear(String projectId) throws IOException { return ok; } - public JSONArray getRawJson(String projectSlug, int group) throws IOException { + public JSONArray getRawJson(String projectSlug, String group) throws IOException { HttpResponse response = client.execute(new HttpGet(host + "/" + projectSlug + "/group/" + group + "/events/json/")); String raw = EntityUtils.toString(response.getEntity()); if (StringUtils.isBlank(raw)) { @@ -108,35 +105,36 @@ public JSONArray getRawJson(String projectSlug, int group) throws IOException { } } - public List getEvents(String projectSlug) throws IOException { - HttpResponse response = client.execute(new HttpGet(host + "/" + projectSlug)); - Document doc = Jsoup.parse(EntityUtils.toString(response.getEntity())); - Elements items = doc.select("ul#event_list li.event"); - List events = new LinkedList(); - for (Element item : items) { - if (item.hasClass("resolved")) { - continue; + public List getEvents(String projectId) throws IOException { + HttpResponse response = client.execute(new HttpGet(host + "/api/" + projectId + "/poll/")); + try { + Object json = new JSONParser().parse(EntityUtils.toString(response.getEntity())); + List eventJson = null; + if (json instanceof JSONObject) { + eventJson = Arrays.asList((JSONObject) json); + } else { + eventJson = (List) json; } - int group = Integer.parseInt(item.attr("data-group")); - int count = Integer.parseInt(item.attr("data-count")); - String levelName = extractLevel(item.classNames()); - int level = -1; - if (levelName != null) { - try { - level = Integer.parseInt(levelName); - } catch (NumberFormatException e) { - // Ignore + List events = new LinkedList(); + for (JSONObject item : eventJson) { + Boolean resolved = (Boolean) item.get("isResolved"); + if (resolved != null && resolved) { + continue; } + String group = (String) item.get("id"); + Integer count = Integer.parseInt((String) item.get("count")); + Integer level = ((Long) item.get("level")).intValue(); + String levelName = (String) item.get("levelName"); + String link = (String) item.get("permalink"); + String title = (String) item.get("title"); + String message = (String) item.get("message"); + String logger = (String) item.get("logger"); + events.add(new Event(group, count, level, levelName, link, title, message, logger)); } - Element anchor = item.select("h3 a").get(0); - String link = anchor.attr("href"); - String title = StringUtils.trim(anchor.text()); - Element messageElement = item.select("p.message").get(0); - String message = StringUtils.trim(messageElement.attr("title")); - String logger = StringUtils.trim(messageElement.select("span.tag-logger").text()); - events.add(new Event(group, count, level, levelName, link, title, message, logger)); + return events; + } catch (ParseException e) { + throw new IOException(e); } - return events; } public List getAvailableTags(String projectSlug) throws IOException { @@ -164,7 +162,7 @@ protected static String extractLevel(Collection classNames) { } public static class Event { - public final int group; + public final String group; public final int count; public final int level; public final String levelName; @@ -173,7 +171,7 @@ public static class Event { public final String message; public final String logger; - public Event(int group, int count, int level, String levelName, String url, String title, String message, String logger) { + public Event(String group, int count, int level, String levelName, String url, String title, String message, String logger) { this.group = group; this.count = count; this.level = level; diff --git a/raven-integration-tests/src/main/resources/boot_sentry.db b/raven-integration-tests/src/main/resources/boot_sentry.db index d719cb45ae444df708c635621c15f70e515c011d..9524252387d093cfc0e367a006921c5da9ffea7d 100644 GIT binary patch delta 237 zcmZoTpxkgkd4jZ{Cj$dR9}ueoF)ITD|nKLz0Fp`uFigg zBbwtrXA$KtiHGECR$JF|o~p4lno@1q3V#5LgtTu*rd2pq={x6A&{4F$)m0 O0x{cm?g#8&{{sM%=sxHG delta 190 zcmZoTpxkgkd4jZ{8v_GF9}q(U{#;k4(x{p*EQ?{ls&W&dQ%7{#7O<+`K;tSZW zm%ymRx4E2g1LL*=Mn(aBprj~cF#}^UQxx-lmL072thd=}*wxu@a71(5=S<>!%azS7 z#(kHko#zVg9Nwqf4H_7o8Mg;CFe)={pVq{9m7S$gLy3KRei!4!t(y{90yeWG{E=T2 fps>k-TcDl$0TU2212GE_vjQ>OcJ2r4U;hIDCyqD? diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/IntegrationContext.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/IntegrationContext.java index bb40b202ea1..d53151b3f6e 100644 --- a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/IntegrationContext.java +++ b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/IntegrationContext.java @@ -17,6 +17,7 @@ public class IntegrationContext { public static SentryDsn httpDsn; public static SentryDsn udpDsn; public static String projectSlug = "ravenjava"; + public static String projectId = "2"; protected static boolean initialized = false; public static void init() throws IOException { diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java index 49a22b7d909..b3871ad29aa 100644 --- a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java +++ b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java @@ -135,7 +135,7 @@ public void tearDown() throws IOException { } private void verify(String loggerName, String message, String levelName, String title) throws IOException { - List events = api.getEvents(IntegrationContext.projectSlug); + List events = api.getEvents(IntegrationContext.projectId); assertEquals(1, events.size()); SentryApi.Event event = events.get(0); assertTrue(event.count > 0); diff --git a/raven/src/main/java/net/kencochrane/raven/Utils.java b/raven/src/main/java/net/kencochrane/raven/Utils.java index 17b257e80e6..e96fd6f4f2e 100644 --- a/raven/src/main/java/net/kencochrane/raven/Utils.java +++ b/raven/src/main/java/net/kencochrane/raven/Utils.java @@ -20,7 +20,7 @@ public abstract class Utils { private static final Map CACHE = new HashMap(); public interface Client { - String VERSION = "1.0-SNAPSHOT"; + String VERSION = "2.0"; String NAME = "Raven-Java " + VERSION; } From a2463df1d7a18b7057a17a5b2d289a0cd5998ff3 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Wed, 16 Jan 2013 20:19:00 +0100 Subject: [PATCH 0094/2152] Set version number to 2.0-SNAPSHOT --- pom.xml | 2 +- raven-integration-tests/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index b7a06f93ccc..af0f153375f 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ net.kencochrane raven-all - 1.0-SNAPSHOT + 2.0-SNAPSHOT pom raven-all Java Raven client and loggers. diff --git a/raven-integration-tests/pom.xml b/raven-integration-tests/pom.xml index c9a5d16e9fc..8e2e7bcc0b5 100644 --- a/raven-integration-tests/pom.xml +++ b/raven-integration-tests/pom.xml @@ -6,7 +6,7 @@ net.kencochrane raven-integration-tests - 1.0-SNAPSHOT + 2.0-SNAPSHOT jar raven-integration-tests Raven-Java/Sentry integration tests diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 0e87cc12029..33cf651eb94 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -7,7 +7,7 @@ net.kencochrane raven-all - 1.0-SNAPSHOT + 2.0-SNAPSHOT raven-log4j jar diff --git a/raven/pom.xml b/raven/pom.xml index a05a810cf61..7f7efe2dba0 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -7,7 +7,7 @@ net.kencochrane raven-all - 1.0-SNAPSHOT + 2.0-SNAPSHOT raven jar From 5bd956793e63c61528bf0f0992fd821bced31fe3 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Sun, 20 Jan 2013 11:17:19 +0100 Subject: [PATCH 0095/2152] Update README --- README.md | 50 ++++++++++++------- pom.xml | 38 ++++++++++++++ .../java/net/kencochrane/raven/Transport.java | 4 +- 3 files changed, 73 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index e9b08f740c8..b5a369fcec3 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Raven-Java is a Java client for [Sentry](https://www.getsentry.com/). Besides a Raven-Java supports both HTTP(S) and UDP transport of messages. ## Sentry Versions Supported -This client supports Sentry protocol version 2.0 (which is Sentry >= 2.0). +This client supports Sentry protocol version 2.0 (which is Sentry >= 2.0). Since version 4.6.0 of Sentry, signed messages have been deprecated. If you still use an earlier version, have a look at the client options below to find out how to enable signed messages. @@ -26,14 +26,14 @@ public class Example { Client = new Client(dsn); client.captureMessage("Hello from Raven-Java!"); } - + } ```` A full example is available in `raven/src/main/test/net/kencochrane/raven/ClientExample`. -Note that `SentryDsn` will first examine the environment variables and system properties for a variable called `SENTRY_DSN`. If such a variable is available, *that* value will be used instead of the DSN you supply (the `rawDsn` variable in the example above). +Note that `SentryDsn` will first examine the environment variables and system properties for a variable called `SENTRY_DSN`. If such a variable is available, *that* value will be used instead of the DSN you supply (the `rawDsn` variable in the example above). This allows flexible usage of the library on PaaS providers such as Heroku. This also means the above example can be simplified to the following *if* you specify `SENTRY_DSN` in your: @@ -51,7 +51,7 @@ public class Example { Client = new Client(); client.captureMessage("Hello from Raven-Java!"); } - + } ```` @@ -67,7 +67,7 @@ You can either utilize the `net.kencochrane.raven.log4j.SentryAppender` or `net. Like the client, these appenders will examine the system properties and environment variables for a better `SENTRY_DSN` candidate. Which log messages are ultimately sent to your Sentry instance, depends on the configuration of your Sentry appender. If you only want to send error messages, use the following: log4j.appender.sentry.Threshold=ERROR - + ### Asynchronous logging If you use log4j's XML configuration, you can use its [AsyncAppender](http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/AsyncAppender.html) to wrap Raven-Java's `SentryAppender`. @@ -79,26 +79,28 @@ But even if you use a properties file to configure log4j, you can log asynchrono Client configuration is completely driven through the Sentry DSN. The DSN you can copy-paste from your Sentry instance looks like this: http://public:private@host:port/1 - + Changing the behavior of the client is done through the *scheme* and *querystring* of the DSN. - + ### Transport protocols - + #### HTTPS If you're using https, your DSN should look like this: https://public:private@host:port/1 - + #### Naive HTTPS -If you're using https with a self-signed certificate and you're too lazy to add the certificate to your truststore, you can tell the client to be naive and allow it to ignore the certificate (which you really shouldn't do in a production environment): +If you're using https with a wildcard certificate (which most Java versions can't handle) and you're too lazy +to add the certificate to your truststore, you can tell the client to be naive and allow it to ignore the +hostname verification: naive+https://public:private@host:port/1 - + #### UDP Prefer udp? Change your DSN to this: udp://public:private@host:port/1 - + #### Asynchronous The client can use a separate thread to actually send the messages to Sentry. To enable this feature, add `async+` to the scheme in your DSN: @@ -109,7 +111,7 @@ The client can use a separate thread to actually send the messages to Sentry. To async+naive+https://public:private@host:port/1 # Or async+udp://public:private@host:port/1 - + #### Adding other schemes You can add your own custom transport and scheme through the `register` method of the client. @@ -122,12 +124,12 @@ More client configuration can be specified through the querystring of the DSN li Signed messages have been deprecated in Sentry 4.6.0. If you're using an earlier version, you'll have to tell the client to sign messages through the option `raven.includeSignature`: http://public:private@host:port/1?raven.includeSignature=true - + #### HTTP/HTTPS timeout The default timeout for HTTP/HTTPS transport is set to 10 seconds. If you want to change this value, use the `raven.timeout` option to specify the timeout in milliseconds. http://public:private@host:port/1?raven.timeout=10000 - + #### Async queue configuration When using the async transport (this is not the same as using the `AsyncSentryAppender`), you can configure the behavior of the underlying `java.util.concurrent.BlockingQueue` through the `raven.waitWhenFull` and `raven.capacity` options. @@ -163,11 +165,25 @@ Then, add the following lines to web.xml: * * * ## Installation -TODO - + +This version isn't available in the Central Maven repository yet. The easiest +way to get started is to clone this repository and install the artifacts into +your local repository or proxy like Nexus: + + $ git clone https://github.com/kencochrane/raven-java.git + $ cd raven-java + $ mvn clean install + +This will build and test the Raven client and Log4J appender and install it +into your local repository. You shouldn't worry about stacktraces appearing in +the output unless tests are failing; they are *supposed* to be there. + * * * ## History + +- 2.0-SNAPSHOT + - Version increment to reduce confusion about releases - 1.0 - Rewrite - Support tags diff --git a/pom.xml b/pom.xml index b7a06f93ccc..4361a2bbd55 100644 --- a/pom.xml +++ b/pom.xml @@ -10,6 +10,44 @@ pom raven-all Java Raven client and loggers. + http://sentry.readthedocs.org/en/latest/client/index.html + + + + BSD New + http://opensource.org/licenses/BSD-3-Clause + repo + + + + + git@github.com:kencochrane/raven-java.git + scm:git:git://github.com/kencochrane/raven-java.git + scm:git:git@github.com:kencochrane/raven-java.git + + + + + kencochrane + Ken Cochrane + kencochrane+maven@gmail.com + + + roambe + Kevin Wetzels + kevin@roam.be + + + zeeg + David Cramer + contact@email.com + + + griphiam + Mark Philpot + contact@email.com + + raven diff --git a/raven/src/main/java/net/kencochrane/raven/Transport.java b/raven/src/main/java/net/kencochrane/raven/Transport.java index 10ecb1dab33..3a80b06341d 100644 --- a/raven/src/main/java/net/kencochrane/raven/Transport.java +++ b/raven/src/main/java/net/kencochrane/raven/Transport.java @@ -230,8 +230,8 @@ protected HttpURLConnection getConnection() throws IOException { } /** - * A naive HTTPS transport layer, useful in case you set up your own Sentry instance with self-signed - * certificates and don't want to add the certificate to your truststore (you really should though). + * A naive HTTPS transport layer, useful in case you're using wildcard SSL certificates which + * Java doesn't handle that well. */ public static class NaiveHttps extends Http { From 4fea17770ecc36f7a1fd48ad61c918f6a50bad35 Mon Sep 17 00:00:00 2001 From: Cyril Couturier Date: Mon, 21 Jan 2013 15:12:52 +0100 Subject: [PATCH 0096/2152] Change slf4j dependency scope. --- raven-logback/pom.xml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index c3c9360e34a..b2a7b45d775 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -39,11 +39,10 @@ - org.slf4j - slf4j-api - 1.7.2 - test - + org.slf4j + slf4j-api + 1.7.2 + From e60b675d1a6ebe31bcfcf5bdd05f962b843a7ad0 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Wed, 23 Jan 2013 20:45:02 +0100 Subject: [PATCH 0097/2152] Clean up logback README --- raven-logback/README.md | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/raven-logback/README.md b/raven-logback/README.md index 1a02c58341f..e5c0b97d674 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -1,12 +1,7 @@ -## Logback ## -"Logback is intended as a successor to the popular log4j project, picking up where log4j leaves off.[...] -Logback brings a very large number of improvements over log4j, big and small. [...] -Keep in mind that logback is conceptually very similar to log4j as both projects were founded by the same developer. If you are already familiar with log4j, you will quickly feel at home using logback. If you like log4j, you will probably love logback." +# Raven-Logback +A [logback](http://logback.qos.ch/) appender passing messages along to [Sentry](http://www.getsentry.com/). -Logback project: http://logback.qos.ch/ -Reasons to prefer logback over log4j: http://logback.qos.ch/reasonsToSwitch.html - -## Maven dependencies ## +## Maven dependencies ````xml @@ -24,7 +19,7 @@ Reasons to prefer logback over log4j: http://logback.qos.ch/reasonsToSwitch.html net.kencochrane raven-logback - 1.0-SNAPSHOT + 2.0-SNAPSHOT ```` @@ -50,8 +45,8 @@ public class Example { } ```` -## Appender configuration ## -Example of configuration in src/main/resources/logback.xml +## Appender configuration +Example of configuration in src/test/resources/sentryappender.logback.xml ````xml From 46868e1b5b35346a2315b2ea44e2538839466e6a Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Wed, 23 Jan 2013 20:45:48 +0100 Subject: [PATCH 0098/2152] Update raven-logback pom to use parent pom 2.0-SNAPSHOT --- raven-logback/pom.xml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index e0f0b77218a..932f1a7a2b2 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -7,7 +7,7 @@ net.kencochrane raven-all - 1.0-SNAPSHOT + 2.0-SNAPSHOT raven-logback jar @@ -40,11 +40,10 @@ org.slf4j - slf4j-api - 1.7.2 - - ->>>>>>> 7b2ec58b05074fbb4d39a4888a93a8eb9b02d7d9 + slf4j-api + 1.7.2 + + From 1c301229187c61c96d92d07335e50c0732d61b1b Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Wed, 23 Jan 2013 20:56:10 +0100 Subject: [PATCH 0099/2152] Remove "encoder" property from logback.SentryAppender --- .../raven/logback/SentryAppender.java | 38 +++++++------------ .../sentryappender-no-dsn.logback.xml | 3 -- .../test/resources/sentryappender.logback.xml | 3 -- 3 files changed, 14 insertions(+), 30 deletions(-) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index d4d9985cc67..f46838bb184 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -1,18 +1,17 @@ package net.kencochrane.raven.logback; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import net.kencochrane.raven.Client; -import net.kencochrane.raven.SentryDsn; -import net.kencochrane.raven.spi.JSONProcessor; -import net.kencochrane.raven.spi.RavenMDC; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.IThrowableProxy; import ch.qos.logback.classic.spi.ThrowableProxy; import ch.qos.logback.core.AppenderBase; -import ch.qos.logback.core.encoder.Encoder; +import net.kencochrane.raven.Client; +import net.kencochrane.raven.SentryDsn; +import net.kencochrane.raven.spi.JSONProcessor; +import net.kencochrane.raven.spi.RavenMDC; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * Logback appender that will send messages to Sentry. @@ -25,7 +24,6 @@ public class SentryAppender extends AppenderBase { protected Client client; protected boolean messageCompressionEnabled = true; private List jsonProcessors = Collections.emptyList(); - private Encoder encoder; public SentryAppender() { initMDC(); @@ -59,10 +57,9 @@ public void setMessageCompressionEnabled(boolean messageCompressionEnabled) { /** * Set a comma-separated list of fully qualified class names of * JSONProcessors to be used. - * - * @param setting - * a comma-separated list of fully qualified class names of - * JSONProcessors + * + * @param setting a comma-separated list of fully qualified class names of + * JSONProcessors */ public void setJsonProcessors(String setting) { this.jsonProcessors = loadJSONProcessors(setting); @@ -119,9 +116,9 @@ protected void append(ILoggingEvent event) { String message = event.getMessage(); String logger = event.getLoggerName(); int level = event.getLevel().toInt() / 1000; // Need to divide by - // 1000 to keep - // consistent with - // sentry + // 1000 to keep + // consistent with + // sentry String culprit = event.getLoggerName(); IThrowableProxy throwable = event.getThrowableProxy(); @@ -178,11 +175,4 @@ public static void initMDC() { RavenMDC.setInstance(new LogbackMDC()); } - public Encoder getEncoder() { - return encoder; - } - - public void setEncoder(Encoder encoder) { - this.encoder = encoder; - } } diff --git a/raven-logback/src/test/resources/sentryappender-no-dsn.logback.xml b/raven-logback/src/test/resources/sentryappender-no-dsn.logback.xml index 4b8ace1e590..3e382054f3d 100644 --- a/raven-logback/src/test/resources/sentryappender-no-dsn.logback.xml +++ b/raven-logback/src/test/resources/sentryappender-no-dsn.logback.xml @@ -8,9 +8,6 @@ - - No Sentry DSN Test: %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - diff --git a/raven-logback/src/test/resources/sentryappender.logback.xml b/raven-logback/src/test/resources/sentryappender.logback.xml index 825ccbe3fa2..cee81a13f3b 100644 --- a/raven-logback/src/test/resources/sentryappender.logback.xml +++ b/raven-logback/src/test/resources/sentryappender.logback.xml @@ -9,9 +9,6 @@ ${SENTRY_DSN} - - No Sentry DSN Test: %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - From e240d339d1b49d7e90ebe25ed6512b951b082208 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Wed, 23 Jan 2013 20:56:32 +0100 Subject: [PATCH 0100/2152] Update README --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2373f44bfd8..b7f6201dbfb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Raven-Java -Raven-Java is a Java client for [Sentry](https://www.getsentry.com/). Besides a regular client you can use within your application code, Raven-Java also provides the `raven-log4j` package you can use to send logging to Sentry via [log4j](http://logging.apache.org/log4j/). +Raven-Java is a Java client for [Sentry](https://www.getsentry.com/). Besides a regular client you can use within your application code, Raven-Java also provides the `raven-log4j` package you can use to send logging to Sentry via [log4j](http://logging.apache.org/log4j/) and the `raven-logback` package to do the same through [logback](http://logback.qos.ch/). Raven-Java supports both HTTP(S) and UDP transport of messages. @@ -75,6 +75,16 @@ But even if you use a properties file to configure log4j, you can log asynchrono * * * +## Using the logback appender +Make sure to use the `raven-logback` artifact and add something like the following to your logback configuration: + + + http://public:private@host:port/project + + +* * * + ## Client configuration Client configuration is completely driven through the Sentry DSN. The DSN you can copy-paste from your Sentry instance looks like this: @@ -194,12 +204,21 @@ Or if you simply want to log to Sentry from Log4J: 2.0-SNAPSHOT +Or Logback: + + + net.kencochrane + raven-logback + 2.0-SNAPSHOT + + * * * ## History - 2.0-SNAPSHOT - Version increment to reduce confusion about releases + - Added Logback appender (thanks to [ccouturi](https://github.com/ccouturi)) - 1.0 - Rewrite - Support tags @@ -233,3 +252,4 @@ Or if you simply want to log to Sentry from Log4J: - David Cramer (@zeeg) - Mark Philpot (@griphiam) - Brad Chen (@vvasabi) +- @ccouturi From 1d099ce911d26ce63d58e9b09db33f7606d93d32 Mon Sep 17 00:00:00 2001 From: Ian Walter Date: Tue, 5 Feb 2013 17:32:24 -0500 Subject: [PATCH 0101/2152] Fixed code samples --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b7f6201dbfb..90efdab925c 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ public class Example { // The DSN from Sentry: "http://public:private@host:port/1" String rawDsn = args[0]; SentryDsn dsn = SentryDsn.build(rawDsn); - Client = new Client(dsn); + Client client = new Client(dsn); client.captureMessage("Hello from Raven-Java!"); } @@ -48,7 +48,7 @@ public class Example { public static void main(String[] args) { // DSN is determined by the client from system properties or env - Client = new Client(); + Client client = new Client(); client.captureMessage("Hello from Raven-Java!"); } From 796280f67e7aa0537195db9adf15eb009e4ebc75 Mon Sep 17 00:00:00 2001 From: Kevin Wetzels Date: Sat, 9 Feb 2013 12:16:28 +0100 Subject: [PATCH 0102/2152] Update README.md Make sure to actually use the logback appender in your config. --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 90efdab925c..2d810b3504f 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,14 @@ Make sure to use the `raven-logback` artifact and add something like the followi http://public:private@host:port/project +And don't forget to use the appender. For example: + + + + + + + * * * ## Client configuration From e04a4c19761a53c4cfd2cfab4da1db29c2f4dd99 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 3 Apr 2013 17:23:48 +0100 Subject: [PATCH 0103/2152] Create POJOs to make event creation easier --- .../kencochrane/raven/event/LoggedEvent.java | 146 ++++++++++++++++++ .../event/interfaces/SentryInterface.java | 28 ++++ 2 files changed, 174 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java create mode 100644 raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryInterface.java diff --git a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java b/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java new file mode 100644 index 00000000000..1e724ca5238 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java @@ -0,0 +1,146 @@ +package net.kencochrane.raven.event; + +import net.kencochrane.raven.event.interfaces.SentryInterface; + +import java.util.*; + +/** + * Plain Old Java Object describing an event that will be sent to a Sentry instance. + *

    + * For security purposes, an event should be created from an {@link EventBuilder} only, and be completely immutable + * once it has been fully generated. + *

    + *

    + * Notes to developers: + *

      + *
    • + * In order to ensure that a LoggedEvent can't be modified externally, the setters should have a package visibility. + *
    • + *
    • + * A proper immutable Object should only contain immutable Objects and primitives, this must be ensured before making + * publishing the LoggedEvent. + *
    • + *
    + *

    + */ +public class LoggedEvent { + private static final int MAX_MESSAGE_LENGTH = 1000; + private final UUID eventId; + private String message; + // TODO: Rely on Joda time instead? (making it completely immutable!) Or wait for Java8 and JSR-310? + private Date timestamp; + private Level level; + private String logger; + private String platform; + private String culprit; + private Map> tags = new HashMap>(); + private String serverName; + private String checksum; + private Map sentryInterfaces = new HashMap(); + + /** + * Creates a new LoggedEvent (should be called only through {@link EventBuilder} with the specified identifier. + * + * @param eventId unique identifier of the event. + */ + LoggedEvent(UUID eventId) { + if (eventId == null) + throw new IllegalArgumentException("The eventId can't be null"); + this.eventId = eventId; + } + + public UUID getEventId() { + return eventId; + } + + public String getMessage() { + return message; + } + + void setMessage(String message) { + // TODO: Keep the message whatever the size is, and let the marshaller take care of cutting long messages? + if (message != null && message.length() > MAX_MESSAGE_LENGTH) + throw new IllegalArgumentException("A message can't be larger than " + MAX_MESSAGE_LENGTH + " characters"); + this.message = message; + } + + public Date getTimestamp() { + return timestamp; + } + + void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + public String getLogger() { + return logger; + } + + void setLogger(String logger) { + this.logger = logger; + } + + public Level getLevel() { + return level; + } + + void setLevel(Level level) { + this.level = level; + } + + public String getCulprit() { + return culprit; + } + + void setCulprit(String culprit) { + this.culprit = culprit; + } + + public String getChecksum() { + return checksum; + } + + void setChecksum(String checksum) { + this.checksum = checksum; + } + + public Map> getTags() { + return tags; + } + + void setTags(Map> tags) { + this.tags = tags; + } + + public String getPlatform() { + return platform; + } + + void setPlatform(String platform) { + this.platform = platform; + } + + public String getServerName() { + return serverName; + } + + void setServerName(String serverName) { + this.serverName = serverName; + } + + public Map getSentryInterfaces() { + return sentryInterfaces; + } + + void setSentryInterfaces(Map sentryInterfaces) { + this.sentryInterfaces = sentryInterfaces; + } + + public static enum Level { + FATAL, + ERROR, + WARNING, + INFO, + DEBUG + } +} diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryInterface.java new file mode 100644 index 00000000000..f9c0f0a6d67 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryInterface.java @@ -0,0 +1,28 @@ +package net.kencochrane.raven.event.interfaces; + +import java.util.Map; + +public interface SentryInterface { + /** + * Gets the unique name of the interface. + * + * @return name of the interface. + */ + String getInterfaceName(); + + /** + * Gets the content of the interface as a Map. + *

    + * The values contained in the Map can only be: + *

      + *
    • A {@code String}
    • + *
    • A wrapped primitive
    • + *
    • A {@code Collection}
    • + *
    • A {@code Map} where {@code V} is one of the types currently listed
    • + *
    + *

    + * + * @return the content of the interface. + */ + Map getInterfaceContent(); +} From 68b11bd582d9696fb5f549834fbfcd57ebd45a5b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 3 Apr 2013 17:24:22 +0100 Subject: [PATCH 0104/2152] Create an EventBuilder to create immutable events --- .../kencochrane/raven/event/EventBuilder.java | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java new file mode 100644 index 00000000000..2e0cc60c494 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -0,0 +1,167 @@ +package net.kencochrane.raven.event; + +import net.kencochrane.raven.event.interfaces.SentryInterface; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.*; +import java.util.zip.CRC32; +import java.util.zip.Checksum; + +/** + * Builder to assist the creation of {@link LoggedEvent}s. + */ +public class EventBuilder { + /** + * Default log level if it isn't set manually. + */ + public static final LoggedEvent.Level DEFAULT_LEVEL = LoggedEvent.Level.ERROR; + /** + * Default logger if it isn't set manually. + */ + public static final String DEFAULT_LOGGER = "root"; + /** + * Default platform if it isn't set manually. + */ + public static final String DEFAULT_PLATFORM = "java"; + /** + * Default hostname if it isn't set manually (or can't be determined). + */ + public static final String DEFAULT_HOSTNAME = "unavailable"; + /** + * Default log level if it isn't provided (either directly or through an exception). + */ + public static final String DEFAULT_MESSAGE = "(empty)"; + private final LoggedEvent event; + private boolean alreadyBuilt = false; + + public EventBuilder() { + this(UUID.randomUUID()); + } + + public EventBuilder(UUID eventId) { + this.event = new LoggedEvent(eventId); + } + + private static String calculateChecksum(String message) { + // TODO: Large strings will be poorly handled + byte[] bytes = message.getBytes(); + Checksum checksum = new CRC32(); + checksum.update(bytes, 0, bytes.length); + return String.valueOf(checksum.getValue()); + } + + private static String getHostname() { + try { + // TODO: Cache this info + return InetAddress.getLocalHost().getCanonicalHostName(); + } catch (UnknownHostException e) { + return DEFAULT_HOSTNAME; + } + } + + private static void autoSetMissingValues(LoggedEvent event) { + // Ensure that an actual message is set + if (event.getMessage() == null) + event.setMessage(DEFAULT_MESSAGE); + + // Ensure that a timestamp is set (to now at least!) + if (event.getTimestamp() == null) + event.setTimestamp(new Date()); + + // Ensure that a log level is set + if (event.getLevel() == null) + event.setLevel(DEFAULT_LEVEL); + + // Ensure that a logger is set + if (event.getLogger() == null) + event.setLogger(DEFAULT_LOGGER); + + // Ensure that a platform is set + if (event.getPlatform() == null) + event.setPlatform(DEFAULT_PLATFORM); + + // Ensure that a hostname is set + if (event.getServerName() == null) + event.setServerName(getHostname()); + + // TODO: As the server can figure that itself, wouldn't it be better to assume that if no checksum is given + // the server will be in charge of generating it? + + // Ensure that a checksum is present + if (event.getChecksum() == null) + event.setChecksum(calculateChecksum(event.getMessage())); + } + + private static void makeImmutable(LoggedEvent event) { + // Make the tags unmodifiable + Map> unmodifiablesTags = new HashMap>(event.getTags().size()); + for (Map.Entry> tag : event.getTags().entrySet()) { + unmodifiablesTags.put(tag.getKey(), Collections.unmodifiableSet(tag.getValue())); + } + event.setTags(Collections.unmodifiableMap(unmodifiablesTags)); + + // Make the SentryInterfaces unmodifiable + event.setSentryInterfaces(Collections.unmodifiableMap(event.getSentryInterfaces())); + } + + public EventBuilder addTag(String tagKey, String tagValue) { + Set tagValues = event.getTags().get(tagKey); + if (tagValues == null) { + tagValues = new HashSet(); + event.getTags().put(tagKey, tagValues); + } + tagValues.add(tagValue); + + return this; + } + + public EventBuilder addSentryInterface(SentryInterface sentryInterface) { + event.getSentryInterfaces().put(sentryInterface.getInterfaceName(), sentryInterface); + return this; + } + + /** + * Manually set the checksum for the current event. + *

    + * It's recommended to use instead {@link #generateChecksume(String)} which will avoid any problem with + * the checksum generation. + *

    + * + * @param checksum + * @return + */ + public EventBuilder setChecksum(String checksum) { + event.setChecksum(checksum); + return this; + } + + /** + * + * @param contentToCheckSum + * @return + */ + public EventBuilder generateChecksum(String contentToCheckSum) { + return setChecksum(calculateChecksum(contentToCheckSum)); + } + + /** + * Finalise the {@link LoggedEvent} and returns it. + *

    + * This operations will automatically set the missing values and make the mutable values immutable. + *

    + * + * @return an immutable event. + */ + public LoggedEvent build() { + if (alreadyBuilt) + throw new IllegalStateException("A message can't be built twice"); + + autoSetMissingValues(event); + makeImmutable(event); + + // Lock it only when everything has been set, in case of exception it should be possible to try to build again. + alreadyBuilt = true; + return event; + } +} From 07312d20e5ee8d74d2e2695c0e2edfda73842d17 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 3 Apr 2013 17:24:40 +0100 Subject: [PATCH 0105/2152] Create basic implementation of SentryInterfaces --- .../event/interfaces/ExceptionInterface.java | 31 ++++++++ .../event/interfaces/MessageInterface.java | 35 +++++++++ .../event/interfaces/StackTraceInterface.java | 72 +++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java create mode 100644 raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java create mode 100644 raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java new file mode 100644 index 00000000000..27cc193ce5f --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java @@ -0,0 +1,31 @@ +package net.kencochrane.raven.event.interfaces; + +import java.util.HashMap; +import java.util.Map; + +public class ExceptionInterface implements SentryInterface { + private static final String EXCEPTION_INTERFACE = "sentry.interface.Exception"; + private static final String TYPE_PARAMETER = "type"; + private static final String VALUE_PARAMETER = "value"; + private static final String MODULE_PARAMETER = "module"; + private final Throwable throwable; + + public ExceptionInterface(Throwable throwable) { + this.throwable = throwable; + } + + @Override + public String getInterfaceName() { + return EXCEPTION_INTERFACE; + } + + @Override + public Map getInterfaceContent() { + Map content = new HashMap(); + content.put(TYPE_PARAMETER, throwable.getClass().getSimpleName()); + // TODO: The message can be empty here, should something be done? + content.put(VALUE_PARAMETER, throwable.getMessage()); + content.put(MODULE_PARAMETER, throwable.getClass().getPackage().getName()); + return content; + } +} diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java new file mode 100644 index 00000000000..3a1ea28eb05 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java @@ -0,0 +1,35 @@ +package net.kencochrane.raven.event.interfaces; + +import java.util.*; + +public class MessageInterface implements SentryInterface { + private static final String MESSAGE_INTERFACE = "sentry.interfaces.Message"; + private static final String MESSAGE_PARAMETER = "message"; + private static final String PARAMS_PARAMETER = "params"; + private final String message; + private final List params; + + public MessageInterface(String message) { + this(message, Collections.emptyList()); + } + + public MessageInterface(String message, List params) { + this.message = message; + this.params = Collections.unmodifiableList(new ArrayList(params)); + } + + @Override + public String getInterfaceName() { + return MESSAGE_INTERFACE; + } + + @Override + public Map getInterfaceContent() { + Map content = new HashMap(); + content.put(MESSAGE_PARAMETER, message); + if (!params.isEmpty()) + content.put(PARAMS_PARAMETER, params); + + return content; + } +} diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java new file mode 100644 index 00000000000..dbb70dd168e --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java @@ -0,0 +1,72 @@ +package net.kencochrane.raven.event.interfaces; + +import java.util.*; + +public class StackTraceInterface implements SentryInterface { + private static final String STACKTRACE_INTERFACE = "sentry.interface.Stacktrace"; + private static final String FRAMES_PARAMETER = "frames"; + private static final String FILENAME_PARAMETER = "filename"; + private static final String FUNCTION_PARAMETER = "function"; + private static final String MODULE_PARAMETER = "module"; + private static final String LINE_NO_PARAMETER = "lineno"; + private static final String ABSOLUTE_PATH_PARAMETER = "abs_path"; + private static final String CONTEXT_LINE_PARAMETER = "context_line"; + private static final String PRE_CONTEXT_PARAMETER = "pre_context"; + private static final String POST_CONTEXT_PARAMETER = "post_context"; + private static final String IN_APP_PARAMETER = "in_app"; + private static final String VARIABLES_PARAMETER = "vars"; + private final Throwable throwable; + + public StackTraceInterface(Throwable throwable) { + this.throwable = throwable; + } + + /** + * Create a fake frame to allow chained exceptions. + * + * @param throwable Exception for which a fake frame should be created + * @return a fake frame allowing to chain exceptions smoothly in Sentry. + */ + private static Map createFakeFrame(Throwable throwable) { + Map fakeFrame = new HashMap(); + String message = "Caused by: " + throwable.getClass().getName(); + if (throwable.getMessage() != null) + message += " (\"" + throwable.getMessage() + "\")"; + fakeFrame.put(FILENAME_PARAMETER, message); + fakeFrame.put(LINE_NO_PARAMETER, -1); + return fakeFrame; + } + + /** + * Creates a single frame based on a {@code StackTraceElement}. + * + * @param stackTraceElement current frame in the stackTrace. + * @return frame extracted from the stackTraceElement. + */ + private static Map createFrame(StackTraceElement stackTraceElement) { + Map currentFrame = new HashMap(); + currentFrame.put(FILENAME_PARAMETER, stackTraceElement.getClassName()); + currentFrame.put(FUNCTION_PARAMETER, stackTraceElement.getMethodName()); + currentFrame.put(LINE_NO_PARAMETER, stackTraceElement.getLineNumber()); + return currentFrame; + } + + @Override + public String getInterfaceName() { + return STACKTRACE_INTERFACE; + } + + @Override + public Map getInterfaceContent() { + List> frames = new LinkedList>(); + Throwable currentThrowable = throwable; + while (currentThrowable != null) { + frames.add(createFakeFrame(currentThrowable)); + for (StackTraceElement stackTraceElement : throwable.getStackTrace()) { + frames.add(createFrame(stackTraceElement)); + } + currentThrowable = currentThrowable.getCause(); + } + return Collections.singletonMap(FRAMES_PARAMETER, frames); + } +} From 4225977878ae08cb9f7bece308b1b60ec7eb09e2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 3 Apr 2013 22:36:11 +0100 Subject: [PATCH 0106/2152] Add a naive connection to which the event is sent --- .../java/net/kencochrane/raven/Client.java | 22 +++++++++++++++++++ .../raven/connection/Connection.java | 7 ++++++ 2 files changed, 29 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/connection/Connection.java diff --git a/raven/src/main/java/net/kencochrane/raven/Client.java b/raven/src/main/java/net/kencochrane/raven/Client.java index b3bde65af9b..4f3eb313ba4 100644 --- a/raven/src/main/java/net/kencochrane/raven/Client.java +++ b/raven/src/main/java/net/kencochrane/raven/Client.java @@ -1,5 +1,8 @@ package net.kencochrane.raven; +import net.kencochrane.raven.connection.Connection; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.spi.JSONProcessor; import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang.time.DateFormatUtils; @@ -75,6 +78,12 @@ public interface Default { */ protected Transport transport; + /** + * The connection to the Sentry server + */ + //TODO: Set the connection + private Connection connection; + /** * Whether messages should be compressed or not - defaults to true. */ @@ -181,6 +190,19 @@ public void setMessageCompressionEnabled(boolean messageCompressionEnabled) { this.messageCompressionEnabled = messageCompressionEnabled; } + /** + * Gets the final information necessary for the event, and submit it to Sentry. + * + * @param eventBuilder pre-set event builder. + * @return the unique identifier of the newly created event. + */ + public UUID processEvent(EventBuilder eventBuilder) { + //TODO: grab more information that couldn't be provided by someone else? + LoggedEvent event = eventBuilder.build(); + connection.send(event); + return event.getEventId(); + } + public String captureMessage(String msg) { return captureMessage(msg, null, null, null, null); } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/Connection.java b/raven/src/main/java/net/kencochrane/raven/connection/Connection.java new file mode 100644 index 00000000000..fb7e7471f6e --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/connection/Connection.java @@ -0,0 +1,7 @@ +package net.kencochrane.raven.connection; + +import net.kencochrane.raven.event.LoggedEvent; + +public interface Connection { + void send(LoggedEvent event); +} From 3e5b203c3bc409851ce4fdcd2d6e883546c7e50d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 00:35:15 +0100 Subject: [PATCH 0107/2152] Add methods to set the culprit of an event --- .../kencochrane/raven/event/EventBuilder.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 2e0cc60c494..79d4bc49136 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -105,6 +105,20 @@ private static void makeImmutable(LoggedEvent event) { event.setSentryInterfaces(Collections.unmodifiableMap(event.getSentryInterfaces())); } + private static String determineCulprit(Throwable throwable) { + Throwable currentThrowable = throwable; + String culprit = null; + while (currentThrowable != null) { + StackTraceElement[] elements = currentThrowable.getStackTrace(); + if (elements.length > 0) { + StackTraceElement trace = elements[0]; + culprit = trace.getClassName() + "." + trace.getMethodName(); + } + currentThrowable = currentThrowable.getCause(); + } + return culprit; + } + public EventBuilder addTag(String tagKey, String tagValue) { Set tagValues = event.getTags().get(tagKey); if (tagValues == null) { @@ -116,6 +130,15 @@ public EventBuilder addTag(String tagKey, String tagValue) { return this; } + public EventBuilder setCulprit(Throwable throwable) { + return setCulprit(determineCulprit(throwable)); + } + + public EventBuilder setCulprit(String culprit) { + event.setCulprit(culprit); + return this; + } + public EventBuilder addSentryInterface(SentryInterface sentryInterface) { event.getSentryInterfaces().put(sentryInterface.getInterfaceName(), sentryInterface); return this; From 1e92b87d6b60b8b81028b88f8f6e3de787c1a429 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 00:37:37 +0100 Subject: [PATCH 0108/2152] The only id in event has to be an eventId Do not name the id in an event eventId, it's redundant --- .../main/java/net/kencochrane/raven/Client.java | 2 +- .../net/kencochrane/raven/event/LoggedEvent.java | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Client.java b/raven/src/main/java/net/kencochrane/raven/Client.java index 4f3eb313ba4..4a405b69d5e 100644 --- a/raven/src/main/java/net/kencochrane/raven/Client.java +++ b/raven/src/main/java/net/kencochrane/raven/Client.java @@ -200,7 +200,7 @@ public UUID processEvent(EventBuilder eventBuilder) { //TODO: grab more information that couldn't be provided by someone else? LoggedEvent event = eventBuilder.build(); connection.send(event); - return event.getEventId(); + return event.getId(); } public String captureMessage(String msg) { diff --git a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java b/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java index 1e724ca5238..37785f81047 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java +++ b/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java @@ -25,7 +25,7 @@ */ public class LoggedEvent { private static final int MAX_MESSAGE_LENGTH = 1000; - private final UUID eventId; + private final UUID id; private String message; // TODO: Rely on Joda time instead? (making it completely immutable!) Or wait for Java8 and JSR-310? private Date timestamp; @@ -41,16 +41,16 @@ public class LoggedEvent { /** * Creates a new LoggedEvent (should be called only through {@link EventBuilder} with the specified identifier. * - * @param eventId unique identifier of the event. + * @param id unique identifier of the event. */ - LoggedEvent(UUID eventId) { - if (eventId == null) - throw new IllegalArgumentException("The eventId can't be null"); - this.eventId = eventId; + LoggedEvent(UUID id) { + if (id == null) + throw new IllegalArgumentException("The id can't be null"); + this.id = id; } - public UUID getEventId() { - return eventId; + public UUID getId() { + return id; } public String getMessage() { From 4d99d5ef79a9dd95c537d05184e84a0648375e49 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 00:38:28 +0100 Subject: [PATCH 0109/2152] Remove comments about generateChecksum Sentry's checksum system should be prefered to a custom checksum --- .../java/net/kencochrane/raven/event/EventBuilder.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 79d4bc49136..5fdb6ca1423 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -87,10 +87,6 @@ private static void autoSetMissingValues(LoggedEvent event) { // TODO: As the server can figure that itself, wouldn't it be better to assume that if no checksum is given // the server will be in charge of generating it? - - // Ensure that a checksum is present - if (event.getChecksum() == null) - event.setChecksum(calculateChecksum(event.getMessage())); } private static void makeImmutable(LoggedEvent event) { @@ -147,8 +143,7 @@ public EventBuilder addSentryInterface(SentryInterface sentryInterface) { /** * Manually set the checksum for the current event. *

    - * It's recommended to use instead {@link #generateChecksume(String)} which will avoid any problem with - * the checksum generation. + * It's recommended to rely instead on the checksum system provided by Sentry. *

    * * @param checksum From b1395e51f87ecd6f9d4cb1fa0dbde18b22da0fc9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 10:17:55 +0100 Subject: [PATCH 0110/2152] Fix typo in interfaces name --- .../kencochrane/raven/event/interfaces/ExceptionInterface.java | 2 +- .../kencochrane/raven/event/interfaces/StackTraceInterface.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java index 27cc193ce5f..7c7a3b72617 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java @@ -4,7 +4,7 @@ import java.util.Map; public class ExceptionInterface implements SentryInterface { - private static final String EXCEPTION_INTERFACE = "sentry.interface.Exception"; + private static final String EXCEPTION_INTERFACE = "sentry.interfaces.Exception"; private static final String TYPE_PARAMETER = "type"; private static final String VALUE_PARAMETER = "value"; private static final String MODULE_PARAMETER = "module"; diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java index dbb70dd168e..2a0d5ae4879 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java @@ -3,7 +3,7 @@ import java.util.*; public class StackTraceInterface implements SentryInterface { - private static final String STACKTRACE_INTERFACE = "sentry.interface.Stacktrace"; + private static final String STACKTRACE_INTERFACE = "sentry.interfaces.Stacktrace"; private static final String FRAMES_PARAMETER = "frames"; private static final String FILENAME_PARAMETER = "filename"; private static final String FUNCTION_PARAMETER = "function"; From ec9ec094a9cf74b5260400f626de8abcae5c8a5d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 11:06:27 +0100 Subject: [PATCH 0111/2152] Add a tool to encode LoggedEvent over a Stream --- .../connection/encoder/SimpleJsonEncoder.java | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/connection/encoder/SimpleJsonEncoder.java diff --git a/raven/src/main/java/net/kencochrane/raven/connection/encoder/SimpleJsonEncoder.java b/raven/src/main/java/net/kencochrane/raven/connection/encoder/SimpleJsonEncoder.java new file mode 100644 index 00000000000..493172b0ab1 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/connection/encoder/SimpleJsonEncoder.java @@ -0,0 +1,158 @@ +package net.kencochrane.raven.connection.encoder; + +import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.event.interfaces.SentryInterface; +import org.apache.commons.lang.time.DateFormatUtils; +import org.json.simple.JSONObject; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.Date; +import java.util.Map; +import java.util.UUID; + +/** + * Encoder allowing to transform a simple {@link LoggedEvent} into a JSON String sent over a stream. + */ +public class SimpleJsonEncoder { + /** + * Hexadecimal string representing a uuid4 value. + */ + public static final String EVENT_ID = "event_id"; + /** + * User-readable representation of this event. + */ + public static final String MESSAGE = "message"; + /** + * Indicates when the logging record was created. + */ + public static final String TIMESTAMP = "timestamp"; + /** + * The record severity. + */ + public static final String LEVEL = "level"; + /** + * The name of the logger which created the record. + */ + public static final String LOGGER = "logger"; + /** + * A string representing the platform the client is submitting from. + */ + public static final String PLATFORM = "platform"; + /** + * Function call which was the primary perpetrator of this event. + */ + public static final String CULPRIT = "culprit"; + /** + * A map or list of tags for this event. + */ + public static final String TAGS = "tags"; + /** + * Identifies the host client from which the event was recorded. + */ + public static final String SERVER_NAME = "server_name"; + /** + * A list of relevant modules and their versions. + */ + public static final String MODULES = "modules"; + /** + * An arbitrary mapping of additional metadata to store with the event. + */ + public static final String EXTRA = "extra"; + /** + * Checksum for the event, allowing to group events with a similar checksum. + */ + public static final String CHECKSUM = "checksum"; + /** + * Maximum length for a message. + */ + public static final int MAX_MESSAGE_LENGTH = 1000; + + /** + * Encodes an event as a JSON string and sends it through an {@code OutputStream}. + * + * @param event event to encode as a JSON string. + * @param destination destination stream. + * @throws IOException occurs when the serialisation to JSON failed. + */ + public void encodeEvent(LoggedEvent event, OutputStream destination) throws IOException { + JSONObject jsonObject = encodeToJSONObject(event); + jsonObject.writeJSONString(new OutputStreamWriter(destination)); + } + + @SuppressWarnings("unchecked") + private JSONObject encodeToJSONObject(LoggedEvent event) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put(EVENT_ID, formatId(event.getId())); + jsonObject.put(MESSAGE, formatMessage(event.getMessage())); + jsonObject.put(TIMESTAMP, formatTimestamp(event.getTimestamp())); + jsonObject.put(LEVEL, formatLevel(event.getLevel())); + jsonObject.put(LOGGER, event.getLogger()); + jsonObject.put(PLATFORM, event.getPlatform()); + jsonObject.put(CULPRIT, event.getCulprit()); + jsonObject.put(TAGS, event.getTags()); + jsonObject.put(SERVER_NAME, event.getServerName()); + jsonObject.put(CHECKSUM, event.getChecksum()); + + for (Map.Entry sentryInterfaceEntry : event.getSentryInterfaces().entrySet()) { + //TODO: Usually we would look for marshallers for each interface type. + jsonObject.put(sentryInterfaceEntry.getKey(), sentryInterfaceEntry.getValue()); + } + + return jsonObject; + } + + /** + * Formats a message, ensuring that the maximum length {@link #MAX_MESSAGE_LENGTH} isn't reached. + * + * @param message message to format. + * @return formatted message (shortened if necessary). + */ + private String formatMessage(String message) { + return (message.length() > MAX_MESSAGE_LENGTH) ? message.substring(0, MAX_MESSAGE_LENGTH) : message; + } + + /** + * Formats the {@code UUID} to send only the 32 necessary characters. + * + * @param id uuid to format. + * @return a {@code UUID} stripped from the "-" characters. + */ + private String formatId(UUID id) { + return id.toString().replaceAll("-", ""); + } + + /** + * Formats a log level into one of the accepted string representation of a log level. + * + * @param level log level to format. + * @return log level as a String. + */ + private String formatLevel(LoggedEvent.Level level) { + switch (level) { + case DEBUG: + return "debug"; + case FATAL: + return "fatal"; + case WARNING: + return "warning"; + case INFO: + return "info"; + case ERROR: + default: + return "error"; + } + } + + /** + * Formats a timestamp in the ISO-8601 format without timezone. + * + * @param timestamp date to format. + * @return timestamp as a formatted String. + */ + private String formatTimestamp(Date timestamp) { + return DateFormatUtils.formatUTC(timestamp, + DateFormatUtils.ISO_DATETIME_FORMAT.getPattern()); + } +} From c8bf8562d7402a76816f2c7bec807bce7541bfcb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 11:11:07 +0100 Subject: [PATCH 0112/2152] The message length is handled by the encoder --- .../main/java/net/kencochrane/raven/event/LoggedEvent.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java b/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java index 37785f81047..bf25acd9453 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java +++ b/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java @@ -24,7 +24,6 @@ *

    */ public class LoggedEvent { - private static final int MAX_MESSAGE_LENGTH = 1000; private final UUID id; private String message; // TODO: Rely on Joda time instead? (making it completely immutable!) Or wait for Java8 and JSR-310? @@ -58,9 +57,6 @@ public String getMessage() { } void setMessage(String message) { - // TODO: Keep the message whatever the size is, and let the marshaller take care of cutting long messages? - if (message != null && message.length() > MAX_MESSAGE_LENGTH) - throw new IllegalArgumentException("A message can't be larger than " + MAX_MESSAGE_LENGTH + " characters"); this.message = message; } From 88400675e0cfd3c598c234073bafda50db748a00 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 11:17:40 +0100 Subject: [PATCH 0113/2152] Document LoggedEvent --- .../kencochrane/raven/event/LoggedEvent.java | 73 ++++++++++++++----- 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java b/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java index bf25acd9453..a508b7f85d6 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java +++ b/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java @@ -24,17 +24,50 @@ *

    */ public class LoggedEvent { + /** + * Unique identifier of the event. + */ private final UUID id; + /** + * User-readable representation of this event. + */ private String message; + /** + * Exact time when the logging occurred. + */ // TODO: Rely on Joda time instead? (making it completely immutable!) Or wait for Java8 and JSR-310? private Date timestamp; + /** + * The record severity. + */ private Level level; + /** + * The name of the logger which created the record. + */ private String logger; + /** + * A string representing the currently used platform (java/python). + */ private String platform; + /** + * Function call which was the primary perpetrator of this event. + */ private String culprit; + /** + * A map or list of tags for this event. + */ private Map> tags = new HashMap>(); + /** + * Identifies the host client from which the event was recorded. + */ private String serverName; + /** + * Checksum for the event, allowing to group events with a similar checksum. + */ private String checksum; + /** + * Additional interfaces for other information and metadata. + */ private Map sentryInterfaces = new HashMap(); /** @@ -68,6 +101,14 @@ void setTimestamp(Date timestamp) { this.timestamp = timestamp; } + public Level getLevel() { + return level; + } + + void setLevel(Level level) { + this.level = level; + } + public String getLogger() { return logger; } @@ -76,12 +117,12 @@ void setLogger(String logger) { this.logger = logger; } - public Level getLevel() { - return level; + public String getPlatform() { + return platform; } - void setLevel(Level level) { - this.level = level; + void setPlatform(String platform) { + this.platform = platform; } public String getCulprit() { @@ -92,14 +133,6 @@ void setCulprit(String culprit) { this.culprit = culprit; } - public String getChecksum() { - return checksum; - } - - void setChecksum(String checksum) { - this.checksum = checksum; - } - public Map> getTags() { return tags; } @@ -108,14 +141,6 @@ void setTags(Map> tags) { this.tags = tags; } - public String getPlatform() { - return platform; - } - - void setPlatform(String platform) { - this.platform = platform; - } - public String getServerName() { return serverName; } @@ -124,6 +149,14 @@ void setServerName(String serverName) { this.serverName = serverName; } + public String getChecksum() { + return checksum; + } + + void setChecksum(String checksum) { + this.checksum = checksum; + } + public Map getSentryInterfaces() { return sentryInterfaces; } From 183debd9fff6ad8f61b092588a04ef57d8f3ea44 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 11:53:34 +0100 Subject: [PATCH 0114/2152] Improve the documentation of EventBuilder --- .../kencochrane/raven/event/EventBuilder.java | 98 ++++++++++++++++--- 1 file changed, 85 insertions(+), 13 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 5fdb6ca1423..c76168c726f 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -35,22 +35,44 @@ public class EventBuilder { private final LoggedEvent event; private boolean alreadyBuilt = false; + /** + * Creates a new EventBuilder to prepare a new {@link LoggedEvent}. + *

    + * Automatically generates the id of the new event. + *

    + */ public EventBuilder() { this(UUID.randomUUID()); } + /** + * Creates a new EventBuilder to prepare a new {@link LoggedEvent}. + * + * @param eventId unique identifier for the new event. + */ public EventBuilder(UUID eventId) { this.event = new LoggedEvent(eventId); } - private static String calculateChecksum(String message) { + /** + * Calculates a checksum for a given string. + * + * @param string string from which a checksum should be obtained + * @return a checksum allowing two events with the same properties to be grouped later. + */ + private static String calculateChecksum(String string) { // TODO: Large strings will be poorly handled - byte[] bytes = message.getBytes(); + byte[] bytes = string.getBytes(); Checksum checksum = new CRC32(); checksum.update(bytes, 0, bytes.length); return String.valueOf(checksum.getValue()); } + /** + * Obtains the current hostname. + * + * @return the current hostname, or {@link #DEFAULT_HOSTNAME} if it couldn't be determined. + */ private static String getHostname() { try { // TODO: Cache this info @@ -60,6 +82,12 @@ private static String getHostname() { } } + /** + * Sets default values for each field that hasn't been provided manually. + * + * @param event currently handled event. + */ + //TODO: Shouldn't this be removed to rely on sentry default settings? private static void autoSetMissingValues(LoggedEvent event) { // Ensure that an actual message is set if (event.getMessage() == null) @@ -84,11 +112,13 @@ private static void autoSetMissingValues(LoggedEvent event) { // Ensure that a hostname is set if (event.getServerName() == null) event.setServerName(getHostname()); - - // TODO: As the server can figure that itself, wouldn't it be better to assume that if no checksum is given - // the server will be in charge of generating it? } + /** + * Ensures that every field in the {@code LoggedEvent} are immutable to avoid confusion later. + * + * @param event event to make immutable. + */ private static void makeImmutable(LoggedEvent event) { // Make the tags unmodifiable Map> unmodifiablesTags = new HashMap>(event.getTags().size()); @@ -101,9 +131,16 @@ private static void makeImmutable(LoggedEvent event) { event.setSentryInterfaces(Collections.unmodifiableMap(event.getSentryInterfaces())); } + /** + * Determines the culprit value for an event based on a {@code Throwable}. + * + * @param throwable throwable caught, responsible of the event. + * @return the name of the method/class responsible for the event, based on the {@code Throwable}. + */ private static String determineCulprit(Throwable throwable) { Throwable currentThrowable = throwable; String culprit = null; + // Attempts to go through each cause, in case the last ones do not provide a stacktrace. while (currentThrowable != null) { StackTraceElement[] elements = currentThrowable.getStackTrace(); if (elements.length > 0) { @@ -115,6 +152,18 @@ private static String determineCulprit(Throwable throwable) { return culprit; } + /** + * Adds a tag to an event. + *

    + * Multiple calls to {@code addTag} allow to have more that one value for a single tag.
    + * This allows to set a tag value in different contexts. + *

    + * + * @param tagKey name of the tag. + * @param tagValue value of the tag. + * @return the current {@code EventBuilder} for chained calls. + */ + //TODO: Check that the tag system works indeed this way. public EventBuilder addTag(String tagKey, String tagValue) { Set tagValues = event.getTags().get(tagKey); if (tagValues == null) { @@ -126,28 +175,50 @@ public EventBuilder addTag(String tagKey, String tagValue) { return this; } + /** + * Sets the culprit in the event based on a {@code Throwable}. + * + * @param throwable throwable responsible of the event. + * @return the current {@code EventBuilder} for chained calls. + */ public EventBuilder setCulprit(Throwable throwable) { return setCulprit(determineCulprit(throwable)); } + /** + * Sets the culprit in the event. + * + * @param culprit culprit. + * @return the current {@code EventBuilder} for chained calls. + */ public EventBuilder setCulprit(String culprit) { event.setCulprit(culprit); return this; } + /** + * Adds a {@link SentryInterface} to the event. + *

    + * If a {@code SentryInterface} with the same interface name has already been added, the new one will replace + * the old one. + *

    + * + * @param sentryInterface sentry interface to add to the event. + * @return the current {@code EventBuilder} for chained calls. + */ public EventBuilder addSentryInterface(SentryInterface sentryInterface) { event.getSentryInterfaces().put(sentryInterface.getInterfaceName(), sentryInterface); return this; } /** - * Manually set the checksum for the current event. + * Sets the checksum for the current event. *

    * It's recommended to rely instead on the checksum system provided by Sentry. *

    * - * @param checksum - * @return + * @param checksum checksum for the event. + * @return the current {@code EventBuilder} for chained calls. */ public EventBuilder setChecksum(String checksum) { event.setChecksum(checksum); @@ -155,16 +226,17 @@ public EventBuilder setChecksum(String checksum) { } /** + * Generates a checksum from a given content and set it to the current event. * - * @param contentToCheckSum - * @return + * @param contentToChecksum content to checksum. + * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder generateChecksum(String contentToCheckSum) { - return setChecksum(calculateChecksum(contentToCheckSum)); + public EventBuilder generateChecksum(String contentToChecksum) { + return setChecksum(calculateChecksum(contentToChecksum)); } /** - * Finalise the {@link LoggedEvent} and returns it. + * Finalises the {@link LoggedEvent} and returns it. *

    * This operations will automatically set the missing values and make the mutable values immutable. *

    From db0a83a475386f3939d110a35d36360982b2f3c1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 12:01:28 +0100 Subject: [PATCH 0115/2152] Add missing setters for the builder --- .../kencochrane/raven/event/EventBuilder.java | 116 ++++++++++++++---- 1 file changed, 91 insertions(+), 25 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index c76168c726f..5c0615322b4 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -153,25 +153,57 @@ private static String determineCulprit(Throwable throwable) { } /** - * Adds a tag to an event. - *

    - * Multiple calls to {@code addTag} allow to have more that one value for a single tag.
    - * This allows to set a tag value in different contexts. - *

    + * Sets the message in the event. * - * @param tagKey name of the tag. - * @param tagValue value of the tag. + * @param message message of the event. * @return the current {@code EventBuilder} for chained calls. */ - //TODO: Check that the tag system works indeed this way. - public EventBuilder addTag(String tagKey, String tagValue) { - Set tagValues = event.getTags().get(tagKey); - if (tagValues == null) { - tagValues = new HashSet(); - event.getTags().put(tagKey, tagValues); - } - tagValues.add(tagValue); + public EventBuilder setMessage(String message) { + event.setMessage(message); + return this; + } + + /** + * Sets the timestamp in the event. + * + * @param timestamp timestamp of the event. + * @return the current {@code EventBuilder} for chained calls. + */ + public EventBuilder setTimestamp(Date timestamp) { + event.setTimestamp(timestamp); + return this; + } + + /** + * Sets the log level in the event. + * + * @param level log level of the event. + * @return the current {@code EventBuilder} for chained calls. + */ + public EventBuilder setLevel(LoggedEvent.Level level) { + event.setLevel(level); + return this; + } + /** + * Sets the logger in the event. + * + * @param logger logger of the event. + * @return the current {@code EventBuilder} for chained calls. + */ + public EventBuilder setLogger(String logger) { + event.setLogger(logger); + return this; + } + + /** + * Sets the platform in the event. + * + * @param platform platform of the event. + * @return the current {@code EventBuilder} for chained calls. + */ + public EventBuilder setPlatform(String platform) { + event.setPlatform(platform); return this; } @@ -197,20 +229,49 @@ public EventBuilder setCulprit(String culprit) { } /** - * Adds a {@link SentryInterface} to the event. + * Adds a tag to an event. *

    - * If a {@code SentryInterface} with the same interface name has already been added, the new one will replace - * the old one. + * Multiple calls to {@code addTag} allow to have more that one value for a single tag.
    + * This allows to set a tag value in different contexts. *

    * - * @param sentryInterface sentry interface to add to the event. + * @param tagKey name of the tag. + * @param tagValue value of the tag. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder addSentryInterface(SentryInterface sentryInterface) { - event.getSentryInterfaces().put(sentryInterface.getInterfaceName(), sentryInterface); + //TODO: Check that the tag system works indeed this way. + public EventBuilder addTag(String tagKey, String tagValue) { + Set tagValues = event.getTags().get(tagKey); + if (tagValues == null) { + tagValues = new HashSet(); + event.getTags().put(tagKey, tagValues); + } + tagValues.add(tagValue); + + return this; + } + + /** + * Sets the serverName in the event. + * + * @param serverName name of the server responsible for the event. + * @return the current {@code EventBuilder} for chained calls. + */ + public EventBuilder setServerName(String serverName) { + event.setServerName(serverName); return this; } + /** + * Generates a checksum from a given content and set it to the current event. + * + * @param contentToChecksum content to checksum. + * @return the current {@code EventBuilder} for chained calls. + */ + public EventBuilder generateChecksum(String contentToChecksum) { + return setChecksum(calculateChecksum(contentToChecksum)); + } + /** * Sets the checksum for the current event. *

    @@ -226,13 +287,18 @@ public EventBuilder setChecksum(String checksum) { } /** - * Generates a checksum from a given content and set it to the current event. + * Adds a {@link SentryInterface} to the event. + *

    + * If a {@code SentryInterface} with the same interface name has already been added, the new one will replace + * the old one. + *

    * - * @param contentToChecksum content to checksum. + * @param sentryInterface sentry interface to add to the event. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder generateChecksum(String contentToChecksum) { - return setChecksum(calculateChecksum(contentToChecksum)); + public EventBuilder addSentryInterface(SentryInterface sentryInterface) { + event.getSentryInterfaces().put(sentryInterface.getInterfaceName(), sentryInterface); + return this; } /** From fb6ede1e8c1c8ac5fa26dd6bd54237747f7c1566 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 14:20:22 +0100 Subject: [PATCH 0116/2152] Add a connection to send events asynchronously --- .../raven/connection/AsyncConnection.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java new file mode 100644 index 00000000000..77445eca153 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -0,0 +1,54 @@ +package net.kencochrane.raven.connection; + +import net.kencochrane.raven.event.LoggedEvent; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Asynchronous usage of a connection. + *

    + * Instead of synchronously sending each event to a connection, use a ThreadPool to establish the connection + * and submit the event. + *

    + */ +public class AsyncConnection implements Connection { + /** + * Connection used to actually send the events. + */ + private final Connection actualConnection; + // TODO: Allow the user to change to a custom executor? + private final ExecutorService executorService = Executors.newCachedThreadPool(); + + /** + * Create a connection which will rely on an executor to send events. + * + * @param actualConnection connection used to send the events. + */ + public AsyncConnection(Connection actualConnection) { + this.actualConnection = actualConnection; + } + + @Override + public void send(LoggedEvent event) { + // TODO: Consider adding an option to wait when it's full? + executorService.execute(new LoggedEventSubmitter(event)); + } + + /** + * Simple runnable using the {@link #send(net.kencochrane.raven.event.LoggedEvent)} method of the + * {@link #actualConnection}. + */ + private final class LoggedEventSubmitter implements Runnable { + private final LoggedEvent event; + + private LoggedEventSubmitter(LoggedEvent event) { + this.event = event; + } + + @Override + public void run() { + actualConnection.send(event); + } + } +} From 22dee1452a1ef21eba260e1a48cd7f77485ee499 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 16:30:17 +0100 Subject: [PATCH 0117/2152] Add a DSN class for Connections --- .../net/kencochrane/raven/connection/Dsn.java | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/connection/Dsn.java diff --git a/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java b/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java new file mode 100644 index 00000000000..b8d9f49fa99 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java @@ -0,0 +1,132 @@ +package net.kencochrane.raven.connection; + +import java.net.URI; +import java.util.*; + +/** + * Data Source name allowing a direct connection to a Sentry server. + */ +public class Dsn { + /** + * Option specific to raven-java, allowing to enable/disable the compression of the requests to the Sentry Server. + */ + public static final String COMPRESSION_OPTION = "raven.compression"; + /** + * Option specific to raven-java, allowing to set a timeout (in ms) for a request to the Sentry server. + */ + public static final String TIMEOUT_OPTION = "raven.timeout"; + private String secretKey; + private String publicKey; + private String projectId; + private String protocol; + private String host; + private int port; + private String path; + private Set protocolSettings; + private Map options; + + /** + * Creates a DS based on a String. + * + * @param dsn dsn in a string form. + */ + public Dsn(String dsn) { + options = new HashMap(); + protocolSettings = new HashSet(); + + URI uri = URI.create(dsn); + extractProtocolInfo(uri); + extractUserKeys(uri); + extractHostInfo(uri); + extractPathInfo(uri); + extractOptions(uri); + + makeOptionsImmutable(); + } + + private void extractPathInfo(URI uri) { + String uriPath = uri.getPath(); + int projectIdStart = uriPath.lastIndexOf("/") + 1; + path = uriPath.substring(0, projectIdStart); + projectId = uriPath.substring(projectIdStart); + } + + private void extractHostInfo(URI uri) { + host = uri.getHost(); + port = uri.getPort(); + } + + private void extractProtocolInfo(URI uri) { + String[] schemeDetails = uri.getScheme().split("\\+"); + protocolSettings.addAll(Arrays.asList(schemeDetails).subList(0, schemeDetails.length - 1)); + protocol = schemeDetails[schemeDetails.length - 1]; + } + + private void extractUserKeys(URI uri) { + String[] userDetails = uri.getUserInfo().split(":"); + publicKey = userDetails[0]; + secretKey = userDetails[1]; + } + + private void extractOptions(URI uri) { + String[] optionPairs = uri.getQuery().split("&"); + for (String optionPair : optionPairs) { + String[] pairDetails = optionPair.split("="); + options.put(pairDetails[0], (pairDetails.length > 1) ? pairDetails[1] : ""); + } + } + + private void makeOptionsImmutable() { + // Make the options immutable + options = Collections.unmodifiableMap(options); + protocolSettings = Collections.unmodifiableSet(protocolSettings); + } + + public String getSecretKey() { + return secretKey; + } + + public String getPublicKey() { + return publicKey; + } + + public String getProjectId() { + return projectId; + } + + public String getProtocol() { + return protocol; + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public String getPath() { + return path; + } + + public Set getProtocolSettings() { + return protocolSettings; + } + + public Map getOptions() { + return options; + } + + /** + * Creates the URI of the sentry server. + * + * @return the URI of the sentry server. + * @throws Exception if an URI couldn't be created. + */ + //TODO: Return as a String instead? + //TODO: Exception, really? + public URI getUri() throws Exception { + return new URI(protocol, "", host, port, path, "", ""); + } +} From 10e351d3a52eb3049bacae12ba778b9cba7fa7f6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 16:30:47 +0100 Subject: [PATCH 0118/2152] Add an abstract implementation of connection This should make the creation of different protocols easier --- .../raven/connection/AbstractConnection.java | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java new file mode 100644 index 00000000000..7417e754fad --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -0,0 +1,102 @@ +package net.kencochrane.raven.connection; + +import net.kencochrane.raven.Utils; +import org.apache.commons.codec.binary.Base64OutputStream; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.DeflaterOutputStream; + +/** + * Abstract connection to a Sentry server. + *

    + * Provide the basic tools to submit events to the server (authentication header, compression, base64encoding). + *

    + */ +public abstract class AbstractConnection implements Connection { + /** + * Current sentry protocol version. + */ + public static final String SENTRY_PROTOCOL_VERSION = "3"; + private final Dsn dsn; + private boolean compress = true; + + /** + * Creates a connection based on a DSN. + * + * @param dsn Data Source Name of the sentry server. + */ + protected AbstractConnection(Dsn dsn) { + if (dsn == null) + throw new IllegalArgumentException("The DSN must be not null for the connection to work"); + this.dsn = dsn; + + // Check if compression is disabled + if (dsn.getOptions().containsKey(Dsn.COMPRESSION_OPTION)) + setCompress(Boolean.parseBoolean(dsn.getOptions().get(Dsn.COMPRESSION_OPTION))); + } + + /** + * Creates an authentication header for the sentry protocol. + * + * @return an authentication header as a String. + */ + protected String getAuthHeader() { + //TODO : Consider caching everything but the timestamp + StringBuilder header = new StringBuilder(); + header.append("Sentry sentry_version=").append(SENTRY_PROTOCOL_VERSION); + header.append(",sentry_client=").append(Utils.Client.NAME); + header.append(",sentry_key=").append(dsn.getPublicKey()); + header.append(",sentry_secret=").append(dsn.getSecretKey()); + //TODO: Check sentry_timestamp + return header.toString(); + } + + /** + * Obtains an output stream where the data should be sent. + * + * @return an output stream where the data should be sent. + * @throws IOException if the {@code OutputStream} couldn't be created. + */ + protected OutputStream getOutput() throws IOException { + OutputStream out = getOutputStream(); + + // Compress if possible + if (compress) + out = new DeflaterOutputStream(out); + + // Encode in base64 + out = new Base64OutputStream(out); + return out; + + } + + /** + * Provides a raw {@code OutputStream} which will be the destination of the data. + *

    + * To send events, use the stream provided by {@link #getOutput()}. + *

    + * + * @return a raw {@code OutputStream}. + * @throws IOException if the raw {@code OutputStream} couldn't be created. + */ + protected abstract OutputStream getOutputStream() throws IOException; + + /** + * Get the Data Source Name used by the current connection. + * + * @return DSN for the current connection. + */ + protected Dsn getDsn() { + return dsn; + } + + /** + * Enables or disables the compression of the requests to Sentry. + * + * @param compress whether the compression should be enabled or not. + */ + public void setCompress(boolean compress) { + this.compress = compress; + } +} From adc375ce6096e3c3a64231d080458fb916322c79 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 16:40:30 +0100 Subject: [PATCH 0119/2152] Add a todo for interfaces --- .../net/kencochrane/raven/event/interfaces/SentryInterface.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryInterface.java index f9c0f0a6d67..c34e3f9f816 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryInterface.java @@ -2,6 +2,8 @@ import java.util.Map; +//TODO: Change interfaces so they do not behave like pre-made JSON objects +//Instead the encoder should take care of interpreting each interface in a specific way (more code, but more sensible). public interface SentryInterface { /** * Gets the unique name of the interface. From 862597429dca441dce4cb27da22cba03f5385eea Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 16:40:39 +0100 Subject: [PATCH 0120/2152] Basic implementation of Connection using Http --- .../raven/connection/HttpConnection.java | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java new file mode 100644 index 00000000000..1e25bf14571 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -0,0 +1,80 @@ +package net.kencochrane.raven.connection; + +import net.kencochrane.raven.connection.encoder.SimpleJsonEncoder; +import net.kencochrane.raven.event.LoggedEvent; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Basic connection to a Sentry server, using HTTP. + */ +public class HttpConnection extends AbstractConnection { + private static final Logger logger = Logger.getLogger(HttpConnection.class.getCanonicalName()); + private static final String SENTRY_AUTH = "X-Sentry-Auth"; + private static final int DEFAULT_TIMEOUT = 10000; + private SimpleJsonEncoder simpleJsonEncoder = new SimpleJsonEncoder(); + private int timeout = DEFAULT_TIMEOUT; + + public HttpConnection(Dsn dsn) { + super(dsn); + + // Check if a timeout is set + if (dsn.getOptions().containsKey(Dsn.TIMEOUT_OPTION)) + setTimeout(Integer.parseInt(dsn.getOptions().get(Dsn.TIMEOUT_OPTION))); + } + + private URL getSentryUrl() { + try { + String url = getDsn().getUri().toString() + "/api/" + getDsn().getProjectId() + "/store/"; + //TODO: Cache the URL? + return new URL(url); + } catch (Exception e) { + // TODO: Runtime exception... Really??? + throw new RuntimeException("Couldn't get a valid URL from the DSN", e); + } + } + + @Override + protected final OutputStream getOutputStream() throws IOException { + HttpURLConnection connection = (HttpURLConnection) getSentryUrl().openConnection(); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + connection.setDoInput(false); + connection.setConnectTimeout(timeout); + connection.setRequestProperty(SENTRY_AUTH, getAuthHeader()); + connection.connect(); + + return connection.getOutputStream(); + } + + @Override + public void send(LoggedEvent event) { + OutputStream out = null; + try { + out = getOutput(); + simpleJsonEncoder.encodeEvent(event, out); + } catch (IOException e) { + logger.log(Level.SEVERE, + "An exception occurred while trying to establish a connection to the sentry server", e); + } finally { + try { + if (out != null) { + out.flush(); + out.close(); + } + } catch (IOException e) { + logger.log(Level.SEVERE, + "An exception occurred while closing the connection to the sentry server", e); + } + } + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } +} From f918345893887bb5c8197dfdfe85db14d7b48e89 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 17:05:32 +0100 Subject: [PATCH 0121/2152] Add more todos --- .../net/kencochrane/raven/connection/AbstractConnection.java | 1 + 1 file changed, 1 insertion(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 7417e754fad..f7e493ead14 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -42,6 +42,7 @@ protected AbstractConnection(Dsn dsn) { * @return an authentication header as a String. */ protected String getAuthHeader() { + //TODO: Consider adding back signature? Not a priority, probably not worth it. //TODO : Consider caching everything but the timestamp StringBuilder header = new StringBuilder(); header.append("Sentry sentry_version=").append(SENTRY_PROTOCOL_VERSION); From 0a752d76abcb63a56022fa2c855bd5058e7f192d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 17:05:46 +0100 Subject: [PATCH 0122/2152] Add documentation to the Connection interface --- .../java/net/kencochrane/raven/connection/Connection.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/Connection.java b/raven/src/main/java/net/kencochrane/raven/connection/Connection.java index fb7e7471f6e..e01fa88cfee 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/Connection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/Connection.java @@ -2,6 +2,14 @@ import net.kencochrane.raven.event.LoggedEvent; +/** + * Connection to a Sentry server, allowing to send captured events. + */ public interface Connection { + /** + * Sends an event to the sentry server. + * + * @param event captured event to add in Sentry. + */ void send(LoggedEvent event); } From 3f6aeabf2ddc7469bc8f53faad068279091c458b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 23:12:16 +0100 Subject: [PATCH 0123/2152] Remove calls to DateFormatUtils --- .../raven/connection/encoder/SimpleJsonEncoder.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/encoder/SimpleJsonEncoder.java b/raven/src/main/java/net/kencochrane/raven/connection/encoder/SimpleJsonEncoder.java index 493172b0ab1..39f8e208b04 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/encoder/SimpleJsonEncoder.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/encoder/SimpleJsonEncoder.java @@ -2,12 +2,13 @@ import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.event.interfaces.SentryInterface; -import org.apache.commons.lang.time.DateFormatUtils; import org.json.simple.JSONObject; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; import java.util.UUID; @@ -68,6 +69,10 @@ public class SimpleJsonEncoder { * Maximum length for a message. */ public static final int MAX_MESSAGE_LENGTH = 1000; + /** + * Date format for ISO 8601 + */ + private static final DateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ"); /** * Encodes an event as a JSON string and sends it through an {@code OutputStream}. @@ -152,7 +157,6 @@ private String formatLevel(LoggedEvent.Level level) { * @return timestamp as a formatted String. */ private String formatTimestamp(Date timestamp) { - return DateFormatUtils.formatUTC(timestamp, - DateFormatUtils.ISO_DATETIME_FORMAT.getPattern()); + return isoFormat.format(timestamp); } } From 919576b25da1dcf9d4dcce19d3b4119d217a1751 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 23:12:42 +0100 Subject: [PATCH 0124/2152] Do not wait for joda-time, clone dates instead Joda time would be an additional non strictly necessary dependency cloning while not being nice allows a smaller raven library --- .../src/main/java/net/kencochrane/raven/event/LoggedEvent.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java b/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java index a508b7f85d6..663bb2805c7 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java +++ b/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java @@ -35,7 +35,6 @@ public class LoggedEvent { /** * Exact time when the logging occurred. */ - // TODO: Rely on Joda time instead? (making it completely immutable!) Or wait for Java8 and JSR-310? private Date timestamp; /** * The record severity. @@ -94,7 +93,7 @@ void setMessage(String message) { } public Date getTimestamp() { - return timestamp; + return (Date) timestamp.clone(); } void setTimestamp(Date timestamp) { From 38f4665f8950e49d1e12bd8d598101a91993e34f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Apr 2013 23:29:57 +0100 Subject: [PATCH 0125/2152] Remove compression/base64encoding from Connection Those steps are related to the encoding/marshalling of the event not the network protocol. --- .../raven/connection/AbstractConnection.java | 51 +------------------ .../raven/connection/HttpConnection.java | 5 +- 2 files changed, 3 insertions(+), 53 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index f7e493ead14..44c54971099 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -1,16 +1,11 @@ package net.kencochrane.raven.connection; import net.kencochrane.raven.Utils; -import org.apache.commons.codec.binary.Base64OutputStream; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.zip.DeflaterOutputStream; /** * Abstract connection to a Sentry server. *

    - * Provide the basic tools to submit events to the server (authentication header, compression, base64encoding). + * Provide the basic tools to submit events to the server (authentication header, dsn). *

    */ public abstract class AbstractConnection implements Connection { @@ -19,7 +14,6 @@ public abstract class AbstractConnection implements Connection { */ public static final String SENTRY_PROTOCOL_VERSION = "3"; private final Dsn dsn; - private boolean compress = true; /** * Creates a connection based on a DSN. @@ -30,10 +24,6 @@ protected AbstractConnection(Dsn dsn) { if (dsn == null) throw new IllegalArgumentException("The DSN must be not null for the connection to work"); this.dsn = dsn; - - // Check if compression is disabled - if (dsn.getOptions().containsKey(Dsn.COMPRESSION_OPTION)) - setCompress(Boolean.parseBoolean(dsn.getOptions().get(Dsn.COMPRESSION_OPTION))); } /** @@ -53,36 +43,6 @@ protected String getAuthHeader() { return header.toString(); } - /** - * Obtains an output stream where the data should be sent. - * - * @return an output stream where the data should be sent. - * @throws IOException if the {@code OutputStream} couldn't be created. - */ - protected OutputStream getOutput() throws IOException { - OutputStream out = getOutputStream(); - - // Compress if possible - if (compress) - out = new DeflaterOutputStream(out); - - // Encode in base64 - out = new Base64OutputStream(out); - return out; - - } - - /** - * Provides a raw {@code OutputStream} which will be the destination of the data. - *

    - * To send events, use the stream provided by {@link #getOutput()}. - *

    - * - * @return a raw {@code OutputStream}. - * @throws IOException if the raw {@code OutputStream} couldn't be created. - */ - protected abstract OutputStream getOutputStream() throws IOException; - /** * Get the Data Source Name used by the current connection. * @@ -91,13 +51,4 @@ protected OutputStream getOutput() throws IOException { protected Dsn getDsn() { return dsn; } - - /** - * Enables or disables the compression of the requests to Sentry. - * - * @param compress whether the compression should be enabled or not. - */ - public void setCompress(boolean compress) { - this.compress = compress; - } } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 1e25bf14571..0f0e807fce7 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -39,8 +39,7 @@ private URL getSentryUrl() { } } - @Override - protected final OutputStream getOutputStream() throws IOException { + private OutputStream getOutputStream() throws IOException { HttpURLConnection connection = (HttpURLConnection) getSentryUrl().openConnection(); connection.setRequestMethod("POST"); connection.setDoOutput(true); @@ -56,7 +55,7 @@ protected final OutputStream getOutputStream() throws IOException { public void send(LoggedEvent event) { OutputStream out = null; try { - out = getOutput(); + out = getOutputStream(); simpleJsonEncoder.encodeEvent(event, out); } catch (IOException e) { logger.log(Level.SEVERE, From fe82c06e12a64d6c937999ac9f1348984441e6ec Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 5 Apr 2013 08:55:08 +0100 Subject: [PATCH 0126/2152] Create UDP connections --- .../raven/connection/UdpConnection.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java new file mode 100644 index 00000000000..b10dd944479 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -0,0 +1,60 @@ +package net.kencochrane.raven.connection; + +import net.kencochrane.raven.event.LoggedEvent; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.nio.charset.Charset; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Connection to a Sentry server through an UDP connection. + */ +public class UdpConnection extends AbstractConnection { + private static final Logger logger = Logger.getLogger(UdpConnection.class.getCanonicalName()); + private DatagramSocket socket; + private Charset charset = Charset.defaultCharset(); + + public UdpConnection(Dsn dsn) { + super(dsn); + openSocket(); + } + + @Override + public void send(LoggedEvent event) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + writeHeader(baos); + baos.flush(); + baos.close(); + + byte[] message = baos.toByteArray(); + DatagramPacket packet = new DatagramPacket(message, message.length); + socket.send(packet); + } catch (IOException e) { + logger.log(Level.SEVERE, + "An exception occurred while trying to establish a connection to the sentry server"); + } + } + + private void writeHeader(OutputStream os) throws IOException { + os.write(getAuthHeader().getBytes(charset)); + os.write("\n\n".getBytes(charset)); + } + + private void openSocket() { + try { + socket = new DatagramSocket(); + socket.connect(new InetSocketAddress(getDsn().getHost(), getDsn().getPort())); + } catch (SocketException e) { + throw new IllegalStateException("The UDP connection couldn't be used, impossible to send anything " + + "to sentry", e); + } + } +} From d9577d10e288cce2c8236c7a08015f3686b85178 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 5 Apr 2013 10:44:28 +0100 Subject: [PATCH 0127/2152] Replace the encoders with marshallers --- .../raven/connection/HttpConnection.java | 7 ++++--- .../connection/marshaller/Marshaller.java | 20 +++++++++++++++++++ .../SimpleJsonMarshaller.java} | 17 ++++++---------- 3 files changed, 30 insertions(+), 14 deletions(-) create mode 100644 raven/src/main/java/net/kencochrane/raven/connection/marshaller/Marshaller.java rename raven/src/main/java/net/kencochrane/raven/connection/{encoder/SimpleJsonEncoder.java => marshaller/SimpleJsonMarshaller.java} (89%) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 0f0e807fce7..233f522c09c 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -1,6 +1,7 @@ package net.kencochrane.raven.connection; -import net.kencochrane.raven.connection.encoder.SimpleJsonEncoder; +import net.kencochrane.raven.connection.marshaller.Marshaller; +import net.kencochrane.raven.connection.marshaller.SimpleJsonMarshaller; import net.kencochrane.raven.event.LoggedEvent; import java.io.IOException; @@ -17,7 +18,7 @@ public class HttpConnection extends AbstractConnection { private static final Logger logger = Logger.getLogger(HttpConnection.class.getCanonicalName()); private static final String SENTRY_AUTH = "X-Sentry-Auth"; private static final int DEFAULT_TIMEOUT = 10000; - private SimpleJsonEncoder simpleJsonEncoder = new SimpleJsonEncoder(); + private Marshaller marshaller = new SimpleJsonMarshaller(); private int timeout = DEFAULT_TIMEOUT; public HttpConnection(Dsn dsn) { @@ -56,7 +57,7 @@ public void send(LoggedEvent event) { OutputStream out = null; try { out = getOutputStream(); - simpleJsonEncoder.encodeEvent(event, out); + marshaller.marshall(event, out); } catch (IOException e) { logger.log(Level.SEVERE, "An exception occurred while trying to establish a connection to the sentry server", e); diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/Marshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/Marshaller.java new file mode 100644 index 00000000000..a8b25f33ceb --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/Marshaller.java @@ -0,0 +1,20 @@ +package net.kencochrane.raven.connection.marshaller; + +import net.kencochrane.raven.event.LoggedEvent; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Marshaller allows to serialise a {@link LoggedEvent} and sends over a stream. + */ +public interface Marshaller { + /** + * Serialise an event and sends it through an {@code OutputStream}. + * + * @param event event to serialise. + * @param destination destination stream. + * @throws IOException occurs when the serialisation failed. + */ + void marshall(LoggedEvent event, OutputStream destination) throws IOException; +} diff --git a/raven/src/main/java/net/kencochrane/raven/connection/encoder/SimpleJsonEncoder.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java similarity index 89% rename from raven/src/main/java/net/kencochrane/raven/connection/encoder/SimpleJsonEncoder.java rename to raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java index 39f8e208b04..68b90dac5b2 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/encoder/SimpleJsonEncoder.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven.connection.encoder; +package net.kencochrane.raven.connection.marshaller; import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.event.interfaces.SentryInterface; @@ -12,11 +12,12 @@ import java.util.Date; import java.util.Map; import java.util.UUID; +import java.util.zip.DeflaterOutputStream; /** - * Encoder allowing to transform a simple {@link LoggedEvent} into a JSON String sent over a stream. + * Marshaller allowing to transform a simple {@link LoggedEvent} into a JSON String send over a stream. */ -public class SimpleJsonEncoder { +public class SimpleJsonMarshaller implements Marshaller { /** * Hexadecimal string representing a uuid4 value. */ @@ -74,14 +75,8 @@ public class SimpleJsonEncoder { */ private static final DateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ"); - /** - * Encodes an event as a JSON string and sends it through an {@code OutputStream}. - * - * @param event event to encode as a JSON string. - * @param destination destination stream. - * @throws IOException occurs when the serialisation to JSON failed. - */ - public void encodeEvent(LoggedEvent event, OutputStream destination) throws IOException { + @Override + public void marshall(LoggedEvent event, OutputStream destination) throws IOException { JSONObject jsonObject = encodeToJSONObject(event); jsonObject.writeJSONString(new OutputStreamWriter(destination)); } From 4410043aa797aefe91724b982e4a54c1775ba448 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 5 Apr 2013 10:44:45 +0100 Subject: [PATCH 0128/2152] Allow a connection to switch between marshallers --- .../java/net/kencochrane/raven/connection/HttpConnection.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 233f522c09c..a0e8551fae6 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -77,4 +77,8 @@ public void send(LoggedEvent event) { public void setTimeout(int timeout) { this.timeout = timeout; } + + public void setMarshaller(Marshaller marshaller) { + this.marshaller = marshaller; + } } From b454514529adae39f1a697e191b7419971664a14 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 5 Apr 2013 10:45:51 +0100 Subject: [PATCH 0129/2152] Let the simpleJSonMarshaller compress the content --- .../marshaller/SimpleJsonMarshaller.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java index 68b90dac5b2..1aa566037e6 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java @@ -2,6 +2,7 @@ import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.event.interfaces.SentryInterface; +import org.apache.commons.codec.binary.Base64OutputStream; import org.json.simple.JSONObject; import java.io.IOException; @@ -15,7 +16,7 @@ import java.util.zip.DeflaterOutputStream; /** - * Marshaller allowing to transform a simple {@link LoggedEvent} into a JSON String send over a stream. + * Marshaller allowing to transform a simple {@link LoggedEvent} into a compressed JSON String send over a stream. */ public class SimpleJsonMarshaller implements Marshaller { /** @@ -74,11 +75,22 @@ public class SimpleJsonMarshaller implements Marshaller { * Date format for ISO 8601 */ private static final DateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ"); + /** + * Enables disables the compression of JSON. + */ + private boolean compression = true; @Override public void marshall(LoggedEvent event, OutputStream destination) throws IOException { + OutputStream outputStream = destination; + + if (compression) + outputStream = new DeflaterOutputStream(outputStream); + + outputStream = new Base64OutputStream(outputStream); + JSONObject jsonObject = encodeToJSONObject(event); - jsonObject.writeJSONString(new OutputStreamWriter(destination)); + jsonObject.writeJSONString(new OutputStreamWriter(outputStream)); } @SuppressWarnings("unchecked") @@ -154,4 +166,8 @@ private String formatLevel(LoggedEvent.Level level) { private String formatTimestamp(Date timestamp) { return isoFormat.format(timestamp); } + + public void setCompression(boolean compression) { + this.compression = compression; + } } From 7de503d21a73fb3ddf17c8bd402e3a38c6881a93 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 5 Apr 2013 10:46:13 +0100 Subject: [PATCH 0130/2152] Fix constant name --- .../raven/connection/marshaller/SimpleJsonMarshaller.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java index 1aa566037e6..222b3968604 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java @@ -74,7 +74,7 @@ public class SimpleJsonMarshaller implements Marshaller { /** * Date format for ISO 8601 */ - private static final DateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ"); + private static final DateFormat ISO_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ"); /** * Enables disables the compression of JSON. */ @@ -164,7 +164,7 @@ private String formatLevel(LoggedEvent.Level level) { * @return timestamp as a formatted String. */ private String formatTimestamp(Date timestamp) { - return isoFormat.format(timestamp); + return ISO_FORMAT.format(timestamp); } public void setCompression(boolean compression) { From 42678ec617d94ff359fe7173276393fcf18bdfc4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 5 Apr 2013 13:02:28 +0100 Subject: [PATCH 0131/2152] Add a class to make Throwable immutable --- .../event/interfaces/ImmutableThrowable.java | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java new file mode 100644 index 00000000000..a7b4967fa11 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java @@ -0,0 +1,77 @@ +package net.kencochrane.raven.event.interfaces; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.Arrays; + +/** + * Wrapper for {@code Throwable} to make it immutable. + */ +public class ImmutableThrowable extends Throwable { + private final Throwable actualThrowable; + + /** + * Creates an immutable wrapper for the given throwable. + * + * @param actualThrowable exception wrapped. + */ + public ImmutableThrowable(Throwable actualThrowable) { + this.actualThrowable = actualThrowable; + } + + @Override + public String getMessage() { + return actualThrowable.getMessage(); + } + + @Override + public String getLocalizedMessage() { + return actualThrowable.getLocalizedMessage(); + } + + @Override + public Throwable getCause() { + return actualThrowable.getCause(); + } + + @Override + public Throwable initCause(Throwable cause) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return actualThrowable.toString(); + } + + @Override + public void printStackTrace() { + throw new UnsupportedOperationException(); + } + + @Override + public void printStackTrace(PrintStream s) { + throw new UnsupportedOperationException(); + } + + @Override + public void printStackTrace(PrintWriter s) { + throw new UnsupportedOperationException(); + } + + @Override + public Throwable fillInStackTrace() { + throw new UnsupportedOperationException(); + } + + @Override + public StackTraceElement[] getStackTrace() { + StackTraceElement[] stackTrace = actualThrowable.getStackTrace(); + return Arrays.copyOf(stackTrace, stackTrace.length); + } + + @Override + public void setStackTrace(StackTraceElement[] stackTrace) { + throw new UnsupportedOperationException(); + } +} From aace0c5820cd6c7d655c335df96be54e03765bc1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 5 Apr 2013 13:03:16 +0100 Subject: [PATCH 0132/2152] Make Throwables immutable in interfaces --- .../kencochrane/raven/event/interfaces/ExceptionInterface.java | 2 +- .../kencochrane/raven/event/interfaces/StackTraceInterface.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java index 7c7a3b72617..13bdb44c89e 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java @@ -11,7 +11,7 @@ public class ExceptionInterface implements SentryInterface { private final Throwable throwable; public ExceptionInterface(Throwable throwable) { - this.throwable = throwable; + this.throwable = new ImmutableThrowable(throwable); } @Override diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java index 2a0d5ae4879..97767b32d15 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java @@ -18,7 +18,7 @@ public class StackTraceInterface implements SentryInterface { private final Throwable throwable; public StackTraceInterface(Throwable throwable) { - this.throwable = throwable; + this.throwable = new ImmutableThrowable(throwable); } /** From 477ff29a3cc8f1547b9626d9e057cfb036faed7d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 5 Apr 2013 16:35:16 +0100 Subject: [PATCH 0133/2152] Do not consider interfaces as JSON containers The sentry interfaces shouldn't be designed as JSON containers (maps) but should only hold the raw data --- .../event/interfaces/ExceptionInterface.java | 18 +----- .../event/interfaces/MessageInterface.java | 19 +++---- .../event/interfaces/SentryInterface.java | 23 +------- .../event/interfaces/StackTraceInterface.java | 57 +------------------ 4 files changed, 18 insertions(+), 99 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java index 13bdb44c89e..2a326f82022 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java @@ -1,13 +1,7 @@ package net.kencochrane.raven.event.interfaces; -import java.util.HashMap; -import java.util.Map; - public class ExceptionInterface implements SentryInterface { - private static final String EXCEPTION_INTERFACE = "sentry.interfaces.Exception"; - private static final String TYPE_PARAMETER = "type"; - private static final String VALUE_PARAMETER = "value"; - private static final String MODULE_PARAMETER = "module"; + public static final String EXCEPTION_INTERFACE = "sentry.interfaces.Exception"; private final Throwable throwable; public ExceptionInterface(Throwable throwable) { @@ -19,13 +13,7 @@ public String getInterfaceName() { return EXCEPTION_INTERFACE; } - @Override - public Map getInterfaceContent() { - Map content = new HashMap(); - content.put(TYPE_PARAMETER, throwable.getClass().getSimpleName()); - // TODO: The message can be empty here, should something be done? - content.put(VALUE_PARAMETER, throwable.getMessage()); - content.put(MODULE_PARAMETER, throwable.getClass().getPackage().getName()); - return content; + public Throwable getThrowable() { + return throwable; } } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java index 3a1ea28eb05..93ecab8c6a0 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java @@ -2,10 +2,11 @@ import java.util.*; +/** + * The Message interface for Sentry allows to add a message that will be formatted by sentry. + */ public class MessageInterface implements SentryInterface { - private static final String MESSAGE_INTERFACE = "sentry.interfaces.Message"; - private static final String MESSAGE_PARAMETER = "message"; - private static final String PARAMS_PARAMETER = "params"; + public static final String MESSAGE_INTERFACE = "sentry.interfaces.Message"; private final String message; private final List params; @@ -23,13 +24,11 @@ public String getInterfaceName() { return MESSAGE_INTERFACE; } - @Override - public Map getInterfaceContent() { - Map content = new HashMap(); - content.put(MESSAGE_PARAMETER, message); - if (!params.isEmpty()) - content.put(PARAMS_PARAMETER, params); + public String getMessage() { + return message; + } - return content; + public List getParams() { + return params; } } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryInterface.java index c34e3f9f816..2e182501777 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryInterface.java @@ -1,9 +1,8 @@ package net.kencochrane.raven.event.interfaces; -import java.util.Map; - -//TODO: Change interfaces so they do not behave like pre-made JSON objects -//Instead the encoder should take care of interpreting each interface in a specific way (more code, but more sensible). +/** + * A SentryInterface is an additional structured data that can be provided with a message. + */ public interface SentryInterface { /** * Gets the unique name of the interface. @@ -11,20 +10,4 @@ public interface SentryInterface { * @return name of the interface. */ String getInterfaceName(); - - /** - * Gets the content of the interface as a Map. - *

    - * The values contained in the Map can only be: - *

      - *
    • A {@code String}
    • - *
    • A wrapped primitive
    • - *
    • A {@code Collection}
    • - *
    • A {@code Map} where {@code V} is one of the types currently listed
    • - *
    - *

    - * - * @return the content of the interface. - */ - Map getInterfaceContent(); } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java index 97767b32d15..91995a6e026 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java @@ -3,70 +3,19 @@ import java.util.*; public class StackTraceInterface implements SentryInterface { - private static final String STACKTRACE_INTERFACE = "sentry.interfaces.Stacktrace"; - private static final String FRAMES_PARAMETER = "frames"; - private static final String FILENAME_PARAMETER = "filename"; - private static final String FUNCTION_PARAMETER = "function"; - private static final String MODULE_PARAMETER = "module"; - private static final String LINE_NO_PARAMETER = "lineno"; - private static final String ABSOLUTE_PATH_PARAMETER = "abs_path"; - private static final String CONTEXT_LINE_PARAMETER = "context_line"; - private static final String PRE_CONTEXT_PARAMETER = "pre_context"; - private static final String POST_CONTEXT_PARAMETER = "post_context"; - private static final String IN_APP_PARAMETER = "in_app"; - private static final String VARIABLES_PARAMETER = "vars"; + public static final String STACKTRACE_INTERFACE = "sentry.interfaces.Stacktrace"; private final Throwable throwable; public StackTraceInterface(Throwable throwable) { this.throwable = new ImmutableThrowable(throwable); } - /** - * Create a fake frame to allow chained exceptions. - * - * @param throwable Exception for which a fake frame should be created - * @return a fake frame allowing to chain exceptions smoothly in Sentry. - */ - private static Map createFakeFrame(Throwable throwable) { - Map fakeFrame = new HashMap(); - String message = "Caused by: " + throwable.getClass().getName(); - if (throwable.getMessage() != null) - message += " (\"" + throwable.getMessage() + "\")"; - fakeFrame.put(FILENAME_PARAMETER, message); - fakeFrame.put(LINE_NO_PARAMETER, -1); - return fakeFrame; - } - - /** - * Creates a single frame based on a {@code StackTraceElement}. - * - * @param stackTraceElement current frame in the stackTrace. - * @return frame extracted from the stackTraceElement. - */ - private static Map createFrame(StackTraceElement stackTraceElement) { - Map currentFrame = new HashMap(); - currentFrame.put(FILENAME_PARAMETER, stackTraceElement.getClassName()); - currentFrame.put(FUNCTION_PARAMETER, stackTraceElement.getMethodName()); - currentFrame.put(LINE_NO_PARAMETER, stackTraceElement.getLineNumber()); - return currentFrame; - } - @Override public String getInterfaceName() { return STACKTRACE_INTERFACE; } - @Override - public Map getInterfaceContent() { - List> frames = new LinkedList>(); - Throwable currentThrowable = throwable; - while (currentThrowable != null) { - frames.add(createFakeFrame(currentThrowable)); - for (StackTraceElement stackTraceElement : throwable.getStackTrace()) { - frames.add(createFrame(stackTraceElement)); - } - currentThrowable = currentThrowable.getCause(); - } - return Collections.singletonMap(FRAMES_PARAMETER, frames); + public Throwable getThrowable() { + return throwable; } } From 1540b28ac88d8498b2bff6a8098946a922f6b9b6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 5 Apr 2013 16:37:58 +0100 Subject: [PATCH 0134/2152] Create simple JSON marshaller for each interface --- ...impleJsonExceptionInterfaceMarshaller.java | 28 ++++++ .../SimpleJsonInterfaceMarshaller.java | 8 ++ .../marshaller/SimpleJsonMarshaller.java | 23 ++++- .../SimpleJsonMessageInterfaceMarshaller.java | 24 +++++ ...mpleJsonStackTraceInterfaceMarshaller.java | 89 +++++++++++++++++++ 5 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonExceptionInterfaceMarshaller.java create mode 100644 raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonInterfaceMarshaller.java create mode 100644 raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMessageInterfaceMarshaller.java create mode 100644 raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonExceptionInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonExceptionInterfaceMarshaller.java new file mode 100644 index 00000000000..313ef71fc5a --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonExceptionInterfaceMarshaller.java @@ -0,0 +1,28 @@ +package net.kencochrane.raven.connection.marshaller; + +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.SentryInterface; +import org.json.simple.JSONObject; + +public class SimpleJsonExceptionInterfaceMarshaller implements SimpleJsonInterfaceMarshaller { + private static final String TYPE_PARAMETER = "type"; + private static final String VALUE_PARAMETER = "value"; + private static final String MODULE_PARAMETER = "module"; + + @Override + public JSONObject serialiseInterface(SentryInterface sentryInterface) { + if (!(sentryInterface instanceof ExceptionInterface)) { + //TODO: Do something better here! + throw new IllegalArgumentException(); + } + + ExceptionInterface messageInterface = (ExceptionInterface) sentryInterface; + JSONObject jsonObject = new JSONObject(); + Throwable throwable = messageInterface.getThrowable(); + jsonObject.put(TYPE_PARAMETER, throwable.getClass().getSimpleName()); + // TODO: The message can be empty here, should something be done? + jsonObject.put(VALUE_PARAMETER, throwable.getMessage()); + jsonObject.put(MODULE_PARAMETER, throwable.getClass().getPackage().getName()); + return jsonObject; + } +} diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonInterfaceMarshaller.java new file mode 100644 index 00000000000..c9e1509d8e7 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonInterfaceMarshaller.java @@ -0,0 +1,8 @@ +package net.kencochrane.raven.connection.marshaller; + +import net.kencochrane.raven.event.interfaces.SentryInterface; +import org.json.simple.JSONObject; + +public interface SimpleJsonInterfaceMarshaller { + JSONObject serialiseInterface(SentryInterface sentryInterface); +} diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java index 222b3968604..820bcfd175d 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java @@ -1,7 +1,10 @@ package net.kencochrane.raven.connection.marshaller; import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.SentryInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.commons.codec.binary.Base64OutputStream; import org.json.simple.JSONObject; @@ -108,8 +111,24 @@ private JSONObject encodeToJSONObject(LoggedEvent event) { jsonObject.put(CHECKSUM, event.getChecksum()); for (Map.Entry sentryInterfaceEntry : event.getSentryInterfaces().entrySet()) { - //TODO: Usually we would look for marshallers for each interface type. - jsonObject.put(sentryInterfaceEntry.getKey(), sentryInterfaceEntry.getValue()); + jsonObject.put(sentryInterfaceEntry.getKey(), formatInterface(sentryInterfaceEntry.getValue())); + } + + return jsonObject; + } + + private JSONObject formatInterface(SentryInterface sentryInterface) { + JSONObject jsonObject; + //TODO: Use an factory here (and do not instanciate a marshaller each time? + if (sentryInterface.getInterfaceName().equals(MessageInterface.MESSAGE_INTERFACE)) { + jsonObject = new SimpleJsonMessageInterfaceMarshaller().serialiseInterface(sentryInterface); + } else if (sentryInterface.getInterfaceName().equals(ExceptionInterface.EXCEPTION_INTERFACE)) { + jsonObject = new SimpleJsonExceptionInterfaceMarshaller().serialiseInterface(sentryInterface); + } else if (sentryInterface.getInterfaceName().equals(StackTraceInterface.STACKTRACE_INTERFACE)) { + jsonObject = new SimpleJsonStackTraceInterfaceMarshaller().serialiseInterface(sentryInterface); + } else { + //TODO: log something? + jsonObject = new JSONObject(); } return jsonObject; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMessageInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMessageInterfaceMarshaller.java new file mode 100644 index 00000000000..4c073964d5a --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMessageInterfaceMarshaller.java @@ -0,0 +1,24 @@ +package net.kencochrane.raven.connection.marshaller; + +import net.kencochrane.raven.event.interfaces.MessageInterface; +import net.kencochrane.raven.event.interfaces.SentryInterface; +import org.json.simple.JSONObject; + +public class SimpleJsonMessageInterfaceMarshaller implements SimpleJsonInterfaceMarshaller { + private static final String MESSAGE_PARAMETER = "message"; + private static final String PARAMS_PARAMETER = "params"; + + @Override + public JSONObject serialiseInterface(SentryInterface sentryInterface) { + if (!(sentryInterface instanceof MessageInterface)) { + //TODO: Do something better here! + throw new IllegalArgumentException(); + } + + MessageInterface messageInterface = (MessageInterface) sentryInterface; + JSONObject jsonObject = new JSONObject(); + jsonObject.put(MESSAGE_PARAMETER, messageInterface.getMessage()); + jsonObject.put(PARAMS_PARAMETER, messageInterface.getParams()); + return jsonObject; + } +} diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java new file mode 100644 index 00000000000..7dee3e2baeb --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java @@ -0,0 +1,89 @@ +package net.kencochrane.raven.connection.marshaller; + +import net.kencochrane.raven.event.interfaces.SentryInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import java.util.HashSet; +import java.util.Set; + +public class SimpleJsonStackTraceInterfaceMarshaller implements SimpleJsonInterfaceMarshaller { + private static final String FRAMES_PARAMETER = "frames"; + private static final String FILENAME_PARAMETER = "filename"; + private static final String FUNCTION_PARAMETER = "function"; + private static final String MODULE_PARAMETER = "module"; + private static final String LINE_NO_PARAMETER = "lineno"; + private static final String ABSOLUTE_PATH_PARAMETER = "abs_path"; + private static final String CONTEXT_LINE_PARAMETER = "context_line"; + private static final String PRE_CONTEXT_PARAMETER = "pre_context"; + private static final String POST_CONTEXT_PARAMETER = "post_context"; + private static final String IN_APP_PARAMETER = "in_app"; + private static final String VARIABLES_PARAMETER = "vars"; + //TODO: add a way to add content here. + private Set notInAppFrames = new HashSet(); + + /** + * Create a fake frame to allow chained exceptions. + * + * @param throwable Exception for which a fake frame should be created + * @return a fake frame allowing to chain exceptions smoothly in Sentry. + */ + private JSONObject createFakeFrame(Throwable throwable) { + JSONObject fakeFrame = new JSONObject(); + String message = "Caused by: " + throwable.getClass().getName(); + if (throwable.getMessage() != null) + message += " (\"" + throwable.getMessage() + "\")"; + fakeFrame.put(FILENAME_PARAMETER, message); + return fakeFrame; + } + + /** + * Creates a single frame based on a {@code StackTraceElement}. + * + * @param stackTraceElement current frame in the stackTrace. + * @return frame extracted from the stackTraceElement. + */ + private JSONObject createFrame(StackTraceElement stackTraceElement) { + JSONObject currentFrame = new JSONObject(); + currentFrame.put(FILENAME_PARAMETER, stackTraceElement.getFileName()); + currentFrame.put(MODULE_PARAMETER, stackTraceElement.getClassName()); + currentFrame.put(IN_APP_PARAMETER, isFrameInApp(stackTraceElement)); + currentFrame.put(FUNCTION_PARAMETER, stackTraceElement.getMethodName()); + currentFrame.put(LINE_NO_PARAMETER, stackTraceElement.getLineNumber()); + return currentFrame; + } + + private boolean isFrameInApp(StackTraceElement stackTraceElement) { + //TODO: A set is absolutely not performant here, a Trie could be a better solution. + for (String notInAppFrame : notInAppFrames) { + if (stackTraceElement.getClassName().startsWith(notInAppFrame)) { + return false; + } + } + return true; + } + + @Override + public JSONObject serialiseInterface(SentryInterface sentryInterface) { + if (!(sentryInterface instanceof StackTraceInterface)) { + //TODO: Do something better here! + throw new IllegalArgumentException(); + } + + StackTraceInterface stackTraceInterface = (StackTraceInterface) sentryInterface; + JSONObject jsonObject = new JSONObject(); + JSONArray frames = new JSONArray(); + Throwable currentThrowable = stackTraceInterface.getThrowable(); + while (currentThrowable != null) { + frames.add(createFakeFrame(currentThrowable)); + for (StackTraceElement stackTraceElement : currentThrowable.getStackTrace()) { + frames.add(createFrame(stackTraceElement)); + } + currentThrowable = currentThrowable.getCause(); + } + jsonObject.put(FRAMES_PARAMETER, frames); + + return jsonObject; + } +} From d9d724f4e6a3b5cdcfe99182200445bbc41d32fb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 5 Apr 2013 16:38:36 +0100 Subject: [PATCH 0135/2152] Allow to select a charset in the JSONMarshaller --- .../connection/marshaller/SimpleJsonMarshaller.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java index 820bcfd175d..16f2a03daea 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.nio.charset.Charset; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; @@ -82,6 +83,11 @@ public class SimpleJsonMarshaller implements Marshaller { * Enables disables the compression of JSON. */ private boolean compression = true; + /** + * Charset used to transmit data. + */ + //TODO: Force or default to UTF-8? + private Charset charset = Charset.defaultCharset(); @Override public void marshall(LoggedEvent event, OutputStream destination) throws IOException { @@ -93,7 +99,7 @@ public void marshall(LoggedEvent event, OutputStream destination) throws IOExcep outputStream = new Base64OutputStream(outputStream); JSONObject jsonObject = encodeToJSONObject(event); - jsonObject.writeJSONString(new OutputStreamWriter(outputStream)); + jsonObject.writeJSONString(new OutputStreamWriter(outputStream, charset)); } @SuppressWarnings("unchecked") @@ -189,4 +195,8 @@ private String formatTimestamp(Date timestamp) { public void setCompression(boolean compression) { this.compression = compression; } + + public void setCharset(Charset charset) { + this.charset = charset; + } } From 9f6db89a20d565e3a0cc1a635ef07ce7e4956458 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 5 Apr 2013 16:39:22 +0100 Subject: [PATCH 0136/2152] Improve documentation --- .../raven/connection/marshaller/SimpleJsonMarshaller.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java index 16f2a03daea..96b455f798c 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java @@ -76,7 +76,7 @@ public class SimpleJsonMarshaller implements Marshaller { */ public static final int MAX_MESSAGE_LENGTH = 1000; /** - * Date format for ISO 8601 + * Date format for ISO 8601. */ private static final DateFormat ISO_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ"); /** @@ -192,6 +192,11 @@ private String formatTimestamp(Date timestamp) { return ISO_FORMAT.format(timestamp); } + /** + * Enables the JSON compression with GZip. + * + * @param compression state of the compression. + */ public void setCompression(boolean compression) { this.compression = compression; } From fdf2b2674bc9759cdd43d891eb4ed13eb32eabd0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 5 Apr 2013 16:39:31 +0100 Subject: [PATCH 0137/2152] Flush the OutputStream when finished --- .../raven/connection/marshaller/SimpleJsonMarshaller.java | 1 + 1 file changed, 1 insertion(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java index 96b455f798c..3ff59489bd2 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java @@ -100,6 +100,7 @@ public void marshall(LoggedEvent event, OutputStream destination) throws IOExcep JSONObject jsonObject = encodeToJSONObject(event); jsonObject.writeJSONString(new OutputStreamWriter(outputStream, charset)); + outputStream.flush(); } @SuppressWarnings("unchecked") From 8c56a0421bb545d78b7aa6354b929226ee0abfd8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 11:43:23 +0100 Subject: [PATCH 0138/2152] The timestamp isn't necessary in the Header --- .../net/kencochrane/raven/connection/AbstractConnection.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 44c54971099..1059a64bb97 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -33,13 +33,11 @@ protected AbstractConnection(Dsn dsn) { */ protected String getAuthHeader() { //TODO: Consider adding back signature? Not a priority, probably not worth it. - //TODO : Consider caching everything but the timestamp StringBuilder header = new StringBuilder(); header.append("Sentry sentry_version=").append(SENTRY_PROTOCOL_VERSION); header.append(",sentry_client=").append(Utils.Client.NAME); header.append(",sentry_key=").append(dsn.getPublicKey()); header.append(",sentry_secret=").append(dsn.getSecretKey()); - //TODO: Check sentry_timestamp return header.toString(); } From 028e1fd5c8319012665adc3144efa8d7a0e02c88 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 11:44:13 +0100 Subject: [PATCH 0139/2152] The queryString in the DSN can be null --- .../src/main/java/net/kencochrane/raven/connection/Dsn.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java b/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java index b8d9f49fa99..1435efbb889 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java @@ -69,7 +69,10 @@ private void extractUserKeys(URI uri) { } private void extractOptions(URI uri) { - String[] optionPairs = uri.getQuery().split("&"); + String query = uri.getQuery(); + if (query == null) + return; + String[] optionPairs = query.split("&"); for (String optionPair : optionPairs) { String[] pairDetails = optionPair.split("="); options.put(pairDetails[0], (pairDetails.length > 1) ? pairDetails[1] : ""); From 9fa76ef13924986f8d2e866c42269ea08e536f2d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 11:44:40 +0100 Subject: [PATCH 0140/2152] Do not use empty Strings when building a URI --- raven/src/main/java/net/kencochrane/raven/connection/Dsn.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java b/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java index 1435efbb889..776888c7021 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java @@ -130,6 +130,6 @@ public Map getOptions() { //TODO: Return as a String instead? //TODO: Exception, really? public URI getUri() throws Exception { - return new URI(protocol, "", host, port, path, "", ""); + return new URI(protocol, null, host, port, path, null, null); } } From a42a9e073e0041629263d2bcbe12bc79df89889b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 11:45:42 +0100 Subject: [PATCH 0141/2152] Send a User-Agent when using HTTP connections --- .../java/net/kencochrane/raven/connection/HttpConnection.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index a0e8551fae6..af047235aa0 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -1,5 +1,6 @@ package net.kencochrane.raven.connection; +import net.kencochrane.raven.Utils; import net.kencochrane.raven.connection.marshaller.Marshaller; import net.kencochrane.raven.connection.marshaller.SimpleJsonMarshaller; import net.kencochrane.raven.event.LoggedEvent; @@ -15,6 +16,7 @@ * Basic connection to a Sentry server, using HTTP. */ public class HttpConnection extends AbstractConnection { + public static final String USER_AGENT = "User-Agent"; private static final Logger logger = Logger.getLogger(HttpConnection.class.getCanonicalName()); private static final String SENTRY_AUTH = "X-Sentry-Auth"; private static final int DEFAULT_TIMEOUT = 10000; @@ -46,6 +48,7 @@ private OutputStream getOutputStream() throws IOException { connection.setDoOutput(true); connection.setDoInput(false); connection.setConnectTimeout(timeout); + connection.setRequestProperty(USER_AGENT, Utils.Client.NAME); connection.setRequestProperty(SENTRY_AUTH, getAuthHeader()); connection.connect(); From 2b78ff2a952e0e0a55dec212eba8fc4733c241f5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 11:46:15 +0100 Subject: [PATCH 0142/2152] Fix the sentry URL No need for an additional "/" in the URL --- .../java/net/kencochrane/raven/connection/HttpConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index af047235aa0..60fb169e981 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -33,7 +33,7 @@ public HttpConnection(Dsn dsn) { private URL getSentryUrl() { try { - String url = getDsn().getUri().toString() + "/api/" + getDsn().getProjectId() + "/store/"; + String url = getDsn().getUri().toString() + "api/" + getDsn().getProjectId() + "/store/"; //TODO: Cache the URL? return new URL(url); } catch (Exception e) { From fa2565a0c54ced44a99b401dff485ff045f3c2e9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 11:47:01 +0100 Subject: [PATCH 0143/2152] doInput is mandatory with HttpUrlConnections --- .../java/net/kencochrane/raven/connection/HttpConnection.java | 1 - 1 file changed, 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 60fb169e981..8f30efe349a 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -46,7 +46,6 @@ private OutputStream getOutputStream() throws IOException { HttpURLConnection connection = (HttpURLConnection) getSentryUrl().openConnection(); connection.setRequestMethod("POST"); connection.setDoOutput(true); - connection.setDoInput(false); connection.setConnectTimeout(timeout); connection.setRequestProperty(USER_AGENT, Utils.Client.NAME); connection.setRequestProperty(SENTRY_AUTH, getAuthHeader()); From 8c206f7bb2a62318d943537f8fc85f8b1af0b93e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 11:49:45 +0100 Subject: [PATCH 0144/2152] Ensure that streams are flushed in JsonMarshaller --- .../marshaller/SimpleJsonMarshaller.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java index 3ff59489bd2..d33096003b2 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.io.Writer; import java.nio.charset.Charset; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -91,16 +92,21 @@ public class SimpleJsonMarshaller implements Marshaller { @Override public void marshall(LoggedEvent event, OutputStream destination) throws IOException { - OutputStream outputStream = destination; + OutputStream outputStream = new Base64OutputStream(destination); + DeflaterOutputStream compressionStream = null; if (compression) - outputStream = new DeflaterOutputStream(outputStream); - - outputStream = new Base64OutputStream(outputStream); + outputStream = compressionStream = new DeflaterOutputStream(outputStream); + Writer writer = new OutputStreamWriter(outputStream, charset); JSONObject jsonObject = encodeToJSONObject(event); - jsonObject.writeJSONString(new OutputStreamWriter(outputStream, charset)); - outputStream.flush(); + jsonObject.writeJSONString(writer); + + writer.flush(); + + if (compressionStream != null) { + compressionStream.finish(); + } } @SuppressWarnings("unchecked") From e193d25211231fa666f9700036ae06b73fd47274 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 12:58:11 +0100 Subject: [PATCH 0145/2152] Nothing to do here --- .../marshaller/SimpleJsonExceptionInterfaceMarshaller.java | 1 - 1 file changed, 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonExceptionInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonExceptionInterfaceMarshaller.java index 313ef71fc5a..8d671795ef9 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonExceptionInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonExceptionInterfaceMarshaller.java @@ -20,7 +20,6 @@ public JSONObject serialiseInterface(SentryInterface sentryInterface) { JSONObject jsonObject = new JSONObject(); Throwable throwable = messageInterface.getThrowable(); jsonObject.put(TYPE_PARAMETER, throwable.getClass().getSimpleName()); - // TODO: The message can be empty here, should something be done? jsonObject.put(VALUE_PARAMETER, throwable.getMessage()); jsonObject.put(MODULE_PARAMETER, throwable.getClass().getPackage().getName()); return jsonObject; From 24e9b65453f1d727e3279e45844bc1a5f106b86c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 12:58:40 +0100 Subject: [PATCH 0146/2152] Avoid NPE when cloning date --- .../src/main/java/net/kencochrane/raven/event/LoggedEvent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java b/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java index 663bb2805c7..077f7f85155 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java +++ b/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java @@ -93,7 +93,7 @@ void setMessage(String message) { } public Date getTimestamp() { - return (Date) timestamp.clone(); + return (timestamp != null) ? (Date) timestamp.clone() : null; } void setTimestamp(Date timestamp) { From 696d36a2f94c464d816081585c703564a801ff1f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 12:59:55 +0100 Subject: [PATCH 0147/2152] Frames should be sent from the last to the first --- .../marshaller/SimpleJsonStackTraceInterfaceMarshaller.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java index 7dee3e2baeb..762f8a82cd1 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java @@ -5,6 +5,7 @@ import org.json.simple.JSONArray; import org.json.simple.JSONObject; +import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -82,6 +83,7 @@ public JSONObject serialiseInterface(SentryInterface sentryInterface) { } currentThrowable = currentThrowable.getCause(); } + Collections.reverse(frames); jsonObject.put(FRAMES_PARAMETER, frames); return jsonObject; From 23de56384350c4bbcf29473c6e986421a299e635 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 13:00:31 +0100 Subject: [PATCH 0148/2152] Fake frames are IN_APP frames --- .../marshaller/SimpleJsonStackTraceInterfaceMarshaller.java | 1 + 1 file changed, 1 insertion(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java index 762f8a82cd1..955b9b9673b 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java @@ -36,6 +36,7 @@ private JSONObject createFakeFrame(Throwable throwable) { if (throwable.getMessage() != null) message += " (\"" + throwable.getMessage() + "\")"; fakeFrame.put(FILENAME_PARAMETER, message); + fakeFrame.put(IN_APP_PARAMETER, true); return fakeFrame; } From 78443d428b149ccb93df3ae151c567cce4a607b7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 13:01:00 +0100 Subject: [PATCH 0149/2152] Do not send the java file name Right now the java file name would overwrite the module name in the sentry interface. It shouldn't be sent. As this could change (and the file name can be provided easily) the code is only commented. --- .../marshaller/SimpleJsonStackTraceInterfaceMarshaller.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java index 955b9b9673b..d529cbc4d2e 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java @@ -48,7 +48,8 @@ private JSONObject createFakeFrame(Throwable throwable) { */ private JSONObject createFrame(StackTraceElement stackTraceElement) { JSONObject currentFrame = new JSONObject(); - currentFrame.put(FILENAME_PARAMETER, stackTraceElement.getFileName()); + // Do not display the file name (irrelevant) as it replaces the module in the sentry interface. + //currentFrame.put(FILENAME_PARAMETER, stackTraceElement.getFileName()); currentFrame.put(MODULE_PARAMETER, stackTraceElement.getClassName()); currentFrame.put(IN_APP_PARAMETER, isFrameInApp(stackTraceElement)); currentFrame.put(FUNCTION_PARAMETER, stackTraceElement.getMethodName()); From dd79b95d7b7d7a6a5daa3139c4d499da8caf3a44 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 13:02:49 +0100 Subject: [PATCH 0150/2152] Do not add a fake frame for the first exception --- .../SimpleJsonStackTraceInterfaceMarshaller.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java index d529cbc4d2e..6569530c6e8 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java @@ -78,8 +78,13 @@ public JSONObject serialiseInterface(SentryInterface sentryInterface) { JSONObject jsonObject = new JSONObject(); JSONArray frames = new JSONArray(); Throwable currentThrowable = stackTraceInterface.getThrowable(); + boolean firstFrame = true; while (currentThrowable != null) { - frames.add(createFakeFrame(currentThrowable)); + if (firstFrame) { + firstFrame = false; + } else { + frames.add(createFakeFrame(currentThrowable)); + } for (StackTraceElement stackTraceElement : currentThrowable.getStackTrace()) { frames.add(createFrame(stackTraceElement)); } From 6f3f86387bd7492bfa490b2188f8d5ccbcef92c3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 14:19:24 +0100 Subject: [PATCH 0151/2152] Leave the charset choice to the DSN config --- raven/src/main/java/net/kencochrane/raven/connection/Dsn.java | 4 ++++ .../java/net/kencochrane/raven/connection/UdpConnection.java | 4 ++++ .../raven/connection/marshaller/SimpleJsonMarshaller.java | 1 - 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java b/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java index 776888c7021..087132cbdef 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java @@ -15,6 +15,10 @@ public class Dsn { * Option specific to raven-java, allowing to set a timeout (in ms) for a request to the Sentry server. */ public static final String TIMEOUT_OPTION = "raven.timeout"; + /** + * Option to set the charset for strings sent to sentry. + */ + public static final String CHARSET_OPTION = "raven.charset"; private String secretKey; private String publicKey; private String projectId; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index b10dd944479..9c83ac9e9ee 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -57,4 +57,8 @@ private void openSocket() { "to sentry", e); } } + + public void setCharset(Charset charset) { + this.charset = charset; + } } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java index d33096003b2..ecb2a5270e0 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java @@ -87,7 +87,6 @@ public class SimpleJsonMarshaller implements Marshaller { /** * Charset used to transmit data. */ - //TODO: Force or default to UTF-8? private Charset charset = Charset.defaultCharset(); @Override From 7364d55cada41cbc5e74f982734c07b5b3311682 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 14:20:15 +0100 Subject: [PATCH 0152/2152] Use a marshaller in the UPDConnection --- .../kencochrane/raven/connection/UdpConnection.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index 9c83ac9e9ee..2bb29485227 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -1,5 +1,7 @@ package net.kencochrane.raven.connection; +import net.kencochrane.raven.connection.marshaller.Marshaller; +import net.kencochrane.raven.connection.marshaller.SimpleJsonMarshaller; import net.kencochrane.raven.event.LoggedEvent; import java.io.ByteArrayOutputStream; @@ -20,6 +22,7 @@ public class UdpConnection extends AbstractConnection { private static final Logger logger = Logger.getLogger(UdpConnection.class.getCanonicalName()); private DatagramSocket socket; private Charset charset = Charset.defaultCharset(); + private Marshaller marshaller = new SimpleJsonMarshaller(); public UdpConnection(Dsn dsn) { super(dsn); @@ -31,8 +34,7 @@ public void send(LoggedEvent event) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); writeHeader(baos); - baos.flush(); - baos.close(); + marshaller.marshall(event, baos); byte[] message = baos.toByteArray(); DatagramPacket packet = new DatagramPacket(message, message.length); @@ -58,6 +60,10 @@ private void openSocket() { } } + public void setMarshaller(Marshaller marshaller) { + this.marshaller = marshaller; + } + public void setCharset(Charset charset) { this.charset = charset; } From e653a6fcb89f9510cefa937498da2db13a69c223 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 14:22:21 +0100 Subject: [PATCH 0153/2152] Let the marshaller close the stream If you want to close the stream manually later, wrap the stream so the close() method isn't accessible. --- .../connection/marshaller/Marshaller.java | 8 ++++--- .../marshaller/SimpleJsonMarshaller.java | 24 +++++++++++-------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/Marshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/Marshaller.java index a8b25f33ceb..52dbdb381c6 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/Marshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/Marshaller.java @@ -2,7 +2,6 @@ import net.kencochrane.raven.event.LoggedEvent; -import java.io.IOException; import java.io.OutputStream; /** @@ -11,10 +10,13 @@ public interface Marshaller { /** * Serialise an event and sends it through an {@code OutputStream}. + *

    + * The marshaller will close the given stream once it's done sending content. + * If it should stay open, use a wrapper that will intercept the call to {@code OutputStream#close()}. + *

    * * @param event event to serialise. * @param destination destination stream. - * @throws IOException occurs when the serialisation failed. */ - void marshall(LoggedEvent event, OutputStream destination) throws IOException; + void marshall(LoggedEvent event, OutputStream destination); } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java index ecb2a5270e0..53874fb4f17 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java @@ -18,6 +18,8 @@ import java.util.Date; import java.util.Map; import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.zip.DeflaterOutputStream; /** @@ -88,23 +90,25 @@ public class SimpleJsonMarshaller implements Marshaller { * Charset used to transmit data. */ private Charset charset = Charset.defaultCharset(); + private static final Logger logger = Logger.getLogger(SimpleJsonMarshaller.class.getCanonicalName()); @Override - public void marshall(LoggedEvent event, OutputStream destination) throws IOException { + public void marshall(LoggedEvent event, OutputStream destination) { OutputStream outputStream = new Base64OutputStream(destination); - DeflaterOutputStream compressionStream = null; - if (compression) - outputStream = compressionStream = new DeflaterOutputStream(outputStream); + outputStream = new DeflaterOutputStream(outputStream); Writer writer = new OutputStreamWriter(outputStream, charset); JSONObject jsonObject = encodeToJSONObject(event); - jsonObject.writeJSONString(writer); - - writer.flush(); - - if (compressionStream != null) { - compressionStream.finish(); + try { + jsonObject.writeJSONString(writer); + } catch (Exception e) { + logger.log(Level.SEVERE, "An exception occurred serialising the event.", e); + } finally { + try { + writer.close(); + } catch (IOException e) { + } } } From 8b001cac8b2515cfdac2a41d6568c0844182f7e9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 14:31:19 +0100 Subject: [PATCH 0154/2152] Enforce the immutability of throwables --- .../SimpleJsonExceptionInterfaceMarshaller.java | 7 ++++--- .../SimpleJsonStackTraceInterfaceMarshaller.java | 7 ++++--- .../raven/event/interfaces/ExceptionInterface.java | 4 ++-- .../raven/event/interfaces/ImmutableThrowable.java | 8 ++++++-- .../raven/event/interfaces/StackTraceInterface.java | 4 ++-- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonExceptionInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonExceptionInterfaceMarshaller.java index 8d671795ef9..74f33d8544d 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonExceptionInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonExceptionInterfaceMarshaller.java @@ -1,6 +1,7 @@ package net.kencochrane.raven.connection.marshaller; import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.ImmutableThrowable; import net.kencochrane.raven.event.interfaces.SentryInterface; import org.json.simple.JSONObject; @@ -18,10 +19,10 @@ public JSONObject serialiseInterface(SentryInterface sentryInterface) { ExceptionInterface messageInterface = (ExceptionInterface) sentryInterface; JSONObject jsonObject = new JSONObject(); - Throwable throwable = messageInterface.getThrowable(); - jsonObject.put(TYPE_PARAMETER, throwable.getClass().getSimpleName()); + ImmutableThrowable throwable = messageInterface.getThrowable(); + jsonObject.put(TYPE_PARAMETER, throwable.getActualClass().getSimpleName()); jsonObject.put(VALUE_PARAMETER, throwable.getMessage()); - jsonObject.put(MODULE_PARAMETER, throwable.getClass().getPackage().getName()); + jsonObject.put(MODULE_PARAMETER, throwable.getActualClass().getPackage().getName()); return jsonObject; } } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java index 6569530c6e8..3dfd2283f6f 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java @@ -1,5 +1,6 @@ package net.kencochrane.raven.connection.marshaller; +import net.kencochrane.raven.event.interfaces.ImmutableThrowable; import net.kencochrane.raven.event.interfaces.SentryInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.json.simple.JSONArray; @@ -30,9 +31,9 @@ public class SimpleJsonStackTraceInterfaceMarshaller implements SimpleJsonInterf * @param throwable Exception for which a fake frame should be created * @return a fake frame allowing to chain exceptions smoothly in Sentry. */ - private JSONObject createFakeFrame(Throwable throwable) { + private JSONObject createFakeFrame(ImmutableThrowable throwable) { JSONObject fakeFrame = new JSONObject(); - String message = "Caused by: " + throwable.getClass().getName(); + String message = "Caused by: " + throwable.getActualClass().getName(); if (throwable.getMessage() != null) message += " (\"" + throwable.getMessage() + "\")"; fakeFrame.put(FILENAME_PARAMETER, message); @@ -77,7 +78,7 @@ public JSONObject serialiseInterface(SentryInterface sentryInterface) { StackTraceInterface stackTraceInterface = (StackTraceInterface) sentryInterface; JSONObject jsonObject = new JSONObject(); JSONArray frames = new JSONArray(); - Throwable currentThrowable = stackTraceInterface.getThrowable(); + ImmutableThrowable currentThrowable = stackTraceInterface.getThrowable(); boolean firstFrame = true; while (currentThrowable != null) { if (firstFrame) { diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java index 2a326f82022..da281a7645a 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java @@ -2,7 +2,7 @@ public class ExceptionInterface implements SentryInterface { public static final String EXCEPTION_INTERFACE = "sentry.interfaces.Exception"; - private final Throwable throwable; + private final ImmutableThrowable throwable; public ExceptionInterface(Throwable throwable) { this.throwable = new ImmutableThrowable(throwable); @@ -13,7 +13,7 @@ public String getInterfaceName() { return EXCEPTION_INTERFACE; } - public Throwable getThrowable() { + public ImmutableThrowable getThrowable() { return throwable; } } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java index a7b4967fa11..943bdb36e7f 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java @@ -19,6 +19,10 @@ public ImmutableThrowable(Throwable actualThrowable) { this.actualThrowable = actualThrowable; } + public Class getActualClass(){ + return actualThrowable.getClass(); + } + @Override public String getMessage() { return actualThrowable.getMessage(); @@ -30,8 +34,8 @@ public String getLocalizedMessage() { } @Override - public Throwable getCause() { - return actualThrowable.getCause(); + public ImmutableThrowable getCause() { + return new ImmutableThrowable(actualThrowable.getCause()); } @Override diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java index 91995a6e026..0dbe468ca3f 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java @@ -4,7 +4,7 @@ public class StackTraceInterface implements SentryInterface { public static final String STACKTRACE_INTERFACE = "sentry.interfaces.Stacktrace"; - private final Throwable throwable; + private final ImmutableThrowable throwable; public StackTraceInterface(Throwable throwable) { this.throwable = new ImmutableThrowable(throwable); @@ -15,7 +15,7 @@ public String getInterfaceName() { return STACKTRACE_INTERFACE; } - public Throwable getThrowable() { + public ImmutableThrowable getThrowable() { return throwable; } } From 1494cf0b2a8c6bc82e02682629df49d7cf394f8c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 14:33:23 +0100 Subject: [PATCH 0155/2152] Fix imports --- .../kencochrane/raven/event/interfaces/MessageInterface.java | 4 +++- .../raven/event/interfaces/StackTraceInterface.java | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java index 93ecab8c6a0..4584c22318a 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java @@ -1,6 +1,8 @@ package net.kencochrane.raven.event.interfaces; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * The Message interface for Sentry allows to add a message that will be formatted by sentry. diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java index 0dbe468ca3f..2076a1af55e 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java @@ -1,7 +1,5 @@ package net.kencochrane.raven.event.interfaces; -import java.util.*; - public class StackTraceInterface implements SentryInterface { public static final String STACKTRACE_INTERFACE = "sentry.interfaces.Stacktrace"; private final ImmutableThrowable throwable; From 6e5c084852edb2720bc182f0f5c14b01ea0e3a95 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 14:33:53 +0100 Subject: [PATCH 0156/2152] Fix indentation --- .../kencochrane/raven/event/interfaces/ImmutableThrowable.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java index 943bdb36e7f..51436a0eb73 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java @@ -19,7 +19,7 @@ public ImmutableThrowable(Throwable actualThrowable) { this.actualThrowable = actualThrowable; } - public Class getActualClass(){ + public Class getActualClass() { return actualThrowable.getClass(); } From bfd60cec70a3c53c0f5fd9b24f06d7a1d9de3497 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 14:34:02 +0100 Subject: [PATCH 0157/2152] Use the module name for fake frames message --- .../marshaller/SimpleJsonStackTraceInterfaceMarshaller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java index 3dfd2283f6f..6b9310a6a8d 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java @@ -36,7 +36,7 @@ private JSONObject createFakeFrame(ImmutableThrowable throwable) { String message = "Caused by: " + throwable.getActualClass().getName(); if (throwable.getMessage() != null) message += " (\"" + throwable.getMessage() + "\")"; - fakeFrame.put(FILENAME_PARAMETER, message); + fakeFrame.put(MODULE_PARAMETER, message); fakeFrame.put(IN_APP_PARAMETER, true); return fakeFrame; } From abdfe9c4cd03403381b627fca1e62d1506898ca3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 14:35:09 +0100 Subject: [PATCH 0158/2152] Re organise HttpConnection to handle exceptions --- .../raven/connection/HttpConnection.java | 64 ++++++++++++------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 8f30efe349a..71f957d13c7 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -5,8 +5,10 @@ import net.kencochrane.raven.connection.marshaller.SimpleJsonMarshaller; import net.kencochrane.raven.event.LoggedEvent; +import java.io.BufferedReader; import java.io.IOException; -import java.io.OutputStream; +import java.io.InputStream; +import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.logging.Level; @@ -42,38 +44,52 @@ private URL getSentryUrl() { } } - private OutputStream getOutputStream() throws IOException { - HttpURLConnection connection = (HttpURLConnection) getSentryUrl().openConnection(); - connection.setRequestMethod("POST"); - connection.setDoOutput(true); - connection.setConnectTimeout(timeout); - connection.setRequestProperty(USER_AGENT, Utils.Client.NAME); - connection.setRequestProperty(SENTRY_AUTH, getAuthHeader()); - connection.connect(); - - return connection.getOutputStream(); + private HttpURLConnection getConnection() { + try { + HttpURLConnection connection = (HttpURLConnection) getSentryUrl().openConnection(); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + connection.setConnectTimeout(timeout); + connection.setRequestProperty(USER_AGENT, Utils.Client.NAME); + connection.setRequestProperty(SENTRY_AUTH, getAuthHeader()); + return connection; + } catch (IOException e) { + throw new IllegalStateException("Couldn't set up a connection to the sentry server.", e); + } } @Override public void send(LoggedEvent event) { - OutputStream out = null; + HttpURLConnection connection = getConnection(); try { - out = getOutputStream(); - marshaller.marshall(event, out); + connection.connect(); + marshaller.marshall(event, connection.getOutputStream()); + connection.getOutputStream().close(); + connection.getInputStream().close(); } catch (IOException e) { - logger.log(Level.SEVERE, - "An exception occurred while trying to establish a connection to the sentry server", e); - } finally { - try { - if (out != null) { - out.flush(); - out.close(); - } - } catch (IOException e) { + if (connection.getErrorStream() != null) { + logger.log(Level.SEVERE, getErrorMessageFromStream(connection.getErrorStream()), e); + } else { logger.log(Level.SEVERE, - "An exception occurred while closing the connection to the sentry server", e); + "An exception occurred while submitting the event to the sentry server.", e); } + } finally { + connection.disconnect(); + } + } + + private String getErrorMessageFromStream(InputStream errorStream) { + BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream)); + StringBuilder sb = new StringBuilder(); + try { + String line; + while ((line = reader.readLine()) != null) + sb.append(line); + + } catch (Exception e2) { + logger.log(Level.SEVERE, "Exception while reading the error message from the connection.", e2); } + return sb.toString(); } public void setTimeout(int timeout) { From 1f665f28859199970c77fdfea545307775cce89c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 14:36:34 +0100 Subject: [PATCH 0159/2152] Hardcode (bad) sensible defaults for notInApp --- .../SimpleJsonStackTraceInterfaceMarshaller.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java index 6b9310a6a8d..d36191382a9 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java @@ -25,6 +25,17 @@ public class SimpleJsonStackTraceInterfaceMarshaller implements SimpleJsonInterf //TODO: add a way to add content here. private Set notInAppFrames = new HashSet(); + //TODO: Remove this attempt to set a sensible default setting. + { + notInAppFrames.add("com.sun."); + notInAppFrames.add("java."); + notInAppFrames.add("javax."); + notInAppFrames.add("org.omg."); + notInAppFrames.add("sun."); + notInAppFrames.add("junit."); + notInAppFrames.add("com.intellij.rt."); + } + /** * Create a fake frame to allow chained exceptions. * From c2798844c74bd20ee75b21d99d21520bbe3541fc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 15:24:54 +0100 Subject: [PATCH 0160/2152] Do not throw an exception during fillInStackTrace --- .../kencochrane/raven/event/interfaces/ImmutableThrowable.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java index 51436a0eb73..5dad5b2b4eb 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java @@ -65,7 +65,7 @@ public void printStackTrace(PrintWriter s) { @Override public Throwable fillInStackTrace() { - throw new UnsupportedOperationException(); + return null; } @Override From 649593c6082db1b905c47d715aea49281c253d84 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 15:25:44 +0100 Subject: [PATCH 0161/2152] Do not wrap the cause when it's null --- .../kencochrane/raven/event/interfaces/ImmutableThrowable.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java index 5dad5b2b4eb..43e30b3c135 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java @@ -35,7 +35,8 @@ public String getLocalizedMessage() { @Override public ImmutableThrowable getCause() { - return new ImmutableThrowable(actualThrowable.getCause()); + Throwable cause = actualThrowable.getCause(); + return (cause != null) ? new ImmutableThrowable(cause) : null; } @Override From 52863212860c414519c79bf69c8d59bb41786108 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 15:51:56 +0100 Subject: [PATCH 0162/2152] Create the sentry URI when the DSN is created --- .../net/kencochrane/raven/connection/Dsn.java | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java b/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java index 087132cbdef..13f7f79d169 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java @@ -1,6 +1,7 @@ package net.kencochrane.raven.connection; import java.net.URI; +import java.net.URISyntaxException; import java.util.*; /** @@ -28,6 +29,7 @@ public class Dsn { private String path; private Set protocolSettings; private Map options; + private URI uri; /** * Creates a DS based on a String. @@ -38,14 +40,20 @@ public Dsn(String dsn) { options = new HashMap(); protocolSettings = new HashSet(); - URI uri = URI.create(dsn); - extractProtocolInfo(uri); - extractUserKeys(uri); - extractHostInfo(uri); - extractPathInfo(uri); - extractOptions(uri); + URI dsnUri = URI.create(dsn); + extractProtocolInfo(dsnUri); + extractUserKeys(dsnUri); + extractHostInfo(dsnUri); + extractPathInfo(dsnUri); + extractOptions(dsnUri); makeOptionsImmutable(); + + try { + uri = new URI(protocol, null, host, port, path, null, null); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Impossible to determine Sentry's URI from the DSN '"+dsn+"'"); + } } private void extractPathInfo(URI uri) { @@ -129,11 +137,8 @@ public Map getOptions() { * Creates the URI of the sentry server. * * @return the URI of the sentry server. - * @throws Exception if an URI couldn't be created. */ - //TODO: Return as a String instead? - //TODO: Exception, really? - public URI getUri() throws Exception { - return new URI(protocol, null, host, port, path, null, null); + public URI getUri() { + return uri; } } From 5a599b6d368e2aa7b25b8d9ab564eb4f92e075e4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 15:53:47 +0100 Subject: [PATCH 0163/2152] Allow HTTPS with invalid certificates (optional). --- .../net/kencochrane/raven/connection/Dsn.java | 4 ++++ .../raven/connection/HttpConnection.java | 23 ++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java b/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java index 13f7f79d169..2741e273903 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java @@ -20,6 +20,10 @@ public class Dsn { * Option to set the charset for strings sent to sentry. */ public static final String CHARSET_OPTION = "raven.charset"; + /** + * Protocol setting to disable security checks over an SSL connection. + */ + public static final String NAIVE_PROTOCOL = "naive"; private String secretKey; private String publicKey; private String projectId; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 71f957d13c7..c96c972a320 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -5,6 +5,9 @@ import net.kencochrane.raven.connection.marshaller.SimpleJsonMarshaller; import net.kencochrane.raven.event.LoggedEvent; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSession; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -15,15 +18,22 @@ import java.util.logging.Logger; /** - * Basic connection to a Sentry server, using HTTP. + * Basic connection to a Sentry server, using HTTP and HTTPS. */ public class HttpConnection extends AbstractConnection { public static final String USER_AGENT = "User-Agent"; private static final Logger logger = Logger.getLogger(HttpConnection.class.getCanonicalName()); private static final String SENTRY_AUTH = "X-Sentry-Auth"; private static final int DEFAULT_TIMEOUT = 10000; + private static final HostnameVerifier NAIVE_VERIFIER = new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession sslSession) { + return true; + } + }; private Marshaller marshaller = new SimpleJsonMarshaller(); private int timeout = DEFAULT_TIMEOUT; + private boolean bypassSecurity; public HttpConnection(Dsn dsn) { super(dsn); @@ -31,6 +41,10 @@ public HttpConnection(Dsn dsn) { // Check if a timeout is set if (dsn.getOptions().containsKey(Dsn.TIMEOUT_OPTION)) setTimeout(Integer.parseInt(dsn.getOptions().get(Dsn.TIMEOUT_OPTION))); + + // Check if the naive mode is on + if (dsn.getProtocolSettings().contains(Dsn.NAIVE_PROTOCOL)) + setBypassSecurity(true); } private URL getSentryUrl() { @@ -47,6 +61,9 @@ private URL getSentryUrl() { private HttpURLConnection getConnection() { try { HttpURLConnection connection = (HttpURLConnection) getSentryUrl().openConnection(); + if (bypassSecurity && connection instanceof HttpsURLConnection) { + ((HttpsURLConnection) connection).setHostnameVerifier(NAIVE_VERIFIER); + } connection.setRequestMethod("POST"); connection.setDoOutput(true); connection.setConnectTimeout(timeout); @@ -99,4 +116,8 @@ public void setTimeout(int timeout) { public void setMarshaller(Marshaller marshaller) { this.marshaller = marshaller; } + + public void setBypassSecurity(boolean bypassSecurity) { + this.bypassSecurity = bypassSecurity; + } } From 28b4afda590fb86516225f7dc3d39e0395fd6de1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 15:55:10 +0100 Subject: [PATCH 0164/2152] Build the sentry URL only once --- .../kencochrane/raven/connection/HttpConnection.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index c96c972a320..083a6e1e58e 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -13,6 +13,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; +import java.net.MalformedURLException; import java.net.URL; import java.util.logging.Level; import java.util.logging.Logger; @@ -31,6 +32,7 @@ public boolean verify(String hostname, SSLSession sslSession) { return true; } }; + private final URL sentryUrl; private Marshaller marshaller = new SimpleJsonMarshaller(); private int timeout = DEFAULT_TIMEOUT; private boolean bypassSecurity; @@ -38,6 +40,8 @@ public boolean verify(String hostname, SSLSession sslSession) { public HttpConnection(Dsn dsn) { super(dsn); + sentryUrl = getSentryUrl(); + // Check if a timeout is set if (dsn.getOptions().containsKey(Dsn.TIMEOUT_OPTION)) setTimeout(Integer.parseInt(dsn.getOptions().get(Dsn.TIMEOUT_OPTION))); @@ -50,17 +54,15 @@ public HttpConnection(Dsn dsn) { private URL getSentryUrl() { try { String url = getDsn().getUri().toString() + "api/" + getDsn().getProjectId() + "/store/"; - //TODO: Cache the URL? return new URL(url); - } catch (Exception e) { - // TODO: Runtime exception... Really??? - throw new RuntimeException("Couldn't get a valid URL from the DSN", e); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Couldn't get a valid URL from the DSN.", e); } } private HttpURLConnection getConnection() { try { - HttpURLConnection connection = (HttpURLConnection) getSentryUrl().openConnection(); + HttpURLConnection connection = (HttpURLConnection) sentryUrl.openConnection(); if (bypassSecurity && connection instanceof HttpsURLConnection) { ((HttpsURLConnection) connection).setHostnameVerifier(NAIVE_VERIFIER); } From d21e94dfd74bec05232292c90855d259059f0d19 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 18:11:44 +0100 Subject: [PATCH 0165/2152] Use a DaemonThreadFactory for AsyncConnection --- .../raven/connection/AsyncConnection.java | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 77445eca153..3163aecd052 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -4,6 +4,10 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Asynchronous usage of a connection. @@ -13,12 +17,13 @@ *

    */ public class AsyncConnection implements Connection { + private static final Logger logger = Logger.getLogger(AsyncConnection.class.getCanonicalName()); /** * Connection used to actually send the events. */ private final Connection actualConnection; // TODO: Allow the user to change to a custom executor? - private final ExecutorService executorService = Executors.newCachedThreadPool(); + private final ExecutorService executorService = Executors.newCachedThreadPool(new DaemonThreadFactory()); /** * Create a connection which will rely on an executor to send events. @@ -35,6 +40,28 @@ public void send(LoggedEvent event) { executorService.execute(new LoggedEventSubmitter(event)); } + private static final class DaemonThreadFactory implements ThreadFactory { + private static final AtomicInteger poolNumber = new AtomicInteger(1); + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + + private DaemonThreadFactory() { + SecurityManager s = System.getSecurityManager(); + group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); + namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; + } + + public Thread newThread(Runnable r) { + Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); + if (!t.isDaemon()) + t.setDaemon(true); + if (t.getPriority() != Thread.NORM_PRIORITY) + t.setPriority(Thread.NORM_PRIORITY); + return t; + } + } + /** * Simple runnable using the {@link #send(net.kencochrane.raven.event.LoggedEvent)} method of the * {@link #actualConnection}. @@ -48,7 +75,11 @@ private LoggedEventSubmitter(LoggedEvent event) { @Override public void run() { - actualConnection.send(event); + try { + actualConnection.send(event); + } catch (Exception e) { + logger.log(Level.SEVERE, "An exception occurred while sending the event to Sentry.", e); + } } } } From eddc3de3e4df2fb9be99f4f3b99b7968eb91cd42 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 18:12:35 +0100 Subject: [PATCH 0166/2152] Change the compression option to nocompression --- raven/src/main/java/net/kencochrane/raven/connection/Dsn.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java b/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java index 2741e273903..742dbebbaee 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java @@ -9,9 +9,9 @@ */ public class Dsn { /** - * Option specific to raven-java, allowing to enable/disable the compression of the requests to the Sentry Server. + * Option specific to raven-java, allowing to disable the compression of requests to the Sentry Server. */ - public static final String COMPRESSION_OPTION = "raven.compression"; + public static final String NOCOMPRESSION_OPTION = "raven.nocompression"; /** * Option specific to raven-java, allowing to set a timeout (in ms) for a request to the Sentry server. */ From 5e093221ef25bed53e52475b012682931276d594 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 18:12:59 +0100 Subject: [PATCH 0167/2152] Add a new async option --- raven/src/main/java/net/kencochrane/raven/connection/Dsn.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java b/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java index 742dbebbaee..163ec5f90ac 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java @@ -16,6 +16,10 @@ public class Dsn { * Option specific to raven-java, allowing to set a timeout (in ms) for a request to the Sentry server. */ public static final String TIMEOUT_OPTION = "raven.timeout"; + /** + * Option to send events asynchronously. + */ + public static final String ASYNC_OPTION = "raven.async"; /** * Option to set the charset for strings sent to sentry. */ From e11adcda2ea12ae478cdfb885f0d61827d05b8f4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 18:13:30 +0100 Subject: [PATCH 0168/2152] Reduce constant visibility --- .../java/net/kencochrane/raven/connection/HttpConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 083a6e1e58e..7c8d85a25a2 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -22,7 +22,7 @@ * Basic connection to a Sentry server, using HTTP and HTTPS. */ public class HttpConnection extends AbstractConnection { - public static final String USER_AGENT = "User-Agent"; + private static final String USER_AGENT = "User-Agent"; private static final Logger logger = Logger.getLogger(HttpConnection.class.getCanonicalName()); private static final String SENTRY_AUTH = "X-Sentry-Auth"; private static final int DEFAULT_TIMEOUT = 10000; From 78082e8e7f9297e8d6277eb098853236618fe34b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 18:13:42 +0100 Subject: [PATCH 0169/2152] Define equals/hashCode/toString in LoggedEvent --- .../kencochrane/raven/event/LoggedEvent.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java b/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java index 077f7f85155..c82f5cd3500 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java +++ b/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java @@ -171,4 +171,27 @@ public static enum Level { INFO, DEBUG } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + return id.equals(((LoggedEvent) o).id); + + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public String toString() { + return "LoggedEvent{" + + "level=" + level + + ", message='" + message + '\'' + + ", logger='" + logger + '\'' + + '}'; + } } From 2c2da5b7b0bfae85d61e73142460ba9f0950a38f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 18:14:13 +0100 Subject: [PATCH 0170/2152] Add details on the StackTraceInterface --- .../raven/event/interfaces/StackTraceInterface.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java index 2076a1af55e..0abdb2d8246 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java @@ -4,6 +4,9 @@ public class StackTraceInterface implements SentryInterface { public static final String STACKTRACE_INTERFACE = "sentry.interfaces.Stacktrace"; private final ImmutableThrowable throwable; + //TODO: Base this interface on a unique stacktrace (rather than an entire exception) + //This should be done when the exception system in Sentry will be improved to support chained exception + //For now, a fake stacktrace (containing the parent exceptions and their stacktraces) will be used. public StackTraceInterface(Throwable throwable) { this.throwable = new ImmutableThrowable(throwable); } From a019bf100ca8b3a1ece360c586c43c694460993b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 18:22:41 +0100 Subject: [PATCH 0171/2152] Big cleanup --- docs/allclasses-frame.html | 35 - docs/allclasses-noframe.html | 35 - docs/constant-values.html | 142 ---- docs/deprecated-list.html | 142 ---- docs/help-doc.html | 209 ------ docs/index-files/index-1.html | 141 ---- docs/index-files/index-2.html | 150 ----- docs/index-files/index-3.html | 196 ------ docs/index-files/index-4.html | 142 ---- docs/index-files/index-5.html | 138 ---- docs/index-files/index-6.html | 155 ----- docs/index-files/index-7.html | 170 ----- docs/index-files/index-8.html | 141 ---- docs/index-files/index-9.html | 141 ---- docs/index.html | 36 -- docs/net/kencochrane/sentry/RavenConfig.html | 609 ------------------ docs/net/kencochrane/sentry/RavenUtils.html | 509 --------------- .../kencochrane/sentry/SentryAppender.html | 402 ------------ .../net/kencochrane/sentry/SentryExample.html | 252 -------- .../net/kencochrane/sentry/package-frame.html | 36 -- .../kencochrane/sentry/package-summary.html | 168 ----- docs/net/kencochrane/sentry/package-tree.html | 149 ----- docs/overview-tree.html | 151 ----- docs/package-list | 1 - docs/resources/inherit.gif | Bin 57 -> 0 bytes docs/stylesheet.css | 29 - old/README.rst | 212 ------ old/pom.xml | 124 ---- .../net/kencochrane/sentry/RavenClient.java | 418 ------------ .../net/kencochrane/sentry/RavenConfig.java | 213 ------ .../net/kencochrane/sentry/RavenUtils.java | 248 ------- .../kencochrane/sentry/SentryAppender.java | 106 --- .../net/kencochrane/sentry/SentryQueue.java | 75 --- .../net/kencochrane/sentry/SentryWorker.java | 83 --- .../kencochrane/sentry/PerformanceTest.java | 44 -- .../kencochrane/sentry/RavenConfigTest.java | 26 - .../kencochrane/sentry/SentryClientTest.java | 73 --- .../net/kencochrane/sentry/SentryExample.java | 61 -- .../test/resources/log4j_configuration.txt | 28 - raven-integration-tests/README.md | 22 - raven-integration-tests/pom.xml | 111 ---- .../java/net/kencochrane/raven/SentryApi.java | 187 ------ .../src/main/resources/Procfile | 2 - .../src/main/resources/boot_sentry.db | Bin 581632 -> 0 bytes .../src/main/resources/default_config.py | 49 -- .../src/main/resources/run_sentry.sh | 5 - .../raven/integration/ClientTest.java | 145 ----- .../raven/integration/IntegrationContext.java | 35 - .../log4j/AsyncSentryAppenderTest.java | 153 ----- .../integration/log4j/SentryAppenderTest.java | 148 ----- .../asyncsentryappender.log4j.properties | 15 - .../resources/sentryappender.log4j.properties | 23 - .../raven/log4j/AsyncSentryAppender.java | 76 --- .../net/kencochrane/raven/log4j/Log4jMDC.java | 39 -- .../raven/log4j/SentryAppender.java | 173 ----- .../raven/log4j/AsyncSentryAppenderTest.java | 182 ------ .../raven/log4j/SentryAppenderTest.java | 250 ------- ...syncsentryappender-no-dsn.log4j.properties | 24 - .../asyncsentryappender.log4j.properties | 23 - .../sentryappender-no-dsn.log4j.properties | 24 - .../resources/sentryappender.log4j.properties | 23 - .../kencochrane/raven/logback/LogbackMDC.java | 39 -- .../raven/logback/SentryAppender.java | 178 ----- .../raven/logback/SentryAppenderTest.java | 235 ------- .../sentryappender-no-dsn.logback.xml | 17 - .../test/resources/sentryappender.logback.xml | 18 - .../net/kencochrane/raven/AsyncTransport.java | 203 ------ .../java/net/kencochrane/raven/Client.java | 514 --------------- .../java/net/kencochrane/raven/Events.java | 127 ---- .../java/net/kencochrane/raven/SentryDsn.java | 412 ------------ .../java/net/kencochrane/raven/Transport.java | 301 --------- .../java/net/kencochrane/raven/Utils.java | 108 ---- .../ext/RavenServletRequestListener.java | 35 - .../raven/ext/ServletJSONProcessor.java | 137 ---- .../kencochrane/raven/spi/JSONProcessor.java | 47 -- .../net/kencochrane/raven/spi/RavenMDC.java | 70 -- .../kencochrane/raven/AsyncTransportTest.java | 57 -- .../net/kencochrane/raven/ClientExample.java | 53 -- .../kencochrane/raven/ClientSetupTest.java | 237 ------- .../kencochrane/raven/HttpTransportTest.java | 167 ----- .../kencochrane/raven/JSONProcessorTest.java | 70 -- .../net/kencochrane/raven/SentryDsnTest.java | 150 ----- .../kencochrane/raven/UdpTransportTest.java | 92 --- .../java/net/kencochrane/raven/UtilsTest.java | 17 - .../raven/ext/ServletJSONProcessorTest.java | 152 ----- .../resources/java.util.logging.properties | 4 - 86 files changed, 11139 deletions(-) delete mode 100644 docs/allclasses-frame.html delete mode 100644 docs/allclasses-noframe.html delete mode 100644 docs/constant-values.html delete mode 100644 docs/deprecated-list.html delete mode 100644 docs/help-doc.html delete mode 100644 docs/index-files/index-1.html delete mode 100644 docs/index-files/index-2.html delete mode 100644 docs/index-files/index-3.html delete mode 100644 docs/index-files/index-4.html delete mode 100644 docs/index-files/index-5.html delete mode 100644 docs/index-files/index-6.html delete mode 100644 docs/index-files/index-7.html delete mode 100644 docs/index-files/index-8.html delete mode 100644 docs/index-files/index-9.html delete mode 100644 docs/index.html delete mode 100644 docs/net/kencochrane/sentry/RavenConfig.html delete mode 100644 docs/net/kencochrane/sentry/RavenUtils.html delete mode 100644 docs/net/kencochrane/sentry/SentryAppender.html delete mode 100644 docs/net/kencochrane/sentry/SentryExample.html delete mode 100644 docs/net/kencochrane/sentry/package-frame.html delete mode 100644 docs/net/kencochrane/sentry/package-summary.html delete mode 100644 docs/net/kencochrane/sentry/package-tree.html delete mode 100644 docs/overview-tree.html delete mode 100644 docs/package-list delete mode 100644 docs/resources/inherit.gif delete mode 100644 docs/stylesheet.css delete mode 100644 old/README.rst delete mode 100644 old/pom.xml delete mode 100644 old/src/main/java/net/kencochrane/sentry/RavenClient.java delete mode 100644 old/src/main/java/net/kencochrane/sentry/RavenConfig.java delete mode 100644 old/src/main/java/net/kencochrane/sentry/RavenUtils.java delete mode 100644 old/src/main/java/net/kencochrane/sentry/SentryAppender.java delete mode 100644 old/src/main/java/net/kencochrane/sentry/SentryQueue.java delete mode 100644 old/src/main/java/net/kencochrane/sentry/SentryWorker.java delete mode 100644 old/src/test/java/net/kencochrane/sentry/PerformanceTest.java delete mode 100644 old/src/test/java/net/kencochrane/sentry/RavenConfigTest.java delete mode 100644 old/src/test/java/net/kencochrane/sentry/SentryClientTest.java delete mode 100644 old/src/test/java/net/kencochrane/sentry/SentryExample.java delete mode 100644 old/src/test/resources/log4j_configuration.txt delete mode 100644 raven-integration-tests/README.md delete mode 100644 raven-integration-tests/pom.xml delete mode 100644 raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java delete mode 100644 raven-integration-tests/src/main/resources/Procfile delete mode 100644 raven-integration-tests/src/main/resources/boot_sentry.db delete mode 100644 raven-integration-tests/src/main/resources/default_config.py delete mode 100755 raven-integration-tests/src/main/resources/run_sentry.sh delete mode 100644 raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java delete mode 100644 raven-integration-tests/src/test/java/net/kencochrane/raven/integration/IntegrationContext.java delete mode 100644 raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/AsyncSentryAppenderTest.java delete mode 100644 raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java delete mode 100644 raven-integration-tests/src/test/resources/asyncsentryappender.log4j.properties delete mode 100644 raven-integration-tests/src/test/resources/sentryappender.log4j.properties delete mode 100644 raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java delete mode 100644 raven-log4j/src/main/java/net/kencochrane/raven/log4j/Log4jMDC.java delete mode 100644 raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java delete mode 100644 raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java delete mode 100644 raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java delete mode 100644 raven-log4j/src/test/resources/asyncsentryappender-no-dsn.log4j.properties delete mode 100644 raven-log4j/src/test/resources/asyncsentryappender.log4j.properties delete mode 100644 raven-log4j/src/test/resources/sentryappender-no-dsn.log4j.properties delete mode 100644 raven-log4j/src/test/resources/sentryappender.log4j.properties delete mode 100644 raven-logback/src/main/java/net/kencochrane/raven/logback/LogbackMDC.java delete mode 100644 raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java delete mode 100644 raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java delete mode 100644 raven-logback/src/test/resources/sentryappender-no-dsn.logback.xml delete mode 100644 raven-logback/src/test/resources/sentryappender.logback.xml delete mode 100644 raven/src/main/java/net/kencochrane/raven/AsyncTransport.java delete mode 100644 raven/src/main/java/net/kencochrane/raven/Client.java delete mode 100644 raven/src/main/java/net/kencochrane/raven/Events.java delete mode 100644 raven/src/main/java/net/kencochrane/raven/SentryDsn.java delete mode 100644 raven/src/main/java/net/kencochrane/raven/Transport.java delete mode 100644 raven/src/main/java/net/kencochrane/raven/Utils.java delete mode 100644 raven/src/main/java/net/kencochrane/raven/ext/RavenServletRequestListener.java delete mode 100644 raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java delete mode 100644 raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java delete mode 100644 raven/src/main/java/net/kencochrane/raven/spi/RavenMDC.java delete mode 100644 raven/src/test/java/net/kencochrane/raven/AsyncTransportTest.java delete mode 100644 raven/src/test/java/net/kencochrane/raven/ClientExample.java delete mode 100644 raven/src/test/java/net/kencochrane/raven/ClientSetupTest.java delete mode 100644 raven/src/test/java/net/kencochrane/raven/HttpTransportTest.java delete mode 100644 raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java delete mode 100644 raven/src/test/java/net/kencochrane/raven/SentryDsnTest.java delete mode 100644 raven/src/test/java/net/kencochrane/raven/UdpTransportTest.java delete mode 100644 raven/src/test/java/net/kencochrane/raven/UtilsTest.java delete mode 100644 raven/src/test/java/net/kencochrane/raven/ext/ServletJSONProcessorTest.java delete mode 100644 raven/src/test/resources/java.util.logging.properties diff --git a/docs/allclasses-frame.html b/docs/allclasses-frame.html deleted file mode 100644 index e7b4c4f89a4..00000000000 --- a/docs/allclasses-frame.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - -All Classes - - - - - - - - - - - -All Classes -
    - - - - - -
    RavenConfig -
    -RavenUtils -
    -SentryAppender -
    -
    - - - diff --git a/docs/allclasses-noframe.html b/docs/allclasses-noframe.html deleted file mode 100644 index 2752d537041..00000000000 --- a/docs/allclasses-noframe.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - -All Classes - - - - - - - - - - - -All Classes -
    - - - - - -
    RavenConfig -
    -RavenUtils -
    -SentryAppender -
    -
    - - - diff --git a/docs/constant-values.html b/docs/constant-values.html deleted file mode 100644 index 537ac550158..00000000000 --- a/docs/constant-values.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - -Constant Field Values - - - - - - - - - - - - -
    - - - -
    - - - - - - - - - - - -
    - -
    - - - -
    -
    -

    -Constant Field Values

    -
    -
    -Contents
      -
    - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/deprecated-list.html b/docs/deprecated-list.html deleted file mode 100644 index a018fbe56d6..00000000000 --- a/docs/deprecated-list.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - -Deprecated List - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    -
    -

    -Deprecated API

    -
    -
    -Contents
      -
    - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/help-doc.html b/docs/help-doc.html deleted file mode 100644 index f2ea01c3dc4..00000000000 --- a/docs/help-doc.html +++ /dev/null @@ -1,209 +0,0 @@ - - - - - - -API Help - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    -
    -

    -How This API Document Is Organized

    -
    -This API (Application Programming Interface) document has pages corresponding to the items in the navigation bar, described as follows.

    -Package

    -
    - -

    -Each package has a page that contains a list of its classes and interfaces, with a summary for each. This page can contain four categories:

      -
    • Interfaces (italic)
    • Classes
    • Enums
    • Exceptions
    • Errors
    • Annotation Types
    -
    -

    -Class/Interface

    -
    - -

    -Each class, interface, nested class and nested interface has its own separate page. Each of these pages has three sections consisting of a class/interface description, summary tables, and detailed member descriptions:

      -
    • Class inheritance diagram
    • Direct Subclasses
    • All Known Subinterfaces
    • All Known Implementing Classes
    • Class/interface declaration
    • Class/interface description -

      -

    • Nested Class Summary
    • Field Summary
    • Constructor Summary
    • Method Summary -

      -

    • Field Detail
    • Constructor Detail
    • Method Detail
    -Each summary entry contains the first sentence from the detailed description for that item. The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.
    - -

    -Annotation Type

    -
    - -

    -Each annotation type has its own separate page with the following sections:

      -
    • Annotation Type declaration
    • Annotation Type description
    • Required Element Summary
    • Optional Element Summary
    • Element Detail
    -
    - -

    -Enum

    -
    - -

    -Each enum has its own separate page with the following sections:

      -
    • Enum declaration
    • Enum description
    • Enum Constant Summary
    • Enum Constant Detail
    -
    -

    -Tree (Class Hierarchy)

    -
    -There is a Class Hierarchy page for all packages, plus a hierarchy for each package. Each hierarchy page contains a list of classes and a list of interfaces. The classes are organized by inheritance structure starting with java.lang.Object. The interfaces do not inherit from java.lang.Object.
      -
    • When viewing the Overview page, clicking on "Tree" displays the hierarchy for all packages.
    • When viewing a particular package, class or interface page, clicking "Tree" displays the hierarchy for only that package.
    -
    -

    -Deprecated API

    -
    -The Deprecated API page lists all of the API that have been deprecated. A deprecated API is not recommended for use, generally due to improvements, and a replacement API is usually given. Deprecated APIs may be removed in future implementations.
    -

    -Index

    -
    -The Index contains an alphabetic list of all classes, interfaces, constructors, methods, and fields.
    -

    -Prev/Next

    -These links take you to the next or previous class, interface, package, or related page.

    -Frames/No Frames

    -These links show and hide the HTML frames. All pages are available with or without frames. -

    -

    -Serialized Form

    -Each serializable or externalizable class has a description of its serialization fields and methods. This information is of interest to re-implementors, not to developers using the API. While there is no link in the navigation bar, you can get to this information by going to any serialized class and clicking "Serialized Form" in the "See also" section of the class description. -

    -

    -Constant Field Values

    -The Constant Field Values page lists the static final fields and their values. -

    - - -This help file applies to API documentation generated using the standard doclet. - -
    -


    - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/index-files/index-1.html b/docs/index-files/index-1.html deleted file mode 100644 index cfde7705385..00000000000 --- a/docs/index-files/index-1.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - -A-Index - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    -

    -A

    -
    -
    append(LoggingEvent) - -Method in class net.kencochrane.sentry.SentryAppender -
      -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    - - - diff --git a/docs/index-files/index-2.html b/docs/index-files/index-2.html deleted file mode 100644 index 35d190bea13..00000000000 --- a/docs/index-files/index-2.html +++ /dev/null @@ -1,150 +0,0 @@ - - - - - - -C-Index - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    -

    -C

    -
    -
    calculateChecksum(String) - -Static method in class net.kencochrane.sentry.RavenUtils -
    An almost-unique hash identifying the this event to improve aggregation. -
    calculateHMAC(String, String) - -Static method in class net.kencochrane.sentry.RavenUtils -
    Computes RFC 2104-compliant HMAC signature. -
    close() - -Method in class net.kencochrane.sentry.SentryAppender -
      -
    compressAndEncode(String) - -Static method in class net.kencochrane.sentry.RavenUtils -
    Gzip then base64 encode the str value -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    - - - diff --git a/docs/index-files/index-3.html b/docs/index-files/index-3.html deleted file mode 100644 index f7a73534998..00000000000 --- a/docs/index-files/index-3.html +++ /dev/null @@ -1,196 +0,0 @@ - - - - - - -G-Index - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    -

    -G

    -
    -
    getDateAsISO8601String(Date) - -Static method in class net.kencochrane.sentry.RavenUtils -
    Convert a java date into the ISO8601 format YYYYMMDDTHH:mm:ss+HH:00 - note the added colon for the Timezone -
    getHost() - -Method in class net.kencochrane.sentry.RavenConfig -
    The sentry server host -
    getHostname() - -Static method in class net.kencochrane.sentry.RavenUtils -
    Get the hostname for the system throwing the error. -
    getPath() - -Method in class net.kencochrane.sentry.RavenConfig -
    sentry url path -
    getPort() - -Method in class net.kencochrane.sentry.RavenConfig -
    sentry server port -
    getProjectId() - -Method in class net.kencochrane.sentry.RavenConfig -
    Sentry project Id -
    getProtocol() - -Method in class net.kencochrane.sentry.RavenConfig -
    Sentry server protocol http https? -
    getProxy() - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    getProxy() - -Method in class net.kencochrane.sentry.SentryAppender -
      -
    getPublicKey() - -Method in class net.kencochrane.sentry.RavenConfig -
    The Sentry public key -
    getRandomUUID() - -Static method in class net.kencochrane.sentry.RavenUtils -
    Hexadecimal string representing a uuid4 value. -
    getSecretKey() - -Method in class net.kencochrane.sentry.RavenConfig -
    The Sentry secret key -
    getSentry_dsn() - -Method in class net.kencochrane.sentry.SentryAppender -
      -
    getSentryURL() - -Method in class net.kencochrane.sentry.RavenConfig -
    The Sentry server URL that we post the message to. -
    getSignature(String, long, String) - -Static method in class net.kencochrane.sentry.RavenUtils -
    build the HMAC sentry signature -

    - The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, - and an arbitrary client version string. -

    getTimestampLong() - -Static method in class net.kencochrane.sentry.RavenUtils -
    Get the timestamp for right now as a long -
    getTimestampString(long) - -Static method in class net.kencochrane.sentry.RavenUtils -
    Given a long timestamp convert to a ISO8601 formatted date string -
    getTimestampString() - -Static method in class net.kencochrane.sentry.RavenUtils -
    Given the time right now return a ISO8601 formatted date string -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    - - - diff --git a/docs/index-files/index-4.html b/docs/index-files/index-4.html deleted file mode 100644 index 356aa1c1dde..00000000000 --- a/docs/index-files/index-4.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - -H-Index - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    -

    -H

    -
    -
    hexEncode(byte[]) - -Static method in class net.kencochrane.sentry.RavenUtils -
    The byte[] returned by MessageDigest does not have a nice - textual representation, so some form of encoding is usually performed. -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    - - - diff --git a/docs/index-files/index-5.html b/docs/index-files/index-5.html deleted file mode 100644 index 7e13001cf05..00000000000 --- a/docs/index-files/index-5.html +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - -N-Index - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    -

    -N

    -
    -
    net.kencochrane.sentry - package net.kencochrane.sentry
     
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    - - - diff --git a/docs/index-files/index-6.html b/docs/index-files/index-6.html deleted file mode 100644 index 9c15e42b7a3..00000000000 --- a/docs/index-files/index-6.html +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - -R-Index - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    -

    -R

    -
    -
    RavenConfig - Class in net.kencochrane.sentry
    User: ken cochrane - Date: 2/8/12 - Time: 1:16 PM
    RavenConfig(String) - -Constructor for class net.kencochrane.sentry.RavenConfig -
    Takes in a sentryDSN and builds up the configuration -
    RavenConfig(String, String) - -Constructor for class net.kencochrane.sentry.RavenConfig -
    Takes in a sentryDSN and builds up the configuration -
    RavenUtils - Class in net.kencochrane.sentry
    User: ken Cochrane - Date: 2/10/12 - Time: 10:43 AM - Utility class for the Raven client.
    RavenUtils() - -Constructor for class net.kencochrane.sentry.RavenUtils -
      -
    requiresLayout() - -Method in class net.kencochrane.sentry.SentryAppender -
      -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    - - - diff --git a/docs/index-files/index-7.html b/docs/index-files/index-7.html deleted file mode 100644 index 1ea2f3449b7..00000000000 --- a/docs/index-files/index-7.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - -S-Index - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    -

    -S

    -
    -
    SentryAppender - Class in net.kencochrane.sentry
    User: ken cochrane - Date: 2/6/12 - Time: 10:54 AM
    SentryAppender() - -Constructor for class net.kencochrane.sentry.SentryAppender -
      -
    setHost(String) - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    setPath(String) - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    setPort(int) - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    setProjectId(String) - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    setProtocol(String) - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    setProxy(String) - -Method in class net.kencochrane.sentry.SentryAppender -
      -
    setPublicKey(String) - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    setSecretKey(String) - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    setSentry_dsn(String) - -Method in class net.kencochrane.sentry.SentryAppender -
      -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    - - - diff --git a/docs/index-files/index-8.html b/docs/index-files/index-8.html deleted file mode 100644 index 52856f4706b..00000000000 --- a/docs/index-files/index-8.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - -T-Index - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    -

    -T

    -
    -
    toString() - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    - - - diff --git a/docs/index-files/index-9.html b/docs/index-files/index-9.html deleted file mode 100644 index 86c49a23322..00000000000 --- a/docs/index-files/index-9.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - -T-Index - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H M N R S T
    -

    -T

    -
    -
    toString() - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H M N R S T
    - - - diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 7105375e378..00000000000 --- a/docs/index.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - -Generated Documentation (Untitled) - - - - - - - - -<H2> -Frame Alert</H2> - -<P> -This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client. -<BR> -Link to<A HREF="net/kencochrane/sentry/package-summary.html">Non-frame version.</A> - - - diff --git a/docs/net/kencochrane/sentry/RavenConfig.html b/docs/net/kencochrane/sentry/RavenConfig.html deleted file mode 100644 index ebaf0884315..00000000000 --- a/docs/net/kencochrane/sentry/RavenConfig.html +++ /dev/null @@ -1,609 +0,0 @@ - - - - - - -RavenConfig - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - -
    - -
    - - - -
    - -

    - -net.kencochrane.sentry -
    -Class RavenConfig

    -
    -java.lang.Object
    -  extended by net.kencochrane.sentry.RavenConfig
    -
    -
    -
    -
    public class RavenConfig
    extends java.lang.Object
    -
  • - -

    -User: ken cochrane - Date: 2/8/12 - Time: 1:16 PM -

    - -

    -


    - -

    - - - - - - - - - - - - - - -
    -Constructor Summary
    RavenConfig(java.lang.String sentryDSN) - -
    -          Takes in a sentryDSN and builds up the configuration
    RavenConfig(java.lang.String sentryDSN, - java.lang.String proxy) - -
    -          Takes in a sentryDSN and builds up the configuration
    -  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -Method Summary
    - java.lang.StringgetHost() - -
    -          The sentry server host
    - java.lang.StringgetPath() - -
    -          sentry url path
    - intgetPort() - -
    -          sentry server port
    - java.lang.StringgetProjectId() - -
    -          Sentry project Id
    - java.lang.StringgetProtocol() - -
    -          Sentry server protocol http https?
    - java.net.ProxygetProxy() - -
    -           
    - java.lang.StringgetPublicKey() - -
    -          The Sentry public key
    - java.lang.StringgetSecretKey() - -
    -          The Sentry secret key
    - java.lang.StringgetSentryURL() - -
    -          The Sentry server URL that we post the message to.
    - voidsetHost(java.lang.String host) - -
    -           
    - voidsetPath(java.lang.String path) - -
    -           
    - voidsetPort(int port) - -
    -           
    - voidsetProjectId(java.lang.String projectId) - -
    -           
    - voidsetProtocol(java.lang.String protocol) - -
    -           
    - voidsetPublicKey(java.lang.String publicKey) - -
    -           
    - voidsetSecretKey(java.lang.String secretKey) - -
    -           
    - java.lang.StringtoString() - -
    -           
    - - - - - - - -
    Methods inherited from class java.lang.Object
    clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    -  -

    - - - - - - - - -
    -Constructor Detail
    - -

    -RavenConfig

    -
    -public RavenConfig(java.lang.String sentryDSN)
    -
    -
    Takes in a sentryDSN and builds up the configuration -

    -

    -
    Parameters:
    sentryDSN - '{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}{PROJECT_ID}'
    -
    -
    - -

    -RavenConfig

    -
    -public RavenConfig(java.lang.String sentryDSN,
    -                   java.lang.String proxy)
    -
    -
    Takes in a sentryDSN and builds up the configuration -

    -

    -
    Parameters:
    sentryDSN - '{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}{PROJECT_ID}'
    proxy - proxy to use for the HTTP connections; blank or null when no proxy is to be used
    -
    - - - - - - - - -
    -Method Detail
    - -

    -getSentryURL

    -
    -public java.lang.String getSentryURL()
    -
    -
    The Sentry server URL that we post the message to. -

    -

    - -
    Returns:
    sentry server url
    -
    -
    -
    - -

    -getProxy

    -
    -public java.net.Proxy getProxy()
    -
    -
    -
    -
    -
    -
    - -

    -getHost

    -
    -public java.lang.String getHost()
    -
    -
    The sentry server host -

    -

    - -
    Returns:
    server host
    -
    -
    -
    - -

    -setHost

    -
    -public void setHost(java.lang.String host)
    -
    -
    -
    -
    -
    -
    - -

    -getProtocol

    -
    -public java.lang.String getProtocol()
    -
    -
    Sentry server protocol http https? -

    -

    - -
    Returns:
    http or https
    -
    -
    -
    - -

    -setProtocol

    -
    -public void setProtocol(java.lang.String protocol)
    -
    -
    -
    -
    -
    -
    - -

    -getPublicKey

    -
    -public java.lang.String getPublicKey()
    -
    -
    The Sentry public key -

    -

    - -
    Returns:
    Sentry public key
    -
    -
    -
    - -

    -setPublicKey

    -
    -public void setPublicKey(java.lang.String publicKey)
    -
    -
    -
    -
    -
    -
    - -

    -getSecretKey

    -
    -public java.lang.String getSecretKey()
    -
    -
    The Sentry secret key -

    -

    - -
    Returns:
    Sentry secret key
    -
    -
    -
    - -

    -setSecretKey

    -
    -public void setSecretKey(java.lang.String secretKey)
    -
    -
    -
    -
    -
    -
    - -

    -getPath

    -
    -public java.lang.String getPath()
    -
    -
    sentry url path -

    -

    - -
    Returns:
    url path
    -
    -
    -
    - -

    -setPath

    -
    -public void setPath(java.lang.String path)
    -
    -
    -
    -
    -
    -
    - -

    -getProjectId

    -
    -public java.lang.String getProjectId()
    -
    -
    Sentry project Id -

    -

    - -
    Returns:
    project Id
    -
    -
    -
    - -

    -setProjectId

    -
    -public void setProjectId(java.lang.String projectId)
    -
    -
    -
    -
    -
    -
    - -

    -getPort

    -
    -public int getPort()
    -
    -
    sentry server port -

    -

    - -
    Returns:
    server port
    -
    -
    -
    - -

    -setPort

    -
    -public void setPort(int port)
    -
    -
    -
    -
    -
    -
    - -

    -toString

    -
    -public java.lang.String toString()
    -
    -
    -
    Overrides:
    toString in class java.lang.Object
    -
    -
    -
    -
    -
    - -
    - - - - - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/net/kencochrane/sentry/RavenUtils.html b/docs/net/kencochrane/sentry/RavenUtils.html deleted file mode 100644 index 086068cd946..00000000000 --- a/docs/net/kencochrane/sentry/RavenUtils.html +++ /dev/null @@ -1,509 +0,0 @@ - - - - - - -RavenUtils - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - -
    - -
    - - - -
    - -

    - -net.kencochrane.sentry -
    -Class RavenUtils

    -
    -java.lang.Object
    -  extended by net.kencochrane.sentry.RavenUtils
    -
    -
    -
    -
    public class RavenUtils
    extends java.lang.Object
    - - -

    -User: ken Cochrane - Date: 2/10/12 - Time: 10:43 AM - Utility class for the Raven client. -

    - -

    -


    - -

    - - - - - - - - - - - -
    -Constructor Summary
    RavenUtils() - -
    -           
    -  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -Method Summary
    -static java.lang.StringcalculateChecksum(java.lang.String message) - -
    -          An almost-unique hash identifying the this event to improve aggregation.
    -static java.lang.StringcalculateHMAC(java.lang.String data, - java.lang.String key) - -
    -          Computes RFC 2104-compliant HMAC signature.
    -static java.lang.StringcompressAndEncode(java.lang.String str) - -
    -          Gzip then base64 encode the str value
    -static java.lang.StringgetDateAsISO8601String(java.util.Date date) - -
    -          Convert a java date into the ISO8601 format YYYYMMDDTHH:mm:ss+HH:00 - note the added colon for the Timezone
    -static java.lang.StringgetHostname() - -
    -          Get the hostname for the system throwing the error.
    -static java.lang.StringgetRandomUUID() - -
    -          Hexadecimal string representing a uuid4 value.
    -static java.lang.StringgetSignature(java.lang.String message, - long timestamp, - java.lang.String key) - -
    -          build the HMAC sentry signature -

    - The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, - and an arbitrary client version string.

    -static longgetTimestampLong() - -
    -          Get the timestamp for right now as a long
    -static java.lang.StringgetTimestampString() - -
    -          Given the time right now return a ISO8601 formatted date string
    -static java.lang.StringgetTimestampString(long timestamp) - -
    -          Given a long timestamp convert to a ISO8601 formatted date string
    -static java.lang.StringhexEncode(byte[] aInput) - -
    -          The byte[] returned by MessageDigest does not have a nice - textual representation, so some form of encoding is usually performed.
    - - - - - - - -
    Methods inherited from class java.lang.Object
    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    -  -

    - - - - - - - - -
    -Constructor Detail
    - -

    -RavenUtils

    -
    -public RavenUtils()
    -
    -
    - - - - - - - - -
    -Method Detail
    - -

    -calculateHMAC

    -
    -public static java.lang.String calculateHMAC(java.lang.String data,
    -                                             java.lang.String key)
    -                                      throws java.security.SignatureException
    -
    -
    Computes RFC 2104-compliant HMAC signature. - Based off of the sample here. http://docs.amazonwebservices.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AuthJavaSampleHMACSignature.html -

    -

    -
    Parameters:
    data - The data to be signed.
    key - The signing key. -
    Returns:
    The hex-encoded RFC 2104-compliant HMAC signature. -
    Throws: -
    java.security.SignatureException - when signature generation fails
    -
    -
    -
    - -

    -getHostname

    -
    -public static java.lang.String getHostname()
    -
    -
    Get the hostname for the system throwing the error. -

    -

    - -
    Returns:
    The hostname for the server
    -
    -
    -
    - -

    -getDateAsISO8601String

    -
    -public static java.lang.String getDateAsISO8601String(java.util.Date date)
    -
    -
    Convert a java date into the ISO8601 format YYYYMMDDTHH:mm:ss+HH:00 - note the added colon for the Timezone -

    -

    -
    Parameters:
    date - the date to convert -
    Returns:
    ISO8601 formatted date as a String
    -
    -
    -
    - -

    -getTimestampLong

    -
    -public static long getTimestampLong()
    -
    -
    Get the timestamp for right now as a long -

    -

    - -
    Returns:
    timestamp for now as long
    -
    -
    -
    - -

    -getTimestampString

    -
    -public static java.lang.String getTimestampString(long timestamp)
    -
    -
    Given a long timestamp convert to a ISO8601 formatted date string -

    -

    -
    Parameters:
    timestamp - the timestamp to convert -
    Returns:
    ISO8601 formatted date string
    -
    -
    -
    - -

    -getTimestampString

    -
    -public static java.lang.String getTimestampString()
    -
    -
    Given the time right now return a ISO8601 formatted date string -

    -

    - -
    Returns:
    ISO8601 formatted date string
    -
    -
    -
    - -

    -getSignature

    -
    -public static java.lang.String getSignature(java.lang.String message,
    -                                            long timestamp,
    -                                            java.lang.String key)
    -
    -
    build the HMAC sentry signature -

    - The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, - and an arbitrary client version string. -

    - The client version should be something distinct to your client, and is simply for reporting purposes. - To generate the HMAC signature, take the following example (in Python): -

    - hmac.new(public_key, '%s %s' % (timestamp, message), hashlib.sha1).hexdigest() -

    -

    -
    Parameters:
    message - the error message to send to sentry
    timestamp - the timestamp for when the message was created
    key - sentry public key -
    Returns:
    SHA1-signed HMAC string
    -
    -
    -
    - -

    -hexEncode

    -
    -public static java.lang.String hexEncode(byte[] aInput)
    -
    -
    The byte[] returned by MessageDigest does not have a nice - textual representation, so some form of encoding is usually performed. -

    - This implementation follows the example of David Flanagan's book - "Java In A Nutshell", and converts a byte array into a String - of hex characters. -

    - Another popular alternative is to use a "Base64" encoding. -

    -

    -
    Parameters:
    aInput - what we are hex encoding -
    Returns:
    hex encoded string.
    -
    -
    -
    - -

    -calculateChecksum

    -
    -public static java.lang.String calculateChecksum(java.lang.String message)
    -
    -
    An almost-unique hash identifying the this event to improve aggregation. -

    -

    -
    Parameters:
    message - The message we are sending to sentry -
    Returns:
    CRC32 Checksum string
    -
    -
    -
    - -

    -getRandomUUID

    -
    -public static java.lang.String getRandomUUID()
    -
    -
    Hexadecimal string representing a uuid4 value. -

    -

    - -
    Returns:
    Hexadecimal UUID4 String
    -
    -
    -
    - -

    -compressAndEncode

    -
    -public static java.lang.String compressAndEncode(java.lang.String str)
    -
    -
    Gzip then base64 encode the str value -

    -

    -
    Parameters:
    str - the value we want to compress and encode. -
    Returns:
    Base64 encoded compressed version of the string passed in.
    -
    -
    - -
    - - - - - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/net/kencochrane/sentry/SentryAppender.html b/docs/net/kencochrane/sentry/SentryAppender.html deleted file mode 100644 index a3237462969..00000000000 --- a/docs/net/kencochrane/sentry/SentryAppender.html +++ /dev/null @@ -1,402 +0,0 @@ - - - - - - -SentryAppender - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - -
    - -
    - - - -
    - -

    - -net.kencochrane.sentry -
    -Class SentryAppender

    -
    -java.lang.Object
    -  extended by org.apache.log4j.AppenderSkeleton
    -      extended by net.kencochrane.sentry.SentryAppender
    -
    -
    -
    All Implemented Interfaces:
    org.apache.log4j.Appender, org.apache.log4j.spi.OptionHandler
    -
    -
    -
    -
    public class SentryAppender
    extends org.apache.log4j.AppenderSkeleton
    - - -

    -User: ken cochrane - Date: 2/6/12 - Time: 10:54 AM -

    - -

    -


    - -

    - - - - - - - -
    -Field Summary
    - - - - - - - -
    Fields inherited from class org.apache.log4j.AppenderSkeleton
    closed, errorHandler, headFilter, layout, name, tailFilter, threshold
    -  - - - - - - - - - - -
    -Constructor Summary
    SentryAppender() - -
    -           
    -  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -Method Summary
    -protected  voidappend(org.apache.log4j.spi.LoggingEvent loggingEvent) - -
    -           
    - voidclose() - -
    -           
    - java.lang.StringgetProxy() - -
    -           
    - java.lang.StringgetSentry_dsn() - -
    -           
    - booleanrequiresLayout() - -
    -           
    - voidsetProxy(java.lang.String proxy) - -
    -           
    - voidsetSentry_dsn(java.lang.String sentry_dsn) - -
    -           
    - - - - - - - -
    Methods inherited from class org.apache.log4j.AppenderSkeleton
    activateOptions, addFilter, clearFilters, doAppend, finalize, getErrorHandler, getFilter, getFirstFilter, getLayout, getName, getThreshold, isAsSevereAsThreshold, setErrorHandler, setLayout, setName, setThreshold
    - - - - - - - -
    Methods inherited from class java.lang.Object
    clone, equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    -  -

    - - - - - - - - -
    -Constructor Detail
    - -

    -SentryAppender

    -
    -public SentryAppender()
    -
    -
    - - - - - - - - -
    -Method Detail
    - -

    -getSentry_dsn

    -
    -public java.lang.String getSentry_dsn()
    -
    -
    -
    -
    -
    -
    - -

    -setSentry_dsn

    -
    -public void setSentry_dsn(java.lang.String sentry_dsn)
    -
    -
    -
    -
    -
    -
    - -

    -getProxy

    -
    -public java.lang.String getProxy()
    -
    -
    -
    -
    -
    -
    - -

    -setProxy

    -
    -public void setProxy(java.lang.String proxy)
    -
    -
    -
    -
    -
    -
    - -

    -append

    -
    -protected void append(org.apache.log4j.spi.LoggingEvent loggingEvent)
    -
    -
    -
    Specified by:
    append in class org.apache.log4j.AppenderSkeleton
    -
    -
    -
    -
    -
    -
    - -

    -close

    -
    -public void close()
    -
    -
    -
    -
    -
    -
    - -

    -requiresLayout

    -
    -public boolean requiresLayout()
    -
    -
    -
    -
    -
    - -
    - - - - - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/net/kencochrane/sentry/SentryExample.html b/docs/net/kencochrane/sentry/SentryExample.html deleted file mode 100644 index de225a0af7e..00000000000 --- a/docs/net/kencochrane/sentry/SentryExample.html +++ /dev/null @@ -1,252 +0,0 @@ - - - - - - -SentryExample - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - -
    - -
    - - - -
    - -

    - -net.kencochrane.sentry -
    -Class SentryExample

    -
    -java.lang.Object
    -  extended by net.kencochrane.sentry.SentryExample
    -
    -
    -
    -
    public class SentryExample
    extends java.lang.Object
    - - -

    -Simple example used to test out the sentry logger. -

    - -

    -


    - -

    - - - - - - - - - - - -
    -Constructor Summary
    SentryExample() - -
    -           
    -  - - - - - - - - - - - -
    -Method Summary
    -static voidmain(java.lang.String[] args) - -
    -           
    - - - - - - - -
    Methods inherited from class java.lang.Object
    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    -  -

    - - - - - - - - -
    -Constructor Detail
    - -

    -SentryExample

    -
    -public SentryExample()
    -
    -
    - - - - - - - - -
    -Method Detail
    - -

    -main

    -
    -public static void main(java.lang.String[] args)
    -
    -
    -
    -
    -
    - -
    - - - - - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/net/kencochrane/sentry/package-frame.html b/docs/net/kencochrane/sentry/package-frame.html deleted file mode 100644 index 22a070b2216..00000000000 --- a/docs/net/kencochrane/sentry/package-frame.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - -net.kencochrane.sentry - - - - - - - - - - - -net.kencochrane.sentry - - - - -
    -Classes  - -
    -RavenConfig -
    -RavenUtils -
    -SentryAppender
    - - - - diff --git a/docs/net/kencochrane/sentry/package-summary.html b/docs/net/kencochrane/sentry/package-summary.html deleted file mode 100644 index 8b3e221fe43..00000000000 --- a/docs/net/kencochrane/sentry/package-summary.html +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - -net.kencochrane.sentry - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    -

    -Package net.kencochrane.sentry -

    - - - - - - - - - - - - - - - - - -
    -Class Summary
    RavenConfigUser: ken cochrane - Date: 2/8/12 - Time: 1:16 PM
    RavenUtilsUser: ken Cochrane - Date: 2/10/12 - Time: 10:43 AM - Utility class for the Raven client.
    SentryAppenderUser: ken cochrane - Date: 2/6/12 - Time: 10:54 AM
    -  - -

    -

    -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/net/kencochrane/sentry/package-tree.html b/docs/net/kencochrane/sentry/package-tree.html deleted file mode 100644 index 05116620130..00000000000 --- a/docs/net/kencochrane/sentry/package-tree.html +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - -net.kencochrane.sentry Class Hierarchy - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    -
    -

    -Hierarchy For Package net.kencochrane.sentry -

    -
    -

    -Class Hierarchy -

    -
      -
    • java.lang.Object
        -
      • org.apache.log4j.AppenderSkeleton (implements org.apache.log4j.Appender, org.apache.log4j.spi.OptionHandler) - -
      • net.kencochrane.sentry.RavenConfig
      • net.kencochrane.sentry.RavenUtils
      -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/overview-tree.html b/docs/overview-tree.html deleted file mode 100644 index 0d8261f2a30..00000000000 --- a/docs/overview-tree.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - -Class Hierarchy - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    -
    -

    -Hierarchy For All Packages

    -
    -
    -
    Package Hierarchies:
    net.kencochrane.sentry
    -
    -

    -Class Hierarchy -

    -
      -
    • java.lang.Object
        -
      • org.apache.log4j.AppenderSkeleton (implements org.apache.log4j.Appender, org.apache.log4j.spi.OptionHandler) - -
      • net.kencochrane.sentry.RavenConfig
      • net.kencochrane.sentry.RavenUtils
      -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/package-list b/docs/package-list deleted file mode 100644 index b263a2d0e9d..00000000000 --- a/docs/package-list +++ /dev/null @@ -1 +0,0 @@ -net.kencochrane.sentry diff --git a/docs/resources/inherit.gif b/docs/resources/inherit.gif deleted file mode 100644 index c814867a13deb0ca7ea2156c6ca1d5a03372af7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57 zcmZ?wbhEHbIIT!9-C*e{wE9>Kx3D)-;0v)C; KYxQGgum%9JOA&7X diff --git a/docs/stylesheet.css b/docs/stylesheet.css deleted file mode 100644 index 6ea9e516161..00000000000 --- a/docs/stylesheet.css +++ /dev/null @@ -1,29 +0,0 @@ -/* Javadoc style sheet */ - -/* Define colors, fonts and other style attributes here to override the defaults */ - -/* Page background color */ -body { background-color: #FFFFFF; color:#000000 } - -/* Headings */ -h1 { font-size: 145% } - -/* Table colors */ -.TableHeadingColor { background: #CCCCFF; color:#000000 } /* Dark mauve */ -.TableSubHeadingColor { background: #EEEEFF; color:#000000 } /* Light mauve */ -.TableRowColor { background: #FFFFFF; color:#000000 } /* White */ - -/* Font used in left-hand frame lists */ -.FrameTitleFont { font-size: 100%; font-family: Helvetica, Arial, sans-serif; color:#000000 } -.FrameHeadingFont { font-size: 90%; font-family: Helvetica, Arial, sans-serif; color:#000000 } -.FrameItemFont { font-size: 90%; font-family: Helvetica, Arial, sans-serif; color:#000000 } - -/* Navigation bar fonts and colors */ -.NavBarCell1 { background-color:#EEEEFF; color:#000000} /* Light mauve */ -.NavBarCell1Rev { background-color:#00008B; color:#FFFFFF} /* Dark Blue */ -.NavBarFont1 { font-family: Arial, Helvetica, sans-serif; color:#000000;color:#000000;} -.NavBarFont1Rev { font-family: Arial, Helvetica, sans-serif; color:#FFFFFF;color:#FFFFFF;} - -.NavBarCell2 { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF; color:#000000} -.NavBarCell3 { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF; color:#000000} - diff --git a/old/README.rst b/old/README.rst deleted file mode 100644 index 8bcfc1b062c..00000000000 --- a/old/README.rst +++ /dev/null @@ -1,212 +0,0 @@ -Raven-java -========== -Raven-java is a Java client for Sentry. It is a basic log4j appender that will send your log messages to a sentry server of your choice. - -It is officially recognized as production-ready by Sentry: http://sentry.readthedocs.org/en/latest/client/index.html - -The log4j appender is asyncronous by design so there is no need to put it in a AsyncAppender. - -Current Status --------------- -.. image:: https://secure.travis-ci.org/kencochrane/raven-java.png - :target: http://travis-ci.org/kencochrane/raven-java - - -Installation ------------- - -Download Jars -~~~~~~~~~~~~~ -Precompiled jars are available for download directly from github. - - https://github.com/kencochrane/raven-java/downloads - -Build Jars yourself -~~~~~~~~~~~~~~~~~~~ -You'll need Maven 2 to build the project:: - - $ cd raven-java - $ mvn package -Dmaven.test.skip - -The last step will build the standalone raven-java jar file but also a jar file containing raven-java and all dependencies, which -you'll find in the target directory of the project. - -**Option 1**: add raven-java as a dependency when you're using Maven:: - - - net.kencochrane - raven-java - 0.6-SNAPSHOT - - -**Option 2**: add the plain jar and the jar files of all dependencies to your classpath - -**Option 3**: add the self contained jar file to your classpath - -Configuration -------------- - -Log4J configuration -~~~~~~~~~~~~~~~~~~~ -Check out ``src/test/java/resources/log4j_configuration.txt`` where you can see an example log4j config file. - -You will need to add the SentryAppender and the sentry_dsn properties. - -sentry_dsn -^^^^^^^^^^ -You will get this value from the "Projects / default / Manage / Member: " page. It will be under "Client DSN". -Don't put quotes around it, because it will mess it up, just put it like you see it below. - -Log4j Config example:: - - log4j.appender.sentry=net.kencochrane.sentry.SentryAppender - log4j.appender.sentry.sentry_dsn=http://b4935bdd7862409:7a37d9ad47654281@localhost:8000/1 - -Proxy -^^^^^ -If you need to use a proxy for HTTP transport, you can configure it as well:: - - log4j.appender.sentry.proxy=HTTP:proxyhost:proxyport - -Queue Size -^^^^^^^^^^ -By default, the appender is configured with a queue of 1000 events. To tune this parameter:: - - log4j.appender.sentry.queue_size=100 - -Blocking -^^^^^^^^ -By default, the appender is non-blocking. If the queue is filled then log events will be written to Standard Error. -If you want to make sure log events always reach sentry, you can turn blocking on:: - - log4j.appender.sentry.blocking=true - -WARNING: By setting blocking true, you will effectively lock up the thread doing the logging! Use with care. - -Naive SSL -^^^^^^^^^ -If you're using Java 6 or earlier, you might encounter errors when connecting to a Sentry instance using a TLS (https) -connection since Server Name Indication (SNI) support has only been available in Java since Java 7. You can either add -the corresponding certificate to your keystore (recommended!) or enable ``naiveSsl`` on the Sentry appender. This will -make the Raven client use a custom hostname verifier that *will* allow the JVM to connect with the host - in fact it -will let the Raven client connect to any host even if the certificate is invalid. Use at your own risk:: - - log4j.appender.sentry.naiveSsl=true - - -SENTRY_DSN Environment Variable -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Raven-Java will first look to see if there is an environment variable called ``SENTRY_DSN`` set before it looks at the log4j config. If the environment variable is set, it will use that value instead of the one set in ``log4j.appender.sentry.sentry_dsn``. This is for a few reasons. - -1. Some hosting providers (Heroku, etc) have http://GetSentry.com plugins and they expose your Sentry settings via an environment variable. -2. This allows you to specify your Sentry config by server. You could put a development Sentry server in your log4j properties file and then on each server override those values to point to different sentry servers if you need to. -3. It allows you to use the RavenClient directly outside of log4J, without having to hard code the Sentry DSN in your source code. - -Linux example:: - - # put this in your profile, or add it to a shell script that calls your java program. - - $ export SENTRY_DSN=http://b4935bdd78624092a:7a37d9ad47654281803f@localhost:8000/1 - -Usage ------ - -Log4J -~~~~~ - -If you configure log4j to only error messages to Sentry, it will ignore all other message levels and only send the logger.error() messages - -Example:: - - // configure log4j the normal way, and then just use it like you normally would. - - logger.debug("Debug example"); // ignored - logger.error("Error example"); // sent to sentry - logger.info("info Example"); // ignored - - try { - throw new RuntimeException("Uh oh!"); - } catch (RuntimeException e) { - logger.error("Error example with stacktrace", e); //sent to sentry - } - - -RavenClient -~~~~~~~~~~~ -Set the SENTRY_DSN Environment Variable with your sentry DSN. - -Create an instance of the client:: - - RavenClient client = new RavenClient(); - -Now call out to the raven client to capture events:: - - // record a simple message - client.captureMessage("hello world!"); - - // capture an exception - try { - throw new RuntimeException("Uh oh!"); - } - catch (Throwable e) { - client.captureException(e); - } - - -Sentry Versions Supported -------------------------- -This client supports Sentry protocol version 2.0 (which is Sentry >= 2.0) - -Other ------ -If you want to generate the javadocs for this project, simply run ``mvn javadoc:javadoc`` and you'll be able to browse the -docs from the target directory of the project. - -Running Tests -------------- -We are using maven, so all that you need to do in order to run the test is run the following:: - - $ cd raven-java - $ mvn test - -TODO ----- -- Create better documentation -- Add more unit tests -- Add more examples -- Get compression to work on message body, it isn't working now, not sure if it is sentry server or raven-java. Might be incompatible versions of zlib Java->python. - - -History -------- -- 0.6 - - Added support for sending messages through UDP -- 0.5 - - Added async support - - Fixed issue with parsing of path and port in DSN -- 0.4 - - Added the ability to get the SENTRY_DSN from the ENV - - Added RavenClient.captureMessage - - Added RavenClient.captureException -- 0.3 - - Added Maven support - - Merged with log4sentry project by Kevin Wetzels - - Added Proxy support - - Added full stack trace to logs - -- 0.2 - - code refactor and cleanup - -- 0.1 - - initial version - -Contributors ------------- -- Ken Cochrane (@KenCochrane) -- Kevin Wetzels (@roambe) -- David Cramer (@zeeg) -- Mark Philpot (@griphiam) - -License -------- -We are using the same license as the Sentry master project which is a BSD license. For more information see the LICENSE file, or follow this link: http://en.wikipedia.org/wiki/BSD_licenses diff --git a/old/pom.xml b/old/pom.xml deleted file mode 100644 index cc3af1de8c6..00000000000 --- a/old/pom.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - 4.0.0 - - net.kencochrane - raven-java - 0.6-SNAPSHOT - jar - raven-java - Java Raven client and log4j appender. - - - - commons-lang - commons-lang - 2.5 - - - commons-codec - commons-codec - 1.6 - - - com.googlecode.json-simple - json-simple - 1.1 - - - log4j - log4j - 1.2.16 - - - com.googlecode.jmockit - jmockit - 0.999.12 - test - - - junit - junit - 4.8.1 - test - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - UTF-8 - - - - org.apache.maven.plugins - maven-resources-plugin - 2.5 - - UTF-8 - - - - - maven-assembly-plugin - 2.3 - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - - - - - ${basedir}/src/main/java - - **/*.json - **/*.yml - - - - ${basedir}/src/main/resources - - **/*.* - - - - - - - ${basedir}/src/test/java - - **/*.json - **/*.yml - **/*.txt - - - - ${basedir}/src/test/resources - - **/*.* - - - - - - \ No newline at end of file diff --git a/old/src/main/java/net/kencochrane/sentry/RavenClient.java b/old/src/main/java/net/kencochrane/sentry/RavenClient.java deleted file mode 100644 index 7398b94072c..00000000000 --- a/old/src/main/java/net/kencochrane/sentry/RavenClient.java +++ /dev/null @@ -1,418 +0,0 @@ -package net.kencochrane.sentry; - -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSession; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.*; - -import static org.apache.commons.codec.binary.Base64.encodeBase64String; - -/** - * User: ken cochrane - * Date: 2/6/12 - * Time: 11:59 AM - */ - -public class RavenClient { - - public static final String RAVEN_JAVA_VERSION = "Raven-Java 0.6"; - private RavenConfig config; - private String sentryDSN; - private String lastID; - private MessageSender messageSender; - - public RavenClient() { - this.sentryDSN = System.getenv("SENTRY_DSN"); - if (this.sentryDSN == null || this.sentryDSN.length() == 0) { - throw new RuntimeException("You must provide a DSN to RavenClient"); - } - setConfig(new RavenConfig(this.sentryDSN)); - } - - public RavenClient(String sentryDSN) { - this.sentryDSN = sentryDSN; - setConfig(new RavenConfig(sentryDSN)); - } - - public RavenClient(String sentryDSN, String proxy, boolean naiveSsl) { - this.sentryDSN = sentryDSN; - setConfig(new RavenConfig(sentryDSN, proxy, naiveSsl)); - } - - public RavenConfig getConfig() { - return config; - } - - public void setConfig(RavenConfig config) { - this.config = config; - try { - String protocol = config.getProtocol(); - if ("udp".equals(protocol)) { - messageSender = new UdpMessageSender(config, null); - } else { - URL endpoint = new URL(config.getSentryURL()); - if (config.isNaiveSsl() && "https".equals(protocol)) { - messageSender = new NaiveHttpsMessageSender(config, endpoint); - } else { - messageSender = new MessageSender(config, endpoint); - } - } - } catch (MalformedURLException e) { - throw new RuntimeException("Sentry URL is malformed", e); - } - } - - public String getSentryDSN() { - return sentryDSN; - } - - public void setSentryDSN(String sentryDSN) { - this.sentryDSN = sentryDSN; - } - - public void setLastID(String lastID) { - this.lastID = lastID; - } - - public String getLastID() { - return lastID; - } - - /** - * Build up the JSON body for the POST that is sent to sentry - * - * @param message The log message - * @param timestamp ISO8601 formatted date string - * @param loggerClass The class associated with the log message - * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.) - * @param culprit Who we think caused the problem. - * @return JSON String of message body - */ - private String buildJSON(String message, String timestamp, String loggerClass, int logLevel, String culprit, Throwable exception) { - JSONObject obj = new JSONObject(); - String lastID = RavenUtils.getRandomUUID(); - obj.put("event_id", lastID); //Hexadecimal string representing a uuid4 value. - obj.put("checksum", RavenUtils.calculateChecksum(message)); - if (exception == null) { - obj.put("culprit", culprit); - } else { - obj.put("culprit", determineCulprit(exception)); - obj.put("sentry.interfaces.Exception", buildException(exception)); - obj.put("sentry.interfaces.Stacktrace", buildStacktrace(exception)); - } - obj.put("timestamp", timestamp); - obj.put("message", message); - obj.put("project", getConfig().getProjectId()); - obj.put("level", logLevel); - obj.put("logger", loggerClass); - obj.put("server_name", RavenUtils.getHostname()); - setLastID(lastID); - return obj.toJSONString(); - } - - /** - * Determines the class and method name where the root cause exception occurred. - * - * @param exception exception - * @return the culprit - */ - private String determineCulprit(Throwable exception) { - Throwable cause = exception; - String culprit = null; - while (cause != null) { - StackTraceElement[] elements = cause.getStackTrace(); - if (elements.length > 0) { - StackTraceElement trace = elements[0]; - culprit = trace.getClassName() + "." + trace.getMethodName(); - } - cause = cause.getCause(); - } - return culprit; - } - - private JSONObject buildException(Throwable exception) { - JSONObject json = new JSONObject(); - json.put("type", exception.getClass().getSimpleName()); - json.put("value", exception.getMessage()); - json.put("module", exception.getClass().getPackage().getName()); - return json; - } - - private JSONObject buildStacktrace(Throwable exception) { - JSONArray array = new JSONArray(); - Throwable cause = exception; - while (cause != null) { - StackTraceElement[] elements = cause.getStackTrace(); - for (int index = 0; index < elements.length; ++index) { - if (index == 0) { - JSONObject causedByFrame = new JSONObject(); - String msg = "Caused by: " + cause.getClass().getName(); - if (cause.getMessage() != null) { - msg += " (\"" + cause.getMessage() + "\")"; - } - causedByFrame.put("filename", msg); - causedByFrame.put("lineno", -1); - array.add(causedByFrame); - } - StackTraceElement element = elements[index]; - JSONObject frame = new JSONObject(); - frame.put("filename", element.getClassName()); - frame.put("function", element.getMethodName()); - frame.put("lineno", element.getLineNumber()); - array.add(frame); - } - cause = cause.getCause(); - } - JSONObject stacktrace = new JSONObject(); - stacktrace.put("frames", array); - return stacktrace; - } - - /** - * Take the raw message body and get it ready for sending. Encode and compress it. - * - * @param jsonMessage the message we want to prepare - * @return Encode and compressed version of the jsonMessage - */ - private String buildMessageBody(String jsonMessage) { - //need to zip and then base64 encode the message. - // compressing doesn't work right now, sentry isn't decompressing correctly. - // come back to it later. - //return compressAndEncode(jsonMessage); - - // in the meantime just base64 encode it. - return encodeBase64String(jsonMessage.getBytes()); - - } - - /** - * Build up the JSON body and then Encode and compress it. - * - * @param message The log message - * @param timestamp ISO8601 formatted date string - * @param loggerClass The class associated with the log message - * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.) - * @param culprit Who we think caused the problem. - * @param exception exception causing the problem - * @return Encode and compressed version of the JSON Message body - */ - private String buildMessage(String message, String timestamp, String loggerClass, int logLevel, String culprit, Throwable exception) { - // get the json version of the body - String jsonMessage = buildJSON(message, timestamp, loggerClass, logLevel, culprit, exception); - - // compress and encode the json message. - return buildMessageBody(jsonMessage); - } - - /** - * Send the message to the sentry server. - * - * @param messageBody the encoded json message we are sending to the sentry server - * @param timestamp the timestamp of the message - */ - private void sendMessage(String messageBody, long timestamp) { - try { - messageSender.send(messageBody, timestamp); - } catch (IOException e) { - // Eat the errors, we don't want to cause problems if there are major issues. - e.printStackTrace(); - } - } - - /** - * Send the log message to the sentry server. - *

    - * This method is deprecated. You should use captureMessage or captureException instead. - * - * @param theLogMessage The log message - * @param timestamp unix timestamp - * @param loggerClass The class associated with the log message - * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.) - * @param culprit Who we think caused the problem. - * @param exception exception that occurred - * @deprecated - */ - public void logMessage(String theLogMessage, long timestamp, String loggerClass, int logLevel, String culprit, Throwable exception) { - String message = buildMessage(theLogMessage, RavenUtils.getTimestampString(timestamp), loggerClass, logLevel, culprit, exception); - sendMessage(message, timestamp); - } - - - /** - * Send the log message to the sentry server. - * - * @param message The log message - * @param timestamp unix timestamp - * @param loggerClass The class associated with the log message - * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.) - * @param culprit Who we think caused the problem. - * @return lastID The ID for the last message. - */ - public String captureMessage(String message, long timestamp, String loggerClass, int logLevel, String culprit) { - String body = buildMessage(message, RavenUtils.getTimestampString(timestamp), loggerClass, logLevel, culprit, null); - sendMessage(body, timestamp); - return getLastID(); - } - - /** - * Send the log message to the sentry server. - * - * @param message The log message - * @return lastID The ID for the last message. - */ - public String captureMessage(String message) { - return captureMessage(message, RavenUtils.getTimestampLong(), "root", 50, null); - } - - /** - * Send the exception to the sentry server. - * - * @param message The log message - * @param timestamp unix timestamp - * @param loggerClass The class associated with the log message - * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.) - * @param culprit Who we think caused the problem. - * @param exception exception that occurred - * @return lastID The ID for the last message. - */ - public String captureException(String message, long timestamp, String loggerClass, int logLevel, String culprit, Throwable exception) { - String body = buildMessage(message, RavenUtils.getTimestampString(timestamp), loggerClass, logLevel, culprit, exception); - sendMessage(body, timestamp); - return getLastID(); - } - - /** - * Send an exception to the sentry server. - * - * @param exception exception that occurred - * @return lastID The ID for the last message. - */ - public String captureException(Throwable exception) { - return captureException(exception.getMessage(), RavenUtils.getTimestampLong(), "root", 50, null, exception); - } - - public static class MessageSender { - - public final RavenConfig config; - public final URL endpoint; - - public MessageSender(RavenConfig config, URL endpoint) { - this.config = config; - this.endpoint = endpoint; - } - - public void send(String messageBody, long timestamp) throws IOException { - // get the hmac Signature for the header - String hmacSignature = RavenUtils.getSignature(messageBody, timestamp, config.getSecretKey()); - - // get the auth header - String authHeader = buildAuthHeader(hmacSignature, timestamp, config.getPublicKey()); - - doSend(messageBody, authHeader); - } - - protected void doSend(String messageBody, String authHeader) throws IOException { - HttpURLConnection connection = getConnection(); - connection.setRequestMethod("POST"); - connection.setDoOutput(true); - connection.setReadTimeout(10000); - connection.setRequestProperty("X-Sentry-Auth", authHeader); - OutputStream output = connection.getOutputStream(); - output.write(messageBody.getBytes()); - output.close(); - connection.connect(); - InputStream input = connection.getInputStream(); - input.close(); - } - - /** - * Build up the sentry auth header in the following format. - *

    - * The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, and an - * arbitrary client version string. The client version should be something distinct to your client, - * and is simply for reporting purposes. - *

    - * X-Sentry-Auth: Sentry sentry_version=2.0, - * sentry_signature=, - * sentry_timestamp=[, - * sentry_key=,[ - * sentry_client=]] - * - * @param hmacSignature SHA1-signed HMAC - * @param timestamp is the timestamp of which this message was generated - * @param publicKey is either the public_key or the shared global key between client and server. - * @return String version of the sentry auth header - */ - protected String buildAuthHeader(String hmacSignature, long timestamp, String publicKey) { - StringBuilder header = new StringBuilder(); - header.append("Sentry sentry_version=2.0,sentry_signature="); - header.append(hmacSignature); - header.append(",sentry_timestamp="); - header.append(timestamp); - header.append(",sentry_key="); - header.append(publicKey); - header.append(",sentry_client="); - header.append(RAVEN_JAVA_VERSION); - - return header.toString(); - } - - protected HttpURLConnection getConnection() throws IOException { - return (HttpURLConnection) endpoint.openConnection(config.getProxy()); - } - } - - public static class NaiveHttpsMessageSender extends MessageSender { - - public final HostnameVerifier hostnameVerifier; - - public NaiveHttpsMessageSender(RavenConfig config, URL endpoint) { - super(config, endpoint); - this.hostnameVerifier = new AcceptAllHostnameVerifier(); - } - - @Override - protected HttpURLConnection getConnection() throws IOException { - HttpsURLConnection connection = (HttpsURLConnection) endpoint.openConnection(config.getProxy()); - connection.setHostnameVerifier(hostnameVerifier); - return connection; - } - } - - public static class UdpMessageSender extends MessageSender { - - private final DatagramSocket socket; - - public UdpMessageSender(RavenConfig config, URL endpoint) { - super(config, endpoint); - try { - socket = new DatagramSocket(); - socket.connect(new InetSocketAddress(config.getHost(), config.getPort())); - } catch (SocketException e) { - throw new IllegalStateException(e); - } - } - - @Override - protected void doSend(String messageBody, String authHeader) throws IOException { - byte[] message = (authHeader + "\n\n" + messageBody).getBytes("UTF-8"); - DatagramPacket packet = new DatagramPacket(message, message.length); - socket.send(packet); - } - - } - - public static class AcceptAllHostnameVerifier implements HostnameVerifier { - @Override - public boolean verify(String hostname, SSLSession sslSession) { - return true; - } - } - -} \ No newline at end of file diff --git a/old/src/main/java/net/kencochrane/sentry/RavenConfig.java b/old/src/main/java/net/kencochrane/sentry/RavenConfig.java deleted file mode 100644 index 56e4a0bea54..00000000000 --- a/old/src/main/java/net/kencochrane/sentry/RavenConfig.java +++ /dev/null @@ -1,213 +0,0 @@ -package net.kencochrane.sentry; - -import java.net.*; - -/** - * User: ken cochrane - * Date: 2/8/12 - * Time: 1:16 PM - */ -public class RavenConfig { - - private String host, protocol, publicKey, secretKey, path, projectId; - private int port; - private String proxyType, proxyHost; - private int proxyPort; - private boolean naiveSsl; - - /** - * Takes in a sentryDSN and builds up the configuration - * - * @param sentryDSN '{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}/{PROJECT_ID}' - */ - public RavenConfig(String sentryDSN) { - this(sentryDSN, null, false); - } - - /** - * Takes in a sentryDSN and builds up the configuration - * - * @param sentryDSN '{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}/{PROJECT_ID}' - * @param proxy proxy to use for the HTTP connections; blank or null when no proxy is to be used - * @param naiveSsl use a hostname verifier for SSL connections that allows all connections - */ - public RavenConfig(String sentryDSN, String proxy, boolean naiveSsl) { - this.naiveSsl = naiveSsl; - try { - boolean udp = sentryDSN.startsWith("udp://"); - if (udp) { - // So either we have to start registering protocol handlers which is a PITA to do decently in Java - // without causing problems for the actual application, or we hack our way around it. - sentryDSN = sentryDSN.replace("udp://", "http://"); - } - URL url = new URL(sentryDSN); - this.host = url.getHost(); - this.protocol = udp ? "udp" : url.getProtocol(); - String urlPath = url.getPath(); - - int lastSlash = urlPath.lastIndexOf("/"); - this.path = urlPath.substring(0, lastSlash); - // ProjectId is the integer after the last slash in the path - this.projectId = urlPath.substring(lastSlash + 1); - - String userInfo = url.getUserInfo(); - String[] userParts = userInfo.split(":"); - - this.secretKey = userParts[1]; - this.publicKey = userParts[0]; - - this.port = url.getPort(); - - if (proxy != null && !proxy.isEmpty()) { - String[] proxyParts = proxy.split(":"); - this.proxyType = proxyParts[0]; - this.proxyHost = proxyParts[1]; - this.proxyPort = Integer.parseInt(proxyParts[2]); - } - - } catch (MalformedURLException e) { - e.printStackTrace(); - } - - } - - /** - * The Sentry server URL that we post the message to. - * - * @return sentry server url - */ - public String getSentryURL() { - StringBuilder serverUrl = new StringBuilder(); - serverUrl.append(getProtocol()); - serverUrl.append("://"); - serverUrl.append(getHost()); - if ((getPort() != 0) && (getPort() != 80) && getPort() != -1) { - serverUrl.append(":").append(getPort()); - } - serverUrl.append(getPath()); - serverUrl.append("/api/store/"); - return serverUrl.toString(); - } - - public Proxy getProxy() { - if (proxyType == null || Proxy.Type.DIRECT.name().equals(proxyType)) { - return Proxy.NO_PROXY; - } - SocketAddress proxyAddress = new InetSocketAddress(proxyHost, proxyPort); - return new Proxy(Proxy.Type.valueOf(proxyType), proxyAddress); - } - - /** - * The sentry server host - * - * @return server host - */ - public String getHost() { - return host; - } - - public void setHost(String host) { - this.host = host; - } - - /** - * Sentry server protocol http https? - * - * @return http or https - */ - public String getProtocol() { - return protocol; - } - - public void setProtocol(String protocol) { - this.protocol = protocol; - } - - /** - * The Sentry public key - * - * @return Sentry public key - */ - public String getPublicKey() { - return publicKey; - } - - public void setPublicKey(String publicKey) { - this.publicKey = publicKey; - } - - /** - * The Sentry secret key - * - * @return Sentry secret key - */ - public String getSecretKey() { - return secretKey; - } - - public void setSecretKey(String secretKey) { - this.secretKey = secretKey; - } - - /** - * sentry url path - * - * @return url path - */ - public String getPath() { - return path; - } - - public void setPath(String path) { - this.path = path; - } - - /** - * Sentry project Id - * - * @return project Id - */ - public String getProjectId() { - return projectId; - } - - public void setProjectId(String projectId) { - this.projectId = projectId; - } - - /** - * sentry server port - * - * @return server port - */ - public int getPort() { - return port; - } - - public void setPort(int port) { - this.port = port; - } - - public boolean isNaiveSsl() { - return naiveSsl; - } - - public void setNaiveSsl(boolean naiveSsl) { - this.naiveSsl = naiveSsl; - } - - @Override - public String toString() { - return "RavenConfig{" + - "host='" + host + '\'' + - ", protocol='" + protocol + '\'' + - ", publicKey='" + publicKey + '\'' + - ", secretKey='" + secretKey + '\'' + - ", path='" + path + '\'' + - ", projectId='" + projectId + '\'' + - ", naiveSsl='" + naiveSsl + '\'' + - ", SentryUrl='" + getSentryURL() + '\'' + - '}'; - } - -} diff --git a/old/src/main/java/net/kencochrane/sentry/RavenUtils.java b/old/src/main/java/net/kencochrane/sentry/RavenUtils.java deleted file mode 100644 index e280afbb6e2..00000000000 --- a/old/src/main/java/net/kencochrane/sentry/RavenUtils.java +++ /dev/null @@ -1,248 +0,0 @@ -package net.kencochrane.sentry; - -import org.apache.commons.lang.time.DateFormatUtils; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.security.SignatureException; -import java.util.Date; -import java.util.UUID; -import java.util.zip.CRC32; -import java.util.zip.Checksum; -import java.util.zip.GZIPOutputStream; - -import static org.apache.commons.codec.binary.Base64.encodeBase64String; - -/** - * User: ken Cochrane - * Date: 2/10/12 - * Time: 10:43 AM - * Utility class for the Raven client. - */ -public class RavenUtils { - - private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; - - /** - * Computes RFC 2104-compliant HMAC signature. - * Based off of the sample here. http://docs.amazonwebservices.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AuthJavaSampleHMACSignature.html - * - * @param data The data to be signed. - * @param key The signing key. - * @return The hex-encoded RFC 2104-compliant HMAC signature. - * @throws java.security.SignatureException - * when signature generation fails - */ - public static String calculateHMAC(String data, String key) throws java.security.SignatureException { - String result; - try { - - // get an hmac_sha1 key from the raw key bytes - SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM); - - // get an hmac_sha1 Mac instance and initialize with the signing key - Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); - mac.init(signingKey); - - // compute the hmac on input data bytes - byte[] rawHmac = mac.doFinal(data.getBytes()); - - result = hexEncode(rawHmac); - - } catch (Exception e) { - throw new SignatureException("Failed to generate HMAC : " + e.getMessage()); - } - return result; - } - - /** - * Get the hostname for the system throwing the error. - * - * @return The hostname for the server - */ - public static String getHostname() { - String hostname; - try { - hostname = InetAddress.getLocalHost().getCanonicalHostName(); - } catch (UnknownHostException e) { - // can't get hostname - hostname = "unavailable"; - } - return hostname; - } - - /** - * Convert a java date into the ISO8601 format YYYYMMDDTHH:mm:ss - * in timezone UTC - * - * @param date the date to convert - * @return ISO8601 formatted date as a String - */ - public static String getDateAsISO8601String(Date date) { - return DateFormatUtils.formatUTC(date, DateFormatUtils.ISO_DATETIME_FORMAT.getPattern()); - } - - /** - * Get the timestamp for right now as a long - * - * @return timestamp for now as long - */ - public static long getTimestampLong() { - return System.currentTimeMillis(); - } - - /** - * Given a long timestamp convert to a ISO8601 formatted date string - * - * @param timestamp the timestamp to convert - * @return ISO8601 formatted date string - */ - public static String getTimestampString(long timestamp) { - - java.util.Date date = new java.util.Date(timestamp); - return getDateAsISO8601String(date); - } - - /** - * Given the time right now return a ISO8601 formatted date string - * - * @return ISO8601 formatted date string - */ - public static String getTimestampString() { - - java.util.Date date = new java.util.Date(); - return getDateAsISO8601String(date); - } - - /** - * build the HMAC sentry signature - *

    - * The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, - * and an arbitrary client version string. - *

    - * The client version should be something distinct to your client, and is simply for reporting purposes. - * To generate the HMAC signature, take the following example (in Python): - *

    - * hmac.new(public_key, '%s %s' % (timestamp, message), hashlib.sha1).hexdigest() - * - * @param message the error message to send to sentry - * @param timestamp the timestamp for when the message was created - * @param key sentry public key - * @return SHA1-signed HMAC string - */ - public static String getSignature(String message, long timestamp, String key) { - String full_message = timestamp + " " + message; - String hmac = null; - try { - hmac = calculateHMAC(full_message, key); - - } catch (SignatureException e) { - e.printStackTrace(); - } - return hmac; - } - - - /** - * The byte[] returned by MessageDigest does not have a nice - * textual representation, so some form of encoding is usually performed. - *

    - * This implementation follows the example of David Flanagan's book - * "Java In A Nutshell", and converts a byte array into a String - * of hex characters. - *

    - * Another popular alternative is to use a "Base64" encoding. - * - * @param aInput what we are hex encoding - * @return hex encoded string. - */ - public static String hexEncode(byte[] aInput) { - StringBuilder result = new StringBuilder(); - char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - for (byte b : aInput) { - result.append(digits[(b & 0xf0) >> 4]); - result.append(digits[b & 0x0f]); - } - return result.toString(); - } - - /** - * An almost-unique hash identifying the this event to improve aggregation. - * - * @param message The message we are sending to sentry - * @return CRC32 Checksum string - */ - public static String calculateChecksum(String message) { - - // get bytes from string - byte bytes[] = message.getBytes(); - - Checksum checksum = new CRC32(); - - // update the current checksum with the specified array of bytes - checksum.update(bytes, 0, bytes.length); - - // get the current checksum value - long checksumValue = checksum.getValue(); - return String.valueOf(checksumValue); - } - - /** - * Hexadecimal string representing a uuid4 value. - * - * @return Hexadecimal UUID4 String - */ - public static String getRandomUUID() { - UUID uuid = UUID.randomUUID(); - String uuid_string = uuid.toString(); - // if we keep the -'s in the uuid, it is too long, remove them - uuid_string = uuid_string.replaceAll("-", ""); - return uuid_string; - } - - /** - * Gzip then base64 encode the str value - * - * @param str the value we want to compress and encode. - * @return Base64 encoded compressed version of the string passed in. - */ - public static String compressAndEncode(String str) { - if (str == null || str.length() == 0) { - return str; - } - ByteArrayOutputStream out = new ByteArrayOutputStream(); - GZIPOutputStream gzip; - try { - gzip = new GZIPOutputStream(out); - - gzip.write(str.getBytes()); - gzip.close(); - } catch (IOException e) { - e.printStackTrace(); - //todo do something here better then this. - } - return encodeBase64String(out.toByteArray()); - } - - public static byte[] toUtf8(String s) { - try { - return s == null ? new byte[0] : s.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - public static String fromUtf8(byte[] b) { - try { - return new String(b, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - -} diff --git a/old/src/main/java/net/kencochrane/sentry/SentryAppender.java b/old/src/main/java/net/kencochrane/sentry/SentryAppender.java deleted file mode 100644 index 71b176a5707..00000000000 --- a/old/src/main/java/net/kencochrane/sentry/SentryAppender.java +++ /dev/null @@ -1,106 +0,0 @@ -package net.kencochrane.sentry; - -import org.apache.log4j.AppenderSkeleton; -import org.apache.log4j.spi.LoggingEvent; - -/** - * User: ken cochrane - * Date: 2/6/12 - * Time: 10:54 AM - */ -public class SentryAppender extends AppenderSkeleton { - - private String sentry_dsn; - private String proxy; - private int queue_size; - private boolean blocking; - private boolean naiveSsl; - - public SentryAppender() - { - queue_size = 1000; - blocking = false; - } - - public String getSentry_dsn() { - return sentry_dsn; - } - - public void setSentry_dsn(String sentry_dsn) { - this.sentry_dsn = sentry_dsn; - } - - public String getProxy() { - return proxy; - } - - public void setProxy(String proxy) { - this.proxy = proxy; - } - - public int getQueue_size() { - return queue_size; - } - - public void setQueue_size(int queue_size) { - this.queue_size = queue_size; - } - - public boolean getBlocking() { - return blocking; - } - - public void setBlocking(boolean blocking) { - this.blocking = blocking; - } - - public boolean isNaiveSsl() { - return naiveSsl; - } - - public void setNaiveSsl(boolean naiveSsl) { - this.naiveSsl = naiveSsl; - } - - /** - * Look for the ENV variable first, and if it isn't there, then look in the log4j properties - * - */ - private String findSentryDSN(){ - String sentryDSN = System.getenv("SENTRY_DSN"); - if (sentryDSN == null || sentryDSN.length() == 0) { - sentryDSN = getSentry_dsn(); - if (sentryDSN == null) { - throw new RuntimeException("ERROR: You do not have a Sentry DSN configured! make sure you add sentry_dsn to your log4j properties, or have set SENTRY_DSN as an envirornment variable."); - } - } - return sentryDSN; - } - - @Override - protected void append(LoggingEvent loggingEvent) { - - //find the sentry DSN. - String sentryDSN = findSentryDSN(); - - synchronized (this) - { - if(!SentryQueue.getInstance().isSetup()) - { - SentryQueue.getInstance().setup(sentryDSN, getProxy(), queue_size, blocking, naiveSsl); - } - } - - SentryQueue.getInstance().addEvent(loggingEvent); - } - - public void close() - { - SentryQueue.getInstance().shutdown(); - } - - public boolean requiresLayout() - { - return false; - } -} diff --git a/old/src/main/java/net/kencochrane/sentry/SentryQueue.java b/old/src/main/java/net/kencochrane/sentry/SentryQueue.java deleted file mode 100644 index 7842755a6fb..00000000000 --- a/old/src/main/java/net/kencochrane/sentry/SentryQueue.java +++ /dev/null @@ -1,75 +0,0 @@ -package net.kencochrane.sentry; - -import org.apache.log4j.spi.LoggingEvent; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; - -/** - * User: mphilpot - * Date: 3/29/12 - */ -public class SentryQueue -{ - private static SentryQueue ourInstance = new SentryQueue(); - private static BlockingQueue queue; - - private SentryWorker worker; - private boolean blocking; - - public static SentryQueue getInstance() - { - return ourInstance; - } - - private SentryQueue() - { - queue = null; - - worker = null; - - } - - public void shutdown() - { - worker.shutdown(); - worker.interrupt(); - } - - public synchronized boolean isSetup() - { - return (queue != null); - } - - public synchronized void setup(String sentryDSN, String proxy, int queueSize, boolean blocking, boolean naiveSsl) - { - queue = new LinkedBlockingQueue(queueSize); - this.blocking = blocking; - - worker = new SentryWorker(queue, sentryDSN, proxy, naiveSsl); - worker.start(); - } - - public void addEvent(LoggingEvent le) - { - try - { - if(blocking) - { - queue.put(le); - } - else - { - queue.add(le); - } - } - catch(IllegalStateException e) - { - System.err.println("Sentry Queue Full :: " + le); - } - catch(InterruptedException e) - { - System.err.println("Sentry Queue Interrupted :: "+ le); - } - } -} diff --git a/old/src/main/java/net/kencochrane/sentry/SentryWorker.java b/old/src/main/java/net/kencochrane/sentry/SentryWorker.java deleted file mode 100644 index c36e4d71575..00000000000 --- a/old/src/main/java/net/kencochrane/sentry/SentryWorker.java +++ /dev/null @@ -1,83 +0,0 @@ -package net.kencochrane.sentry; - -import org.apache.log4j.spi.LoggingEvent; -import org.apache.log4j.spi.ThrowableInformation; - -import java.util.concurrent.BlockingQueue; - -/** - * User: mphilpot - * Date: 3/29/12 - */ -public class SentryWorker extends Thread -{ - private boolean shouldShutdown; - - private RavenClient client; - - private BlockingQueue queue; - - public SentryWorker(BlockingQueue queue, String sentryDSN, String proxy, boolean naiveSsl) - { - this.shouldShutdown = false; - this.queue = queue; - this.client = new RavenClient(sentryDSN, proxy, naiveSsl); - } - - @Override - public void run() - { - while(!shouldShutdown) - { - try - { - LoggingEvent le = queue.take(); - - sendToSentry(le); - } - catch (InterruptedException e) - { - // Thread interrupted... probably shutting down - } - } - } - - public void shutdown() - { - shouldShutdown = true; - } - - public void sendToSentry(LoggingEvent loggingEvent) - { - synchronized (this) - { - try - { - // get timestamp and timestamp in correct string format. - long timestamp = loggingEvent.getTimeStamp(); - - // get the log and info about the log. - String logMessage = loggingEvent.getRenderedMessage(); - String loggingClass = loggingEvent.getLogger().getName(); - int logLevel = (loggingEvent.getLevel().toInt() / 1000); //Need to divide by 1000 to keep consistent with sentry - String culprit = loggingEvent.getLoggerName(); - - // is it an exception? - ThrowableInformation throwableInformation = loggingEvent.getThrowableInformation(); - - // send the message to the sentry server - if (throwableInformation == null){ - client.captureMessage(logMessage, timestamp, loggingClass, logLevel, culprit); - }else{ - client.captureException(logMessage, timestamp, loggingClass, logLevel, culprit, throwableInformation.getThrowable()); - } - - } catch (Exception e) - { - // Can we tell if there is another logger to send the event to? - System.err.println(e); - } - } - } - -} diff --git a/old/src/test/java/net/kencochrane/sentry/PerformanceTest.java b/old/src/test/java/net/kencochrane/sentry/PerformanceTest.java deleted file mode 100644 index 8df5204ba1f..00000000000 --- a/old/src/test/java/net/kencochrane/sentry/PerformanceTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package net.kencochrane.sentry; - -import org.apache.log4j.Logger; -import org.apache.log4j.PropertyConfigurator; -import org.junit.Test; - -import java.util.Date; - -/** - * User: mphilpot - * Date: 3/29/12 - */ -public class PerformanceTest -{ - static final Logger log = Logger.getLogger(PerformanceTest.class); - - @Test - public void testPerformance() throws InterruptedException - { - PropertyConfigurator.configure(getClass().getResource("/log4j_configuration.txt")); - - Date start = new Date(); - for(int i = 0; i < 100; i++) - { - log.info("Simple log message w/ no exception"); - } - Date end = new Date(); - - System.out.println(String.format("Simple test :: %d ms", end.getTime() - start.getTime())); - - Date startE = new Date(); - Exception e = new Exception("Test Exception"); - for(int i = 0; i < 100; i++ ) - { - log.warn("Log message w/ exception", e); - } - Date endE = new Date(); - - System.out.println(String.format("Exception test :: %d ms", endE.getTime() - startE.getTime())); - - // To see the messages get sent to the server - //TimeUnit.SECONDS.sleep(30); - } -} diff --git a/old/src/test/java/net/kencochrane/sentry/RavenConfigTest.java b/old/src/test/java/net/kencochrane/sentry/RavenConfigTest.java deleted file mode 100644 index db99232acb9..00000000000 --- a/old/src/test/java/net/kencochrane/sentry/RavenConfigTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.kencochrane.sentry; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class RavenConfigTest -{ - @Test - public void testDSNComponents() { - - // Without Port - RavenClient client = new RavenClient("http://public:secret@example.com/path/sentry/1"); - assertEquals("http://public:secret@example.com/path/sentry/1", client.getSentryDSN()); - assertEquals("example.com", client.getConfig().getHost()); - assertEquals("/path/sentry", client.getConfig().getPath()); - assertEquals(-1, client.getConfig().getPort()); - assertEquals("1", client.getConfig().getProjectId()); - assertEquals("http://example.com/path/sentry/api/store/", client.getConfig().getSentryURL()); - - // With Port - client = new RavenClient("http://public:secret@example.com:9000/path/sentry/1"); - assertEquals(9000, client.getConfig().getPort()); - assertEquals("http://example.com:9000/path/sentry/api/store/", client.getConfig().getSentryURL()); - } -} diff --git a/old/src/test/java/net/kencochrane/sentry/SentryClientTest.java b/old/src/test/java/net/kencochrane/sentry/SentryClientTest.java deleted file mode 100644 index 651bb016a0d..00000000000 --- a/old/src/test/java/net/kencochrane/sentry/SentryClientTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package net.kencochrane.sentry; - -import mockit.Mocked; -import mockit.Expectations; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class SentryClientTest { - public void triggerRuntimeException() { - try { - triggerNullPointer(); - } catch (Exception e) { - throw new RuntimeException("Error triggering null pointer", e); - } - } - - public String triggerNullPointer() { - String c = null; - return c.toLowerCase(); - } - - @Test(expected=RuntimeException.class) - public void testMissingDSN() { - RavenClient client = new RavenClient(); - } - - @Test - public void testConfigureFromDSN() { - RavenClient client = new RavenClient("http://public:secret@example.com/path/1"); - assertEquals(client.getSentryDSN(), "http://public:secret@example.com/path/1"); - } - - @Test - public void testConfigureFromEnvironment() { - new Expectations() - { - @Mocked("getenv") System mockedSystem; - - { - System.getenv("SENTRY_DSN"); returns("http://public:secret@example.com/path/1"); - } - }; - RavenClient client = new RavenClient(); - assertEquals(client.getSentryDSN(), "http://public:secret@example.com/path/1"); - } - - @Test - public void testCaptureExceptionWithOnlyThrowable() { - RavenClient client = new RavenClient("http://public:secret@example.com/path/1"); - - new Expectations() - { - - @Mocked("getRandomUUID") RavenUtils mockedRavenUtils; - - { - RavenUtils.getRandomUUID(); returns("1234567890"); - } - - // TODO: this should be mocked, somehow - // RavenClient.sendMessage(); minTimes = 1; maxTimes = 1; - }; - - - try { - triggerRuntimeException(); - } catch (RuntimeException e) { - String ident = client.captureException(e); - assertEquals(ident, "1234567890"); - } - } -} \ No newline at end of file diff --git a/old/src/test/java/net/kencochrane/sentry/SentryExample.java b/old/src/test/java/net/kencochrane/sentry/SentryExample.java deleted file mode 100644 index d7f65d0e5a9..00000000000 --- a/old/src/test/java/net/kencochrane/sentry/SentryExample.java +++ /dev/null @@ -1,61 +0,0 @@ -package net.kencochrane.sentry; - -/** - * User: ken cochrane - * Date: 2/6/12 - * Time: 11:35 AM - */ - -// Import log4j classes. - -import org.apache.log4j.Logger; -import org.apache.log4j.PropertyConfigurator; -import org.junit.Test; - -/** - * Simple example used to test out the sentry logger. - */ -public class SentryExample { - - // Define a static logger variable so that it references the - // Logger instance named "MyApp". - static final Logger logger = Logger.getLogger(SentryExample.class); - - public void triggerRuntimeException() { - try { - triggerNullPointer(); - } catch (Exception e) { - throw new RuntimeException("Error triggering null pointer", e); - } - } - - public String triggerNullPointer() { - String c = null; - return c.toLowerCase(); - } - - @Test - public void test_simple() { - - // PropertyConfigurator. - PropertyConfigurator.configure(getClass().getResource("/log4j_configuration.txt")); - - logger.debug("Debug example"); - logger.error("Error example"); - logger.trace("Trace Example"); - logger.fatal("Fatal Example"); - logger.info("info Example"); - logger.warn("Warn Example"); - try { - triggerRuntimeException(); - } catch (RuntimeException e) { - logger.error("Error example with stacktrace", e); - } - // This really shouldn't be necessary - try { - Thread.sleep(5000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } -} diff --git a/old/src/test/resources/log4j_configuration.txt b/old/src/test/resources/log4j_configuration.txt deleted file mode 100644 index a3e61fb9fa2..00000000000 --- a/old/src/test/resources/log4j_configuration.txt +++ /dev/null @@ -1,28 +0,0 @@ -log4j.rootLogger=info, stdout, R, sentry - -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout - -# Pattern to output the caller's file name and line number. -log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n - -log4j.appender.R=org.apache.log4j.RollingFileAppender -log4j.appender.R.File=example.log - -log4j.appender.R.MaxFileSize=100KB -# Keep one backup file -log4j.appender.R.MaxBackupIndex=1 - -log4j.appender.R.layout=org.apache.log4j.PatternLayout -log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n - -# These are for Sentry and Raven-java -log4j.appender.sentry=net.kencochrane.sentry.SentryAppender -log4j.appender.sentry.sentry_dsn=http://7e4dff58960645adb2ade337e6d53425:81fe140206d7464e911b89cd93e2a5a4@localhost:9000/2 - -# If you're using Java 6 or earlier, you might encounter errors when connecting to a Sentry instance using a TLS (https) -# connection since Server Name Indication support has only been available in Java since Java 7. You can either add -# the corresponding certificate to your keystore (recommended!) or enable naiveSsl on the Sentry appender. This will -# make the Raven client use a custom hostname verifier that *will* allow the JVM to connect with the host - in fact it -# will let the Raven client connect to any host even if the certificate is invalid. Use at your own risk. -# log4j.appender.sentry.naiveSsl=true diff --git a/raven-integration-tests/README.md b/raven-integration-tests/README.md deleted file mode 100644 index 31f3fe5b9b6..00000000000 --- a/raven-integration-tests/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Raven Integration Tests - -This module provides integration tests for Raven-Java with Sentry. Running these tests is a bit cumbersome, but worth -it. - -## 1. Install Sentry -Make sure you have Sentry installed. Follow the instructions in -[Sentry's documentation](http://sentry.readthedocs.org/en/latest/quickstart/index.html#install-sentry) if you haven't. - -## 2. Install foreman -[Foreman](http://ddollar.github.com/foreman/) allows you to use a Procfile which we can use to run the HTTP and UDP -services of Sentry in the same terminal. - -## 3. Run `src/main/resources/run_sentry.sh` before running the tests -This script will remove any existing `sentry.db` at the *same* location so we can start fresh, replace it with `boot_sentry.db` and then perform a Sentry -upgrade using `default_config.py` as the Sentry configuration. The database is set up with a user with username and password `test`. - -Once the Sentry installation is finished, the `Procfile` at the same location will be used to kickstart Sentry HTTP and -UDP services at ports 9500 and 9501 respectively. - -## 4. Run the tests -Run the tests like you normally would. \ No newline at end of file diff --git a/raven-integration-tests/pom.xml b/raven-integration-tests/pom.xml deleted file mode 100644 index 8e2e7bcc0b5..00000000000 --- a/raven-integration-tests/pom.xml +++ /dev/null @@ -1,111 +0,0 @@ - - - 4.0.0 - - net.kencochrane - raven-integration-tests - 2.0-SNAPSHOT - jar - raven-integration-tests - Raven-Java/Sentry integration tests - - - - ${project.groupId} - raven - ${project.version} - - - ${project.groupId} - raven-log4j - ${project.version} - - - org.apache.httpcomponents - httpclient - 4.1.2 - - - org.jsoup - jsoup - 1.6.3 - - - com.googlecode.json-simple - json-simple - 1.1 - - - com.googlecode.jmockit - jmockit - 0.999.12 - test - - - junit - junit - 4.8.1 - test - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - UTF-8 - - - - org.apache.maven.plugins - maven-resources-plugin - 2.5 - - UTF-8 - - - - - - - - ${basedir}/src/main/java - - **/*.json - **/*.yml - - - - ${basedir}/src/main/resources - - **/*.* - - - - - - - ${basedir}/src/test/java - - **/*.json - **/*.yml - **/*.txt - - - - ${basedir}/src/test/resources - - **/*.* - - - - - - \ No newline at end of file diff --git a/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java b/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java deleted file mode 100644 index d3d61ba7292..00000000000 --- a/raven-integration-tests/src/main/java/net/kencochrane/raven/SentryApi.java +++ /dev/null @@ -1,187 +0,0 @@ -package net.kencochrane.raven; - -import org.apache.commons.lang.StringUtils; -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.params.ClientPNames; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.protocol.BasicHttpContext; -import org.apache.http.protocol.HttpContext; -import org.apache.http.util.EntityUtils; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Sentry tools. - */ -public class SentryApi { - - public final String host; - public final HttpClient client = new DefaultHttpClient(); - public final HttpContext context = new BasicHttpContext(); - private boolean loggedIn; - - public SentryApi() throws MalformedURLException { - this("http://localhost:9500"); - } - - public SentryApi(String host) { - this.host = host; - // Otherwise HttpClient gets confused when hitting the dashboard without an existing project - client.getParams().setParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true); - } - - public boolean login(String username, String password) throws IOException { - if (loggedIn) { - return true; - } - String url = host + "/login/"; - HttpResponse response = client.execute(new HttpGet(url), context); - String html = EntityUtils.toString(response.getEntity()); - Document doc = Jsoup.parse(html); - Elements inputs = doc.select("input[name=csrfmiddlewaretoken]"); - String token = inputs.get(0).val(); - List formparams = new ArrayList(); - formparams.add(new BasicNameValuePair("username", username)); - formparams.add(new BasicNameValuePair("password", password)); - formparams.add(new BasicNameValuePair("csrfmiddlewaretoken", token)); - HttpPost post = new HttpPost(host + "/login/"); - UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, "UTF-8"); - post.setEntity(entity); - response = client.execute(post, context); - if (response.getStatusLine().getStatusCode() == 302) { - EntityUtils.toString(response.getEntity()); - response = client.execute(new HttpGet(url), context); - html = EntityUtils.toString(response.getEntity()); - doc = Jsoup.parse(html); - html = doc.getElementById("header").select("li.dropdown").get(1).html(); - loggedIn = html.contains(">Logout<"); - } - return loggedIn; - } - - public String getDsn(String projectSlug) throws IOException { - HttpResponse response = client.execute(new HttpGet(host + "/account/projects/" + projectSlug + "/docs/")); - String html = EntityUtils.toString(response.getEntity()); - Document doc = Jsoup.parse(html); - Element wrapper = doc.select("#content code.clippy").get(0); - return StringUtils.trim(wrapper.html()); - } - - public boolean clear(String projectId) throws IOException { - HttpResponse response = client.execute(new HttpPost(host + "/api/" + projectId + "/clear/")); - boolean ok = (response.getStatusLine().getStatusCode() == 200); - EntityUtils.toString(response.getEntity()); - return ok; - } - - public JSONArray getRawJson(String projectSlug, String group) throws IOException { - HttpResponse response = client.execute(new HttpGet(host + "/" + projectSlug + "/group/" + group + "/events/json/")); - String raw = EntityUtils.toString(response.getEntity()); - if (StringUtils.isBlank(raw)) { - return null; - } - try { - return (JSONArray) new JSONParser().parse(raw); - } catch (ParseException e) { - throw new IOException(e); - } - } - - public List getEvents(String projectId) throws IOException { - HttpResponse response = client.execute(new HttpGet(host + "/api/" + projectId + "/poll/")); - try { - Object json = new JSONParser().parse(EntityUtils.toString(response.getEntity())); - List eventJson = null; - if (json instanceof JSONObject) { - eventJson = Arrays.asList((JSONObject) json); - } else { - eventJson = (List) json; - } - List events = new LinkedList(); - for (JSONObject item : eventJson) { - Boolean resolved = (Boolean) item.get("isResolved"); - if (resolved != null && resolved) { - continue; - } - String group = (String) item.get("id"); - Integer count = Integer.parseInt((String) item.get("count")); - Integer level = ((Long) item.get("level")).intValue(); - String levelName = (String) item.get("levelName"); - String link = (String) item.get("permalink"); - String title = (String) item.get("title"); - String message = (String) item.get("message"); - String logger = (String) item.get("logger"); - events.add(new Event(group, count, level, levelName, link, title, message, logger)); - } - return events; - } catch (ParseException e) { - throw new IOException(e); - } - } - - public List getAvailableTags(String projectSlug) throws IOException { - HttpResponse response = client.execute(new HttpGet(host + "/account/projects/" + projectSlug + "/tags/")); - Document doc = Jsoup.parse(EntityUtils.toString(response.getEntity())); - Elements items = doc.select("#div_id_filters label.checkbox"); - Pattern pattern = Pattern.compile(".*\\((\\w+)\\)"); - List tagNames = new LinkedList(); - for (Element item : items) { - Matcher matcher = pattern.matcher(item.text()); - if (matcher.matches()) { - tagNames.add(matcher.group(1)); - } - } - return tagNames; - } - - protected static String extractLevel(Collection classNames) { - for (String name : classNames) { - if (name.startsWith("level-")) { - return name.replace("level-", ""); - } - } - return null; - } - - public static class Event { - public final String group; - public final int count; - public final int level; - public final String levelName; - public final String url; - public final String title; - public final String message; - public final String logger; - - public Event(String group, int count, int level, String levelName, String url, String title, String message, String logger) { - this.group = group; - this.count = count; - this.level = level; - this.levelName = levelName; - this.url = url; - this.title = title; - this.message = message; - this.logger = logger; - } - - } - -} diff --git a/raven-integration-tests/src/main/resources/Procfile b/raven-integration-tests/src/main/resources/Procfile deleted file mode 100644 index e999972c151..00000000000 --- a/raven-integration-tests/src/main/resources/Procfile +++ /dev/null @@ -1,2 +0,0 @@ -web: sentry --config=default_config.py start http -udp: sentry --config=default_config.py start udp \ No newline at end of file diff --git a/raven-integration-tests/src/main/resources/boot_sentry.db b/raven-integration-tests/src/main/resources/boot_sentry.db deleted file mode 100644 index 9524252387d093cfc0e367a006921c5da9ffea7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 581632 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 cFaX1MZK*U;qFB diff --git a/raven-integration-tests/src/main/resources/default_config.py b/raven-integration-tests/src/main/resources/default_config.py deleted file mode 100644 index b6b974479d7..00000000000 --- a/raven-integration-tests/src/main/resources/default_config.py +++ /dev/null @@ -1,49 +0,0 @@ -import os.path - -CONF_ROOT = os.path.dirname(__file__) - -DATABASES = { - 'default': { - # You can swap out the engine for MySQL easily by changing this value - # to ``django.db.backends.mysql`` or to PostgreSQL with - # ``django.db.backends.postgresql_psycopg2`` - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(CONF_ROOT, 'sentry.db'), - 'USER': 'postgres', - 'PASSWORD': '', - 'HOST': '', - 'PORT': '', - } -} - -SENTRY_KEY = 'dhrlPFFtABltJoCdbQ9b327uY6vufXGOymBZnC348p3Hoeot0K8CTw==' - -# Set this to false to require authentication -SENTRY_PUBLIC = True - -# You should configure the absolute URI to Sentry. It will attempt to guess it if you don't -# but proxies may interfere with this. -# SENTRY_URL_PREFIX = 'http://sentry.example.com' # No trailing slash! - -SENTRY_WEB_HOST = '0.0.0.0' -SENTRY_WEB_PORT = 9500 -SENTRY_WEB_OPTIONS = { - 'workers': 3, # the number of gunicorn workers - # 'worker_class': 'gevent', -} - -# Mail server configuration - -# For more information check Django's documentation: -# https://docs.djangoproject.com/en/1.3/topics/email/?from=olddocs#e-mail-backends - -EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' - -EMAIL_HOST = 'localhost' -EMAIL_HOST_PASSWORD = '' -EMAIL_HOST_USER = '' -EMAIL_PORT = 25 -EMAIL_USE_TLS = False - -SENTRY_UDP_HOST = '0.0.0.0' # bind to all addresses -SENTRY_UDP_PORT = 9501 \ No newline at end of file diff --git a/raven-integration-tests/src/main/resources/run_sentry.sh b/raven-integration-tests/src/main/resources/run_sentry.sh deleted file mode 100755 index be2442b5e8c..00000000000 --- a/raven-integration-tests/src/main/resources/run_sentry.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -rm -f sentry.db -cp boot_sentry.db sentry.db -sentry --config=default_config.py upgrade -foreman start diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java deleted file mode 100644 index b1ebb6efb19..00000000000 --- a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/ClientTest.java +++ /dev/null @@ -1,145 +0,0 @@ -package net.kencochrane.raven.integration; - -import net.kencochrane.raven.Client; -import net.kencochrane.raven.SentryApi; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.junit.Before; -import org.junit.Test; - -import java.io.IOException; -import java.util.*; - -import static org.junit.Assert.*; - -/** - * Integration tests for {@link net.kencochrane.raven.Client}. - */ -public class ClientTest { - - private SentryApi api; - - @Test - public void captureMessage_http() throws IOException, InterruptedException { - Client client = new Client(IntegrationContext.httpDsn); - String message = ClientTest.class.getName() + ".captureMessage_http says hi!"; - captureMessage(client, message, false); - } - - @Test - public void captureMessage_withTags() throws IOException, InterruptedException { - Client client = new Client(IntegrationContext.httpDsn); - String message = ClientTest.class.getName() + ".captureMessage_withTags says hi!"; - Map tags = new HashMap(); - tags.put("release", "1.2.0"); - tags.put("uptime", 60000L); - tags.put("client", "Raven-Java"); - captureMessage(client, message, false, tags); - } - - @Test - public void captureMessage_udp() throws IOException, InterruptedException { - Client client = new Client(IntegrationContext.udpDsn); - String message = ClientTest.class.getName() + ".captureMessage_udp says hi!"; - captureMessage(client, message, true); - } - - @Test - public void captureMessage_complex_http() throws IOException { - Client client = new Client(IntegrationContext.httpDsn); - final String message = ClientTest.class.getName() + ".captureMessage_complex_http says hi!"; - final String logger = "some.custom.logger.Name"; - final String culprit = "Damn you!"; - client.captureMessage(message, null, logger, null, culprit); - - List events = api.getEvents(IntegrationContext.projectSlug); - assertEquals(1, events.size()); - SentryApi.Event event = events.get(0); - assertTrue(event.count > 0); - assertEquals(Client.Default.LOG_LEVEL, event.level); - assertEquals(message, event.message); - assertEquals(culprit, event.title); - assertEquals(logger, event.logger); - } - - @Test - public void captureMessage_complex_udp() throws IOException, InterruptedException { - Client client = new Client(IntegrationContext.udpDsn); - final String message = ClientTest.class.getName() + ".captureMessage_complex_udp says hi!"; - final String logger = "some.custom.logger.Name"; - final String culprit = "Damn you!"; - client.captureMessage(message, null, logger, null, culprit); - Thread.sleep(1000); - - List events = api.getEvents(IntegrationContext.projectSlug); - assertEquals(1, events.size()); - SentryApi.Event event = events.get(0); - assertTrue(event.count > 0); - assertEquals(Client.Default.LOG_LEVEL, event.level); - assertEquals(message, event.message); - assertEquals(culprit, event.title); - assertEquals(logger, event.logger); - } - - protected void captureMessage(Client client, String message, boolean wait) throws IOException, InterruptedException { - captureMessage(client, message, wait, null); - } - - protected void captureMessage(Client client, String message, boolean wait, Map tags) throws IOException, InterruptedException { - client.captureMessage(message, tags); - if (wait) { - // Wait a bit in case of UDP transport or tags - Thread.sleep(1000 + (tags == null || tags.isEmpty() ? 0 : 2000)); - } - List events = api.getEvents(IntegrationContext.projectSlug); - assertEquals(1, events.size()); - SentryApi.Event event = events.get(0); - assertTrue(event.count > 0); - assertEquals(Client.Default.LOG_LEVEL, event.level); - assertEquals(message, event.message); - assertEquals(message, event.title); - assertEquals(Client.Default.LOGGER, event.logger); - - // Log the same message; the count should be incremented - client.captureMessage(message, tags); - if (wait) { - Thread.sleep(1000); - } - events = api.getEvents(IntegrationContext.projectSlug); - assertEquals(1, events.size()); - SentryApi.Event newEvent = events.get(0); - assertEquals(event.count + 1, newEvent.count); - assertEquals(Client.Default.LOG_LEVEL, event.level); - assertEquals(message, event.message); - assertEquals(message, event.title); - assertEquals(Client.Default.LOGGER, event.logger); - - JSONArray jsonArray = api.getRawJson(IntegrationContext.projectSlug, newEvent.group); - assertNotNull(jsonArray); - assertTrue(jsonArray.size() > 0); - JSONObject json = (JSONObject) jsonArray.get(0); - JSONObject messageJson = (JSONObject) json.get("sentry.interfaces.Message"); - assertNotNull(messageJson); - assertEquals(message, messageJson.get("message")); - assertTrue(messageJson.get("params") instanceof JSONArray); - - if (tags != null && !tags.isEmpty()) { - List tagNames = api.getAvailableTags(IntegrationContext.projectSlug); - for (String tagName : tagNames) { - System.out.println("Found tag " + tagName); - } - for (String tagName : tags.keySet()) { - System.out.println("Expect tag " + tagName); - } - assertTrue(new HashSet(tagNames).containsAll(tags.keySet())); - } - } - - @Before - public void setUp() throws IOException { - IntegrationContext.init(); - api = IntegrationContext.api; - api.clear(IntegrationContext.httpDsn.projectId); - } - -} diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/IntegrationContext.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/IntegrationContext.java deleted file mode 100644 index d53151b3f6e..00000000000 --- a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/IntegrationContext.java +++ /dev/null @@ -1,35 +0,0 @@ -package net.kencochrane.raven.integration; - -import net.kencochrane.raven.SentryApi; -import net.kencochrane.raven.SentryDsn; - -import java.io.IOException; - -/** - * Context for integration tests. - */ -public class IntegrationContext { - - public static String host = "http://localhost"; - public static int port = 9500; - public static int udpPort = 9501; - public static SentryApi api; - public static SentryDsn httpDsn; - public static SentryDsn udpDsn; - public static String projectSlug = "ravenjava"; - public static String projectId = "2"; - protected static boolean initialized = false; - - public static void init() throws IOException { - api = new SentryApi(host + ":" + port); - boolean loggedOn = api.login("test", "test"); - if (!loggedOn) { - throw new RuntimeException("Could not log on"); - } - String dsn = api.getDsn(projectSlug); - httpDsn = SentryDsn.build(dsn, null, null); - udpDsn = SentryDsn.build(dsn.replace("http://", "udp://").replace(":" + port, ":" + udpPort), null, null); - initialized = true; - } - -} diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/AsyncSentryAppenderTest.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/AsyncSentryAppenderTest.java deleted file mode 100644 index f2cffdfd048..00000000000 --- a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/AsyncSentryAppenderTest.java +++ /dev/null @@ -1,153 +0,0 @@ -package net.kencochrane.raven.integration.log4j; - -import net.kencochrane.raven.SentryApi; -import net.kencochrane.raven.Utils; -import net.kencochrane.raven.integration.IntegrationContext; -import org.apache.log4j.Logger; -import org.apache.log4j.PropertyConfigurator; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.io.IOException; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * Integration tests for {@link net.kencochrane.raven.log4j.AsyncSentryAppender}. - */ -public class AsyncSentryAppenderTest { - - private SentryApi api; - - @Test - public void debug() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "This is a debug message"; - Logger logger = Logger.getLogger(loggerName); - logger.debug(message); - - verify(loggerName, message, "debug", loggerName); - } - - @Test - public void debug_withThrowable() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "This is a debug message"; - Logger logger = Logger.getLogger(loggerName); - logger.debug(message, new NullPointerException("omg!")); - - final String levelName = "debug"; - final String title = this.getClass().getName() + ".debug_withThrowable"; - verify(loggerName, message, levelName, title); - } - - @Test - public void info() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "This is an info message"; - Logger logger = Logger.getLogger(loggerName); - logger.info(message); - - verify(loggerName, message, "info", loggerName); - } - - @Test - public void info_withThrowable() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "This is an info message"; - Logger logger = Logger.getLogger(loggerName); - logger.info(message, new NullPointerException("omg!")); - - verify(loggerName, message, "info", this.getClass().getName() + ".info_withThrowable"); - } - - @Test - public void warn() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "This is a warning!"; - Logger logger = Logger.getLogger(loggerName); - logger.warn(message); - - verify(loggerName, message, "warning", loggerName); - } - - @Test - public void warn_withThrowable() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "This is another warning"; - Logger logger = Logger.getLogger(loggerName); - logger.warn(message, new NullPointerException("no no no!")); - - verify(loggerName, message, "warning", this.getClass().getName() + ".warn_withThrowable"); - } - - @Test - public void error() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "This is an error!"; - Logger logger = Logger.getLogger(loggerName); - logger.error(message); - verify(loggerName, message, "error", loggerName); - } - - @Test - public void error_withThrowable() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "This is an... err.. or"; - Logger logger = Logger.getLogger(loggerName); - logger.error(message, new NullPointerException("no no no!")); - verify(loggerName, message, "error", this.getClass().getName() + ".error_withThrowable"); - } - - @Test - public void fatal() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "Head for the lifeboat!"; - Logger logger = Logger.getLogger(loggerName); - logger.fatal(message); - verify(loggerName, message, "fatal", loggerName); - } - - @Test - public void fatal_withThrowable() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "What lifeboat?"; - Logger logger = Logger.getLogger(loggerName); - logger.fatal(message, new NullPointerException("no no no!")); - verify(loggerName, message, "fatal", this.getClass().getName() + ".fatal_withThrowable"); - } - - @Before - public void setUp() throws IOException { - IntegrationContext.init(); - api = IntegrationContext.api; - api.clear(IntegrationContext.httpDsn.projectId); - System.setProperty(Utils.SENTRY_DSN, IntegrationContext.httpDsn.toString()); - PropertyConfigurator.configure(getClass().getResource("/asyncsentryappender.log4j.properties")); - } - - @After - public void tearDown() throws IOException { - System.setProperty(Utils.SENTRY_DSN, ""); - } - - private void verify(String loggerName, String message, String levelName, String title) throws IOException { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // Ignore - } - List events = api.getEvents(IntegrationContext.projectSlug); - assertEquals(1, events.size()); - SentryApi.Event event = events.get(0); - assertTrue(event.count > 0); - assertEquals(levelName, event.levelName); - assertEquals(message, event.message); - assertEquals(title, event.title); - assertEquals(loggerName, event.logger); - } - -} diff --git a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java b/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java deleted file mode 100644 index b3871ad29aa..00000000000 --- a/raven-integration-tests/src/test/java/net/kencochrane/raven/integration/log4j/SentryAppenderTest.java +++ /dev/null @@ -1,148 +0,0 @@ -package net.kencochrane.raven.integration.log4j; - -import net.kencochrane.raven.SentryApi; -import net.kencochrane.raven.Utils; -import net.kencochrane.raven.integration.IntegrationContext; -import org.apache.log4j.Logger; -import org.apache.log4j.PropertyConfigurator; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.io.IOException; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * Integration tests for {@link net.kencochrane.raven.log4j.SentryAppender}. - */ -public class SentryAppenderTest { - - private SentryApi api; - - @Test - public void debug() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "This is a debug message"; - Logger logger = Logger.getLogger(loggerName); - logger.debug(message); - - verify(loggerName, message, "debug", loggerName); - } - - @Test - public void debug_withThrowable() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "This is a debug message"; - Logger logger = Logger.getLogger(loggerName); - logger.debug(message, new NullPointerException("omg!")); - - final String levelName = "debug"; - final String title = this.getClass().getName() + ".debug_withThrowable"; - verify(loggerName, message, levelName, title); - } - - @Test - public void info() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "This is an info message"; - Logger logger = Logger.getLogger(loggerName); - logger.info(message); - - verify(loggerName, message, "info", loggerName); - } - - @Test - public void info_withThrowable() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "This is an info message"; - Logger logger = Logger.getLogger(loggerName); - logger.info(message, new NullPointerException("omg!")); - - verify(loggerName, message, "info", this.getClass().getName() + ".info_withThrowable"); - } - - @Test - public void warn() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "This is a warning!"; - Logger logger = Logger.getLogger(loggerName); - logger.warn(message); - - verify(loggerName, message, "warning", loggerName); - } - - @Test - public void warn_withThrowable() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "This is another warning"; - Logger logger = Logger.getLogger(loggerName); - logger.warn(message, new NullPointerException("no no no!")); - - verify(loggerName, message, "warning", this.getClass().getName() + ".warn_withThrowable"); - } - - @Test - public void error() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "This is an error!"; - Logger logger = Logger.getLogger(loggerName); - logger.error(message); - verify(loggerName, message, "error", loggerName); - } - - @Test - public void error_withThrowable() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "This is an... err.. or"; - Logger logger = Logger.getLogger(loggerName); - logger.error(message, new NullPointerException("no no no!")); - verify(loggerName, message, "error", this.getClass().getName() + ".error_withThrowable"); - } - - @Test - public void fatal() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "Head for the lifeboat!"; - Logger logger = Logger.getLogger(loggerName); - logger.fatal(message); - verify(loggerName, message, "fatal", loggerName); - } - - @Test - public void fatal_withThrowable() throws IOException { - final String loggerName = this.getClass().getName(); - final String message = "What lifeboat?"; - Logger logger = Logger.getLogger(loggerName); - logger.fatal(message, new NullPointerException("no no no!")); - verify(loggerName, message, "fatal", this.getClass().getName() + ".fatal_withThrowable"); - } - - @Before - public void setUp() throws IOException { - IntegrationContext.init(); - api = IntegrationContext.api; - api.clear(IntegrationContext.httpDsn.projectId); - System.setProperty(Utils.SENTRY_DSN, IntegrationContext.httpDsn.toString()); - PropertyConfigurator.configure(getClass().getResource("/sentryappender.log4j.properties")); - } - - @After - public void tearDown() throws IOException { - System.setProperty(Utils.SENTRY_DSN, ""); - } - - private void verify(String loggerName, String message, String levelName, String title) throws IOException { - List events = api.getEvents(IntegrationContext.projectId); - assertEquals(1, events.size()); - SentryApi.Event event = events.get(0); - assertTrue(event.count > 0); - assertEquals(levelName, event.levelName); - assertEquals(message, event.message); - assertEquals(title, event.title); - assertEquals(loggerName, event.logger); - } - -} diff --git a/raven-integration-tests/src/test/resources/asyncsentryappender.log4j.properties b/raven-integration-tests/src/test/resources/asyncsentryappender.log4j.properties deleted file mode 100644 index 20fb2e2394a..00000000000 --- a/raven-integration-tests/src/test/resources/asyncsentryappender.log4j.properties +++ /dev/null @@ -1,15 +0,0 @@ -log4j.rootLogger=trace, stdout, sentry - -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout - -log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n - -log4j.appender.sentry=net.kencochrane.raven.log4j.AsyncSentryAppender -log4j.appender.sentry.sentryDsn=SENTRY_DSN - -log4j.logger.raven=DEBUG, stdout -log4j.additivity.raven=false - -log4j.logger.org.apache.http=ERROR -log4j.additivity.org.apache.http=false \ No newline at end of file diff --git a/raven-integration-tests/src/test/resources/sentryappender.log4j.properties b/raven-integration-tests/src/test/resources/sentryappender.log4j.properties deleted file mode 100644 index 82a82396918..00000000000 --- a/raven-integration-tests/src/test/resources/sentryappender.log4j.properties +++ /dev/null @@ -1,23 +0,0 @@ -# Basic setup -log4j.rootLogger=trace, stdout, sentry - -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout - -# Pattern to output the caller's file name and line number. -log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n - -# Configure the Sentry appender. Note: when a SENTRY_DSN environment variable -# or system property is available, that value will be used instead of the value -# of log4j.appender.sentry.sentryDsn -log4j.appender.sentry=net.kencochrane.raven.log4j.SentryAppender -log4j.appender.sentry.sentryDsn=SENTRY_DSN - -# Do not let the Raven logging go to Sentry or you could end up in an infinite loop when -# something goes wrong with your configuration. Sentry uses the java.util.logging package -# so this should only happen when you add a bridge from JUL to Log4J. -log4j.logger.raven=DEBUG, stdout -log4j.additivity.raven=false - -log4j.logger.org.apache.http=ERROR -log4j.additivity.org.apache.http=false \ No newline at end of file diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java deleted file mode 100644 index ce743e3ca6a..00000000000 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/AsyncSentryAppender.java +++ /dev/null @@ -1,76 +0,0 @@ -package net.kencochrane.raven.log4j; - -import org.apache.log4j.AsyncAppender; -import org.apache.log4j.spi.LoggingEvent; - -/** - * A Log4J appender that sends events asynchronously to Sentry. - *

    - * This appender extends Log4J's {@link AsyncAppender}. If you use a log4j.xml file to configure Log4J, you can use that - * class directly to wrap a {@link SentryAppender}. If you use a log4j.properties file -- which doesn't provide a way to - * configure the {@link AsyncAppender} -- to configure Log4J and you need to send events asynchronously, you can use - * this class instead. - *

    - */ -public class AsyncSentryAppender extends AsyncAppender { - - private String sentryDsn; - private String jsonProcessors; - private SentryAppender appender; - private boolean messageCompressionEnabled = true; - - public AsyncSentryAppender() { - SentryAppender.initMDC(); - } - - public String getSentryDsn() { - return sentryDsn; - } - - public void setSentryDsn(String sentryDsn) { - this.sentryDsn = sentryDsn; - } - - public boolean isMessageCompressionEnabled() { - return messageCompressionEnabled; - } - - public void setMessageCompressionEnabled(boolean messageCompressionEnabled) { - this.messageCompressionEnabled = messageCompressionEnabled; - } - - /** - * See {@link SentryAppender#setJsonProcessors}. - * - * @param jsonProcessors a comma-separated list of fully qualified class - * names of JSONProcessors - */ - public void setJsonProcessors(String jsonProcessors) { - this.jsonProcessors = jsonProcessors; - } - - @Override - public void append(LoggingEvent event) { - appender.notifyProcessorsBeforeAppending(); - super.append(event); - appender.notifyProcessorsAfterAppending(); - } - - @Override - public void activateOptions() { - SentryAppender appender = new SentryAppender(); - appender.setAsync(true); - appender.setMessageCompressionEnabled(messageCompressionEnabled); - appender.setSentryDsn(sentryDsn); - appender.setJsonProcessors(jsonProcessors); - appender.setErrorHandler(this.getErrorHandler()); - appender.setLayout(this.getLayout()); - appender.setName(this.getName()); - appender.setThreshold(this.getThreshold()); - appender.activateOptions(); - this.appender = appender; - addAppender(appender); - super.activateOptions(); - } - -} diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/Log4jMDC.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/Log4jMDC.java deleted file mode 100644 index f9e4d27585d..00000000000 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/Log4jMDC.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.kencochrane.raven.log4j; - -import org.apache.log4j.MDC; -import org.apache.log4j.spi.LoggingEvent; - -import net.kencochrane.raven.spi.RavenMDC; - -public class Log4jMDC extends RavenMDC { - - private static final ThreadLocal THREAD_LOGGING_EVENT - = new ThreadLocal(); - - public void setThreadLoggingEvent(LoggingEvent event) { - THREAD_LOGGING_EVENT.set(event); - } - - public void removeThreadLoggingEvent() { - THREAD_LOGGING_EVENT.remove(); - } - - @Override - public Object get(String key) { - if (THREAD_LOGGING_EVENT.get() != null) { - return THREAD_LOGGING_EVENT.get().getMDC(key); - } - return MDC.get(key); - } - - @Override - public void put(String key, Object value) { - MDC.put(key, value); - } - - @Override - public void remove(String key) { - MDC.remove(key); - } - -} diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java deleted file mode 100644 index a24d0517d0b..00000000000 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ /dev/null @@ -1,173 +0,0 @@ -package net.kencochrane.raven.log4j; - -import net.kencochrane.raven.Client; -import net.kencochrane.raven.SentryDsn; -import net.kencochrane.raven.spi.JSONProcessor; -import net.kencochrane.raven.spi.RavenMDC; -import org.apache.log4j.AppenderSkeleton; -import org.apache.log4j.spi.LoggingEvent; -import org.apache.log4j.spi.ThrowableInformation; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Log4J appender that will send messages to Sentry. - */ -public class SentryAppender extends AppenderSkeleton { - - private boolean async; - private Log4jMDC mdc; - protected String sentryDsn; - protected Client client; - protected boolean messageCompressionEnabled = true; - private List jsonProcessors = Collections.emptyList(); - - public SentryAppender() { - initMDC(); - mdc = (Log4jMDC) RavenMDC.getInstance(); - } - - public boolean isAsync() { - return async; - } - - public void setAsync(boolean async) { - this.async = async; - } - - public String getSentryDsn() { - return sentryDsn; - } - - public void setSentryDsn(String sentryDsn) { - this.sentryDsn = sentryDsn; - } - - public boolean isMessageCompressionEnabled() { - return messageCompressionEnabled; - } - - public void setMessageCompressionEnabled(boolean messageCompressionEnabled) { - this.messageCompressionEnabled = messageCompressionEnabled; - } - - /** - * Set a comma-separated list of fully qualified class names of - * JSONProcessors to be used. - * - * @param setting a comma-separated list of fully qualified class names of JSONProcessors - */ - public void setJsonProcessors(String setting) { - this.jsonProcessors = loadJSONProcessors(setting); - } - - /** - * Notify processors that a message has been logged. Note that this method - * is intended to be run on the same thread that creates the message. - */ - public void notifyProcessorsBeforeAppending() { - for (JSONProcessor processor : jsonProcessors) { - processor.prepareDiagnosticContext(); - } - } - - /** - * Notify processors after a message has been logged. Note that this method - * is intended to be run on the same thread that creates the message. - */ - public void notifyProcessorsAfterAppending() { - for (JSONProcessor processor : jsonProcessors) { - processor.clearDiagnosticContext(); - } - } - - @Override - public void close() { - if (client != null) { - client.stop(); - } - } - - @Override - public boolean requiresLayout() { - return false; - } - - @Override - public void activateOptions() { - client = (sentryDsn == null ? new Client() : new Client(SentryDsn.buildOptional(sentryDsn))); - client.setJSONProcessors(jsonProcessors); - client.setMessageCompressionEnabled(messageCompressionEnabled); - } - - @Override - protected void append(LoggingEvent event) { - mdc.setThreadLoggingEvent(event); - try { - // get timestamp and timestamp in correct string format. - long timestamp = event.getTimeStamp(); - - // get the log and info about the log. - String message = event.getRenderedMessage(); - String logger = event.getLogger().getName(); - int level = (event.getLevel().toInt() / 1000); //Need to divide by 1000 to keep consistent with sentry - String culprit = event.getLoggerName(); - - // is it an exception? - ThrowableInformation info = event.getThrowableInformation(); - - // notify processors about the message - // (in async mode this is done by AsyncSentryAppender) - if (!async) { - notifyProcessorsBeforeAppending(); - } - - // send the message to the sentry server - if (info == null) { - client.captureMessage(message, timestamp, logger, level, culprit); - } else { - client.captureException(message, timestamp, logger, level, culprit, info.getThrowable()); - } - - if (!async) { - notifyProcessorsAfterAppending(); - } - } finally { - mdc.removeThreadLoggingEvent(); - } - } - - private static List loadJSONProcessors(String setting) { - if (setting == null) { - return Collections.emptyList(); - } - try { - List processors = new ArrayList(); - String[] clazzes = setting.split(",\\s*"); - for (String clazz : clazzes) { - JSONProcessor processor = (JSONProcessor) Class.forName(clazz).newInstance(); - processors.add(processor); - } - return processors; - } catch (ClassNotFoundException exception) { - throw new RuntimeException("Processor could not be found.", exception); - } catch (InstantiationException exception) { - throw new RuntimeException("Processor could not be instantiated.", exception); - } catch (IllegalAccessException exception) { - throw new RuntimeException("Processor could not be instantiated.", exception); - } - } - - public static void initMDC() { - if (RavenMDC.getInstance() != null) { - if (!(RavenMDC.getInstance() instanceof Log4jMDC)) { - throw new IllegalStateException("An incompatible RavenMDC instance has been set. Please check your Raven configuration."); - } - return; - } - RavenMDC.setInstance(new Log4jMDC()); - } - -} diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java deleted file mode 100644 index 852108a3057..00000000000 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/AsyncSentryAppenderTest.java +++ /dev/null @@ -1,182 +0,0 @@ -package net.kencochrane.raven.log4j; - -import net.kencochrane.raven.Utils; -import net.kencochrane.raven.spi.RavenMDC; - -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.apache.log4j.PropertyConfigurator; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.json.simple.parser.ParseException; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; - -import java.io.IOException; -import java.net.SocketException; - -import static org.junit.Assert.*; - -/** - * Test cases for {@link AsyncSentryAppender}. - */ -public class AsyncSentryAppenderTest { - - protected static SentryAppenderTest.SentryMock sentry; - - @BeforeClass - public static void beforeClass() throws SocketException { - sentry = new SentryAppenderTest.SentryMock(); - } - - @AfterClass - public static void afterClass() throws SocketException { - System.setProperty(Utils.SENTRY_DSN, ""); - sentry.stop(); - } - - @Test - public void noSentryDsn() { - PropertyConfigurator.configure(getClass().getResource("/asyncsentryappender-no-dsn.log4j.properties")); - Logger.getLogger(this.getClass()).debug("No Sentry DSN, no messages"); - } - - @Test - public void invalidDsn() { - configureLog4J(); - Logger.getLogger(this.getClass()).debug("No Sentry DSN, no messages"); - } - - @Test - public void debugLevel() throws IOException, ParseException { - final String loggerName = "omg.logger"; - final long logLevel = (long) Level.DEBUG_INT / 1000; - final String projectId = "1"; - final String message = "hi there!"; - - // Log - setSentryDSN(projectId); - configureLog4J(); - Logger.getLogger(loggerName).debug(message); - - // Verify log message - verifyMessage(loggerName, logLevel, projectId, message); - } - - @Test - public void infoLevel() throws IOException, ParseException { - final String loggerName = "dude.wheres.my.ride"; - final long logLevel = (long) Level.INFO_INT / 1000; - final String projectId = "2"; - final String message = "This message will self-destruct in 5...4...3..."; - - // Log - setSentryDSN(projectId); - configureLog4J(); - Logger.getLogger(loggerName).info(message); - - // And verify - verifyMessage(loggerName, logLevel, projectId, message); - } - - @Test - public void warnLevel() throws IOException, ParseException { - final String loggerName = "org.apache.commons.httpclient.andStuff"; - final long logLevel = (long) Level.WARN_INT / 1000; - final String projectId = "20"; - final String message = "Warning! Warning! WARNING! Oh, come on!"; - - // Log - setSentryDSN(projectId); - configureLog4J(); - Logger.getLogger(loggerName).warn(message); - - // And verify - verifyMessage(loggerName, logLevel, projectId, message); - } - - @Test - public void errorLevel() throws IOException, ParseException { - final String loggerName = "org.apache.commons.httpclient.andStuff"; - final long logLevel = (long) Level.ERROR_INT / 1000; - final String projectId = "5"; - final String message = "D'oh!"; - - // Log - setSentryDSN(projectId); - configureLog4J(); - Logger.getLogger(loggerName).error(message); - - // Verify - verifyMessage(loggerName, logLevel, projectId, message); - } - - @Test - public void errorLevel_withException() throws IOException, ParseException { - final String loggerName = "org.apache.commons.httpclient.andStuff"; - // When an exception is logged, the culprit should be the class+method where the exception occurred - final String culprit = getClass().getName() + ".errorLevel_withException"; - final long logLevel = (long) Level.ERROR_INT / 1000; - final String projectId = "5"; - final String message = "D'oh!"; - - // Log - setSentryDSN(projectId); - configureLog4J(); - NullPointerException npe = new NullPointerException("Damn you!"); - Logger.getLogger(loggerName).error(message, npe); - - // Verify - JSONObject json = verifyMessage(culprit, logLevel, projectId, message); - JSONObject stacktrace = (JSONObject) json.get("sentry.interfaces.Stacktrace"); - assertNotNull(stacktrace); - assertNotNull(stacktrace.get("frames")); - JSONArray frames = (JSONArray) stacktrace.get("frames"); - assertTrue(frames.size() > 0); - JSONObject exception = (JSONObject) json.get("sentry.interfaces.Exception"); - assertNotNull(exception); - assertEquals(NullPointerException.class.getSimpleName(), exception.get("type")); - assertEquals(npe.getMessage(), exception.get("value")); - assertEquals(NullPointerException.class.getPackage().getName(), exception.get("module")); - } - - @Test - public void testJSONProcessors() throws IOException, ParseException { - setSentryDSN("6"); - configureLog4J(); - Logger.getLogger("logger").info("test"); - JSONObject json = SentryAppenderTest.fetchJSONObject(sentry); - assertEquals(1, ((Long)json.get("Test")).longValue()); - } - - @Test - public void testClearMDC() throws IOException, ParseException { - RavenMDC mdc = RavenMDC.getInstance(); - mdc.put("test", "test"); - assertNotNull(mdc.get("test")); - setSentryDSN("6"); - configureLog4J(); - Logger.getLogger("logger").info("test"); - assertNull(RavenMDC.getInstance().get("test")); - } - - protected void setSentryDSN(String projectId) { - System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); - } - - protected void configureLog4J() { - PropertyConfigurator.configure(getClass().getResource("/asyncsentryappender.log4j.properties")); - } - - - protected JSONObject verifyMessage(String loggerName, long logLevel, String projectId, String message) throws IOException, ParseException { - try { - Thread.sleep(500); - } catch (InterruptedException e) { - e.printStackTrace(); - } - return SentryAppenderTest.verifyMessage(sentry, loggerName, logLevel, projectId, message); - } - -} diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java deleted file mode 100644 index bb24eb5475e..00000000000 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ /dev/null @@ -1,250 +0,0 @@ -package net.kencochrane.raven.log4j; - -import net.kencochrane.raven.Utils; -import net.kencochrane.raven.spi.JSONProcessor; -import net.kencochrane.raven.spi.RavenMDC; - -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.lang.StringUtils; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.apache.log4j.PropertyConfigurator; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; - -import java.io.IOException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.InetSocketAddress; -import java.net.SocketException; - -import static org.junit.Assert.*; - -/** - * Test cases for {@link SentryAppender}. - */ -public class SentryAppenderTest { - - protected static SentryMock sentry; - - @BeforeClass - public static void beforeClass() throws SocketException { - sentry = new SentryMock(); - } - - @AfterClass - public static void afterClass() throws SocketException { - System.setProperty(Utils.SENTRY_DSN, ""); - sentry.stop(); - } - - @Test - public void noSentryDsn() { - PropertyConfigurator.configure(getClass().getResource("/sentryappender-no-dsn.log4j.properties")); - Logger.getLogger(this.getClass()).debug("No Sentry DSN, no messages"); - } - - @Test - public void invalidDsn() { - configureLog4J(); - Logger.getLogger(this.getClass()).debug("No Sentry DSN, no messages"); - } - - @Test - public void debugLevel() throws IOException, ParseException { - final String loggerName = "omg.logger"; - final long logLevel = (long) Level.DEBUG_INT / 1000; - final String projectId = "1"; - final String message = "hi there!"; - - // Log - setSentryDSN(projectId); - configureLog4J(); - Logger.getLogger(loggerName).debug(message); - - // Verify log message - verifyMessage(loggerName, logLevel, projectId, message); - } - - @Test - public void infoLevel() throws IOException, ParseException { - final String loggerName = "dude.wheres.my.ride"; - final long logLevel = (long) Level.INFO_INT / 1000; - final String projectId = "2"; - final String message = "This message will self-destruct in 5...4...3..."; - - // Log - setSentryDSN(projectId); - configureLog4J(); - Logger.getLogger(loggerName).info(message); - - // And verify - verifyMessage(loggerName, logLevel, projectId, message); - } - - @Test - public void warnLevel() throws IOException, ParseException { - final String loggerName = "org.apache.commons.httpclient.andStuff"; - final long logLevel = (long) Level.WARN_INT / 1000; - final String projectId = "20"; - final String message = "Warning! Warning! WARNING! Oh, come on!"; - - // Log - setSentryDSN(projectId); - configureLog4J(); - Logger.getLogger(loggerName).warn(message); - - // And verify - verifyMessage(loggerName, logLevel, projectId, message); - } - - @Test - public void errorLevel() throws IOException, ParseException { - final String loggerName = "org.apache.commons.httpclient.andStuff"; - final long logLevel = (long) Level.ERROR_INT / 1000; - final String projectId = "5"; - final String message = "D'oh!"; - - // Log - setSentryDSN(projectId); - configureLog4J(); - Logger.getLogger(loggerName).error(message); - - // Verify - verifyMessage(loggerName, logLevel, projectId, message); - } - - @Test - public void errorLevel_withException() throws IOException, ParseException { - final String loggerName = "org.apache.commons.httpclient.andStuff"; - // When an exception is logged, the culprit should be the class+method where the exception occurred - final String culprit = getClass().getName() + ".errorLevel_withException"; - final long logLevel = (long) Level.ERROR_INT / 1000; - final String projectId = "5"; - final String message = "D'oh!"; - - // Log - setSentryDSN(projectId); - configureLog4J(); - NullPointerException npe = new NullPointerException("Damn you!"); - Logger.getLogger(loggerName).error(message, npe); - - // Verify - JSONObject json = verifyMessage(culprit, logLevel, projectId, message); - JSONObject stacktrace = (JSONObject) json.get("sentry.interfaces.Stacktrace"); - assertNotNull(stacktrace); - assertNotNull(stacktrace.get("frames")); - JSONArray frames = (JSONArray) stacktrace.get("frames"); - assertTrue(frames.size() > 0); - JSONObject exception = (JSONObject) json.get("sentry.interfaces.Exception"); - assertNotNull(exception); - assertEquals(NullPointerException.class.getSimpleName(), exception.get("type")); - assertEquals(npe.getMessage(), exception.get("value")); - assertEquals(NullPointerException.class.getPackage().getName(), exception.get("module")); - } - - @Test - public void testJSONProcessors() throws IOException, ParseException { - setSentryDSN("6"); - configureLog4J(); - Logger.getLogger("logger").info("test"); - JSONObject json = fetchJSONObject(sentry); - assertEquals(1, ((Long) json.get("Test")).longValue()); - } - - @Test - public void testClearMDC() throws IOException, ParseException { - RavenMDC mdc = RavenMDC.getInstance(); - mdc.put("test", "test"); - assertNotNull(mdc.get("test")); - setSentryDSN("6"); - configureLog4J(); - Logger.getLogger("logger").info("test"); - assertNull(RavenMDC.getInstance().get("test")); - } - - protected void setSentryDSN(String projectId) { - System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); - } - - protected void configureLog4J() { - PropertyConfigurator.configure(getClass().getResource("/sentryappender.log4j.properties")); - } - - protected JSONObject verifyMessage(String culprit, long logLevel, String projectId, String message) throws IOException, ParseException { - return verifyMessage(sentry, culprit, logLevel, projectId, message); - } - - protected static JSONObject verifyMessage(SentryMock sentry, String culprit, long logLevel, String projectId, String message) throws IOException, ParseException { - JSONObject json = fetchJSONObject(sentry); - assertEquals(message, json.get("message")); - assertEquals(culprit, json.get("culprit")); - assertEquals(projectId, json.get("project")); - assertEquals(logLevel, json.get("level")); - return json; - } - - protected static JSONObject fetchJSONObject(SentryMock sentry) throws IOException, ParseException { - String payload = Utils.fromUtf8(sentry.fetchMessage()); - String[] payloadParts = StringUtils.split(payload, "\n\n"); - assertEquals(2, payloadParts.length); - String raw = Utils.fromUtf8(Utils.decompress(Base64.decodeBase64(payloadParts[1]))); - return (JSONObject) new JSONParser().parse(raw); - } - - public static class SentryMock { - public final DatagramSocket serverSocket; - public final String host; - public final int port; - - public SentryMock() throws SocketException { - this("localhost", 9505); - } - - public SentryMock(String host, int port) throws SocketException { - this.host = host; - this.port = port; - serverSocket = new DatagramSocket(new InetSocketAddress(host, port)); - } - - public void stop() { - serverSocket.close(); - } - - public byte[] fetchMessage() throws IOException { - DatagramPacket packet = new DatagramPacket(new byte[10000], 10000); - serverSocket.receive(packet); - return packet.getData(); - } - - } - - public static class MockJSONProcessor implements JSONProcessor { - - private Long value = 0L; - - @Override - public void prepareDiagnosticContext() { - // this is done to ensure prepareDiagnosticContext is called exactly once - value++; - } - - @Override - public void clearDiagnosticContext() { - RavenMDC.getInstance().remove("test"); - } - - @Override - @SuppressWarnings("unchecked") - public void process(JSONObject json, Throwable exception) { - json.put("Test", value); // value should be 1 - } - - } - -} diff --git a/raven-log4j/src/test/resources/asyncsentryappender-no-dsn.log4j.properties b/raven-log4j/src/test/resources/asyncsentryappender-no-dsn.log4j.properties deleted file mode 100644 index 6830841073c..00000000000 --- a/raven-log4j/src/test/resources/asyncsentryappender-no-dsn.log4j.properties +++ /dev/null @@ -1,24 +0,0 @@ -# Basic setup -log4j.rootLogger=trace, stdout, sentry - -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout - -# Pattern to output the caller's file name and line number. -log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n - -# Configure the Sentry appender. Note: when a SENTRY_DSN environment variable -# or system property is available, that value will be used instead of the value -# of log4j.appender.sentry.sentryDsn -log4j.appender.sentry=net.kencochrane.raven.log4j.AsyncSentryAppender -# Don't set the DSN -#log4j.appender.sentry.sentryDsn=SENTRY_DSN - -# Add a JSONProcessor -log4j.appender.sentry.jsonProcessors=net.kencochrane.raven.log4j.SentryAppenderTest$MockJSONProcessor - -# Do not let the Raven logging go to Sentry or you could end up in an infinite loop when -# something goes wrong with your configuration. Sentry uses the java.util.logging package -# so this should only happen when you add a bridge from JUL to Log4J. -log4j.logger.raven=DEBUG, stdout -log4j.additivity.raven=false diff --git a/raven-log4j/src/test/resources/asyncsentryappender.log4j.properties b/raven-log4j/src/test/resources/asyncsentryappender.log4j.properties deleted file mode 100644 index 73d77a37ef4..00000000000 --- a/raven-log4j/src/test/resources/asyncsentryappender.log4j.properties +++ /dev/null @@ -1,23 +0,0 @@ -# Basic setup -log4j.rootLogger=trace, stdout, sentry - -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout - -# Pattern to output the caller's file name and line number. -log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n - -# Configure the Sentry appender. Note: when a SENTRY_DSN environment variable -# or system property is available, that value will be used instead of the value -# of log4j.appender.sentry.sentryDsn -log4j.appender.sentry=net.kencochrane.raven.log4j.AsyncSentryAppender -log4j.appender.sentry.sentryDsn=SENTRY_DSN - -# Add a JSONProcessor -log4j.appender.sentry.jsonProcessors=net.kencochrane.raven.log4j.SentryAppenderTest$MockJSONProcessor - -# Do not let the Raven logging go to Sentry or you could end up in an infinite loop when -# something goes wrong with your configuration. Sentry uses the java.util.logging package -# so this should only happen when you add a bridge from JUL to Log4J. -log4j.logger.raven=DEBUG, stdout -log4j.additivity.raven=false diff --git a/raven-log4j/src/test/resources/sentryappender-no-dsn.log4j.properties b/raven-log4j/src/test/resources/sentryappender-no-dsn.log4j.properties deleted file mode 100644 index 37972103073..00000000000 --- a/raven-log4j/src/test/resources/sentryappender-no-dsn.log4j.properties +++ /dev/null @@ -1,24 +0,0 @@ -# Basic setup -log4j.rootLogger=trace, stdout, sentry - -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout - -# Pattern to output the caller's file name and line number. -log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n - -# Configure the Sentry appender. Note: when a SENTRY_DSN environment variable -# or system property is available, that value will be used instead of the value -# of log4j.appender.sentry.sentryDsn -log4j.appender.sentry=net.kencochrane.raven.log4j.SentryAppender -# No DSN here -#log4j.appender.sentry.sentryDsn= - -# Add a JSONProcessor -log4j.appender.sentry.jsonProcessors=net.kencochrane.raven.log4j.SentryAppenderTest$MockJSONProcessor - -# Do not let the Raven logging go to Sentry or you could end up in an infinite loop when -# something goes wrong with your configuration. Sentry uses the java.util.logging package -# so this should only happen when you add a bridge from JUL to Log4J. -log4j.logger.raven=DEBUG, stdout -log4j.additivity.raven=false \ No newline at end of file diff --git a/raven-log4j/src/test/resources/sentryappender.log4j.properties b/raven-log4j/src/test/resources/sentryappender.log4j.properties deleted file mode 100644 index 97dcc8563ef..00000000000 --- a/raven-log4j/src/test/resources/sentryappender.log4j.properties +++ /dev/null @@ -1,23 +0,0 @@ -# Basic setup -log4j.rootLogger=trace, stdout, sentry - -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout - -# Pattern to output the caller's file name and line number. -log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n - -# Configure the Sentry appender. Note: when a SENTRY_DSN environment variable -# or system property is available, that value will be used instead of the value -# of log4j.appender.sentry.sentryDsn -log4j.appender.sentry=net.kencochrane.raven.log4j.SentryAppender -log4j.appender.sentry.sentryDsn=SENTRY_DSN - -# Add a JSONProcessor -log4j.appender.sentry.jsonProcessors=net.kencochrane.raven.log4j.SentryAppenderTest$MockJSONProcessor - -# Do not let the Raven logging go to Sentry or you could end up in an infinite loop when -# something goes wrong with your configuration. Sentry uses the java.util.logging package -# so this should only happen when you add a bridge from JUL to Log4J. -log4j.logger.raven=DEBUG, stdout -log4j.additivity.raven=false \ No newline at end of file diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/LogbackMDC.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/LogbackMDC.java deleted file mode 100644 index 43b088c98bd..00000000000 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/LogbackMDC.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.kencochrane.raven.logback; - -import net.kencochrane.raven.spi.RavenMDC; - -import org.slf4j.MDC; - -import ch.qos.logback.classic.spi.ILoggingEvent; - -public class LogbackMDC extends RavenMDC { - - private static final ThreadLocal THREAD_LOGGING_EVENT = new ThreadLocal(); - - public void setThreadLoggingEvent(ILoggingEvent event) { - THREAD_LOGGING_EVENT.set(event); - } - - public void removeThreadLoggingEvent() { - THREAD_LOGGING_EVENT.remove(); - } - - @Override - public Object get(String key) { - if (THREAD_LOGGING_EVENT.get() != null) { - return THREAD_LOGGING_EVENT.get().getMDCPropertyMap().get(key); - } - return MDC.get(key); - } - - @Override - public void put(String key, Object value) { - MDC.put(key, value.toString()); - } - - @Override - public void remove(String key) { - MDC.remove(key); - } - -} diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java deleted file mode 100644 index f46838bb184..00000000000 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ /dev/null @@ -1,178 +0,0 @@ -package net.kencochrane.raven.logback; - -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.classic.spi.IThrowableProxy; -import ch.qos.logback.classic.spi.ThrowableProxy; -import ch.qos.logback.core.AppenderBase; -import net.kencochrane.raven.Client; -import net.kencochrane.raven.SentryDsn; -import net.kencochrane.raven.spi.JSONProcessor; -import net.kencochrane.raven.spi.RavenMDC; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Logback appender that will send messages to Sentry. - */ -public class SentryAppender extends AppenderBase { - - private boolean async; - private LogbackMDC mdc; - protected String sentryDsn; - protected Client client; - protected boolean messageCompressionEnabled = true; - private List jsonProcessors = Collections.emptyList(); - - public SentryAppender() { - initMDC(); - mdc = (LogbackMDC) RavenMDC.getInstance(); - } - - public boolean isAsync() { - return async; - } - - public void setAsync(boolean async) { - this.async = async; - } - - public String getSentryDsn() { - return sentryDsn; - } - - public void setSentryDsn(String sentryDsn) { - this.sentryDsn = sentryDsn; - } - - public boolean isMessageCompressionEnabled() { - return messageCompressionEnabled; - } - - public void setMessageCompressionEnabled(boolean messageCompressionEnabled) { - this.messageCompressionEnabled = messageCompressionEnabled; - } - - /** - * Set a comma-separated list of fully qualified class names of - * JSONProcessors to be used. - * - * @param setting a comma-separated list of fully qualified class names of - * JSONProcessors - */ - public void setJsonProcessors(String setting) { - this.jsonProcessors = loadJSONProcessors(setting); - } - - /** - * Notify processors that a message has been logged. Note that this method - * is intended to be run on the same thread that creates the message. - */ - public void notifyProcessorsBeforeAppending() { - for (JSONProcessor processor : jsonProcessors) { - processor.prepareDiagnosticContext(); - } - } - - /** - * Notify processors after a message has been logged. Note that this method - * is intended to be run on the same thread that creates the message. - */ - public void notifyProcessorsAfterAppending() { - for (JSONProcessor processor : jsonProcessors) { - processor.clearDiagnosticContext(); - } - } - - @Override - public void start() { - activateOptions(); - super.start(); - } - - @Override - public void stop() { - if (client != null) { - client.stop(); - } - super.stop(); - } - - public void activateOptions() { - client = sentryDsn == null ? new Client() : new Client(SentryDsn.buildOptional(sentryDsn)); - client.setJSONProcessors(jsonProcessors); - client.setMessageCompressionEnabled(messageCompressionEnabled); - } - - @Override - protected void append(ILoggingEvent event) { - mdc.setThreadLoggingEvent(event); - try { - // get timestamp and timestamp in correct string format. - long timestamp = event.getTimeStamp(); - - // get the log and info about the log. - String message = event.getMessage(); - String logger = event.getLoggerName(); - int level = event.getLevel().toInt() / 1000; // Need to divide by - // 1000 to keep - // consistent with - // sentry - String culprit = event.getLoggerName(); - - IThrowableProxy throwable = event.getThrowableProxy(); - - // notify processors about the message - // (in async mode this is done by AsyncSentryAppender) - if (!async) { - notifyProcessorsBeforeAppending(); - } - - // send the message to the sentry server - if (event.getThrowableProxy() == null) { - client.captureMessage(message, timestamp, logger, level, culprit); - } else { - client.captureException(message, timestamp, logger, level, culprit, ((ThrowableProxy) throwable).getThrowable()); - } - - if (!async) { - notifyProcessorsAfterAppending(); - } - } finally { - mdc.removeThreadLoggingEvent(); - } - } - - private static List loadJSONProcessors(String setting) { - if (setting == null) { - return Collections.emptyList(); - } - try { - List processors = new ArrayList(); - String[] clazzes = setting.split(",\\s*"); - for (String clazz : clazzes) { - JSONProcessor processor = (JSONProcessor) Class.forName(clazz).newInstance(); - processors.add(processor); - } - return processors; - } catch (ClassNotFoundException exception) { - throw new RuntimeException("Processor could not be found.", exception); - } catch (InstantiationException exception) { - throw new RuntimeException("Processor could not be instantiated.", exception); - } catch (IllegalAccessException exception) { - throw new RuntimeException("Processor could not be instantiated.", exception); - } - } - - public static void initMDC() { - if (RavenMDC.getInstance() != null) { - if (!(RavenMDC.getInstance() instanceof LogbackMDC)) { - throw new IllegalStateException("An incompatible RavenMDC instance has been set. Please check your Raven configuration."); - } - return; - } - RavenMDC.setInstance(new LogbackMDC()); - } - -} diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java deleted file mode 100644 index 950be41f999..00000000000 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ /dev/null @@ -1,235 +0,0 @@ -package net.kencochrane.raven.logback; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.io.IOException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.InetSocketAddress; -import java.net.SocketException; - -import net.kencochrane.raven.Utils; -import net.kencochrane.raven.spi.JSONProcessor; -import net.kencochrane.raven.spi.RavenMDC; - -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.lang.StringUtils; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.slf4j.LoggerFactory; - -import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.classic.joran.JoranConfigurator; -import ch.qos.logback.core.joran.spi.JoranException; - -/** - * Test cases for {@link SentryAppender}. - */ -public class SentryAppenderTest { - - protected static SentryMock sentry; - - @BeforeClass - public static void beforeClass() throws SocketException { - sentry = new SentryMock(); - } - - @AfterClass - public static void afterClass() throws SocketException { - System.setProperty(Utils.SENTRY_DSN, ""); - System.setProperty("logback.configurationFile", ""); - sentry.stop(); - } - - @Test - public void noSentryDsn() { - ((LoggerContext) LoggerFactory.getILoggerFactory()).reset(); - System.setProperty(Utils.SENTRY_DSN, ""); - System.setProperty("logback.configurationFile", "sentryappender-no-dsn.logback.xml"); - LoggerFactory.getLogger(this.getClass()).debug("No Sentry DSN, no messages"); - } - - @Test - public void invalidDsn() throws JoranException { - ((LoggerContext) LoggerFactory.getILoggerFactory()).reset(); - System.setProperty(Utils.SENTRY_DSN, "INVALID"); - configureLogback(); - LoggerFactory.getLogger(this.getClass()).debug("Invalid Sentry DSN, no messages"); - } - - @Test - public void debugLevel() throws IOException, ParseException, JoranException { - final String loggerName = "omg.logger"; - final long logLevel = (long) Level.DEBUG_INT / 1000; - final String projectId = "1"; - final String message = "hi there!"; - - configureLogback(projectId); - LoggerFactory.getLogger(loggerName).debug(message); - verifyMessage(loggerName, logLevel, "1", message); - } - - @Test - public void infoLevel() throws IOException, ParseException, JoranException { - final String loggerName = "dude.wheres.my.ride"; - final long logLevel = (long) Level.INFO_INT / 1000; - final String projectId = "2"; - final String message = "This message will self-destruct in 5...4...3..."; - - configureLogback(projectId); - LoggerFactory.getLogger(loggerName).info(message); - verifyMessage(loggerName, logLevel, projectId, message); - } - - @Test - public void warnLevel() throws IOException, ParseException, JoranException { - final String loggerName = "org.apache.commons.httpclient.andStuff"; - final long logLevel = (long) Level.WARN_INT / 1000; - final String projectId = "20"; - final String message = "Warning! Warning! WARNING! Oh, come on!"; - - configureLogback(projectId); - LoggerFactory.getLogger(loggerName).warn(message); - verifyMessage(loggerName, logLevel, projectId, message); - } - - @Test - public void errorLevel() throws IOException, ParseException, JoranException { - final String loggerName = "org.apache.commons.httpclient.andStuff"; - final long logLevel = (long) Level.ERROR_INT / 1000; - final String projectId = "5"; - final String message = "D'oh!"; - - configureLogback(projectId); - LoggerFactory.getLogger(loggerName).error(message); - verifyMessage(loggerName, logLevel, projectId, message); - } - - @Test - public void errorLevel_withException() throws IOException, ParseException, JoranException { - - final String loggerName = "org.apache.commons.httpclient.andStuff"; - // When an exception is logged, the culprit should be the class+method - // where the exception occurred - final String culprit = getClass().getName() + ".errorLevel_withException"; - final long logLevel = (long) Level.ERROR_INT / 1000; - final String projectId = "5"; - final String message = "D'oh!"; - - configureLogback(projectId); - NullPointerException npe = new NullPointerException("Damn you!"); - - LoggerFactory.getLogger(loggerName).error(message, npe); - - // Verify - JSONObject json = verifyMessage(culprit, logLevel, projectId, message); - JSONObject stacktrace = (JSONObject) json.get("sentry.interfaces.Stacktrace"); - assertNotNull(stacktrace); - assertNotNull(stacktrace.get("frames")); - JSONArray frames = (JSONArray) stacktrace.get("frames"); - assertTrue(frames.size() > 0); - JSONObject exception = (JSONObject) json.get("sentry.interfaces.Exception"); - assertNotNull(exception); - assertEquals(NullPointerException.class.getSimpleName(), exception.get("type")); - assertEquals(npe.getMessage(), exception.get("value")); - assertEquals(NullPointerException.class.getPackage().getName(), exception.get("module")); - } - - protected void setSentryDSN(String projectId) { - System.setProperty(Utils.SENTRY_DSN, String.format("udp://public:private@%s:%d/%s", sentry.host, sentry.port, projectId)); - } - - public void configureLogback(String projectId) throws JoranException { - setSentryDSN(projectId); - configureLogback(); - } - - public void configureLogback() throws JoranException { - LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); - JoranConfigurator jc = new JoranConfigurator(); - jc.setContext(context); - context.reset(); - jc.doConfigure(this.getClass().getClassLoader().getResource("sentryappender.logback.xml")); - } - - protected JSONObject verifyMessage(String culprit, long logLevel, String projectId, String message) throws IOException, ParseException { - return verifyMessage(sentry, culprit, logLevel, projectId, message); - } - - protected static JSONObject verifyMessage(SentryMock sentry, String culprit, long logLevel, String projectId, String message) throws IOException, ParseException { - JSONObject json = fetchJSONObject(sentry); - assertEquals(message, json.get("message")); - assertEquals(culprit, json.get("culprit")); - assertEquals(projectId, json.get("project")); - assertEquals(logLevel, json.get("level")); - return json; - } - - protected static JSONObject fetchJSONObject(SentryMock sentry) throws IOException, ParseException { - String payload = Utils.fromUtf8(sentry.fetchMessage()); - String[] payloadParts = StringUtils.split(payload, "\n\n"); - assertEquals(2, payloadParts.length); - String raw = Utils.fromUtf8(Utils.decompress(Base64.decodeBase64(payloadParts[1]))); - return (JSONObject) new JSONParser().parse(raw); - } - - public static class SentryMock { - public final DatagramSocket serverSocket; - public final String host; - public final int port; - - public SentryMock() throws SocketException { - this("localhost", 9506); - } - - public SentryMock(String host, int port) throws SocketException { - this.host = host; - this.port = port; - serverSocket = new DatagramSocket(new InetSocketAddress(host, port)); - } - - public void stop() { - serverSocket.close(); - } - - public byte[] fetchMessage() throws IOException { - DatagramPacket packet = new DatagramPacket(new byte[10000], 10000); - serverSocket.receive(packet); - return packet.getData(); - } - - } - - public static class MockJSONProcessor implements JSONProcessor { - - private Long value = 0L; - - @Override - public void prepareDiagnosticContext() { - // this is done to ensure prepareDiagnosticContext is called exactly - // once - value++; - } - - @Override - public void clearDiagnosticContext() { - RavenMDC.getInstance().remove("test"); - } - - @Override - @SuppressWarnings("unchecked") - public void process(JSONObject json, Throwable exception) { - json.put("Test", value); // value should be 1 - } - - } - -} diff --git a/raven-logback/src/test/resources/sentryappender-no-dsn.logback.xml b/raven-logback/src/test/resources/sentryappender-no-dsn.logback.xml deleted file mode 100644 index 3e382054f3d..00000000000 --- a/raven-logback/src/test/resources/sentryappender-no-dsn.logback.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - \ No newline at end of file diff --git a/raven-logback/src/test/resources/sentryappender.logback.xml b/raven-logback/src/test/resources/sentryappender.logback.xml deleted file mode 100644 index cee81a13f3b..00000000000 --- a/raven-logback/src/test/resources/sentryappender.logback.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - ${SENTRY_DSN} - - - - - - - \ No newline at end of file diff --git a/raven/src/main/java/net/kencochrane/raven/AsyncTransport.java b/raven/src/main/java/net/kencochrane/raven/AsyncTransport.java deleted file mode 100644 index 561e5469ed4..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/AsyncTransport.java +++ /dev/null @@ -1,203 +0,0 @@ -package net.kencochrane.raven; - -import java.io.IOException; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Wrapper providing asynchronous support for any other concrete transport layer. - */ -public abstract class AsyncTransport extends Transport { - - public static final long WAIT_FOR_SHUTDOWN = 3000; - private static final Logger LOG = Logger.getLogger("raven.transport"); - - /** - * Options for the async transport layer. - */ - public interface Option { - - /** - * Option indicating whether the client should block when the queue is full. - */ - String WAIT_WHEN_FULL = "raven.waitWhenFull"; - - /** - * Default value for {@link #WAIT_WHEN_FULL}: no, do not block the queue. - */ - boolean WAIT_WHEN_FULL_DEFAULT = false; - - /** - * Option to limit the capacity of the underlying queue. - */ - String CAPACITY = "raven.capacity"; - } - - public final Transport transport; - protected final BlockingQueue queue; - protected final Thread workerThread; - - public AsyncTransport(Transport transport, BlockingQueue queue) { - super(transport.dsn); - this.transport = transport; - this.queue = queue; - workerThread = new Thread(new Worker(this)); - } - - public int getQueueSize() { - return queue.size(); - } - - @Override - public void start() { - final String name = "Raven-" + workerThread.getName(); - LOG.log(Level.FINE, "Starting thread " + name); - workerThread.setDaemon(true); - workerThread.setName(name); - workerThread.start(); - super.start(); - } - - @Override - public void stop() { - if (!started) { - return; - } - super.stop(); - workerThread.interrupt(); - try { - workerThread.join(WAIT_FOR_SHUTDOWN); - } catch (InterruptedException e) { - LOG.log(Level.WARNING, e.getMessage(), e); - } - } - - @Override - public void send(String messageBody, long timestamp) throws IOException { - throw new UnsupportedOperationException("You probably need a subclass of " + AsyncTransport.class); - } - - /** - * Builds an async transport wrapper for concrete transport layers. - *

    - * Any other async transport wrapper that's supposed to be registered with the {@link Client} should provide a - * public static build method, accepting the transport instance to wrap. - *

    - *

    - * This method will apply the {@link Option#WAIT_WHEN_FULL} and {@link Option#CAPACITY} options specified in the - * {@link SentryDsn} of the transport or use the default values. - *

    - * - * @param transport transport to wrap - * @return the async transport wrapper - */ - public static AsyncTransport build(Transport transport) { - int capacity = transport.dsn.getOptionAsInt(Option.CAPACITY, -1); - boolean waitWhenFull = transport.dsn.getOptionAsBoolean(Option.WAIT_WHEN_FULL, Option.WAIT_WHEN_FULL_DEFAULT); - return build(transport, waitWhenFull, capacity); - } - - /** - * Convenience method for building an async transport wrapper. - * - * @param transport transport to wrap - * @param waitWhenFull whether the underlying queue should block when full - * @param capacity the capacity of the underlying queue - a negative value means use the maximum capacity possible - * @return the async transport wrapper - */ - public static AsyncTransport build(Transport transport, boolean waitWhenFull, int capacity) { - BlockingQueue queue = null; - if (capacity < 0) { - queue = new LinkedBlockingDeque(); - } else { - queue = new LinkedBlockingDeque(capacity); - } - if (waitWhenFull) { - return new WaitingAsyncTransport(transport, queue); - } - return new LossyAsyncTransport(transport, queue); - } - - /** - * Asynchronous transport layer that will wait for space to come available when the underlying queue has reached its - * maximum capacity. - */ - public static class WaitingAsyncTransport extends AsyncTransport { - - public WaitingAsyncTransport(Transport transport, BlockingQueue queue) { - super(transport, queue); - } - - @Override - public void send(String messageBody, long timestamp) throws IOException { - try { - queue.put(new Message(messageBody, timestamp)); - } catch (InterruptedException e) { - // Ignore - } - } - } - - /** - * Asynchronous transport layer that will drop messages when the underlying queue has reached its maximum capacity. - */ - public static class LossyAsyncTransport extends AsyncTransport { - - public LossyAsyncTransport(Transport transport, BlockingQueue queue) { - super(transport, queue); - } - - @Override - public void send(String messageBody, long timestamp) throws IOException { - try { - queue.add(new Message(messageBody, timestamp)); - } catch (IllegalStateException e) { - throw new IOException(e); - } - } - } - - public static class Message { - public final String messageBody; - public final long timestamp; - - public Message(String messageBody, long timestamp) { - this.messageBody = messageBody; - this.timestamp = timestamp; - } - } - - public class Worker implements Runnable { - - public final AsyncTransport transport; - - public Worker(AsyncTransport transport) { - this.transport = transport; - } - - @Override - public void run() { - while (transport.isStarted()) { - try { - Message m = transport.queue.take(); - transport.transport.send(m.messageBody, m.timestamp); - } catch (IOException e) { - LOG.log(Level.SEVERE, e.getMessage(), e); - } catch (InterruptedException e) { - break; - } - } - // Try to get the remaining message sent - for (Message m : transport.queue) { - try { - transport.transport.send(m.messageBody, m.timestamp); - } catch (IOException e) { - LOG.log(Level.WARNING, e.getMessage(), e); - } - } - } - } - -} diff --git a/raven/src/main/java/net/kencochrane/raven/Client.java b/raven/src/main/java/net/kencochrane/raven/Client.java deleted file mode 100644 index 4a405b69d5e..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/Client.java +++ /dev/null @@ -1,514 +0,0 @@ -package net.kencochrane.raven; - -import net.kencochrane.raven.connection.Connection; -import net.kencochrane.raven.event.EventBuilder; -import net.kencochrane.raven.event.LoggedEvent; -import net.kencochrane.raven.spi.JSONProcessor; -import org.apache.commons.codec.binary.Hex; -import org.apache.commons.lang.time.DateFormatUtils; -import org.json.simple.JSONObject; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.ConnectException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.zip.CRC32; -import java.util.zip.Checksum; - -import static org.apache.commons.codec.binary.Base64.encodeBase64String; - -/** - * Raven client for Java, allowing sending of messages to Sentry. - *

    - * Clients will typically automatically start the underlying transport layer once instantiated. The default client - * configuration relies on the following mapping of schemes to transport classes: - *

    - *
      - *
    • http: {@link Transport.Http}
    • - *
    • https: {@link Transport.Http}
    • - *
    • naive+https: {@link Transport.NaiveHttps}
    • - *
    • udp: {@link Transport.Udp}
    • - *
    - *

    - * Any of these schemes can be prefixed with async+ in which case the transport built using the original - * scheme will be wrapped in the transport class registered for {@link #VARIANT_ASYNC}, which is {@link AsyncTransport} - * by default. - *

    - */ -public class Client { - - public interface Default { - String LOGGER = "root"; - int LOG_LEVEL = Events.LogLevel.ERROR.intValue; - String EMPTY_MESSAGE = "(empty)"; - } - - /** - * Async transport layers require some extra work when instantiating. - */ - public static final String VARIANT_ASYNC = "async"; - - /** - * The registry mapping schemes to transport layer classes. - */ - protected static final Map> TRANSPORT_REGISTRY = new HashMap>(); - - /** - * Logger. - */ - private static final Logger LOG = Logger.getLogger("raven.client"); - - /** - * The dsn used by this client. - */ - public final SentryDsn dsn; - - /** - * The transport layer used by this client. - */ - protected Transport transport; - - /** - * The connection to the Sentry server - */ - //TODO: Set the connection - private Connection connection; - - /** - * Whether messages should be compressed or not - defaults to true. - */ - protected boolean messageCompressionEnabled = true; - - /** - * JSONProcessor instances. Initialized with an empty list to prevent NPE. - */ - private List jsonProcessors = Collections.emptyList(); - - static { - registerDefaults(); - } - - /** - * Default, easy constructor. - *

    - * A client instance instantiated through this constructor will use the Sentry DSN returned by - * {@link SentryDsn#buildOptional()} and perform an automatic start. - *

    - */ - public Client() { - this(true); - } - - /** - * Extension of the easy constructor {@link #Client()} that allows you to turn off the autostart behavior. - * - * @param autoStart whether to start the underlying transport automatically or not - */ - public Client(boolean autoStart) { - this(SentryDsn.buildOptional(), autoStart); - } - - /** - * Constructor that performs an autostart using the transport determined by the supplied dsn. - *

    - * Watch out: this constructor will always use the supplied dsn and not look for a Sentry DSN in other locations - * such as an environment variable or system property. - *

    - * - * @param dsn dsn to use - */ - public Client(SentryDsn dsn) { - this(dsn, true); - } - - /** - * Constructor using the transport determined by the supplied dsn. - *

    - * Watch out: this constructor will always use the supplied dsn and not look for a Sentry DSN in other locations - * such as an environment variable or system property. - *

    - * - * @param dsn dsn to use - * @param autoStart whether to start the underlying transport layer automatically - */ - public Client(SentryDsn dsn, boolean autoStart) { - this.dsn = dsn; - if (autoStart) { - start(); - } - } - - /** - * Construct a client using the given transport. - * - * @param transport transport to use - */ - public Client(Transport transport) { - this(transport, true); - } - - /** - * Construct a client using the given transport. - * - * @param transport transport to use - * @param autoStart whether to start the transport automatically - */ - public Client(Transport transport, boolean autoStart) { - this.dsn = transport.dsn; - this.transport = transport; - if (autoStart) { - start(); - } - } - - /** - * Set the processors to be used by this client. Instances from the list are - * copied over. - * - * @param processors a list of processors to be used by this client - */ - public synchronized void setJSONProcessors(List processors) { - this.jsonProcessors = new ArrayList(processors.size()); - this.jsonProcessors.addAll(processors); - } - - public boolean isMessageCompressionEnabled() { - return messageCompressionEnabled; - } - - public void setMessageCompressionEnabled(boolean messageCompressionEnabled) { - this.messageCompressionEnabled = messageCompressionEnabled; - } - - /** - * Gets the final information necessary for the event, and submit it to Sentry. - * - * @param eventBuilder pre-set event builder. - * @return the unique identifier of the newly created event. - */ - public UUID processEvent(EventBuilder eventBuilder) { - //TODO: grab more information that couldn't be provided by someone else? - LoggedEvent event = eventBuilder.build(); - connection.send(event); - return event.getId(); - } - - public String captureMessage(String msg) { - return captureMessage(msg, null, null, null, null); - } - - public String captureMessage(String msg, Map tags) { - return captureMessage(msg, null, null, null, null, tags); - } - - public String captureMessage(String message, Long timestamp, String loggerClass, Integer logLevel, String culprit) { - return captureMessage(message, timestamp, loggerClass, logLevel, culprit, null); - } - - public String captureMessage(String message, Long timestamp, String loggerClass, Integer logLevel, String culprit, Map tags) { - timestamp = (timestamp == null ? Utils.now() : timestamp); - Message msg = buildMessage(message, formatTimestamp(timestamp), loggerClass, logLevel, culprit, null, tags); - send(msg, timestamp); - return msg.eventId; - } - - public String captureException(Throwable exception) { - long timestamp = Utils.now(); - return captureException(exception.getMessage(), timestamp, null, null, null, exception); - } - - public String captureException(Throwable exception, Map tags) { - long timestamp = Utils.now(); - return captureException(exception.getMessage(), timestamp, null, null, null, exception, tags); - } - - public String captureException(String logMessage, long timestamp, String loggerName, Integer logLevel, String culprit, Throwable exception) { - return captureException(logMessage, timestamp, loggerName, logLevel, culprit, exception, null); - } - - public String captureException(String logMessage, long timestamp, String loggerName, Integer logLevel, String culprit, Throwable exception, Map tags) { - Message message = buildMessage(logMessage, formatTimestamp(timestamp), loggerName, logLevel, culprit, exception, tags); - send(message, timestamp); - return message.eventId; - } - - public synchronized void start() { - if (isDisabled()) { - return; - } - if (transport == null) { - transport = newTransport(dsn); - } - transport.start(); - } - - public boolean isStarted() { - return transport != null && transport.isStarted(); - } - - public synchronized void stop() { - if (transport == null) { - return; - } - transport.stop(); - transport = null; - } - - public boolean isDisabled() { - return dsn == null; - } - - @SuppressWarnings("unchecked") - protected Message buildMessage(String message, String timestamp, String loggerClass, Integer logLevel, String culprit, Throwable exception, Map tags) { - if (isDisabled()) { - return Message.NONE; - } - String eventId = generateEventId(); - JSONObject obj = new JSONObject(); - if (exception == null) { - obj.put("culprit", culprit); - Events.message(obj, message); - } else { - Events.exception(obj, exception); - } - if (message == null) { - message = (exception == null ? null : exception.getMessage()); - message = (message == null ? Default.EMPTY_MESSAGE : message); - } - obj.put("event_id", eventId); - obj.put("checksum", calculateChecksum(message)); - obj.put("timestamp", timestamp); - obj.put("message", message); - obj.put("project", dsn.projectId); - obj.put("level", logLevel == null ? Default.LOG_LEVEL : logLevel); - obj.put("logger", loggerClass == null ? Default.LOGGER : loggerClass); - obj.put("server_name", Utils.hostname()); - if (tags != null) { - JSONObject jsonTags = new JSONObject(); - jsonTags.putAll(tags); - obj.put("tags", jsonTags); - } - - for (JSONProcessor processor : jsonProcessors) { - processor.process(obj, exception); - } - return new Message(obj, eventId, messageCompressionEnabled); - } - - protected void send(Message message, long timestamp) { - if (isDisabled()) { - return; - } - try { - transport.send(message.encoded(), timestamp); - } catch (ConnectException e) { - LOG.log(Level.SEVERE, e.getMessage(), e); - // TODO Add backing off in case of errors - } catch (FileNotFoundException e) { - LOG.log(Level.SEVERE, e.getMessage(), e); - // TODO Add backing off in case of errors - } catch (IOException e) { - LOG.log(Level.SEVERE, e.getMessage(), e); - } - } - - /** - * Generates a unique event id. - * - * @return hexadecimal UUID4 String - */ - protected String generateEventId() { - // If we keep the -'s in the uuid, it is too long, remove them - return UUID.randomUUID().toString().replaceAll("-", ""); - } - - /** - * Formats a timestamp in the format expected by Sentry. - * - * @param timestamp timestamp to format - * @return formatted timestamp - */ - protected String formatTimestamp(long timestamp) { - return DateFormatUtils.formatUTC(timestamp, DateFormatUtils.ISO_DATETIME_FORMAT.getPattern()); - } - - /** - * Builds a new transport instance, using a transport class, registered in the {@link #TRANSPORT_REGISTRY} - * through the {@link #register(String, Class)} method, matching the scheme of the dsn. - * - * @param dsn dsn - * @return transport layer for the given dsn - */ - public static Transport newTransport(SentryDsn dsn) { - String fullScheme = dsn.getFullScheme(VARIANT_ASYNC); - Class transportClass = TRANSPORT_REGISTRY.get(fullScheme); - if (transportClass == null) { - throw new InvalidConfig("No transport registered for " + fullScheme); - } - try { - Constructor constructor = transportClass.getConstructor(SentryDsn.class); - Transport transport = constructor.newInstance(dsn); - if (dsn.isVariantIncluded(VARIANT_ASYNC)) { - return newAsyncTransport(transport); - } - return transport; - } catch (NoSuchMethodException e) { - throw new InvalidConfig("A transport class should contain a constructor with a SentryDsn instance as parameter", e); - } catch (InvocationTargetException e) { - throw new InvalidConfig("Could not construct a transport layer for " + fullScheme, e); - } catch (InstantiationException e) { - throw new InvalidConfig("Could not construct a transport layer for " + fullScheme, e); - } catch (IllegalAccessException e) { - throw new InvalidConfig("Could not construct a transport layer for " + fullScheme, e); - } - } - - /** - * Builds a new async transport layer, wrapping the original transport. - * - * @param transport transport to wrap in an async layer - * @return the async transport wrapper - */ - public static Transport newAsyncTransport(Transport transport) { - Class transportClass = TRANSPORT_REGISTRY.get(VARIANT_ASYNC); - if (transportClass == null) { - throw new InvalidConfig("No async transport registered"); - } - final String invalidSignature = "The async transport handler should contain a public static \"build\" method " + // - "with a single parameter of type " + Transport.class + " and returning a new " + Transport.class + " instance."; - try { - Method method = transportClass.getMethod("build", Transport.class); - if (!Modifier.isStatic(method.getModifiers())) { - throw new InvalidConfig(invalidSignature); - } - Object result = method.invoke(null, transport); - if (!(result instanceof Transport)) { - throw new InvalidConfig("The build method of the async transport layer should return an instance of " + Transport.class); - } - return (Transport) result; - } catch (InvocationTargetException e) { - throw new InvalidConfig("Could not invoke the static build method of " + transportClass.getName(), e); - } catch (NoSuchMethodException e) { - throw new InvalidConfig(invalidSignature, e); - } catch (IllegalAccessException e) { - throw new InvalidConfig(invalidSignature, e); - } - } - - /** - * Registers the transport class for the given scheme. - * - * @param scheme scheme to register for - * @param transportClass transport class to register for the scheme - * @return the previously registered transport class for the scheme, if any - */ - public static Class register(String scheme, Class transportClass) { - return TRANSPORT_REGISTRY.put(scheme, transportClass); - } - - /** - * Registers the default transport classes. - */ - public static void registerDefaults() { - TRANSPORT_REGISTRY.put("http", Transport.Http.class); - TRANSPORT_REGISTRY.put("https", Transport.Http.class); - TRANSPORT_REGISTRY.put("naive+https", Transport.NaiveHttps.class); - TRANSPORT_REGISTRY.put("udp", Transport.Udp.class); - TRANSPORT_REGISTRY.put(VARIANT_ASYNC, AsyncTransport.class); - } - - /** - * Builds the HMAC sentry signature. - *

    - * The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, - * and an arbitrary client version string. - *

    - * The client version should be something distinct to your client, and is simply for reporting purposes. - * To generate the HMAC signature, take the following example (in Python): - *

    - * hmac.new(public_key, '%s %s' % (timestamp, message), hashlib.sha1).hexdigest() - * - * @param message the error message to send to sentry - * @param timestamp the timestamp for when the message was created - * @param key sentry public key - * @return SHA1-signed HMAC string - */ - public static String sign(String message, long timestamp, String key) { - final String algo = "HmacSHA1"; - try { - SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), algo); - Mac mac = Mac.getInstance(algo); - mac.init(signingKey); - byte[] rawHmac = mac.doFinal((timestamp + " " + message).getBytes()); - return new String(Hex.encodeHex(rawHmac)); - } catch (NoSuchAlgorithmException e) { - throw new InvalidConfig("Could not sign message: " + e.getMessage(), e); - } catch (InvalidKeyException e) { - throw new InvalidConfig("Could not sign message: " + e.getMessage(), e); - } - } - - /** - * An almost-unique hash identifying the this event to improve aggregation. - * - * @param message The message we are sending to sentry - * @return CRC32 Checksum string - */ - public static String calculateChecksum(String message) { - byte bytes[] = message.getBytes(); - Checksum checksum = new CRC32(); - checksum.update(bytes, 0, bytes.length); - return String.valueOf(checksum.getValue()); - } - - public static class InvalidConfig extends RuntimeException { - - public InvalidConfig(String msg) { - super(msg); - } - - public InvalidConfig(String msg, Throwable t) { - super(msg, t); - } - - } - - public static class Message { - - public static final Message NONE = new Message(null, "-1", false); - - public final JSONObject json; - public final String eventId; - public final boolean compress; - - public Message(JSONObject json, String eventId, boolean compress) { - this.json = json; - this.eventId = eventId; - this.compress = compress; - } - - public String encoded() { - byte[] raw = Utils.toUtf8(json.toJSONString()); - if (compress) { - raw = Utils.compress(raw); - } - return encodeBase64String(raw); - } - - @Override - public String toString() { - return json.toJSONString(); - } - } - -} diff --git a/raven/src/main/java/net/kencochrane/raven/Events.java b/raven/src/main/java/net/kencochrane/raven/Events.java deleted file mode 100644 index f4cc85f5833..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/Events.java +++ /dev/null @@ -1,127 +0,0 @@ -package net.kencochrane.raven; - -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; - -import java.util.Arrays; - -/** - * Collection of builtin Raven/Sentry events. - */ -public abstract class Events { - - public enum LogLevel { - ERROR(5); - - public int intValue; - - LogLevel(int intValue) { - this.intValue = intValue; - } - - } - - public static JSONObject message(String message, Object... params) { - return message(new JSONObject(), message, params); - } - - @SuppressWarnings("unchecked") - public static JSONObject message(JSONObject json, String message, Object... params) { - JSONObject messageJson = new JSONObject(); - messageJson.put("message", message); - JSONArray paramArray = new JSONArray(); - if (params != null) { - paramArray.addAll(Arrays.asList(params)); - } - messageJson.put("params", paramArray); - json.put("sentry.interfaces.Message", messageJson); - return json; - } - - public static JSONObject query(String query, String engine) { - return query(new JSONObject(), query, engine); - } - - @SuppressWarnings("unchecked") - public static JSONObject query(JSONObject json, String query, String engine) { - JSONObject content = new JSONObject(); - content.put("query", query); - content.put("engine", engine); - json.put("sentry.interfaces.Query", content); - return json; - } - - public static JSONObject exception(Throwable exception) { - return exception(new JSONObject(), exception); - } - - @SuppressWarnings("unchecked") - public static JSONObject exception(JSONObject json, Throwable exception) { - json.put("level", LogLevel.ERROR.intValue); - json.put("culprit", determineCulprit(exception)); - json.put("sentry.interfaces.Exception", buildException(exception)); - json.put("sentry.interfaces.Stacktrace", buildStacktrace(exception)); - return json; - } - - /** - * Determines the class and method name where the root cause exception occurred. - * - * @param exception exception - * @return the culprit - */ - public static String determineCulprit(Throwable exception) { - Throwable cause = exception; - String culprit = null; - while (cause != null) { - StackTraceElement[] elements = cause.getStackTrace(); - if (elements.length > 0) { - StackTraceElement trace = elements[0]; - culprit = trace.getClassName() + "." + trace.getMethodName(); - } - cause = cause.getCause(); - } - return culprit; - } - - @SuppressWarnings("unchecked") - public static JSONObject buildException(Throwable exception) { - JSONObject json = new JSONObject(); - json.put("type", exception.getClass().getSimpleName()); - json.put("value", exception.getMessage()); - json.put("module", exception.getClass().getPackage().getName()); - return json; - } - - @SuppressWarnings("unchecked") - public static JSONObject buildStacktrace(Throwable exception) { - JSONArray array = new JSONArray(); - Throwable cause = exception; - while (cause != null) { - StackTraceElement[] elements = cause.getStackTrace(); - for (int index = 0; index < elements.length; ++index) { - if (index == 0) { - JSONObject causedByFrame = new JSONObject(); - String msg = "Caused by: " + cause.getClass().getName(); - if (cause.getMessage() != null) { - msg += " (\"" + cause.getMessage() + "\")"; - } - causedByFrame.put("filename", msg); - causedByFrame.put("lineno", -1); - array.add(causedByFrame); - } - StackTraceElement element = elements[index]; - JSONObject frame = new JSONObject(); - frame.put("filename", element.getClassName()); - frame.put("function", element.getMethodName()); - frame.put("lineno", element.getLineNumber()); - array.add(frame); - } - cause = cause.getCause(); - } - JSONObject stacktrace = new JSONObject(); - stacktrace.put("frames", array); - return stacktrace; - } - -} \ No newline at end of file diff --git a/raven/src/main/java/net/kencochrane/raven/SentryDsn.java b/raven/src/main/java/net/kencochrane/raven/SentryDsn.java deleted file mode 100644 index 7a8429c4b51..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/SentryDsn.java +++ /dev/null @@ -1,412 +0,0 @@ -package net.kencochrane.raven; - -import org.apache.commons.lang.StringUtils; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static org.apache.commons.lang.StringUtils.defaultString; - -/** - * The Sentry DSN is the string you can copy-paste from the Sentry client configuration screen. - *

    - * Use this class to extract the interesting parts from the DSN. - *

    - *

    - * To provide flexible configuration, you can specify configuration options though a query string. For example, to - * enable the sending of the (deprecated) HMAC signature to Sentry, your DSN should look a bit like this: - *

    - *
    http://public:private@host:port/path/projectid?raven.includeSignature=true
    - *

    - * The options that are taken into account depend on the selected transport layer. - *

    - * - * @see Transport.Option#INCLUDE_SIGNATURE - * @see Transport.Http.Option#TIMEOUT - */ -public class SentryDsn { - - /** - * Logger. - */ - private static final Logger LOG = Logger.getLogger("raven.client"); - - /** - * The scheme, e.g. http, https or udp. - */ - public final String scheme; - - /** - * The scheme variants, e.g. in case of a full scheme of "naive+https" this will hold "naive". - */ - public final String[] variants; - - /** - * The Sentry host. - */ - public final String host; - - /** - * The public key of the client. - */ - public final String publicKey; - - /** - * The secret, private key of the client. - */ - public final String secretKey; - - /** - * Optional extra path. - */ - public final String path; - - /** - * The id of the project to log to in Sentry. - */ - public final String projectId; - - /** - * The server port. - */ - public final int port; - - /** - * Extra Raven client options. - */ - public final Map options; - - /** - * Constructor for your convenience. - *

    - * It's recommended to use one of the {@link #build()} or {@link #buildOptional()} methods instead. - *

    - * - * @param scheme scheme - * @param variants scheme variants (e.g. naive, async) - * @param host host - * @param publicKey public key - * @param secretKey private key - * @param path path - * @param projectId project id - * @param port the port - * @param options miscellaneous options - */ - public SentryDsn(String scheme, String[] variants, String host, String publicKey, String secretKey, String path, String projectId, int port, Map options) { - this.scheme = scheme; - this.variants = (variants == null ? new String[0] : variants); - this.host = host; - this.publicKey = publicKey; - this.secretKey = secretKey; - this.path = path; - this.projectId = projectId; - this.port = port; - if (options == null) { - this.options = Collections.emptyMap(); - } else { - this.options = Collections.unmodifiableMap(options); - } - } - - /** - * Gets the value of an option as a boolean. - *

    - * In case no option is specified, the default value is returned. - *

    - * - * @param key key of the option - * @param defaultValue default value to return when no matching option value was found - * @return the value of the option or the default value when absent - */ - public boolean getOptionAsBoolean(String key, boolean defaultValue) { - String value = options.get(key); - if (value == null) { - return defaultValue; - } - return Boolean.parseBoolean(value); - } - - /** - * Gets the value of an option as an int. - * - * @param key key of the option - * @param defaultValue value to return when the option was not specified - * @return the value of the option or the default value when absent - */ - public int getOptionAsInt(String key, int defaultValue) { - String value = options.get(key); - if (value == null) { - return defaultValue; - } - try { - return Integer.parseInt(value); - } catch (NumberFormatException e) { - throw new RuntimeException("Expected " + key + " to be a valid int"); - } - } - - /** - * Checks whether the scheme variant is specified in this dsn. - * - * @param variant variant - * @return true when the variant was specified - */ - public boolean isVariantIncluded(String variant) { - return Arrays.binarySearch(variants, variant) >= 0; - } - - /** - * Gets the full scheme, optionally excluding some parts. - *

    - * This allows the async transport layer to get the actual underlying transport layer to use, ignoring the async - * variant. - *

    - * - * @param excludes variants to exclude from the full scheme - * @return the full scheme - */ - public String getFullScheme(String... excludes) { - Set excludedSchemes = Collections.emptySet(); - if (excludes != null && excludes.length > 0) { - excludedSchemes = new HashSet(Arrays.asList(excludes)); - } - String full = ""; - List parts = new LinkedList(); - for (String variant : variants) { - if (!excludedSchemes.contains(variant)) { - parts.add(variant); - } - } - parts.add(scheme); - return StringUtils.join(parts, '+'); - } - - /** - * Returns the full dsn or the derived dsn typically used for transport. - * - * @param full whether to generate the full or the derived dsn - * @return the full or derived dsn - */ - public String toString(boolean full) { - String protocol = (!full || variants.length == 0 ? scheme : StringUtils.join(variants, '+') + "+" + scheme); - String fullHost = (port < 0 ? host : host + ":" + port); - String fullPath = (path == null ? "" : path); - if (!full) { - return String.format("%s://%s%s", protocol, fullHost, fullPath); - } - fullPath += "/" + projectId; - String user = (StringUtils.isBlank(secretKey) ? publicKey : publicKey + ":" + secretKey); - return String.format("%s://%s@%s%s", protocol, user, fullHost, fullPath); - } - - @Override - public String toString() { - return toString(true); - } - - /** - * Builds the Sentry dsn based on the default lookups as specified by {@link DefaultLookUps}. - *

    - * PaaS providers such as Heroku prefer environment variables. This method will first examine the environment and - * then the system properties to find a Sentry dsn value. - *

    - * - * @return the Sentry dsn when found - */ - public static SentryDsn build() { - return build(null, DefaultLookUps.values(), null); - } - - /** - * Performs the same logic as {@link #build()} but will catch any {@link InvalidDsnException} thrown and return null - * instead. - * - * @return the Sentry dsn when found and valid or null instead - */ - public static SentryDsn buildOptional() { - try { - return build(); - } catch (SentryDsn.InvalidDsnException e) { - LOG.log(Level.WARNING, "Could not automatically determine a valid DSN; client will be disabled", e); - return null; - } - } - - /** - * Builds the Sentry dsn. - *

    - * In case a Sentry dsn is specified in the environment or system properties, that value takes precedence over the - * fullDsn parameter. - *

    - * - * @param fullDsn dsn - * @return the dsn found in either the environment, system properties or derived from the parameter fullDsn - */ - public static SentryDsn build(String fullDsn) { - return build(fullDsn, DefaultLookUps.values(), null); - } - - /** - * Performs the same logic as {@link #build(String)} but will catch any {@link InvalidDsnException} thrown and - * return null instead. - * - * @return the dsn found in either the environment, system properties or derived from the parameter - * fullDsn; if no valid dsn is available, this will return null - */ - public static SentryDsn buildOptional(String fullDsn) { - try { - return build(fullDsn); - } catch (SentryDsn.InvalidDsnException e) { - LOG.log(Level.WARNING, "Could not automatically determine a valid DSN; client will be disabled", e); - return null; - } - } - - /** - * Builds the dsn. - *

    - * The overrides take precedence over the dsn parameter, while the fallbacks provide a way to look for the dsn when - * the dsn parameter was empty. - *

    - * - * @param fullDsn the supplied dsn - * @param overrides places to check for a dsn value before using the supplied dsn - * @param fallbacks places to check for a dsn value when the supplied dsn is an empty or null string - * @return the built Sentry dsn - */ - public static SentryDsn build(final String fullDsn, LookUp[] overrides, LookUp[] fallbacks) { - String dsn = defaultString(firstResult(overrides), defaultString(fullDsn, firstResult(fallbacks))); - if (StringUtils.isBlank(dsn)) { - throw new InvalidDsnException("No valid Sentry DSN found"); - } - int schemeEnd = dsn.indexOf("://"); - if (schemeEnd <= 0) { - throw new InvalidDsnException("Expected to discover a scheme in the Sentry DSN"); - } - String fullScheme = dsn.substring(0, schemeEnd); - String[] schemeParts = StringUtils.split(fullScheme, '+'); - String scheme = fullScheme; - String[] variants = null; - if (schemeParts.length > 1) { - variants = Arrays.copyOfRange(schemeParts, 0, schemeParts.length - 1); - scheme = schemeParts[schemeParts.length - 1]; - } - try { - // To prevent us from having to register a handler for for example udp URLs, we'll replace the original - // scheme with "http" and let the URL code of Java handle the parsing. - URL url = new URL("http" + dsn.substring(schemeEnd)); - String[] userParts = url.getUserInfo().split(":"); - String publicKey = userParts[0]; - String secretKey = null; - if (userParts.length > 1) { - secretKey = userParts[1]; - } - String urlPath = url.getPath(); - int lastSlash = urlPath.lastIndexOf('/'); - String path = urlPath.substring(0, lastSlash); - String projectId = urlPath.substring(lastSlash + 1); - Map options = parseQueryString(url.getQuery()); - return new SentryDsn(scheme, variants, url.getHost(), publicKey, secretKey, path, projectId, url.getPort(), options); - } catch (MalformedURLException e) { - // This exception should only be thrown when an unhandled scheme slips in which the above code should - // prevent. Nevertheless: throw something. - throw new InvalidDsnException("Failed to parse " + dsn, e); - } - } - - /** - * See {@link #build(String, net.kencochrane.raven.SentryDsn.LookUp[], net.kencochrane.raven.SentryDsn.LookUp[])}. - * This method will return null when no valid DSN was found instead of throwing a - * {@link InvalidDsnException}. - * - * @param fullDsn the supplied dsn - * @param overrides places to check for a dsn value before using the supplied dsn - * @param fallbacks places to check for a dsn value when the supplied dsn is an empty or null string - * @return the built Sentry dsn or null when no such value was found - */ - public static SentryDsn buildOptional(final String fullDsn, LookUp[] overrides, LookUp[] fallbacks) { - try { - return build(fullDsn, overrides, fallbacks); - } catch (SentryDsn.InvalidDsnException e) { - LOG.log(Level.WARNING, "Could not automatically determine a valid DSN; client will be disabled", e); - return null; - } - } - - /** - * Parses simple query strings. - *

    - * We don't expect complex query strings - they are only used to pass in Raven options. - *

    - * - * @param q the query string - * @return the key/value pairs in the query string - */ - protected static Map parseQueryString(String q) { - Map map = new HashMap(); - String[] pairs = StringUtils.split(q, '&'); - if (pairs == null) { - return map; - } - for (String pair : pairs) { - String[] components = StringUtils.split(pair, '='); - String value = (components.length == 1 ? null : StringUtils.join(components, '=', 1, components.length)); - map.put(components[0], value); - } - return map; - } - - public static String firstResult(LookUp[] lookups) { - if (lookups == null) { - return null; - } - for (LookUp lookup : lookups) { - String dsn = lookup.findDsn(); - if (!StringUtils.isBlank(dsn)) { - return dsn; - } - } - return null; - } - - public static class InvalidDsnException extends RuntimeException { - - public InvalidDsnException(String message) { - super(message); - } - - public InvalidDsnException(String message, Throwable t) { - super(message, t); - } - - } - - public interface LookUp { - - String findDsn(); - - } - - public enum DefaultLookUps implements LookUp { - - ENV { - @Override - public String findDsn() { - return System.getenv(Utils.SENTRY_DSN); - } - }, - - SYSTEM_PROPERTY { - @Override - public String findDsn() { - return System.getProperty(Utils.SENTRY_DSN); - } - } - - - } - -} diff --git a/raven/src/main/java/net/kencochrane/raven/Transport.java b/raven/src/main/java/net/kencochrane/raven/Transport.java deleted file mode 100644 index 3a80b06341d..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/Transport.java +++ /dev/null @@ -1,301 +0,0 @@ -package net.kencochrane.raven; - -import org.apache.commons.lang.StringUtils; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSession; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.*; - -/** - * Transport class with default implementations for the only/popular Sentry transport methods. - *

    - * As a user of this class you are responsible for selecting the correct transport layer depending on the Sentry DSN. - * The {@link Client} will select the correct transport layer based on its registry of transport layers. - *

    - *

    - * Usage example: - *

    - *
    
    - *     SentryDsn dsn = SentryDsn.build("http://public:secret@host/1");
    - *     Transport transport = new Transport.Http(dsn);
    - *     transport.send(message, System.currentTimeMillis());
    - * 
    - */ -public abstract class Transport { - - /** - * General transport options. - */ - public interface Option { - - /** - * Sending the HMAC signature along with the message has been deprecated but you can still enable through the - * {@link SentryDsn#options}. Set this option to true to send the signature. - */ - String INCLUDE_SIGNATURE = "raven.includeSignature"; - } - - /** - * The DSN used by this transport. - */ - public final SentryDsn dsn; - - /** - * Whether the signature should be included or not. - */ - public final boolean includeSignature; - - /** - * Whether the transport has started. - *

    - * This is mostly provided because the {@link AsyncTransport} wrapper should allow users of this class to control - * the lifecycle through the {@link #start()} and {@link #stop()} methods. - *

    - */ - protected boolean started; - - /** - * The required transport constructor. - *

    - * Each transport class registered with the {@link Client} must provide a similar constructor. - *

    - * - * @param dsn the Sentry DSN - */ - public Transport(SentryDsn dsn) { - this.dsn = dsn; - this.includeSignature = dsn.getOptionAsBoolean(Option.INCLUDE_SIGNATURE, false); - } - - /** - * Starts the transport layer. - */ - public void start() { - started = true; - } - - /** - * Indicates whether the transport layer has been started. - * - * @return true when started - */ - public boolean isStarted() { - return started; - } - - /** - * Stops the transport layer. - */ - public void stop() { - started = false; - } - - /** - * Sends a message to Sentry. - *

    - * Subclasses most likely will be more interested in {@link #doSend(String, String)}. - *

    - * - * @param messageBody message to send - * @param timestamp timestamp of the message - * @throws IOException when something goes wrong when sending - */ - public void send(String messageBody, long timestamp) throws IOException { - if (includeSignature) { - String hmacSignature = Client.sign(messageBody, timestamp, dsn.secretKey); - String authHeader = buildAuthHeader(hmacSignature, timestamp, dsn.publicKey); - doSend(messageBody, authHeader); - } else { - doSend(messageBody, buildAuthHeader(timestamp, dsn.publicKey)); - } - } - - /** - * Performs the actual sending of the message with the accompanying authentication header. - * - * @param messageBody message to send - * @param authHeader the authentication header, built with {@link #buildAuthHeader(String, long, String)} - * @throws IOException when something goes wrong when sending - */ - protected void doSend(String messageBody, String authHeader) throws IOException { - throw new UnsupportedOperationException("Nothing to do here..."); - } - - /** - * Constructs the X-Sentry-Auth header. - * - * @param timestamp timestamp - * @param publicKey public key - * @return the value for the X-Sentry-Auth header. - */ - public static String buildAuthHeader(long timestamp, String publicKey) { - return buildAuthHeader(null, timestamp, publicKey); - } - - /** - * Constructs the X-Sentry-Auth header. - * - * @param hmacSignature the HMAC signature of the message - * @param timestamp timestamp - * @param publicKey public key - * @return the value for the X-Sentry-Auth header. - * @deprecated Usage of the signature has been deprecated in Sentry versions 4.6 and higher. - */ - @Deprecated - public static String buildAuthHeader(String hmacSignature, long timestamp, String publicKey) { - StringBuilder header = new StringBuilder(); - header.append("Sentry sentry_version=2.0"); - if (!StringUtils.isBlank(hmacSignature)) { - header.append(",sentry_signature=").append(hmacSignature); - } - header.append(",sentry_timestamp="); - header.append(timestamp); - header.append(",sentry_key="); - header.append(publicKey); - header.append(",sentry_client="); - header.append(Utils.Client.NAME); - return header.toString(); - } - - /** - * HTTP and HTTPS transport layer. - */ - public static class Http extends Transport { - - /** - * Options for this transport. - */ - public interface Option { - - /** - * The connect timeout option key. - */ - String TIMEOUT = "raven.timeout"; - - /** - * The default timeout applied to connections. - */ - int TIMEOUT_DEFAULT = 10000; - } - - /** - * The URL to post to. - */ - public final URL url; - - /** - * The timeout applied to connections originating from this instance. - */ - public final int timeout; - - /** - * Constructor. - * - * @param dsn the Sentry dsn - */ - public Http(SentryDsn dsn) { - super(dsn); - try { - this.url = new URL(dsn.toString(false) + "/api/store/"); - } catch (MalformedURLException e) { - // We rely on the SentryDsn validating the URL so this really, *really* shouldn't happen - throw new SentryDsn.InvalidDsnException("URL constructed from Sentry DSN is invalid", e); - } - this.timeout = dsn.getOptionAsInt(Option.TIMEOUT, Option.TIMEOUT_DEFAULT); - } - - @Override - protected void doSend(String messageBody, String authHeader) throws IOException { - HttpURLConnection connection = getConnection(); - connection.setRequestMethod("POST"); - connection.setDoOutput(true); - connection.setConnectTimeout(timeout); - connection.setRequestProperty("X-Sentry-Auth", authHeader); - OutputStream output = connection.getOutputStream(); - output.write(messageBody.getBytes()); - output.close(); - connection.connect(); - InputStream input = connection.getInputStream(); - input.close(); - } - - protected HttpURLConnection getConnection() throws IOException { - return (HttpURLConnection) url.openConnection(); - } - - } - - /** - * A naive HTTPS transport layer, useful in case you're using wildcard SSL certificates which - * Java doesn't handle that well. - */ - public static class NaiveHttps extends Http { - - private static final HostnameVerifier ACCEPT_ALL = new AcceptAllHostnameVerifier(); - public final HostnameVerifier hostnameVerifier; - - public NaiveHttps(SentryDsn dsn) { - this(dsn, ACCEPT_ALL); - } - - public NaiveHttps(SentryDsn dsn, HostnameVerifier hostnameVerifier) { - super(dsn); - this.hostnameVerifier = hostnameVerifier; - } - - @Override - protected HttpURLConnection getConnection() throws IOException { - HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); - connection.setHostnameVerifier(hostnameVerifier); - return connection; - } - - } - - /** - * UDP transport layer. - */ - public static class Udp extends Transport { - - private final DatagramSocket socket; - - public Udp(SentryDsn dsn) { - super(dsn); - try { - socket = createSocket(dsn.host, dsn.port); - } catch (SocketException e) { - throw new IllegalStateException(e); - } - } - - @Override - protected void doSend(String messageBody, String authHeader) throws IOException { - byte[] message = Utils.toUtf8(authHeader + "\n\n" + messageBody); - DatagramPacket packet = new DatagramPacket(message, message.length); - socket.send(packet); - } - - protected DatagramSocket createSocket(String host, int port) throws SocketException { - DatagramSocket socket = new DatagramSocket(); - socket.connect(new InetSocketAddress(host, port)); - return socket; - } - - } - - /** - * A hostname verifier that actually just allows all hosts - used in combination with the {@link net.kencochrane.raven.Transport.NaiveHttps} - * transport layer. - */ - public static class AcceptAllHostnameVerifier implements HostnameVerifier { - @Override - public boolean verify(String hostname, SSLSession sslSession) { - return true; - } - } - -} diff --git a/raven/src/main/java/net/kencochrane/raven/Utils.java b/raven/src/main/java/net/kencochrane/raven/Utils.java deleted file mode 100644 index e96fd6f4f2e..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/Utils.java +++ /dev/null @@ -1,108 +0,0 @@ -package net.kencochrane.raven; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.HashMap; -import java.util.Map; -import java.util.zip.DeflaterOutputStream; -import java.util.zip.InflaterOutputStream; - -/** - * Utilities for the Raven client. - */ -public abstract class Utils { - - public static final String SENTRY_DSN = "SENTRY_DSN"; - - private static final Map CACHE = new HashMap(); - - public interface Client { - String VERSION = "2.0"; - String NAME = "Raven-Java " + VERSION; - } - - @SuppressWarnings("unchecked") - public static String hostname() { - final String cacheKey = "hostname"; - String name = fromCache(cacheKey, 360000); - if (name == null) { - try { - name = InetAddress.getLocalHost().getCanonicalHostName(); - } catch (UnknownHostException e) { - // can't get hostname - name = "unavailable"; - } - CACHE.put(cacheKey, new CacheEntry(name)); - } - return name; - } - - public static long now() { - return System.currentTimeMillis(); - } - - public static byte[] toUtf8(String s) { - try { - return s == null ? new byte[0] : s.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - public static String fromUtf8(byte[] b) { - try { - return new String(b, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - public static byte[] compress(byte[] input) { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - DeflaterOutputStream output = new DeflaterOutputStream(bytes); - try { - output.write(input); - output.close(); - return bytes.toByteArray(); - } catch (IOException e) { - // Look, if this thing starts throwing IOExceptions, you're on your own. Sorry. - throw new RuntimeException(e); - } - } - - public static byte[] decompress(byte[] input) { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - InflaterOutputStream output = new InflaterOutputStream(bytes); - try { - output.write(input); - output.close(); - return bytes.toByteArray(); - } catch (IOException e) { - // ... Let things crash if this happens - throw new RuntimeException(e); - } - } - - @SuppressWarnings("unchecked") - protected static T fromCache(String key, long timeout) { - CacheEntry entry = (CacheEntry) CACHE.get(key); - if (entry == null) { - return null; - } - return (entry.timestamp + timeout > now() ? entry.value : null); - } - - protected static class CacheEntry { - public final T value; - public final long timestamp; - - public CacheEntry(T value) { - this.value = value; - timestamp = now(); - } - } - -} diff --git a/raven/src/main/java/net/kencochrane/raven/ext/RavenServletRequestListener.java b/raven/src/main/java/net/kencochrane/raven/ext/RavenServletRequestListener.java deleted file mode 100644 index d2ecfbef898..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/ext/RavenServletRequestListener.java +++ /dev/null @@ -1,35 +0,0 @@ -package net.kencochrane.raven.ext; - -import javax.servlet.ServletRequestEvent; -import javax.servlet.ServletRequestListener; -import javax.servlet.annotation.WebListener; -import javax.servlet.http.HttpServletRequest; - -/** - * Store HttpServletRequest object in RavenMDC, allowing the request to be - * accessed by {@link ServletJSONProcessor}. - * - * @author vvasabi - * @since 1.0 - */ -@WebListener -public class RavenServletRequestListener implements ServletRequestListener { - - private static final ThreadLocal THREAD_REQUEST - = new ThreadLocal(); - - @Override - public void requestInitialized(ServletRequestEvent sre) { - THREAD_REQUEST.set((HttpServletRequest)sre.getServletRequest()); - } - - @Override - public void requestDestroyed(ServletRequestEvent sre) { - THREAD_REQUEST.remove(); - } - - public static HttpServletRequest getRequest() { - return THREAD_REQUEST.get(); - } - -} diff --git a/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java b/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java deleted file mode 100644 index 16e666a4d5a..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/ext/ServletJSONProcessor.java +++ /dev/null @@ -1,137 +0,0 @@ -package net.kencochrane.raven.ext; - -import net.kencochrane.raven.spi.JSONProcessor; -import net.kencochrane.raven.spi.RavenMDC; -import org.apache.commons.lang.StringUtils; -import org.json.simple.JSONObject; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import java.util.Enumeration; -import java.util.Map; -import java.util.Map.Entry; - -/** - * Add HTTP request information to logs when logs are created on HTTP request - * threads. - * - * @author vvasabi - * @since 1.0 - */ -public class ServletJSONProcessor implements JSONProcessor { - - private static final String HTTP_INTERFACE = "sentry.interfaces.Http"; - - @Override - public void prepareDiagnosticContext() { - HttpServletRequest request = RavenServletRequestListener.getRequest(); - if (request == null) { - // no request available; do nothing - return; - } - - RavenMDC.getInstance().put(HTTP_INTERFACE, buildHttpObject(request)); - } - - @Override - public void clearDiagnosticContext() { - RavenMDC.getInstance().remove(HTTP_INTERFACE); - } - - @Override - @SuppressWarnings("unchecked") - public void process(JSONObject json, Throwable exception) { - JSONObject http = (JSONObject) RavenMDC.getInstance().get(HTTP_INTERFACE); - if (http == null) { - return; - } - - json.put(HTTP_INTERFACE, http); - } - - @SuppressWarnings("unchecked") - private static JSONObject buildHttpObject(HttpServletRequest request) { - JSONObject http = new JSONObject(); - http.put("url", getUrl(request)); - http.put("method", request.getMethod()); - http.put("data", getData(request)); - http.put("query_string", request.getQueryString()); - http.put("cookies", getCookies(request)); - http.put("headers", getHeaders(request)); - http.put("env", getEnvironmentVariables(request)); - return http; - } - - private static String getUrl(HttpServletRequest request) { - StringBuffer sb = request.getRequestURL(); - String query = request.getQueryString(); - if (query != null) { - sb.append("?").append(query); - } - return sb.toString(); - } - - @SuppressWarnings("unchecked") - private static JSONObject getData(HttpServletRequest request) { - if (!"POST".equals(request.getMethod())) { - return null; - } - - JSONObject data = new JSONObject(); - Map params = request.getParameterMap(); - for (Entry entry : params.entrySet()) { - data.put(entry.getKey(), entry.getValue()[0]); - } - return data; - } - - @SuppressWarnings("unchecked") - private static JSONObject getCookies(HttpServletRequest request) { - JSONObject cookiesMap = new JSONObject(); - Cookie[] cookies = request.getCookies(); - if (cookies != null) { - for (Cookie cookie : request.getCookies()) { - cookiesMap.put(cookie.getName(), cookie.getValue()); - } - } - return cookiesMap; - } - - @SuppressWarnings("unchecked") - private static JSONObject getHeaders(HttpServletRequest request) { - JSONObject headers = new JSONObject(); - Enumeration headersEnum = request.getHeaderNames(); - while (headersEnum.hasMoreElements()) { - String name = headersEnum.nextElement(); - headers.put(capitalize(name), request.getHeader(name)); - } - return headers; - } - - @SuppressWarnings("unchecked") - private static JSONObject getEnvironmentVariables(HttpServletRequest request) { - JSONObject env = new JSONObject(); - env.put("REMOTE_ADDR", request.getRemoteAddr()); - env.put("SERVER_NAME", request.getServerName()); - env.put("SERVER_PORT", request.getServerPort()); - env.put("SERVER_PROTOCOL", request.getProtocol()); - return env; - } - - /** - * Capitalize the first letter of each part of a header name. This is - * necessary because Sentry currently expects header names to be formatted - * this way. - * - * @param headerName header name to capitalize - * @return capitalized header name - */ - private static String capitalize(String headerName) { - String[] tokens = headerName.split("-"); - for (int i = 0; i < tokens.length; i++) { - tokens[i] = StringUtils.capitalize(tokens[i]); - } - return StringUtils.join(tokens, "-"); - } - -} diff --git a/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java b/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java deleted file mode 100644 index ec7e25332a0..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/spi/JSONProcessor.java +++ /dev/null @@ -1,47 +0,0 @@ -package net.kencochrane.raven.spi; - -import org.json.simple.JSONObject; - -/** - * A JSONProcessor is used to modify JSON requests before they are sent to - * Sentry. It is expected for a JSON processor to be singleton and be - * thread-safe. - * - * To register JSONProcessors, refer to the settings of the appender used. - * - * @author vvasabi - * @since 1.0 - */ -public interface JSONProcessor { - - /** - * This is called when a message is logged. Since - * {@link #process(JSONObject, Throwable)} may be executed on a different - * thread, this method should copy any data the processor needs into - * {@link RavenMDC}. - * - * For each message logged, this method should be called exactly once. - */ - void prepareDiagnosticContext(); - - /** - * This is called after the message logged is processed (in synchronous - * mode), or after the message has been sent to the processing queue (in - * asynchronous mode). The intention of this method is to clear the data - * put in {@link RavenMDC} by {@link #prepareDiagnosticContext()} to prevent - * memory leak. - * - * For each message logged, this method should be called exactly once. - */ - void clearDiagnosticContext(); - - /** - * Modify the JSON request object specified before it is sent to Sentry. - * This method may be called concurrently and therefore must be thread-safe. - * - * @param json request JSON object to be modified - * @param exception exception attached with the message and may be null - */ - void process(JSONObject json, Throwable exception); - -} diff --git a/raven/src/main/java/net/kencochrane/raven/spi/RavenMDC.java b/raven/src/main/java/net/kencochrane/raven/spi/RavenMDC.java deleted file mode 100644 index f738f4480c7..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/spi/RavenMDC.java +++ /dev/null @@ -1,70 +0,0 @@ -package net.kencochrane.raven.spi; - -/** - * Since Raven plugins may be executed on threads different from those that - * produce logs, RavenMDC provides a means for context variables to be passed - * from log spawning threads to log processing threads. - * - * This service is intended to be used by Raven plugins and not by user - * application. An implementation is expected to be singleton and should be - * thread-safe. - * - * @author vvasabi - * @since 1.0 - */ -public abstract class RavenMDC { - - private static RavenMDC instance; - - /** - * Get the current instance. - * - * @return current instance - */ - public static RavenMDC getInstance() { - return instance; - } - - /** - * Set the instance of RavenMDC. Note that this method can only be called - * once. - * - * @param newInstance new instance of RavenMDC - */ - public static synchronized void setInstance(RavenMDC newInstance) { - if (newInstance == null) { - throw new NullPointerException("New instance cannot be null."); - } - if (instance != null) { - throw new IllegalStateException("A RavenMDC instance already exists."); - } - instance = newInstance; - } - - /** - * Get the context variable specified by key from MDC. If the value - * specified does not exist, null is returned. - * - * @param key key of the context variable to get - * @return context variable specified by key, or null if not found - */ - public abstract Object get(String key); - - /** - * Add a context variable to MDC. If an existing value with the same key - * exists, it is overridden. - * - * @param key key of the context variable - * @param value value of the context variable - */ - public abstract void put(String key, Object value); - - /** - * Remove a context variable from MDC. If the value specified by the key - * does not exist, this method does nothing. - * - * @param key key of the context variable to remove - */ - public abstract void remove(String key); - -} diff --git a/raven/src/test/java/net/kencochrane/raven/AsyncTransportTest.java b/raven/src/test/java/net/kencochrane/raven/AsyncTransportTest.java deleted file mode 100644 index df799b1e0aa..00000000000 --- a/raven/src/test/java/net/kencochrane/raven/AsyncTransportTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package net.kencochrane.raven; - -import mockit.Expectations; -import mockit.Mocked; -import org.junit.Before; -import org.junit.Test; - -import java.net.SocketException; - -import static org.junit.Assert.assertEquals; - -/** - * Test cases for {@link AsyncTransport}. - */ -public class AsyncTransportTest { - - private UdpTransportTest.CollectingSocket socket; - private long timestamp = System.currentTimeMillis(); - - @Test - public void ultimatelySent() throws Exception { - String messageBody = "MessageBodyDoesNotReallyMatter"; - String authHeader = Transport.buildAuthHeader(timestamp, "public"); - - // Actual testing - SentryDsn dsn = SentryDsn.build("async+udp://public:private@host:9999/1"); - AsyncTransport transport = AsyncTransport.build(new Transport.Udp(dsn)); - transport.start(); - for (int i = 0; i < 500; ++i) { - transport.send(messageBody, timestamp); - } - transport.stop(); - Thread.sleep(AsyncTransport.WAIT_FOR_SHUTDOWN); - - // Verify - assertEquals(500, socket.packets.size()); - for (int i = 0; i < 500; ++i) { - byte[] data = socket.packets.get(i).getData(); - assertEquals(authHeader + "\n\n" + messageBody, Utils.fromUtf8(data)); - } - } - - @Before - public void setUp() throws SocketException { - socket = new UdpTransportTest.CollectingSocket(); - new Expectations() { - @Mocked("createSocket") - Transport.Udp m; - - { - m.createSocket("host", 9999); - returns(socket); - } - }; - } - -} diff --git a/raven/src/test/java/net/kencochrane/raven/ClientExample.java b/raven/src/test/java/net/kencochrane/raven/ClientExample.java deleted file mode 100644 index 6f7d1278f33..00000000000 --- a/raven/src/test/java/net/kencochrane/raven/ClientExample.java +++ /dev/null @@ -1,53 +0,0 @@ -package net.kencochrane.raven; - -import java.io.IOException; -import java.io.InputStream; -import java.util.logging.LogManager; - -/** - * Simple client example. - */ -public class ClientExample { - - public static void main(String[] args) { - initialize(); - String dsnString = null; - if (args.length > 0) { - dsnString = args[0]; - } else { - dsnString = "async+http://7e4dff58960645adb2ade337e6d53425:81fe140206d7464e911b89cd93e2a5a4@localhost:9000/2"; - } - SentryDsn dsn = SentryDsn.build(dsnString); - System.out.println("Sending to Sentry instance at " + dsn.toString(false)); - Client client = new Client(dsn); - System.out.println("Sending simple message"); - String eventId = client.captureMessage("Hi there"); - System.out.println("Simple message event id: " + eventId); - try { - throw new IllegalArgumentException("Oh no! You can't do *that*!"); - } catch (IllegalArgumentException e) { - System.out.println("Logging exception"); - eventId = client.captureException(e); - System.out.println("Exception event id: " + eventId); - } - client.stop(); - System.out.println("Stopped client"); - } - - protected static void initialize() { - InputStream input = ClientExample.class.getResourceAsStream("/java.util.logging.properties"); - try { - LogManager.getLogManager().readConfiguration(input); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - try { - input.close(); - } catch (IOException e) { - // Oh shut up - } - } - } - - -} diff --git a/raven/src/test/java/net/kencochrane/raven/ClientSetupTest.java b/raven/src/test/java/net/kencochrane/raven/ClientSetupTest.java deleted file mode 100644 index d7cac1f0b03..00000000000 --- a/raven/src/test/java/net/kencochrane/raven/ClientSetupTest.java +++ /dev/null @@ -1,237 +0,0 @@ -package net.kencochrane.raven; - -import org.apache.commons.codec.binary.Base64; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; -import org.junit.After; -import org.junit.Test; - -import java.io.IOException; - -import static net.kencochrane.raven.Utils.decompress; -import static net.kencochrane.raven.Utils.fromUtf8; -import static org.junit.Assert.*; - -/** - * Test cases for {@link Client}. - */ -public class ClientSetupTest { - - @Test - public void defaultConstructor() { - // Plain HTTP - System.setProperty(Utils.SENTRY_DSN, "http://public:private@localhost:9000/1"); - verifyClient(new Client(), Transport.Http.class, false); - // Async HTTP - System.setProperty(Utils.SENTRY_DSN, "async+http://public:private@localhost:9000/1"); - verifyClient(new Client(), Transport.Http.class, true); - // Plain HTTPS - System.setProperty(Utils.SENTRY_DSN, "https://public:private@localhost:9000/1"); - verifyClient(new Client(), Transport.Http.class, false); - // Async HTTPS - System.setProperty(Utils.SENTRY_DSN, "async+https://public:private@localhost:9000/1"); - verifyClient(new Client(), Transport.Http.class, true); - // Naive HTTPS - System.setProperty(Utils.SENTRY_DSN, "naive+https://public:private@localhost:9000/1"); - verifyClient(new Client(), Transport.NaiveHttps.class, false); - // Async Naive HTTPS - System.setProperty(Utils.SENTRY_DSN, "async+naive+https://public:private@localhost:9000/1"); - verifyClient(new Client(), Transport.NaiveHttps.class, true); - // Plain UDP - System.setProperty(Utils.SENTRY_DSN, "udp://public:private@localhost:9000/1"); - verifyClient(new Client(), Transport.Udp.class, false); - // Async UDP - System.setProperty(Utils.SENTRY_DSN, "async+udp://public:private@localhost:9000/1"); - verifyClient(new Client(), Transport.Udp.class, true); - } - - @Test - public void constructor_withOptionToAutoStart() { - // Plain HTTP - System.setProperty(Utils.SENTRY_DSN, "http://public:private@localhost:9000/1"); - verifyClient(new Client(true), Transport.Http.class, false, true); - verifyClient(new Client(false), Transport.Http.class, false, false); - } - - @Test - public void constructor_specifyDsn() { - final String url = "://public:private@localhost:9000/1"; - // Ignore this system property - System.setProperty(Utils.SENTRY_DSN, "udp" + url); - SentryDsn dsn = SentryDsn.build("http" + url, null, null); - // Make sure the supplied dsn was used - verifyClient(new Client(dsn), Transport.Http.class, false, true); - verifyClient(new Client(dsn, true), Transport.Http.class, false, true); - verifyClient(new Client(dsn, false), Transport.Http.class, false, false); - } - - @Test - public void constructor_noDsn() { - Client client = new Client(); - assertTrue(client.isDisabled()); - client.start(); - assertFalse(client.isStarted()); - assertEquals("-1", client.captureMessage("Hi")); - } - - @Test - public void messageCompression() throws ParseException { - Client.register("http", DummyTransport.class); - Client client = new Client(SentryDsn.build("http://public:private@localhost:9000/1")); - client.captureMessage("hello"); - byte[] bytes = Base64.decodeBase64(DummyTransport.messageBody.getBytes()); - // This should not throw an exception - new JSONParser().parse(fromUtf8(decompress(bytes))); - - client.setMessageCompressionEnabled(false); - client.captureMessage("hello"); - bytes = Base64.decodeBase64(DummyTransport.messageBody.getBytes()); - // Neither should this - new JSONParser().parse(fromUtf8(bytes)); - } - - @Test - public void newTransport() { - // HTTP - String dsn = "http://public:private@localhost/1"; - Transport transport = Client.newTransport(SentryDsn.build(dsn)); - assertNotNull(transport); - assertTrue(transport instanceof Transport.Http); - - // HTTP with custom transport - Client.register("http", DummyTransport.class); - transport = Client.newTransport(SentryDsn.build(dsn)); - assertNotNull(transport); - assertTrue(transport instanceof DummyTransport); - - // HTTPS - dsn = "https://public:private@localhost/1"; - transport = Client.newTransport(SentryDsn.build(dsn)); - assertNotNull(transport); - assertTrue(transport instanceof Transport.Http); - - // HTTPS with custom transport - Client.register("https", DummyTransport.class); - transport = Client.newTransport(SentryDsn.build(dsn)); - assertNotNull(transport); - assertTrue(transport instanceof DummyTransport); - - // Naive HTTPS - dsn = "naive+https://public:private@localhost/1"; - transport = Client.newTransport(SentryDsn.build(dsn)); - assertNotNull(transport); - assertTrue(transport instanceof Transport.NaiveHttps); - - // Naive HTTPS with custom transport - Client.register("naive+https", DummyTransport.class); - transport = Client.newTransport(SentryDsn.build(dsn)); - assertNotNull(transport); - assertTrue(transport instanceof DummyTransport); - - // UDP - dsn = "udp://public:private@localhost:9000/1"; - transport = Client.newTransport(SentryDsn.build(dsn)); - assertNotNull(transport); - assertTrue(transport instanceof Transport.Udp); - - // UDP with custom transport - Client.register("udp", DummyTransport.class); - transport = Client.newTransport(SentryDsn.build(dsn)); - assertNotNull(transport); - assertTrue(transport instanceof DummyTransport); - - // Custom - dsn = "custom://public:private@localhost:9000/1"; - try { - Client.newTransport(SentryDsn.build(dsn)); - fail("Expected an exception"); - } catch (Client.InvalidConfig e) { - // Ok - } - - Client.register("custom", DummyTransport.class); - transport = Client.newTransport(SentryDsn.build(dsn)); - assertNotNull(transport); - assertTrue(transport instanceof DummyTransport); - - // Async - Client.register("udp", Transport.Udp.class); - dsn = "async+udp://public:private@localhost:9000/1"; - transport = Client.newTransport(SentryDsn.build(dsn)); - assertNotNull(transport); - assertTrue(transport instanceof AsyncTransport); - assertTrue(((AsyncTransport) transport).transport instanceof Transport.Udp); - - // Async with custom transport - Client.register("async", DummyTransport.class); - try { - Client.newTransport(SentryDsn.build(dsn)); - fail("Expected an exception because " + DummyTransport.class + " does not have the right signature"); - } catch (Client.InvalidConfig e) { - // Ok - } - - // Async with valid custom transport - Client.register("async", DummyAsyncTransport.class); - transport = Client.newTransport(SentryDsn.build(dsn)); - assertNotNull(transport); - assertTrue(transport instanceof DummyAsyncTransport); - assertTrue(((DummyAsyncTransport) transport).transport instanceof Transport.Udp); - } - - @After - public void tearDown() { - System.setProperty(Utils.SENTRY_DSN, ""); - Client.registerDefaults(); - } - - protected void verifyClient(Client client, Class transportClass, boolean async) { - verifyClient(client, transportClass, async, true); - } - - protected void verifyClient(Client client, Class transportClass, boolean async, boolean autostart) { - assertTrue(!autostart || client.isStarted()); - assertFalse(client.isDisabled()); - if (async) { - assertTrue(client.transport instanceof AsyncTransport); - assertTrue(((AsyncTransport) client.transport).transport.getClass().isAssignableFrom(transportClass)); - } else { - if (!client.isStarted()) { - client.start(); - } - assertTrue(client.transport.getClass().isAssignableFrom(transportClass)); - } - } - - protected static class DummyTransport extends Transport { - - public static String messageBody; - public static long timestamp; - - public DummyTransport(SentryDsn dsn) { - super(dsn); - } - - @Override - public void send(String messageBody, long timestamp) throws IOException { - DummyTransport.messageBody = messageBody; - DummyTransport.timestamp = timestamp; - } - } - - protected static class DummyAsyncTransport extends Transport { - - public final Transport transport; - - public DummyAsyncTransport(Transport transport) { - super(transport.dsn); - this.transport = transport; - } - - public static Transport build(Transport transport) { - return new DummyAsyncTransport(transport); - } - - } - -} diff --git a/raven/src/test/java/net/kencochrane/raven/HttpTransportTest.java b/raven/src/test/java/net/kencochrane/raven/HttpTransportTest.java deleted file mode 100644 index 8243c8d28e1..00000000000 --- a/raven/src/test/java/net/kencochrane/raven/HttpTransportTest.java +++ /dev/null @@ -1,167 +0,0 @@ -package net.kencochrane.raven; - -import mockit.Expectations; -import mockit.Mocked; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLPeerUnverifiedException; -import java.io.*; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.cert.Certificate; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * Test cases for {@link Transport.Http} - */ -public class HttpTransportTest { - - private CollectingHttpUrlConnection connection; - private String messageBody = "MessageBodyDoesNotReallyMatter"; - private long timestamp = System.currentTimeMillis(); - - @Test - public void verifyRequest() throws IOException { - SentryDsn dsn = SentryDsn.build("http://public:private@host:9999/1"); - Transport.Http transport = new Transport.Http(dsn); - transport.send(messageBody, timestamp); - - // Verify - assertEquals(Transport.Http.Option.TIMEOUT_DEFAULT, connection.connectTimeout); - byte[] data = connection.output.toByteArray(); - assertEquals(messageBody, Utils.fromUtf8(data)); - assertEquals(Transport.buildAuthHeader(timestamp, "public"), connection.authHeader); - } - - @Test - public void timeoutOption() throws Exception { - int timeout = 6000; - String url = String.format("http://public:private@host:9999/1?%s=%d", Transport.Http.Option.TIMEOUT, timeout); - Transport.Http transport = new Transport.Http(SentryDsn.build(url)); - transport.send(messageBody, timestamp); - - // Verify - assertEquals(timeout, transport.timeout); - assertEquals(timeout, connection.connectTimeout); - } - - @Test - public void withSignature_andTimeout() throws Exception { - int timeout = 6000; - String url = String.format("http://public:private@host:9999/1?%s=%d&%s=true", Transport.Http.Option.TIMEOUT, timeout, Transport.Option.INCLUDE_SIGNATURE); - SentryDsn dsn = SentryDsn.build(url); - Transport.Http transport = new Transport.Http(dsn); - transport.send(messageBody, timestamp); - - // Verify - assertTrue(dsn.getOptionAsBoolean(Transport.Option.INCLUDE_SIGNATURE, false)); - assertEquals(timeout, transport.timeout); - assertEquals(timeout, connection.connectTimeout); - String signature = Client.sign(messageBody, timestamp, "private"); - assertEquals(Transport.buildAuthHeader(signature, timestamp, "public"), connection.authHeader); - } - - protected static CollectingHttpUrlConnection mockConnection() throws IOException { - final CollectingHttpUrlConnection connection = new CollectingHttpUrlConnection(); - new Expectations() { - @Mocked("getConnection") - Transport.Http m; - - { - m.getConnection(); - returns(connection); - } - }; - return connection; - } - - @Before - public void setUp() throws IOException { - connection = mockConnection(); - } - - public static class CollectingHttpUrlConnection extends HttpsURLConnection { - - public static final URL LOCALHOST; - - public int connectTimeout; - public String authHeader; - public ByteArrayOutputStream output = new ByteArrayOutputStream(); - public HostnameVerifier hostnameVerifier; - - static { - try { - LOCALHOST = new URL("http://localhost"); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - - protected CollectingHttpUrlConnection() { - super(LOCALHOST); - } - - @Override - public void setConnectTimeout(int i) { - this.connectTimeout = i; - } - - @Override - public void setRequestProperty(String k, String v) { - Assert.assertEquals(k, "X-Sentry-Auth"); - this.authHeader = v; - } - - @Override - public OutputStream getOutputStream() throws IOException { - return output; - } - - @Override - public InputStream getInputStream() throws IOException { - return new ByteArrayInputStream(new byte[0]); - } - - @Override - public void connect() throws IOException { - // Does nothing - } - - @Override - public void disconnect() { - // Does nothing - } - - @Override - public boolean usingProxy() { - return false; - } - - @Override - public void setHostnameVerifier(HostnameVerifier hostnameVerifier) { - this.hostnameVerifier = hostnameVerifier; - } - - @Override - public String getCipherSuite() { - return null; - } - - @Override - public Certificate[] getLocalCertificates() { - return new Certificate[0]; - } - - @Override - public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException { - return new Certificate[0]; - } - } - -} diff --git a/raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java b/raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java deleted file mode 100644 index 8f65210447a..00000000000 --- a/raven/src/test/java/net/kencochrane/raven/JSONProcessorTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package net.kencochrane.raven; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; - -import net.kencochrane.raven.Events.LogLevel; -import net.kencochrane.raven.spi.JSONProcessor; - -import org.json.simple.JSONObject; -import org.junit.After; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -/** - * This test ensures that {@link Client} would execute the - * {@link net.kencochrane.raven.spi.JSONProcessor} passed in. - * - * @author vvasabi - */ -public class JSONProcessorTest extends Client { - - public JSONProcessorTest() { - super(SentryDsn.build("http://public:private@localhost:9000/1")); - } - - @After - public void tearDown() { - setJSONProcessors(Collections.emptyList()); - } - - @Test - public void testWithProcessor() { - List processors = new ArrayList(); - JSONProcessor mockProcessor = new MockJSONProcessor(); - processors.add(mockProcessor); - setJSONProcessors(processors); - - mockProcessor.prepareDiagnosticContext(); - Message message = buildMessage("test", - formatTimestamp(new Date().getTime()), "test", - LogLevel.ERROR.intValue, "test", null, null); - assertEquals("Value", message.json.get("Test")); - } - - private static class MockJSONProcessor implements JSONProcessor { - - private String testValue; - - @Override - public void prepareDiagnosticContext() { - testValue = "Value"; - } - - @Override - public void clearDiagnosticContext() { - testValue = null; - } - - @Override - @SuppressWarnings("unchecked") - public void process(JSONObject json, Throwable exception) { - json.put("Test", testValue); - } - - } - -} diff --git a/raven/src/test/java/net/kencochrane/raven/SentryDsnTest.java b/raven/src/test/java/net/kencochrane/raven/SentryDsnTest.java deleted file mode 100644 index 131ecabbe61..00000000000 --- a/raven/src/test/java/net/kencochrane/raven/SentryDsnTest.java +++ /dev/null @@ -1,150 +0,0 @@ -package net.kencochrane.raven; - -import mockit.Mock; -import mockit.MockClass; -import mockit.Mockit; -import org.junit.After; -import org.junit.Test; - -import static net.kencochrane.raven.SentryDsn.DefaultLookUps; -import static net.kencochrane.raven.SentryDsn.LookUp; -import static org.junit.Assert.*; - -/** - * Test cases for {@link SentryDsn}. - */ -public class SentryDsnTest { - - @Test - public void simple() { - final String scheme = "http"; - final String publicKey = "7e4dff58960645adb2ade337e6d53425"; - final String secretKey = "81fe140206d7464e911b89cd93e2a5a4"; - final String host = "localhost"; - final int port = 9000; - final String projectId = "2"; - String fullDsn = String.format("%s://%s:%s@%s:%d/%s", scheme, publicKey, secretKey, host, port, projectId); - SentryDsn dsn = SentryDsn.build(fullDsn); - // toString should map to the full DSN - assertEquals(fullDsn, dsn.toString()); - assertEquals(scheme, dsn.scheme); - assertEquals(publicKey, dsn.publicKey); - assertEquals(secretKey, dsn.secretKey); - assertEquals(host, dsn.host); - assertEquals(port, dsn.port); - assertEquals(projectId, dsn.projectId); - } - - @Test - public void schemeVariant() { - final String variant = "naive"; - final String scheme = "https"; - final String publicKey = "7e4dff58960645adb2ade337e6d53425"; - final String secretKey = "81fe140206d7464e911b89cd93e2a5a4"; - final String host = "localhost"; - final int port = 9000; - final String projectId = "2"; - String fullDsn = String.format("%s+%s://%s:%s@%s:%d/%s", variant, scheme, publicKey, secretKey, host, port, projectId); - SentryDsn dsn = SentryDsn.build(fullDsn); - // toString should map to the full DSN - assertEquals(fullDsn, dsn.toString()); - // But toString(false) should report the clean URL - String cleanDsn = String.format("%s://%s:%d", scheme, host, port); - assertEquals(cleanDsn, dsn.toString(false)); - assertArrayEquals(new String[]{variant}, dsn.variants); - assertEquals(scheme, dsn.scheme); - assertEquals(publicKey, dsn.publicKey); - assertEquals(secretKey, dsn.secretKey); - assertEquals(host, dsn.host); - assertEquals(port, dsn.port); - assertEquals(projectId, dsn.projectId); - } - - @Test(expected = SentryDsn.InvalidDsnException.class) - public void emptyScheme() { - SentryDsn.build("://public:secret@host/path/1"); - } - - @Test - public void emptyScheme_optional() { - assertNull(SentryDsn.buildOptional("://public:secret@host/path/1")); - } - - @Test(expected = SentryDsn.InvalidDsnException.class) - public void noScheme() { - SentryDsn.build("public:secret@host/path/1"); - } - - @Test - public void noScheme_optional() { - assertNull(SentryDsn.buildOptional("public:secret@host/path/1")); - } - - @Test - public void noMalformedUrlErrorWithUdp() { - SentryDsn dsn = SentryDsn.build("udp://public@host/path/goes/on/1"); - assertEquals("udp://public@host/path/goes/on/1", dsn.toString()); - assertEquals("udp://host/path/goes/on", dsn.toString(false)); - assertEquals(0, dsn.variants.length); - assertEquals("udp", dsn.scheme); - assertEquals("public", dsn.publicKey); - org.junit.Assert.assertNull(dsn.secretKey); - assertEquals("host", dsn.host); - assertEquals("/path/goes/on", dsn.path); - assertEquals("1", dsn.projectId); - } - - @Test - public void withOptions() { - SentryDsn dsn = SentryDsn.build("async+http://public@host/path/goes/on/1?raven.go&raven.wait=true"); - assertEquals("http://host/path/goes/on", dsn.toString(false)); - assertTrue(dsn.isVariantIncluded("async")); - assertEquals("http", dsn.scheme); - assertEquals("host", dsn.host); - assertEquals("/path/goes/on", dsn.path); - assertEquals("1", dsn.projectId); - assertEquals(2, dsn.options.size()); - assertTrue(dsn.options.containsKey("raven.go")); - org.junit.Assert.assertNull(dsn.options.get("raven.go")); - assertEquals("true", dsn.options.get("raven.wait")); - } - - @Test - public void applyOverrides() { - final String envDsn = "http://a:b@host/path/1"; - final String systemPropertyDsn = "naive+https://k:l@domain/woah/there/15"; - final String suppliedDsn = "https://x:y@localhost/2"; - Mockit.setUpMock(MockSystem.class); - MockSystem.dsn = envDsn; - System.setProperty(Utils.SENTRY_DSN, systemPropertyDsn); - SentryDsn dsn = SentryDsn.build(); - assertEquals(dsn.toString(true), envDsn); - dsn = SentryDsn.build(suppliedDsn); - assertEquals(dsn.toString(true), envDsn); - dsn = SentryDsn.build(suppliedDsn, new LookUp[]{DefaultLookUps.SYSTEM_PROPERTY, DefaultLookUps.ENV}, null); - assertEquals(dsn.toString(true), systemPropertyDsn); - dsn = SentryDsn.build(suppliedDsn, null, null); - assertEquals(dsn.toString(true), suppliedDsn); - } - - @After - public void tearDown() { - System.setProperty(Utils.SENTRY_DSN, ""); - } - - @MockClass(realClass = System.class) - public static class MockSystem { - - public static String dsn; - - @Mock - public static String getenv(String s) { - if (Utils.SENTRY_DSN.equals(s)) { - return dsn; - } - return null; - } - } - - -} diff --git a/raven/src/test/java/net/kencochrane/raven/UdpTransportTest.java b/raven/src/test/java/net/kencochrane/raven/UdpTransportTest.java deleted file mode 100644 index 1f82930f57f..00000000000 --- a/raven/src/test/java/net/kencochrane/raven/UdpTransportTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package net.kencochrane.raven; - -import mockit.Expectations; -import mockit.Mocked; -import org.junit.Before; -import org.junit.Test; - -import java.io.IOException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.SocketAddress; -import java.net.SocketException; -import java.util.LinkedList; -import java.util.List; - -import static org.junit.Assert.assertEquals; - -/** - * Test cases for {@link Transport.Udp}. - */ -public class UdpTransportTest { - - private CollectingSocket socket; - private String messageBody = "MessageBodyDoesNotReallyMatter"; - private long timestamp = System.currentTimeMillis(); - private String authHeader; - - @Test - public void verifyPacketStructure() throws IOException { - authHeader = Transport.buildAuthHeader(timestamp, "public"); - - // Actual testing - SentryDsn dsn = SentryDsn.build("udp://public:private@host:9999/1"); - Transport.Udp transport = new Transport.Udp(dsn); - transport.send(messageBody, timestamp); - - // Verify - assertEquals(1, socket.packets.size()); - byte[] data = socket.packets.get(0).getData(); - assertEquals(authHeader + "\n\n" + messageBody, Utils.fromUtf8(data)); - } - - @Test - public void verifyPacketStructure_withAuthHeader() throws IOException { - String signature = Client.sign(messageBody, timestamp, "private"); - authHeader = Transport.buildAuthHeader(signature, timestamp, "public"); - - // Actual testing - SentryDsn dsn = SentryDsn.build("udp://public:private@host:9999/1?" + Transport.Option.INCLUDE_SIGNATURE + "=true"); - Transport.Udp transport = new Transport.Udp(dsn); - transport.send(messageBody, timestamp); - - // Verify - assertEquals(1, socket.packets.size()); - byte[] data = socket.packets.get(0).getData(); - assertEquals(authHeader + "\n\n" + messageBody, Utils.fromUtf8(data)); - } - - @Before - public void setUp() throws SocketException { - socket = new CollectingSocket(); - new Expectations() { - @Mocked("createSocket") - Transport.Udp m; - - { - m.createSocket("host", 9999); - returns(socket); - } - }; - } - - public static class CollectingSocket extends DatagramSocket { - - public final List packets = new LinkedList(); - - public CollectingSocket() throws SocketException { - super(); - } - - @Override - public void connect(SocketAddress socketAddress) throws SocketException { - // Do nothing - } - - @Override - public void send(DatagramPacket packet) throws IOException { - packets.add(packet); - } - } - -} diff --git a/raven/src/test/java/net/kencochrane/raven/UtilsTest.java b/raven/src/test/java/net/kencochrane/raven/UtilsTest.java deleted file mode 100644 index 8df8641db2a..00000000000 --- a/raven/src/test/java/net/kencochrane/raven/UtilsTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.kencochrane.raven; - -import org.junit.Assert; -import org.junit.Test; - -/** - * Test cases for methods from {@link Utils}. - */ -public class UtilsTest { - - @Test - public void compress_decompress() { - final String s = "this is a string - no really!"; - Assert.assertEquals(s, Utils.fromUtf8(Utils.decompress(Utils.compress(Utils.toUtf8(s))))); - } - -} diff --git a/raven/src/test/java/net/kencochrane/raven/ext/ServletJSONProcessorTest.java b/raven/src/test/java/net/kencochrane/raven/ext/ServletJSONProcessorTest.java deleted file mode 100644 index f909550cd66..00000000000 --- a/raven/src/test/java/net/kencochrane/raven/ext/ServletJSONProcessorTest.java +++ /dev/null @@ -1,152 +0,0 @@ -package net.kencochrane.raven.ext; - -import static org.junit.Assert.*; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.servlet.ServletContext; -import javax.servlet.ServletRequestEvent; -import javax.servlet.ServletRequestListener; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; - -import org.json.simple.JSONObject; -import org.junit.Test; - -import mockit.Mocked; -import mockit.NonStrictExpectations; -import net.kencochrane.raven.Client; -import net.kencochrane.raven.SentryDsn; -import net.kencochrane.raven.Events.LogLevel; -import net.kencochrane.raven.spi.JSONProcessor; -import net.kencochrane.raven.spi.RavenMDC; - -/** - * Check to see if ServletJSONProcessor correctly produces the desired http - * JSON object. - * - * @author vvasabi - */ -public class ServletJSONProcessorTest extends Client { - - @Mocked - ServletContext mockServletContext; - - @Mocked - HttpServletRequest mockHttpServletRequest; - - public ServletJSONProcessorTest() { - super(SentryDsn.build("http://public:private@localhost:9000/1")); - } - - @Test - @SuppressWarnings("unchecked") - public void testConstructHttpJsonResponse() { - List processors = new ArrayList(); - JSONProcessor processor = new ServletJSONProcessor(); - processors.add(processor); - setJSONProcessors(processors); - - setHttpServletRequest(); - RavenMDC mdc = new MockRavenMDC(); - - processor.prepareDiagnosticContext(); - assertNotNull(mdc.get("sentry.interfaces.Http")); - Message message = buildMessage("test", - formatTimestamp(new Date().getTime()), "test", - LogLevel.ERROR.intValue, "test", null, null); - JSONObject http = (JSONObject) message.json.get("sentry.interfaces.Http"); - assertEquals("GET", http.get("method")); - assertEquals("test=abc", http.get("query_string")); - assertEquals("http://example.com/test?test=abc", http.get("url")); - - JSONObject headers = new JSONObject(); - headers.put("Test-Header", "test value"); - assertEquals(headers, http.get("headers")); - - JSONObject env = new JSONObject(); - env.put("SERVER_PORT", 80); - env.put("REMOTE_ADDR", "127.0.0.1"); - env.put("SERVER_PROTOCOL", "http"); - env.put("SERVER_NAME", "localhost"); - assertEquals(env, http.get("env")); - - JSONObject cookies = new JSONObject(); - cookies.put("test", "cookie"); - assertEquals(cookies, http.get("cookies")); - - processor.clearDiagnosticContext(); - assertNull(mdc.get("sentry.interfaces.Http")); - } - - private void setHttpServletRequest() { - new NonStrictExpectations() { - { - mockHttpServletRequest.getRequestURL(); - returns(new StringBuffer("http://example.com/test")); - - mockHttpServletRequest.getQueryString(); - returns("test=abc"); - - mockHttpServletRequest.getMethod(); - returns("GET"); - - mockHttpServletRequest.getHeaderNames(); - List headerNames = new ArrayList(); - headerNames.add("test-header"); - returns(Collections.enumeration(headerNames)); - - mockHttpServletRequest.getHeader("test-header"); - returns("test value"); - - mockHttpServletRequest.getRemoteAddr(); - returns("127.0.0.1"); - - mockHttpServletRequest.getServerName(); - returns("localhost"); - - mockHttpServletRequest.getServerPort(); - returns(80); - - mockHttpServletRequest.getProtocol(); - returns("http"); - - mockHttpServletRequest.getCookies(); - returns(new Cookie[] { new Cookie("test", "cookie") }); - } - }; - ServletRequestListener listener = new RavenServletRequestListener(); - listener.requestInitialized(new ServletRequestEvent(mockServletContext, mockHttpServletRequest)); - } - - protected static class MockRavenMDC extends RavenMDC { - - private Map values = new HashMap(); - - public MockRavenMDC() { - RavenMDC.setInstance(this); - } - - @Override - public Object get(String key) { - return values.get(key); - } - - @Override - public void put(String key, Object value) { - values.put(key, value); - } - - @Override - public void remove(String key) { - values.remove(key); - } - - } - -} diff --git a/raven/src/test/resources/java.util.logging.properties b/raven/src/test/resources/java.util.logging.properties deleted file mode 100644 index 291de9e7ebb..00000000000 --- a/raven/src/test/resources/java.util.logging.properties +++ /dev/null @@ -1,4 +0,0 @@ -handlers=java.util.logging.ConsoleHandler -.level=FINEST -java.util.logging.ConsoleHandler.level=FINEST -java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter \ No newline at end of file From 019133a2e2825324f5f4e96499e8611901e85f04 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 18:45:47 +0100 Subject: [PATCH 0172/2152] Simplify maven configuration Use maven properties rather than a specific plugin version and additionnal settings in the parent pom --- pom.xml | 64 ++++++++------------------------------------------------- 1 file changed, 9 insertions(+), 55 deletions(-) diff --git a/pom.xml b/pom.xml index d5ed8b36782..946e55db280 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,15 @@ Java Raven client and loggers. http://sentry.readthedocs.org/en/latest/client/index.html + + UTF-8 + UTF-8 + + + 6 + 6 + + BSD New @@ -61,60 +70,5 @@ raven-logback - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - UTF-8 - - - - org.apache.maven.plugins - maven-resources-plugin - 2.5 - - UTF-8 - - - - - - - - ${basedir}/src/main/java - - **/*.json - **/*.yml - - - - ${basedir}/src/main/resources - - **/*.* - - - - - - ${basedir}/src/test/java - - **/*.json - **/*.yml - **/*.txt - - - - ${basedir}/src/test/resources - - **/*.* - - - - From 400f642d845e2a9842fef6fdb87b8fb0ace703a5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 18:47:44 +0100 Subject: [PATCH 0173/2152] Remove redundant maven configuration --- raven-log4j/pom.xml | 26 +------------------------- raven-logback/pom.xml | 24 ------------------------ raven/pom.xml | 24 ------------------------ 3 files changed, 1 insertion(+), 73 deletions(-) diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 33cf651eb94..55425b391e4 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -38,28 +38,4 @@ test - - - - - maven-assembly-plugin - 2.3 - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - - - - \ No newline at end of file + diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 932f1a7a2b2..d6f4172a5f4 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -45,28 +45,4 @@ - - - - - maven-assembly-plugin - 2.3 - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - - - diff --git a/raven/pom.xml b/raven/pom.xml index 7f7efe2dba0..6cb31088806 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -50,28 +50,4 @@ test - - - - - maven-assembly-plugin - 2.3 - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - - - From 22fd8394e66f168599d1a4fd36da2bbedf47fb47 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 18:48:19 +0100 Subject: [PATCH 0174/2152] Use managed dependencies when possible --- pom.xml | 52 +++++++++++++++++++++++++++++++++++++++++++ raven-log4j/pom.xml | 8 ++----- raven-logback/pom.xml | 11 --------- raven/pom.xml | 22 +++--------------- 4 files changed, 57 insertions(+), 36 deletions(-) diff --git a/pom.xml b/pom.xml index 946e55db280..53d04413493 100644 --- a/pom.xml +++ b/pom.xml @@ -25,6 +25,14 @@ 6 6 + + + 4.11 + 1.9.5 + 1.0.11 + 1.2.17 + 1.6 + 1.1.1 @@ -70,5 +78,49 @@ raven-logback + + + + ${project.groupId} + raven + ${project.version} + + + commons-codec + commons-codec + ${commons-codec.version} + + + com.googlecode.json-simple + json-simple + ${json-simple.version} + + + log4j + log4j + ${log4j.version} + + + ch.qos.logback + logback-core + ${logback.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + org.mockito + mockito-core + ${mockito.version} + + + junit + junit + ${junit.version} + + + diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 55425b391e4..14aa0cbabe5 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -18,23 +18,19 @@ ${project.groupId} raven - ${project.version} log4j log4j - 1.2.16 - com.googlecode.jmockit - jmockit - 0.999.12 + org.mockito + mockito-core test junit junit - 4.8.1 test diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index d6f4172a5f4..dc166a92deb 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -18,31 +18,20 @@ ${project.groupId} raven - ${project.version} ch.qos.logback logback-core - 1.0.7 ch.qos.logback logback-classic - 1.0.7 junit junit - 4.8.1 test - - - org.slf4j - slf4j-api - 1.7.2 - - diff --git a/raven/pom.xml b/raven/pom.xml index 6cb31088806..06ff83bba83 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -15,38 +15,22 @@ Java Raven client. - - commons-lang - commons-lang - 2.5 - commons-codec commons-codec - 1.6 com.googlecode.json-simple json-simple - 1.1 - - javax.servlet - javax.servlet-api - 3.0.1 - provided - - - com.googlecode.jmockit - jmockit - 0.999.12 - test + org.mockito + mockito-core + test junit junit - 4.8.1 test From f3d68a0ee90444a1d4f069f2ee2e5ed262c79cf8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 18:48:51 +0100 Subject: [PATCH 0175/2152] Use the same pattern as other pom files --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 53d04413493..f9f44d69635 100644 --- a/pom.xml +++ b/pom.xml @@ -2,14 +2,13 @@ + 4.0.0 org.sonatype.oss oss-parent 7 - - 4.0.0 net.kencochrane raven-all 2.0-SNAPSHOT From f4b13b616291151bed96ed6bb1c7fb28b2a063b8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 18:51:49 +0100 Subject: [PATCH 0176/2152] Move marshaller and Dsn out of connection --- .../java/net/kencochrane/raven/{connection => }/Dsn.java | 2 +- .../kencochrane/raven/connection/AbstractConnection.java | 2 +- .../net/kencochrane/raven/connection/HttpConnection.java | 6 +++--- .../net/kencochrane/raven/connection/UdpConnection.java | 5 +++-- .../raven/{connection => }/marshaller/Marshaller.java | 2 +- .../marshaller/SimpleJsonExceptionInterfaceMarshaller.java | 2 +- .../marshaller/SimpleJsonInterfaceMarshaller.java | 2 +- .../{connection => }/marshaller/SimpleJsonMarshaller.java | 2 +- .../marshaller/SimpleJsonMessageInterfaceMarshaller.java | 2 +- .../marshaller/SimpleJsonStackTraceInterfaceMarshaller.java | 2 +- 10 files changed, 14 insertions(+), 13 deletions(-) rename raven/src/main/java/net/kencochrane/raven/{connection => }/Dsn.java (99%) rename raven/src/main/java/net/kencochrane/raven/{connection => }/marshaller/Marshaller.java (92%) rename raven/src/main/java/net/kencochrane/raven/{connection => }/marshaller/SimpleJsonExceptionInterfaceMarshaller.java (95%) rename raven/src/main/java/net/kencochrane/raven/{connection => }/marshaller/SimpleJsonInterfaceMarshaller.java (80%) rename raven/src/main/java/net/kencochrane/raven/{connection => }/marshaller/SimpleJsonMarshaller.java (99%) rename raven/src/main/java/net/kencochrane/raven/{connection => }/marshaller/SimpleJsonMessageInterfaceMarshaller.java (94%) rename raven/src/main/java/net/kencochrane/raven/{connection => }/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java (98%) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java b/raven/src/main/java/net/kencochrane/raven/Dsn.java similarity index 99% rename from raven/src/main/java/net/kencochrane/raven/connection/Dsn.java rename to raven/src/main/java/net/kencochrane/raven/Dsn.java index 163ec5f90ac..0ce9d6a13ff 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/Dsn.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven.connection; +package net.kencochrane.raven; import java.net.URI; import java.net.URISyntaxException; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 1059a64bb97..cea23d10bc2 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -1,6 +1,6 @@ package net.kencochrane.raven.connection; -import net.kencochrane.raven.Utils; +import net.kencochrane.raven.Dsn; /** * Abstract connection to a Sentry server. diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 7c8d85a25a2..7b79f63f0e5 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -1,8 +1,8 @@ package net.kencochrane.raven.connection; -import net.kencochrane.raven.Utils; -import net.kencochrane.raven.connection.marshaller.Marshaller; -import net.kencochrane.raven.connection.marshaller.SimpleJsonMarshaller; +import net.kencochrane.raven.Dsn; +import net.kencochrane.raven.marshaller.Marshaller; +import net.kencochrane.raven.marshaller.SimpleJsonMarshaller; import net.kencochrane.raven.event.LoggedEvent; import javax.net.ssl.HostnameVerifier; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index 2bb29485227..a6e28570ded 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -1,7 +1,8 @@ package net.kencochrane.raven.connection; -import net.kencochrane.raven.connection.marshaller.Marshaller; -import net.kencochrane.raven.connection.marshaller.SimpleJsonMarshaller; +import net.kencochrane.raven.Dsn; +import net.kencochrane.raven.marshaller.Marshaller; +import net.kencochrane.raven.marshaller.SimpleJsonMarshaller; import net.kencochrane.raven.event.LoggedEvent; import java.io.ByteArrayOutputStream; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/Marshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java similarity index 92% rename from raven/src/main/java/net/kencochrane/raven/connection/marshaller/Marshaller.java rename to raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java index 52dbdb381c6..0268902b4ac 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/Marshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven.connection.marshaller; +package net.kencochrane.raven.marshaller; import net.kencochrane.raven.event.LoggedEvent; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonExceptionInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonExceptionInterfaceMarshaller.java similarity index 95% rename from raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonExceptionInterfaceMarshaller.java rename to raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonExceptionInterfaceMarshaller.java index 74f33d8544d..221daa3ec06 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonExceptionInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonExceptionInterfaceMarshaller.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven.connection.marshaller; +package net.kencochrane.raven.marshaller; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.ImmutableThrowable; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonInterfaceMarshaller.java similarity index 80% rename from raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonInterfaceMarshaller.java rename to raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonInterfaceMarshaller.java index c9e1509d8e7..ea98bac7b02 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonInterfaceMarshaller.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven.connection.marshaller; +package net.kencochrane.raven.marshaller; import net.kencochrane.raven.event.interfaces.SentryInterface; import org.json.simple.JSONObject; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java similarity index 99% rename from raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java rename to raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java index 53874fb4f17..6fcea60dc7e 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven.connection.marshaller; +package net.kencochrane.raven.marshaller; import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.event.interfaces.ExceptionInterface; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMessageInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMessageInterfaceMarshaller.java similarity index 94% rename from raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMessageInterfaceMarshaller.java rename to raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMessageInterfaceMarshaller.java index 4c073964d5a..3e94f69067d 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonMessageInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMessageInterfaceMarshaller.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven.connection.marshaller; +package net.kencochrane.raven.marshaller; import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.SentryInterface; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java similarity index 98% rename from raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java rename to raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java index d36191382a9..8aa1a78175f 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven.connection.marshaller; +package net.kencochrane.raven.marshaller; import net.kencochrane.raven.event.interfaces.ImmutableThrowable; import net.kencochrane.raven.event.interfaces.SentryInterface; From e6f3e2b48728edb115e3a5bc1efbfaba63021708 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 18:52:48 +0100 Subject: [PATCH 0177/2152] Create the Raven class --- .../java/net/kencochrane/raven/Raven.java | 94 +++++++++++++++++++ .../raven/connection/AbstractConnection.java | 3 +- .../raven/connection/HttpConnection.java | 3 +- 3 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 raven/src/main/java/net/kencochrane/raven/Raven.java diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java new file mode 100644 index 00000000000..f268498d24c --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -0,0 +1,94 @@ +package net.kencochrane.raven; + +import net.kencochrane.raven.connection.AsyncConnection; +import net.kencochrane.raven.connection.Connection; +import net.kencochrane.raven.connection.HttpConnection; +import net.kencochrane.raven.connection.UdpConnection; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.marshaller.SimpleJsonMarshaller; + +import java.nio.charset.Charset; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Raven { + public static final String DEFAULT_CHARSET = "UTF-8"; + public static final String NAME = "Raven-Java/3.0"; + private static final Logger logger = Logger.getLogger(Raven.class.getCanonicalName()); + private Connection connection; + + public Raven() { + this(dsnLookup()); + } + + public Raven(String dsn) { + this(new Dsn(dsn)); + } + + public Raven(Dsn dsn) { + this(determineConnection(dsn)); + } + + public Raven(Connection connection) { + this.connection = connection; + } + + private static String dsnLookup() { + throw new IllegalStateException("Couldn't find a Sentry DSN in either the Java or System environment."); + } + + private static Charset determineCharset(Dsn dsn) { + String charset = DEFAULT_CHARSET; + + if (dsn.getOptions().containsKey(Dsn.CHARSET_OPTION)) + charset = dsn.getOptions().get(Dsn.CHARSET_OPTION); + + return Charset.forName(charset); + } + + //TODO: Replace with a factory? + private static Connection determineConnection(Dsn dsn) { + String protocol = dsn.getProtocol(); + Connection connection = null; + Charset charset = determineCharset(dsn); + SimpleJsonMarshaller marshaller = new SimpleJsonMarshaller(); + marshaller.setCompression(!dsn.getOptions().containsKey(Dsn.NOCOMPRESSION_OPTION)); + marshaller.setCharset(charset); + + if (protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https")) { + HttpConnection httpConnection = new HttpConnection(dsn); + httpConnection.setMarshaller(marshaller); + connection = httpConnection; + } else if (protocol.equalsIgnoreCase("udp")) { + UdpConnection udpConnection = new UdpConnection(dsn); + udpConnection.setCharset(charset); + udpConnection.setMarshaller(marshaller); + connection = udpConnection; + } else { + logger.log(Level.WARNING, + "Couldn't figure out automatically a connection to Sentry, one should be set manually"); + } + + if (dsn.getOptions().containsKey(Dsn.ASYNC_OPTION)) + connection = new AsyncConnection(connection); + + return connection; + } + + public void sendEvent(EventBuilder eventBuilder) { + sendEvent(eventBuilder.build()); + } + + public void sendEvent(LoggedEvent event) { + try { + connection.send(event); + } catch (Exception e) { + logger.log(Level.SEVERE, "An exception occurred while sending the event to Sentry.", e); + } + } + + public void setConnection(Connection connection) { + this.connection = connection; + } +} diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index cea23d10bc2..a48c6f7826c 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -1,6 +1,7 @@ package net.kencochrane.raven.connection; import net.kencochrane.raven.Dsn; +import net.kencochrane.raven.Raven; /** * Abstract connection to a Sentry server. @@ -35,7 +36,7 @@ protected String getAuthHeader() { //TODO: Consider adding back signature? Not a priority, probably not worth it. StringBuilder header = new StringBuilder(); header.append("Sentry sentry_version=").append(SENTRY_PROTOCOL_VERSION); - header.append(",sentry_client=").append(Utils.Client.NAME); + header.append(",sentry_client=").append(Raven.NAME); header.append(",sentry_key=").append(dsn.getPublicKey()); header.append(",sentry_secret=").append(dsn.getSecretKey()); return header.toString(); diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 7b79f63f0e5..69cfc592d68 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -1,6 +1,7 @@ package net.kencochrane.raven.connection; import net.kencochrane.raven.Dsn; +import net.kencochrane.raven.Raven; import net.kencochrane.raven.marshaller.Marshaller; import net.kencochrane.raven.marshaller.SimpleJsonMarshaller; import net.kencochrane.raven.event.LoggedEvent; @@ -69,7 +70,7 @@ private HttpURLConnection getConnection() { connection.setRequestMethod("POST"); connection.setDoOutput(true); connection.setConnectTimeout(timeout); - connection.setRequestProperty(USER_AGENT, Utils.Client.NAME); + connection.setRequestProperty(USER_AGENT, Raven.NAME); connection.setRequestProperty(SENTRY_AUTH, getAuthHeader()); return connection; } catch (IOException e) { From f1b3adc9af8988074821759402af27c320c20214 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 18:53:42 +0100 Subject: [PATCH 0178/2152] Create a JUL handler using Raven --- .../kencochrane/raven/jul/SentryHandler.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java new file mode 100644 index 00000000000..15ed23082d1 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -0,0 +1,78 @@ +package net.kencochrane.raven.jul; + +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.MessageInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +public class SentryHandler extends Handler { + private final Raven raven; + + public SentryHandler() { + this(new Raven()); + } + + public SentryHandler(Raven raven) { + this.raven = raven; + } + + private static LoggedEvent.Level getLevel(Level level) { + if (level.intValue() >= Level.SEVERE.intValue()) + return LoggedEvent.Level.ERROR; + else if (level.intValue() >= Level.WARNING.intValue()) + return LoggedEvent.Level.WARNING; + else if (level.intValue() >= Level.INFO.intValue()) + return LoggedEvent.Level.INFO; + else if (level.intValue() >= Level.ALL.intValue()) + return LoggedEvent.Level.DEBUG; + else return null; + } + + private static List formatParameters(Object[] parameters) { + List formattedParameters = new ArrayList(parameters.length); + for (Object parameter : parameters) + formattedParameters.add(parameter.toString()); + return formattedParameters; + } + + @Override + public void publish(LogRecord record) { + if (!isLoggable(record)) { + return; + } + + EventBuilder eventBuilder = new EventBuilder() + .setLevel(getLevel(record.getLevel())) + .setTimestamp(new Date(record.getMillis())) + .setLogger(record.getLoggerName()) + .setCulprit(record.getSourceClassName() + "." + record.getSourceMethodName() + "()"); + if (record.getThrown() != null) { + eventBuilder.addSentryInterface(new ExceptionInterface(record.getThrown())) + .addSentryInterface(new StackTraceInterface(record.getThrown())); + } + + if (record.getParameters() != null) + eventBuilder.addSentryInterface(new MessageInterface(record.getMessage(), formatParameters(record.getParameters()))); + else + eventBuilder.setMessage(record.getMessage()); + + raven.sendEvent(eventBuilder); + } + + @Override + public void flush() { + } + + @Override + public void close() throws SecurityException { + } +} From 3a1d4c4436f57486ed9a59e5b7b701fdb0161468 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 19:39:58 +0100 Subject: [PATCH 0179/2152] Create raven appenders for logback and log4j --- .../raven/log4j/SentryAppender.java | 65 ++++++++++++++++++ .../raven/logback/SentryAppender.java | 68 +++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java create mode 100644 raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java new file mode 100644 index 00000000000..99db0e55d1e --- /dev/null +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -0,0 +1,65 @@ +package net.kencochrane.raven.log4j; + +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import org.apache.log4j.AppenderSkeleton; +import org.apache.log4j.Level; +import org.apache.log4j.spi.LoggingEvent; + +import java.util.Date; + +public class SentryAppender extends AppenderSkeleton { + private final Raven raven; + + public SentryAppender() { + this(new Raven()); + } + + public SentryAppender(Raven raven) { + this.raven = raven; + } + + private static LoggedEvent.Level formatLevel(LoggingEvent loggingEvent) { + if (loggingEvent.getLevel().isGreaterOrEqual(Level.FATAL)) { + return LoggedEvent.Level.FATAL; + } else if (loggingEvent.getLevel().isGreaterOrEqual(Level.ERROR)) { + return LoggedEvent.Level.ERROR; + } else if (loggingEvent.getLevel().isGreaterOrEqual(Level.WARN)) { + return LoggedEvent.Level.WARNING; + } else if (loggingEvent.getLevel().isGreaterOrEqual(Level.INFO)) { + return LoggedEvent.Level.INFO; + } else if (loggingEvent.getLevel().isGreaterOrEqual(Level.ALL)) { + return LoggedEvent.Level.DEBUG; + } else return null; + } + + @Override + protected void append(LoggingEvent loggingEvent) { + EventBuilder eventBuilder = new EventBuilder() + .setTimestamp(new Date(loggingEvent.getTimeStamp())) + .setMessage(loggingEvent.getRenderedMessage()) + .setLogger(loggingEvent.getLoggerName()) + .setLevel(formatLevel(loggingEvent)) + .setCulprit(loggingEvent.getLoggerName()); + + if (loggingEvent.getThrowableInformation() != null) { + Throwable throwable = loggingEvent.getThrowableInformation().getThrowable(); + eventBuilder.addSentryInterface(new ExceptionInterface(throwable)) + .addSentryInterface(new StackTraceInterface(throwable)); + } + + raven.sendEvent(eventBuilder); + } + + @Override + public void close() { + } + + @Override + public boolean requiresLayout() { + return false; + } +} diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java new file mode 100644 index 00000000000..e84964af1c0 --- /dev/null +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -0,0 +1,68 @@ +package net.kencochrane.raven.logback; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.ThrowableProxy; +import ch.qos.logback.core.AppenderBase; +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.MessageInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class SentryAppender extends AppenderBase { + private final Raven raven; + + public SentryAppender() { + this(new Raven()); + } + + public SentryAppender(Raven raven) { + this.raven = raven; + } + + private static List formatArguments(Object[] argumentArray) { + List arguments = new ArrayList(argumentArray.length); + for (Object argument : argumentArray) { + arguments.add(argument.toString()); + } + return arguments; + } + + private static LoggedEvent.Level formatLevel(ILoggingEvent iLoggingEvent) { + if (iLoggingEvent.getLevel().isGreaterOrEqual(Level.ERROR)) { + return LoggedEvent.Level.ERROR; + } else if (iLoggingEvent.getLevel().isGreaterOrEqual(Level.WARN)) { + return LoggedEvent.Level.WARNING; + } else if (iLoggingEvent.getLevel().isGreaterOrEqual(Level.INFO)) { + return LoggedEvent.Level.INFO; + } else if (iLoggingEvent.getLevel().isGreaterOrEqual(Level.ALL)) { + return LoggedEvent.Level.DEBUG; + } else return null; + } + + @Override + protected void append(ILoggingEvent iLoggingEvent) { + EventBuilder eventBuilder = new EventBuilder() + .setTimestamp(new Date(iLoggingEvent.getTimeStamp())) + .setMessage(iLoggingEvent.getFormattedMessage()) + .setLogger(iLoggingEvent.getLoggerName()) + .setLevel(formatLevel(iLoggingEvent)) + .setCulprit(iLoggingEvent.getLoggerName()); + + if (iLoggingEvent.getThrowableProxy() != null) { + Throwable throwable = ((ThrowableProxy) iLoggingEvent.getThrowableProxy()).getThrowable(); + eventBuilder.addSentryInterface(new ExceptionInterface(throwable)) + .addSentryInterface(new StackTraceInterface(throwable)); + } else if (iLoggingEvent.getArgumentArray() != null) { + eventBuilder.addSentryInterface(new MessageInterface(iLoggingEvent.getMessage(), + formatArguments(iLoggingEvent.getArgumentArray()))); + } + raven.sendEvent(eventBuilder); + } +} From b15179fe711edb751a3d18e80acd4b57e8e1b832 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 20:24:56 +0100 Subject: [PATCH 0180/2152] Handle exceptions during DSN creation An InvalidDsnException must be thrown during the DSN creation if the DSN is considered as Invalid --- .../main/java/net/kencochrane/raven/Dsn.java | 21 +++++++++++++++++- .../raven/exception/InvalidDsnException.java | 22 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 raven/src/main/java/net/kencochrane/raven/exception/InvalidDsnException.java diff --git a/raven/src/main/java/net/kencochrane/raven/Dsn.java b/raven/src/main/java/net/kencochrane/raven/Dsn.java index 0ce9d6a13ff..0403ad1a599 100644 --- a/raven/src/main/java/net/kencochrane/raven/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/Dsn.java @@ -1,5 +1,7 @@ package net.kencochrane.raven; +import net.kencochrane.raven.exception.InvalidDsnException; + import java.net.URI; import java.net.URISyntaxException; import java.util.*; @@ -57,10 +59,12 @@ public Dsn(String dsn) { makeOptionsImmutable(); + validate(); + try { uri = new URI(protocol, null, host, port, path, null, null); } catch (URISyntaxException e) { - throw new IllegalArgumentException("Impossible to determine Sentry's URI from the DSN '"+dsn+"'"); + throw new InvalidDsnException("Impossible to determine Sentry's URI from the DSN '" + dsn + "'", e); } } @@ -105,6 +109,21 @@ private void makeOptionsImmutable() { protocolSettings = Collections.unmodifiableSet(protocolSettings); } + private void validate() { + List missingElements = new LinkedList(); + if (host == null) + missingElements.add("host"); + if (publicKey == null) + missingElements.add("public key"); + if (secretKey == null) + missingElements.add("secret key"); + if (projectId == null || projectId.isEmpty()) + missingElements.add("project ID"); + + if (!missingElements.isEmpty()) + throw new InvalidDsnException("Invalid DSN, the following properties aren't set '" + missingElements + "'"); + } + public String getSecretKey() { return secretKey; } diff --git a/raven/src/main/java/net/kencochrane/raven/exception/InvalidDsnException.java b/raven/src/main/java/net/kencochrane/raven/exception/InvalidDsnException.java new file mode 100644 index 00000000000..7f7ac52f289 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/exception/InvalidDsnException.java @@ -0,0 +1,22 @@ +package net.kencochrane.raven.exception; + +public class InvalidDsnException extends RuntimeException{ + public InvalidDsnException() { + } + + public InvalidDsnException(String message) { + super(message); + } + + public InvalidDsnException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidDsnException(Throwable cause) { + super(cause); + } + + public InvalidDsnException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} From d6a59fc59d2c026a3afb7e1b6e0b08fdb14d5b76 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 20:25:49 +0100 Subject: [PATCH 0181/2152] Add unit tests for DSN creation --- .../java/net/kencochrane/raven/DsnTest.java | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/DsnTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/DsnTest.java new file mode 100644 index 00000000000..143bb4d7263 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/DsnTest.java @@ -0,0 +1,77 @@ +package net.kencochrane.raven; + +import net.kencochrane.raven.exception.InvalidDsnException; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class DsnTest { + @Test(expected = InvalidDsnException.class) + public void testEmptyDsnInvalid() { + new Dsn(""); + } + + @Test + public void testSimpleDsnValid() { + Dsn dsn = new Dsn("http://publicKey:secretKey@host/9"); + + assertEquals("http", dsn.getProtocol()); + assertEquals("publicKey", dsn.getPublicKey()); + assertEquals("secretKey", dsn.getSecretKey()); + assertEquals("host", dsn.getHost()); + assertEquals("/", dsn.getPath()); + assertEquals("9", dsn.getProjectId()); + } + + @Test(expected = InvalidDsnException.class) + public void testMissingSecretKeyInvalid() { + new Dsn("http://publicKey:@host/9"); + } + + @Test(expected = InvalidDsnException.class) + public void testMissingHostInvalid() { + new Dsn("http://publicKey:secretKey@/9"); + } + + @Test(expected = InvalidDsnException.class) + public void testMissingPathInvalid() { + new Dsn("http://publicKey:secretKey@host"); + } + + @Test(expected = InvalidDsnException.class) + public void testMissingProjectIdInvalid() { + new Dsn("http://publicKey:secretKey@host/"); + } + + @Test + public void testAdvancedDsnValid() { + Dsn dsn = new Dsn("naive+udp://1234567890:0987654321@complete.host.name:1234" + + "/composed/path/1029384756?option1&option2=valueOption2"); + + assertEquals("udp", dsn.getProtocol()); + assertTrue(dsn.getProtocolSettings().contains("naive")); + assertEquals("1234567890", dsn.getPublicKey()); + assertEquals("0987654321", dsn.getSecretKey()); + assertEquals("complete.host.name", dsn.getHost()); + assertEquals(1234, dsn.getPort()); + assertEquals("/composed/path/", dsn.getPath()); + assertEquals("1029384756", dsn.getProjectId()); + assertTrue(dsn.getOptions().containsKey("option1")); + assertEquals("valueOption2", dsn.getOptions().get("option2")); + } + + @Test(expected = UnsupportedOperationException.class) + public void testOptionsImmutable() { + Dsn dsn = new Dsn("http://publicKey:secretKey@host/9"); + + dsn.getOptions().put("test", "test"); + } + + @Test(expected = UnsupportedOperationException.class) + public void testProtocolSettingsImmutable() { + Dsn dsn = new Dsn("http://publicKey:secretKey@host/9"); + + dsn.getProtocolSettings().add("test"); + } +} From d124eac1401886969d1ec9f2c329184a7aa40c16 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 20:26:23 +0100 Subject: [PATCH 0182/2152] Do not thrown an exception while building the DSN The exception should be thrown by the DSN validation that happens later in the DSN constructor --- .../src/main/java/net/kencochrane/raven/Dsn.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Dsn.java b/raven/src/main/java/net/kencochrane/raven/Dsn.java index 0403ad1a599..161d740e0cc 100644 --- a/raven/src/main/java/net/kencochrane/raven/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/Dsn.java @@ -70,6 +70,8 @@ public Dsn(String dsn) { private void extractPathInfo(URI uri) { String uriPath = uri.getPath(); + if (uriPath == null) + return; int projectIdStart = uriPath.lastIndexOf("/") + 1; path = uriPath.substring(0, projectIdStart); projectId = uriPath.substring(projectIdStart); @@ -81,15 +83,22 @@ private void extractHostInfo(URI uri) { } private void extractProtocolInfo(URI uri) { - String[] schemeDetails = uri.getScheme().split("\\+"); + String scheme = uri.getScheme(); + if (scheme == null) + return; + String[] schemeDetails = scheme.split("\\+"); protocolSettings.addAll(Arrays.asList(schemeDetails).subList(0, schemeDetails.length - 1)); protocol = schemeDetails[schemeDetails.length - 1]; } private void extractUserKeys(URI uri) { - String[] userDetails = uri.getUserInfo().split(":"); + String userInfo = uri.getUserInfo(); + if (userInfo == null) + return; + String[] userDetails = userInfo.split(":"); publicKey = userDetails[0]; - secretKey = userDetails[1]; + if (userDetails.length > 1) + secretKey = userDetails[1]; } private void extractOptions(URI uri) { From 3fc98c3c924ce69d7e17d8d7308930b10028a3cd Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 20:33:17 +0100 Subject: [PATCH 0183/2152] Remove Java7 specific method --- .../net/kencochrane/raven/exception/InvalidDsnException.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/exception/InvalidDsnException.java b/raven/src/main/java/net/kencochrane/raven/exception/InvalidDsnException.java index 7f7ac52f289..c9d66c2a645 100644 --- a/raven/src/main/java/net/kencochrane/raven/exception/InvalidDsnException.java +++ b/raven/src/main/java/net/kencochrane/raven/exception/InvalidDsnException.java @@ -15,8 +15,4 @@ public InvalidDsnException(String message, Throwable cause) { public InvalidDsnException(Throwable cause) { super(cause); } - - public InvalidDsnException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } } From 5571f01ea962190ef70085c63fa3b674ba9ba496 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 20:46:31 +0100 Subject: [PATCH 0184/2152] Add tests for Raven.java --- .../java/net/kencochrane/raven/RavenTest.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/RavenTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java new file mode 100644 index 00000000000..4c29b88e6d6 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -0,0 +1,42 @@ +package net.kencochrane.raven; + +import net.kencochrane.raven.connection.Connection; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.LoggedEvent; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class RavenTest { + @Mock + private Connection mockConnection; + private Raven raven; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + raven = new Raven(mockConnection); + } + + @Test + public void testSendEvent() { + LoggedEvent loggedEvent = mock(LoggedEvent.class); + raven.sendEvent(loggedEvent); + + verify(mockConnection).send(loggedEvent); + } + + @Test + public void testSendEventBuilder() { + EventBuilder eventBuilder = mock(EventBuilder.class); + raven.sendEvent(eventBuilder); + + verify(eventBuilder).build(); + verify(mockConnection).send(any(LoggedEvent.class)); + } +} From cbbadac8e5e6d4707601e5b11a5bdb24990484e9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 20:47:19 +0100 Subject: [PATCH 0185/2152] Bump the version to 3.0-SNAPSHOT --- pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index f9f44d69635..8812fa549b9 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ net.kencochrane raven-all - 2.0-SNAPSHOT + 3.0-SNAPSHOT pom raven-all Java Raven client and loggers. diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 14aa0cbabe5..d500cec24c8 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -7,7 +7,7 @@ net.kencochrane raven-all - 2.0-SNAPSHOT + 3.0-SNAPSHOT raven-log4j jar diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index dc166a92deb..4b9832b96d7 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -7,7 +7,7 @@ net.kencochrane raven-all - 2.0-SNAPSHOT + 3.0-SNAPSHOT raven-logback jar diff --git a/raven/pom.xml b/raven/pom.xml index 06ff83bba83..a70fbb44f40 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -7,7 +7,7 @@ net.kencochrane raven-all - 2.0-SNAPSHOT + 3.0-SNAPSHOT raven jar From bb706abc2177892e6c310bcb5117663aeaa6a913 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 20:49:44 +0100 Subject: [PATCH 0186/2152] Load the DSN from the system if possible --- raven/src/main/java/net/kencochrane/raven/Dsn.java | 4 ++++ raven/src/main/java/net/kencochrane/raven/Raven.java | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Dsn.java b/raven/src/main/java/net/kencochrane/raven/Dsn.java index 161d740e0cc..1a04f5adfd6 100644 --- a/raven/src/main/java/net/kencochrane/raven/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/Dsn.java @@ -10,6 +10,10 @@ * Data Source name allowing a direct connection to a Sentry server. */ public class Dsn { + /** + * Name of the environment or system variable containing the DSN. + */ + public static final String DSN_VARIABLE = "SENTRY_DSN"; /** * Option specific to raven-java, allowing to disable the compression of requests to the Sentry Server. */ diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index f268498d24c..f1f362eab9c 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -6,6 +6,7 @@ import net.kencochrane.raven.connection.UdpConnection; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.exception.InvalidDsnException; import net.kencochrane.raven.marshaller.SimpleJsonMarshaller; import java.nio.charset.Charset; @@ -35,7 +36,12 @@ public Raven(Connection connection) { } private static String dsnLookup() { - throw new IllegalStateException("Couldn't find a Sentry DSN in either the Java or System environment."); + if (System.getenv(Dsn.DSN_VARIABLE) != null) + return System.getenv(Dsn.DSN_VARIABLE); + else if (System.getProperty(Dsn.DSN_VARIABLE) != null) + return System.getProperty(Dsn.DSN_VARIABLE); + else + throw new InvalidDsnException("Couldn't find a Sentry DSN in either the Java or System environment."); } private static Charset determineCharset(Dsn dsn) { From f61d18607b9d6f9e0a28b33e382852708836584e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 6 Apr 2013 21:02:15 +0100 Subject: [PATCH 0187/2152] Add the possibility of adding extra properties --- .../kencochrane/raven/event/EventBuilder.java | 16 ++++++++++++++++ .../kencochrane/raven/event/LoggedEvent.java | 17 +++++++++++++++-- .../raven/marshaller/SimpleJsonMarshaller.java | 1 + 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 5c0615322b4..969c62caf81 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -127,6 +127,9 @@ private static void makeImmutable(LoggedEvent event) { } event.setTags(Collections.unmodifiableMap(unmodifiablesTags)); + // Make the extra properties unmodifiable (everything in it is still mutable though) + event.setExtra(Collections.unmodifiableMap(event.getExtra())); + // Make the SentryInterfaces unmodifiable event.setSentryInterfaces(Collections.unmodifiableMap(event.getSentryInterfaces())); } @@ -262,6 +265,19 @@ public EventBuilder setServerName(String serverName) { return this; } + + /** + * Adds an extra property to the event. + * + * @param extraName name of the extra property. + * @param extraValue value of the extra property. + * @return the current {@code EventBuilder} for chained calls. + */ + public EventBuilder addExtra(String extraName, Object extraValue) { + event.getExtra().put(extraName, extraValue); + return this; + } + /** * Generates a checksum from a given content and set it to the current event. * diff --git a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java b/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java index c82f5cd3500..d8256859ec3 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java +++ b/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java @@ -17,8 +17,9 @@ * In order to ensure that a LoggedEvent can't be modified externally, the setters should have a package visibility. * *
  • - * A proper immutable Object should only contain immutable Objects and primitives, this must be ensured before making - * publishing the LoggedEvent. + * A proper immutable Object should only contain immutable Objects and primitives, this must be ensured before + * publishing the LoggedEvent.
    + * There is one exception, the {@link #extra} section can't be transformed to be completely immutable. *
  • * *

    @@ -60,6 +61,10 @@ public class LoggedEvent { * Identifies the host client from which the event was recorded. */ private String serverName; + /** + * A map or list of additional properties for this event. + */ + private Map extra = new HashMap(); /** * Checksum for the event, allowing to group events with a similar checksum. */ @@ -148,6 +153,14 @@ void setServerName(String serverName) { this.serverName = serverName; } + public Map getExtra() { + return extra; + } + + void setExtra(Map extra) { + this.extra = extra; + } + public String getChecksum() { return checksum; } diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java index 6fcea60dc7e..3cb9b971d4e 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java @@ -124,6 +124,7 @@ private JSONObject encodeToJSONObject(LoggedEvent event) { jsonObject.put(CULPRIT, event.getCulprit()); jsonObject.put(TAGS, event.getTags()); jsonObject.put(SERVER_NAME, event.getServerName()); + jsonObject.put(EXTRA, event.getExtra()); jsonObject.put(CHECKSUM, event.getChecksum()); for (Map.Entry sentryInterfaceEntry : event.getSentryInterfaces().entrySet()) { From 4e0ffd767c1484b61665d83aac2093e0af0de0c0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 7 Apr 2013 10:43:59 +0100 Subject: [PATCH 0188/2152] Add MDC/NDC info as extras to the event --- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 8 ++++++++ .../net/kencochrane/raven/logback/SentryAppender.java | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 99db0e55d1e..59cb018247c 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -10,6 +10,8 @@ import org.apache.log4j.spi.LoggingEvent; import java.util.Date; +import java.util.Map; +import java.util.Set; public class SentryAppender extends AppenderSkeleton { private final Raven raven; @@ -51,6 +53,12 @@ protected void append(LoggingEvent loggingEvent) { .addSentryInterface(new StackTraceInterface(throwable)); } + if (loggingEvent.getNDC() != null) + eventBuilder.addExtra("Log4J-NDC", loggingEvent.getNDC()); + + for (Map.Entry mdcEntry : (Set) loggingEvent.getProperties().entrySet()) + eventBuilder.addExtra(mdcEntry.getKey().toString(), mdcEntry.getValue()); + raven.sendEvent(eventBuilder); } diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index e84964af1c0..41e141a131b 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Map; public class SentryAppender extends AppenderBase { private final Raven raven; @@ -63,6 +64,11 @@ protected void append(ILoggingEvent iLoggingEvent) { eventBuilder.addSentryInterface(new MessageInterface(iLoggingEvent.getMessage(), formatArguments(iLoggingEvent.getArgumentArray()))); } + + for (Map.Entry mdcEntry : iLoggingEvent.getMDCPropertyMap().entrySet()) { + eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); + } + raven.sendEvent(eventBuilder); } } From 299b620edcd746462d085a9ab68a27562104e703 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 7 Apr 2013 10:45:11 +0100 Subject: [PATCH 0189/2152] Generate checksum for messages without stacktrace --- .../net/kencochrane/raven/log4j/SentryAppender.java | 4 ++++ .../kencochrane/raven/logback/SentryAppender.java | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 59cb018247c..15d79a5fcb6 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -51,6 +51,10 @@ protected void append(LoggingEvent loggingEvent) { Throwable throwable = loggingEvent.getThrowableInformation().getThrowable(); eventBuilder.addSentryInterface(new ExceptionInterface(throwable)) .addSentryInterface(new StackTraceInterface(throwable)); + } else { + // When it's a message try to rely on the position of the log (the same message can be logged from + // different places, or a same place can log a message in different ways. + eventBuilder.generateChecksum(loggingEvent.getLocationInformation().fullInfo); } if (loggingEvent.getNDC() != null) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 41e141a131b..e2ddf0ef050 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -63,6 +63,8 @@ protected void append(ILoggingEvent iLoggingEvent) { } else if (iLoggingEvent.getArgumentArray() != null) { eventBuilder.addSentryInterface(new MessageInterface(iLoggingEvent.getMessage(), formatArguments(iLoggingEvent.getArgumentArray()))); + if (iLoggingEvent.getCallerData().length > 0) + eventBuilder.generateChecksum(getEventPosition(iLoggingEvent)); } for (Map.Entry mdcEntry : iLoggingEvent.getMDCPropertyMap().entrySet()) { @@ -71,4 +73,14 @@ protected void append(ILoggingEvent iLoggingEvent) { raven.sendEvent(eventBuilder); } + + private String getEventPosition(ILoggingEvent iLoggingEvent) { + StringBuilder sb = new StringBuilder(); + for (StackTraceElement stackTraceElement : iLoggingEvent.getCallerData()) { + sb.append(stackTraceElement.getClassName()) + .append(stackTraceElement.getMethodName()) + .append(stackTraceElement.getLineNumber()); + } + return sb.toString(); + } } From 3e931af37ea307d93376a8f343374911eca9b43b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 7 Apr 2013 10:45:52 +0100 Subject: [PATCH 0190/2152] Set a better culprit in Log4j --- .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 15d79a5fcb6..ff3ce981f8e 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -45,12 +45,13 @@ protected void append(LoggingEvent loggingEvent) { .setMessage(loggingEvent.getRenderedMessage()) .setLogger(loggingEvent.getLoggerName()) .setLevel(formatLevel(loggingEvent)) - .setCulprit(loggingEvent.getLoggerName()); + .setCulprit(loggingEvent.getFQNOfLoggerClass()); if (loggingEvent.getThrowableInformation() != null) { Throwable throwable = loggingEvent.getThrowableInformation().getThrowable(); eventBuilder.addSentryInterface(new ExceptionInterface(throwable)) .addSentryInterface(new StackTraceInterface(throwable)); + eventBuilder.setCulprit(throwable); } else { // When it's a message try to rely on the position of the log (the same message can be logged from // different places, or a same place can log a message in different ways. From 4de1856efd363aa2ae7b2df79820140e8f0fdc45 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 7 Apr 2013 10:46:54 +0100 Subject: [PATCH 0191/2152] Change the ThreadPool to a fixed size pool Instead of a CachedThreadPool, use a fixed size thread pool, based on the number of processors available. --- .../net/kencochrane/raven/connection/AsyncConnection.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 3163aecd052..069247b413d 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -22,8 +22,8 @@ public class AsyncConnection implements Connection { * Connection used to actually send the events. */ private final Connection actualConnection; - // TODO: Allow the user to change to a custom executor? - private final ExecutorService executorService = Executors.newCachedThreadPool(new DaemonThreadFactory()); + private final ExecutorService executorService = + Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new DaemonThreadFactory()); /** * Create a connection which will rely on an executor to send events. From edfadaaa79b49f5e9a626ace2c22fc63f12ab313 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 7 Apr 2013 10:47:58 +0100 Subject: [PATCH 0192/2152] Add a shutdown hook to terminate AsyncConnection --- .../raven/connection/AsyncConnection.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 069247b413d..c32fa3d0ebf 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -2,9 +2,11 @@ import net.kencochrane.raven.event.LoggedEvent; +import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; @@ -32,6 +34,27 @@ public class AsyncConnection implements Connection { */ public AsyncConnection(Connection actualConnection) { this.actualConnection = actualConnection; + addShutdownHook(); + } + + private void addShutdownHook() { + //TODO: JUL loggers are shutdown by an other shutdown hook, it's possible that nothing will get actually logged. + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() { + logger.log(Level.INFO, "Gracefully shutdown sentry threads."); + executorService.shutdown(); + try { + if (!executorService.awaitTermination(1000, TimeUnit.MILLISECONDS)) { + logger.log(Level.WARNING, "Graceful shutdown took too much time, forcing the shutdown."); + List tasks = executorService.shutdownNow(); + logger.log(Level.INFO, tasks.size() + " tasks failed to execute before the shutdown."); + } + logger.log(Level.SEVERE, "Shutdown interrupted."); + } catch (InterruptedException e) { + logger.log(Level.SEVERE, "Shutdown interrupted."); + } + } + }); } @Override From ece72f858752e0dffedeb2e34973d6a2b6fac127 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 7 Apr 2013 10:49:13 +0100 Subject: [PATCH 0193/2152] Reorganise code --- .../net/kencochrane/raven/event/LoggedEvent.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java b/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java index d8256859ec3..e84d2995f48 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java +++ b/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java @@ -177,14 +177,6 @@ void setSentryInterfaces(Map sentryInterfaces) { this.sentryInterfaces = sentryInterfaces; } - public static enum Level { - FATAL, - ERROR, - WARNING, - INFO, - DEBUG - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -207,4 +199,12 @@ public String toString() { ", logger='" + logger + '\'' + '}'; } + + public static enum Level { + FATAL, + ERROR, + WARNING, + INFO, + DEBUG + } } From cc9cd66a503c7ecf3407343c9c16879e336185ef Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 7 Apr 2013 11:15:47 +0100 Subject: [PATCH 0194/2152] Add equals/hashCode to Dsn --- .../main/java/net/kencochrane/raven/Dsn.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/Dsn.java b/raven/src/main/java/net/kencochrane/raven/Dsn.java index 1a04f5adfd6..22d817b921d 100644 --- a/raven/src/main/java/net/kencochrane/raven/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/Dsn.java @@ -181,4 +181,39 @@ public Map getOptions() { public URI getUri() { return uri; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Dsn dsn = (Dsn) o; + + if (port != dsn.port) return false; + if (!host.equals(dsn.host)) return false; + if (!options.equals(dsn.options)) return false; + if (!path.equals(dsn.path)) return false; + if (!projectId.equals(dsn.projectId)) return false; + if (protocol != null ? !protocol.equals(dsn.protocol) : dsn.protocol != null) return false; + if (!protocolSettings.equals(dsn.protocolSettings)) return false; + if (!publicKey.equals(dsn.publicKey)) return false; + if (!secretKey.equals(dsn.secretKey)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = publicKey.hashCode(); + result = 31 * result + projectId.hashCode(); + result = 31 * result + host.hashCode(); + result = 31 * result + port; + result = 31 * result + path.hashCode(); + return result; + } + + @Override + public String toString() { + return getUri().toString(); + } } From 6afee24664ad924958e81ba8b5ae2e8d92b28a21 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 7 Apr 2013 11:17:40 +0100 Subject: [PATCH 0195/2152] Get the DSN from JNDI/Env/JavaProperties --- .../java/net/kencochrane/raven/Raven.java | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index f1f362eab9c..3b8280c052f 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -9,6 +9,10 @@ import net.kencochrane.raven.exception.InvalidDsnException; import net.kencochrane.raven.marshaller.SimpleJsonMarshaller; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.naming.NoInitialContextException; import java.nio.charset.Charset; import java.util.logging.Level; import java.util.logging.Logger; @@ -36,12 +40,33 @@ public Raven(Connection connection) { } private static String dsnLookup() { - if (System.getenv(Dsn.DSN_VARIABLE) != null) - return System.getenv(Dsn.DSN_VARIABLE); - else if (System.getProperty(Dsn.DSN_VARIABLE) != null) - return System.getProperty(Dsn.DSN_VARIABLE); - else + String dsn = null; + + // Try to obtain the DSN from JNDI + try { + Context c = new InitialContext(); + dsn = (String) c.lookup("java:comp/env/sentry/dsn"); + } catch (NoInitialContextException e) { + logger.log(Level.INFO, "JNDI not configured for sentry (NoInitialContextEx)"); + } catch (NamingException e) { + logger.log(Level.INFO, "No /sentry/dsn in JNDI"); + } catch (RuntimeException ex) { + logger.log(Level.INFO, "Odd RuntimeException while testing for JNDI: " + ex.getMessage()); + } + + // Try to obtain the DSN from a System Environment Variable + if (dsn == null) + dsn = System.getenv(Dsn.DSN_VARIABLE); + + // Try to obtain the DSN from a Java System Property + if (dsn == null) + dsn = System.getProperty(Dsn.DSN_VARIABLE); + + if (dsn != null) { + return dsn; + } else { throw new InvalidDsnException("Couldn't find a Sentry DSN in either the Java or System environment."); + } } private static Charset determineCharset(Dsn dsn) { From ee3d16ac1cd2c05609fe3dd0a675a25297728329 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 7 Apr 2013 11:18:55 +0100 Subject: [PATCH 0196/2152] Return UUID if the event isn't built yet --- raven/src/main/java/net/kencochrane/raven/Raven.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 3b8280c052f..35acce794d4 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -14,6 +14,7 @@ import javax.naming.NamingException; import javax.naming.NoInitialContextException; import java.nio.charset.Charset; +import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; @@ -107,8 +108,10 @@ private static Connection determineConnection(Dsn dsn) { return connection; } - public void sendEvent(EventBuilder eventBuilder) { - sendEvent(eventBuilder.build()); + public UUID sendEvent(EventBuilder eventBuilder) { + LoggedEvent event = eventBuilder.build(); + sendEvent(event); + return event.getId(); } public void sendEvent(LoggedEvent event) { From 6d88744a1b1026150e4bf58522bd3dfbb1b6c985 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 08:47:02 +0100 Subject: [PATCH 0197/2152] Fix raven unit test sendEvent(EventBuilder) now returns the UUID of the generated event create a mock to avoid a NPE and test the returned UUID --- .../test/java/net/kencochrane/raven/RavenTest.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index 4c29b88e6d6..8abdcd91bf1 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -3,14 +3,19 @@ import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.LoggedEvent; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class RavenTest { @Mock @@ -34,9 +39,14 @@ public void testSendEvent() { @Test public void testSendEventBuilder() { EventBuilder eventBuilder = mock(EventBuilder.class); - raven.sendEvent(eventBuilder); + LoggedEvent loggedEvent = mock(LoggedEvent.class); + UUID mockUuid = UUID.randomUUID(); + when(eventBuilder.build()).thenReturn(loggedEvent); + when(loggedEvent.getId()).thenReturn(mockUuid); + UUID uuid = raven.sendEvent(eventBuilder); verify(eventBuilder).build(); verify(mockConnection).send(any(LoggedEvent.class)); + assertEquals(mockUuid, uuid); } } From 3488e7856b8bfbb9d702d649e7eb568aa2914dbe Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 08:49:16 +0100 Subject: [PATCH 0198/2152] Add a dependency to javax.servlet-api --- pom.xml | 6 ++++++ raven/pom.xml | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/pom.xml b/pom.xml index 8812fa549b9..db9d4008ef9 100644 --- a/pom.xml +++ b/pom.xml @@ -32,6 +32,7 @@ 1.2.17 1.6 1.1.1 + 3.0.1 @@ -95,6 +96,11 @@ json-simple ${json-simple.version} + + javax.servlet + javax.servlet-api + ${javax.servlet-api.version} + log4j log4j diff --git a/raven/pom.xml b/raven/pom.xml index a70fbb44f40..26cf45ad7a3 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -23,6 +23,11 @@ com.googlecode.json-simple json-simple + + javax.servlet + javax.servlet-api + provided + org.mockito mockito-core From 0f9056bd9de3ac32eab61cf9ba2095a5b5ab96b3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 14:46:02 +0100 Subject: [PATCH 0199/2152] Create the base of a future HttpInterface Creat the base of a sentry interface handling HTTP requests --- .../raven/event/interfaces/HttpInterface.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java new file mode 100644 index 00000000000..bfb4aa41693 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java @@ -0,0 +1,36 @@ +package net.kencochrane.raven.event.interfaces; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * The HTTP interface for Sentry allows to add an HTTP request to an event. + */ +public class HttpInterface implements SentryInterface { + public static final String HTTP_INTERFACE = "sentry.interfaces.Http"; + private final HttpServletRequest request; + + public HttpInterface(HttpServletRequest request) { + this.request = request; + } + + public static Map> extractHeaders(HttpServletRequest servletRequest) { + Collection headerNames = Collections.list(servletRequest.getHeaderNames()); + Map> headers = new HashMap>(headerNames.size()); + for (String headerName : headerNames) + headers.put(headerName, Collections.list(servletRequest.getHeaders(headerName))); + return headers; + } + + @Override + public String getInterfaceName() { + return HTTP_INTERFACE; + } + + public HttpServletRequest getRequest() { + return request; + } +} From 7eaa0c8c52491653ab42ec3af8a67f53ac92f7f5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 15:26:54 +0100 Subject: [PATCH 0200/2152] Remove default values that Sentry guesses sensibly --- .../kencochrane/raven/event/EventBuilder.java | 25 ------------------- .../marshaller/SimpleJsonMarshaller.java | 12 +++++++-- 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 969c62caf81..823228039d5 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -12,14 +12,6 @@ * Builder to assist the creation of {@link LoggedEvent}s. */ public class EventBuilder { - /** - * Default log level if it isn't set manually. - */ - public static final LoggedEvent.Level DEFAULT_LEVEL = LoggedEvent.Level.ERROR; - /** - * Default logger if it isn't set manually. - */ - public static final String DEFAULT_LOGGER = "root"; /** * Default platform if it isn't set manually. */ @@ -28,10 +20,6 @@ public class EventBuilder { * Default hostname if it isn't set manually (or can't be determined). */ public static final String DEFAULT_HOSTNAME = "unavailable"; - /** - * Default log level if it isn't provided (either directly or through an exception). - */ - public static final String DEFAULT_MESSAGE = "(empty)"; private final LoggedEvent event; private boolean alreadyBuilt = false; @@ -87,24 +75,11 @@ private static String getHostname() { * * @param event currently handled event. */ - //TODO: Shouldn't this be removed to rely on sentry default settings? private static void autoSetMissingValues(LoggedEvent event) { - // Ensure that an actual message is set - if (event.getMessage() == null) - event.setMessage(DEFAULT_MESSAGE); - // Ensure that a timestamp is set (to now at least!) if (event.getTimestamp() == null) event.setTimestamp(new Date()); - // Ensure that a log level is set - if (event.getLevel() == null) - event.setLevel(DEFAULT_LEVEL); - - // Ensure that a logger is set - if (event.getLogger() == null) - event.setLogger(DEFAULT_LOGGER); - // Ensure that a platform is set if (event.getPlatform() == null) event.setPlatform(DEFAULT_PLATFORM); diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java index 3cb9b971d4e..3b86da492df 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java @@ -158,7 +158,11 @@ private JSONObject formatInterface(SentryInterface sentryInterface) { * @return formatted message (shortened if necessary). */ private String formatMessage(String message) { - return (message.length() > MAX_MESSAGE_LENGTH) ? message.substring(0, MAX_MESSAGE_LENGTH) : message; + if (message == null) + return null; + else if (message.length() > MAX_MESSAGE_LENGTH) + return message.substring(0, MAX_MESSAGE_LENGTH); + else return message; } /** @@ -178,6 +182,9 @@ private String formatId(UUID id) { * @return log level as a String. */ private String formatLevel(LoggedEvent.Level level) { + if (level == null) + return null; + switch (level) { case DEBUG: return "debug"; @@ -188,8 +195,9 @@ private String formatLevel(LoggedEvent.Level level) { case INFO: return "info"; case ERROR: - default: return "error"; + default: + return null; } } From a9c9d173ca315d28ef22552d0dedeb04bbe00c96 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 15:28:21 +0100 Subject: [PATCH 0201/2152] Deprecate and comment the MessageInterface simple constructor --- .../raven/event/interfaces/MessageInterface.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java index 4584c22318a..aa07a8ef31a 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java @@ -12,6 +12,17 @@ public class MessageInterface implements SentryInterface { private final String message; private final List params; + /** + * Creates a non parametrised message. + *

    + * While it's technically possible to create a non parametrised message with {@code MessageInterface}, it's + * recommended to use {@link net.kencochrane.raven.event.EventBuilder#setMessage(String)} instead. + *

    + * + * @param message message to add to the event. + * @deprecated Use {@link net.kencochrane.raven.event.EventBuilder#setMessage(String)} instead. + */ + @Deprecated public MessageInterface(String message) { this(message, Collections.emptyList()); } From d9e35ec5a5bf0a7054b6be92a0404c5138f7ab67 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 15:30:13 +0100 Subject: [PATCH 0202/2152] Fix indentation and import orders --- .../java/net/kencochrane/raven/connection/HttpConnection.java | 2 +- .../java/net/kencochrane/raven/connection/UdpConnection.java | 2 +- .../src/main/java/net/kencochrane/raven/event/EventBuilder.java | 2 +- .../net/kencochrane/raven/exception/InvalidDsnException.java | 2 +- .../net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 69cfc592d68..236f7d66dd7 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -2,9 +2,9 @@ import net.kencochrane.raven.Dsn; import net.kencochrane.raven.Raven; +import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.marshaller.Marshaller; import net.kencochrane.raven.marshaller.SimpleJsonMarshaller; -import net.kencochrane.raven.event.LoggedEvent; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index a6e28570ded..9a47fc95f48 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -1,9 +1,9 @@ package net.kencochrane.raven.connection; import net.kencochrane.raven.Dsn; +import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.marshaller.Marshaller; import net.kencochrane.raven.marshaller.SimpleJsonMarshaller; -import net.kencochrane.raven.event.LoggedEvent; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 823228039d5..5c47f63c9e8 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -244,7 +244,7 @@ public EventBuilder setServerName(String serverName) { /** * Adds an extra property to the event. * - * @param extraName name of the extra property. + * @param extraName name of the extra property. * @param extraValue value of the extra property. * @return the current {@code EventBuilder} for chained calls. */ diff --git a/raven/src/main/java/net/kencochrane/raven/exception/InvalidDsnException.java b/raven/src/main/java/net/kencochrane/raven/exception/InvalidDsnException.java index c9d66c2a645..1a76b60d26a 100644 --- a/raven/src/main/java/net/kencochrane/raven/exception/InvalidDsnException.java +++ b/raven/src/main/java/net/kencochrane/raven/exception/InvalidDsnException.java @@ -1,6 +1,6 @@ package net.kencochrane.raven.exception; -public class InvalidDsnException extends RuntimeException{ +public class InvalidDsnException extends RuntimeException { public InvalidDsnException() { } diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java index 3b86da492df..eaa8aa208a5 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java @@ -78,6 +78,7 @@ public class SimpleJsonMarshaller implements Marshaller { * Maximum length for a message. */ public static final int MAX_MESSAGE_LENGTH = 1000; + private static final Logger logger = Logger.getLogger(SimpleJsonMarshaller.class.getCanonicalName()); /** * Date format for ISO 8601. */ @@ -90,7 +91,6 @@ public class SimpleJsonMarshaller implements Marshaller { * Charset used to transmit data. */ private Charset charset = Charset.defaultCharset(); - private static final Logger logger = Logger.getLogger(SimpleJsonMarshaller.class.getCanonicalName()); @Override public void marshall(LoggedEvent event, OutputStream destination) { From 2fd43e4229bf3f10760f06caf7b304f79a7ba753 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 16:50:05 +0100 Subject: [PATCH 0203/2152] Group SimpleJsonInterfaceMarshallers Instead of a big if/else for each interface type, use a Map to associate a SentryInterface to a SimpleJSonInterfaceMarshaller. --- .../java/net/kencochrane/raven/Raven.java | 2 +- .../raven/connection/HttpConnection.java | 2 +- .../raven/connection/UdpConnection.java | 2 +- ...impleJsonExceptionInterfaceMarshaller.java | 2 +- .../SimpleJsonInterfaceMarshaller.java | 2 +- .../SimpleJsonMarshaller.java | 28 ++++++++++--------- .../SimpleJsonMessageInterfaceMarshaller.java | 2 +- ...mpleJsonStackTraceInterfaceMarshaller.java | 2 +- 8 files changed, 22 insertions(+), 20 deletions(-) rename raven/src/main/java/net/kencochrane/raven/marshaller/{ => simplejson}/SimpleJsonExceptionInterfaceMarshaller.java (95%) rename raven/src/main/java/net/kencochrane/raven/marshaller/{ => simplejson}/SimpleJsonInterfaceMarshaller.java (80%) rename raven/src/main/java/net/kencochrane/raven/marshaller/{ => simplejson}/SimpleJsonMarshaller.java (88%) rename raven/src/main/java/net/kencochrane/raven/marshaller/{ => simplejson}/SimpleJsonMessageInterfaceMarshaller.java (94%) rename raven/src/main/java/net/kencochrane/raven/marshaller/{ => simplejson}/SimpleJsonStackTraceInterfaceMarshaller.java (98%) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 35acce794d4..18fa4060a4a 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -7,7 +7,7 @@ import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.exception.InvalidDsnException; -import net.kencochrane.raven.marshaller.SimpleJsonMarshaller; +import net.kencochrane.raven.marshaller.simplejson.SimpleJsonMarshaller; import javax.naming.Context; import javax.naming.InitialContext; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 236f7d66dd7..0e57577f157 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -4,7 +4,7 @@ import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.marshaller.Marshaller; -import net.kencochrane.raven.marshaller.SimpleJsonMarshaller; +import net.kencochrane.raven.marshaller.simplejson.SimpleJsonMarshaller; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index 9a47fc95f48..8aa26d915b2 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -3,7 +3,7 @@ import net.kencochrane.raven.Dsn; import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.marshaller.Marshaller; -import net.kencochrane.raven.marshaller.SimpleJsonMarshaller; +import net.kencochrane.raven.marshaller.simplejson.SimpleJsonMarshaller; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonExceptionInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonExceptionInterfaceMarshaller.java similarity index 95% rename from raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonExceptionInterfaceMarshaller.java rename to raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonExceptionInterfaceMarshaller.java index 221daa3ec06..322f6d7e5fe 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonExceptionInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonExceptionInterfaceMarshaller.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven.marshaller; +package net.kencochrane.raven.marshaller.simplejson; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.ImmutableThrowable; diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonInterfaceMarshaller.java similarity index 80% rename from raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonInterfaceMarshaller.java rename to raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonInterfaceMarshaller.java index ea98bac7b02..ac826d1dd4a 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonInterfaceMarshaller.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven.marshaller; +package net.kencochrane.raven.marshaller.simplejson; import net.kencochrane.raven.event.interfaces.SentryInterface; import org.json.simple.JSONObject; diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonMarshaller.java similarity index 88% rename from raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java rename to raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonMarshaller.java index eaa8aa208a5..ff7dc9aebeb 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonMarshaller.java @@ -1,10 +1,11 @@ -package net.kencochrane.raven.marshaller; +package net.kencochrane.raven.marshaller.simplejson; import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.SentryInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import net.kencochrane.raven.marshaller.Marshaller; import org.apache.commons.codec.binary.Base64OutputStream; import org.json.simple.JSONObject; @@ -16,6 +17,7 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.logging.Level; @@ -83,6 +85,8 @@ public class SimpleJsonMarshaller implements Marshaller { * Date format for ISO 8601. */ private static final DateFormat ISO_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ"); + private final Map, SimpleJsonInterfaceMarshaller> interfaceMarshallers = + new HashMap, SimpleJsonInterfaceMarshaller>(); /** * Enables disables the compression of JSON. */ @@ -92,6 +96,12 @@ public class SimpleJsonMarshaller implements Marshaller { */ private Charset charset = Charset.defaultCharset(); + { + interfaceMarshallers.put(ExceptionInterface.class, new SimpleJsonExceptionInterfaceMarshaller()); + interfaceMarshallers.put(MessageInterface.class, new SimpleJsonMessageInterfaceMarshaller()); + interfaceMarshallers.put(StackTraceInterface.class, new SimpleJsonStackTraceInterfaceMarshaller()); + } + @Override public void marshall(LoggedEvent event, OutputStream destination) { OutputStream outputStream = new Base64OutputStream(destination); @@ -135,20 +145,12 @@ private JSONObject encodeToJSONObject(LoggedEvent event) { } private JSONObject formatInterface(SentryInterface sentryInterface) { - JSONObject jsonObject; - //TODO: Use an factory here (and do not instanciate a marshaller each time? - if (sentryInterface.getInterfaceName().equals(MessageInterface.MESSAGE_INTERFACE)) { - jsonObject = new SimpleJsonMessageInterfaceMarshaller().serialiseInterface(sentryInterface); - } else if (sentryInterface.getInterfaceName().equals(ExceptionInterface.EXCEPTION_INTERFACE)) { - jsonObject = new SimpleJsonExceptionInterfaceMarshaller().serialiseInterface(sentryInterface); - } else if (sentryInterface.getInterfaceName().equals(StackTraceInterface.STACKTRACE_INTERFACE)) { - jsonObject = new SimpleJsonStackTraceInterfaceMarshaller().serialiseInterface(sentryInterface); + SimpleJsonInterfaceMarshaller interfaceMarshaller = interfaceMarshallers.get(sentryInterface.getClass()); + if (interfaceMarshaller != null) { + return interfaceMarshaller.serialiseInterface(sentryInterface); } else { - //TODO: log something? - jsonObject = new JSONObject(); + return new JSONObject(); } - - return jsonObject; } /** diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMessageInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonMessageInterfaceMarshaller.java similarity index 94% rename from raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMessageInterfaceMarshaller.java rename to raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonMessageInterfaceMarshaller.java index 3e94f69067d..f420c464512 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonMessageInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonMessageInterfaceMarshaller.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven.marshaller; +package net.kencochrane.raven.marshaller.simplejson; import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.SentryInterface; diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonStackTraceInterfaceMarshaller.java similarity index 98% rename from raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java rename to raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonStackTraceInterfaceMarshaller.java index 8aa1a78175f..08a6bed1aeb 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/SimpleJsonStackTraceInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonStackTraceInterfaceMarshaller.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven.marshaller; +package net.kencochrane.raven.marshaller.simplejson; import net.kencochrane.raven.event.interfaces.ImmutableThrowable; import net.kencochrane.raven.event.interfaces.SentryInterface; From 1db93724985701c3751bb575249f7982aba4c05f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 16:51:49 +0100 Subject: [PATCH 0204/2152] Add a Marshaller (incomplete) for HttpInterface --- .../SimpleJsonHttpInterfaceMarshaller.java | 19 +++++++++++++++++++ .../simplejson/SimpleJsonMarshaller.java | 6 ++---- 2 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonHttpInterfaceMarshaller.java diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonHttpInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonHttpInterfaceMarshaller.java new file mode 100644 index 00000000000..300e42015b2 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonHttpInterfaceMarshaller.java @@ -0,0 +1,19 @@ +package net.kencochrane.raven.marshaller.simplejson; + +import net.kencochrane.raven.event.interfaces.HttpInterface; +import net.kencochrane.raven.event.interfaces.SentryInterface; +import org.json.simple.JSONObject; + +public class SimpleJsonHttpInterfaceMarshaller implements SimpleJsonInterfaceMarshaller { + @Override + public JSONObject serialiseInterface(SentryInterface sentryInterface) { + if (!(sentryInterface instanceof HttpInterface)) { + //TODO: Do something better here! + throw new IllegalArgumentException(); + } + + JSONObject jsonObject = new JSONObject(); + //TODO: make an actual implementation! + return jsonObject; + } +} diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonMarshaller.java index ff7dc9aebeb..f926013c45f 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonMarshaller.java @@ -1,10 +1,7 @@ package net.kencochrane.raven.marshaller.simplejson; import net.kencochrane.raven.event.LoggedEvent; -import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.MessageInterface; -import net.kencochrane.raven.event.interfaces.SentryInterface; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import net.kencochrane.raven.event.interfaces.*; import net.kencochrane.raven.marshaller.Marshaller; import org.apache.commons.codec.binary.Base64OutputStream; import org.json.simple.JSONObject; @@ -98,6 +95,7 @@ public class SimpleJsonMarshaller implements Marshaller { { interfaceMarshallers.put(ExceptionInterface.class, new SimpleJsonExceptionInterfaceMarshaller()); + interfaceMarshallers.put(HttpInterface.class, new SimpleJsonHttpInterfaceMarshaller()); interfaceMarshallers.put(MessageInterface.class, new SimpleJsonMessageInterfaceMarshaller()); interfaceMarshallers.put(StackTraceInterface.class, new SimpleJsonStackTraceInterfaceMarshaller()); } From 0436f0ccdf382cb840831540cafeb28e56896ef0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 16:54:40 +0100 Subject: [PATCH 0205/2152] Reduce SimpleJsonMarshallers names and visibility Instead of using the complete name "SimpleJsonXxxInterfaceMarshaller" use the shorthand "XxxMarshaller". Those marshallers should only be accessed from the SimpleJson marshaller thus the visibility is reduced to "package" when possible. --- .../main/java/net/kencochrane/raven/Raven.java | 4 ++-- .../raven/connection/HttpConnection.java | 4 ++-- .../raven/connection/UdpConnection.java | 4 ++-- ...arshaller.java => ExceptionMarshaller.java} | 2 +- ...faceMarshaller.java => HttpMarshaller.java} | 2 +- ...arshaller.java => InterfaceMarshaller.java} | 2 +- ...JsonMarshaller.java => JsonMarshaller.java} | 18 +++++++++--------- ...eMarshaller.java => MessageMarshaller.java} | 2 +- ...rshaller.java => StackTraceMarshaller.java} | 2 +- 9 files changed, 20 insertions(+), 20 deletions(-) rename raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/{SimpleJsonExceptionInterfaceMarshaller.java => ExceptionMarshaller.java} (92%) rename raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/{SimpleJsonHttpInterfaceMarshaller.java => HttpMarshaller.java} (86%) rename raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/{SimpleJsonInterfaceMarshaller.java => InterfaceMarshaller.java} (81%) rename raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/{SimpleJsonMarshaller.java => JsonMarshaller.java} (89%) rename raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/{SimpleJsonMessageInterfaceMarshaller.java => MessageMarshaller.java} (90%) rename raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/{SimpleJsonStackTraceInterfaceMarshaller.java => StackTraceMarshaller.java} (97%) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 18fa4060a4a..bf96c1ceb78 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -7,7 +7,7 @@ import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.exception.InvalidDsnException; -import net.kencochrane.raven.marshaller.simplejson.SimpleJsonMarshaller; +import net.kencochrane.raven.marshaller.simplejson.JsonMarshaller; import javax.naming.Context; import javax.naming.InitialContext; @@ -84,7 +84,7 @@ private static Connection determineConnection(Dsn dsn) { String protocol = dsn.getProtocol(); Connection connection = null; Charset charset = determineCharset(dsn); - SimpleJsonMarshaller marshaller = new SimpleJsonMarshaller(); + JsonMarshaller marshaller = new JsonMarshaller(); marshaller.setCompression(!dsn.getOptions().containsKey(Dsn.NOCOMPRESSION_OPTION)); marshaller.setCharset(charset); diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 0e57577f157..89c17967132 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -4,7 +4,7 @@ import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.marshaller.Marshaller; -import net.kencochrane.raven.marshaller.simplejson.SimpleJsonMarshaller; +import net.kencochrane.raven.marshaller.simplejson.JsonMarshaller; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; @@ -34,7 +34,7 @@ public boolean verify(String hostname, SSLSession sslSession) { } }; private final URL sentryUrl; - private Marshaller marshaller = new SimpleJsonMarshaller(); + private Marshaller marshaller = new JsonMarshaller(); private int timeout = DEFAULT_TIMEOUT; private boolean bypassSecurity; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index 8aa26d915b2..07051523d89 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -3,7 +3,7 @@ import net.kencochrane.raven.Dsn; import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.marshaller.Marshaller; -import net.kencochrane.raven.marshaller.simplejson.SimpleJsonMarshaller; +import net.kencochrane.raven.marshaller.simplejson.JsonMarshaller; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -23,7 +23,7 @@ public class UdpConnection extends AbstractConnection { private static final Logger logger = Logger.getLogger(UdpConnection.class.getCanonicalName()); private DatagramSocket socket; private Charset charset = Charset.defaultCharset(); - private Marshaller marshaller = new SimpleJsonMarshaller(); + private Marshaller marshaller = new JsonMarshaller(); public UdpConnection(Dsn dsn) { super(dsn); diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonExceptionInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/ExceptionMarshaller.java similarity index 92% rename from raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonExceptionInterfaceMarshaller.java rename to raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/ExceptionMarshaller.java index 322f6d7e5fe..2e0a27e058f 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonExceptionInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/ExceptionMarshaller.java @@ -5,7 +5,7 @@ import net.kencochrane.raven.event.interfaces.SentryInterface; import org.json.simple.JSONObject; -public class SimpleJsonExceptionInterfaceMarshaller implements SimpleJsonInterfaceMarshaller { +class ExceptionMarshaller implements InterfaceMarshaller { private static final String TYPE_PARAMETER = "type"; private static final String VALUE_PARAMETER = "value"; private static final String MODULE_PARAMETER = "module"; diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonHttpInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/HttpMarshaller.java similarity index 86% rename from raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonHttpInterfaceMarshaller.java rename to raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/HttpMarshaller.java index 300e42015b2..4ea33dcd9da 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonHttpInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/HttpMarshaller.java @@ -4,7 +4,7 @@ import net.kencochrane.raven.event.interfaces.SentryInterface; import org.json.simple.JSONObject; -public class SimpleJsonHttpInterfaceMarshaller implements SimpleJsonInterfaceMarshaller { +class HttpMarshaller implements InterfaceMarshaller { @Override public JSONObject serialiseInterface(SentryInterface sentryInterface) { if (!(sentryInterface instanceof HttpInterface)) { diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/InterfaceMarshaller.java similarity index 81% rename from raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonInterfaceMarshaller.java rename to raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/InterfaceMarshaller.java index ac826d1dd4a..92281c32982 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/InterfaceMarshaller.java @@ -3,6 +3,6 @@ import net.kencochrane.raven.event.interfaces.SentryInterface; import org.json.simple.JSONObject; -public interface SimpleJsonInterfaceMarshaller { +interface InterfaceMarshaller { JSONObject serialiseInterface(SentryInterface sentryInterface); } diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/JsonMarshaller.java similarity index 89% rename from raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonMarshaller.java rename to raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/JsonMarshaller.java index f926013c45f..7768ff2903d 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/JsonMarshaller.java @@ -24,7 +24,7 @@ /** * Marshaller allowing to transform a simple {@link LoggedEvent} into a compressed JSON String send over a stream. */ -public class SimpleJsonMarshaller implements Marshaller { +public class JsonMarshaller implements Marshaller { /** * Hexadecimal string representing a uuid4 value. */ @@ -77,13 +77,13 @@ public class SimpleJsonMarshaller implements Marshaller { * Maximum length for a message. */ public static final int MAX_MESSAGE_LENGTH = 1000; - private static final Logger logger = Logger.getLogger(SimpleJsonMarshaller.class.getCanonicalName()); + private static final Logger logger = Logger.getLogger(JsonMarshaller.class.getCanonicalName()); /** * Date format for ISO 8601. */ private static final DateFormat ISO_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ"); - private final Map, SimpleJsonInterfaceMarshaller> interfaceMarshallers = - new HashMap, SimpleJsonInterfaceMarshaller>(); + private final Map, InterfaceMarshaller> interfaceMarshallers = + new HashMap, InterfaceMarshaller>(); /** * Enables disables the compression of JSON. */ @@ -94,10 +94,10 @@ public class SimpleJsonMarshaller implements Marshaller { private Charset charset = Charset.defaultCharset(); { - interfaceMarshallers.put(ExceptionInterface.class, new SimpleJsonExceptionInterfaceMarshaller()); - interfaceMarshallers.put(HttpInterface.class, new SimpleJsonHttpInterfaceMarshaller()); - interfaceMarshallers.put(MessageInterface.class, new SimpleJsonMessageInterfaceMarshaller()); - interfaceMarshallers.put(StackTraceInterface.class, new SimpleJsonStackTraceInterfaceMarshaller()); + interfaceMarshallers.put(ExceptionInterface.class, new ExceptionMarshaller()); + interfaceMarshallers.put(HttpInterface.class, new HttpMarshaller()); + interfaceMarshallers.put(MessageInterface.class, new MessageMarshaller()); + interfaceMarshallers.put(StackTraceInterface.class, new StackTraceMarshaller()); } @Override @@ -143,7 +143,7 @@ private JSONObject encodeToJSONObject(LoggedEvent event) { } private JSONObject formatInterface(SentryInterface sentryInterface) { - SimpleJsonInterfaceMarshaller interfaceMarshaller = interfaceMarshallers.get(sentryInterface.getClass()); + InterfaceMarshaller interfaceMarshaller = interfaceMarshallers.get(sentryInterface.getClass()); if (interfaceMarshaller != null) { return interfaceMarshaller.serialiseInterface(sentryInterface); } else { diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonMessageInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/MessageMarshaller.java similarity index 90% rename from raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonMessageInterfaceMarshaller.java rename to raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/MessageMarshaller.java index f420c464512..c5036d2b0ee 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonMessageInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/MessageMarshaller.java @@ -4,7 +4,7 @@ import net.kencochrane.raven.event.interfaces.SentryInterface; import org.json.simple.JSONObject; -public class SimpleJsonMessageInterfaceMarshaller implements SimpleJsonInterfaceMarshaller { +class MessageMarshaller implements InterfaceMarshaller { private static final String MESSAGE_PARAMETER = "message"; private static final String PARAMS_PARAMETER = "params"; diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonStackTraceInterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/StackTraceMarshaller.java similarity index 97% rename from raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonStackTraceInterfaceMarshaller.java rename to raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/StackTraceMarshaller.java index 08a6bed1aeb..b3d948ff16d 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/SimpleJsonStackTraceInterfaceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/StackTraceMarshaller.java @@ -10,7 +10,7 @@ import java.util.HashSet; import java.util.Set; -public class SimpleJsonStackTraceInterfaceMarshaller implements SimpleJsonInterfaceMarshaller { +class StackTraceMarshaller implements InterfaceMarshaller { private static final String FRAMES_PARAMETER = "frames"; private static final String FILENAME_PARAMETER = "filename"; private static final String FUNCTION_PARAMETER = "function"; From 0359209eab436c60ed5b9971d8e31cf3daf45540 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 17:04:13 +0100 Subject: [PATCH 0206/2152] Avoid initialisers when a Constructor can be used --- .../raven/marshaller/simplejson/JsonMarshaller.java | 2 +- .../marshaller/simplejson/StackTraceMarshaller.java | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/JsonMarshaller.java index 7768ff2903d..f4792237a18 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/JsonMarshaller.java @@ -93,7 +93,7 @@ public class JsonMarshaller implements Marshaller { */ private Charset charset = Charset.defaultCharset(); - { + public JsonMarshaller() { interfaceMarshallers.put(ExceptionInterface.class, new ExceptionMarshaller()); interfaceMarshallers.put(HttpInterface.class, new HttpMarshaller()); interfaceMarshallers.put(MessageInterface.class, new MessageMarshaller()); diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/StackTraceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/StackTraceMarshaller.java index b3d948ff16d..e0c08cd1a41 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/StackTraceMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/StackTraceMarshaller.java @@ -22,11 +22,10 @@ class StackTraceMarshaller implements InterfaceMarshaller { private static final String POST_CONTEXT_PARAMETER = "post_context"; private static final String IN_APP_PARAMETER = "in_app"; private static final String VARIABLES_PARAMETER = "vars"; - //TODO: add a way to add content here. - private Set notInAppFrames = new HashSet(); + private final Set notInAppFrames; - //TODO: Remove this attempt to set a sensible default setting. - { + public StackTraceMarshaller() { + notInAppFrames = new HashSet(); notInAppFrames.add("com.sun."); notInAppFrames.add("java."); notInAppFrames.add("javax."); @@ -36,6 +35,11 @@ class StackTraceMarshaller implements InterfaceMarshaller { notInAppFrames.add("com.intellij.rt."); } + public StackTraceMarshaller(Set notInAppFrames) { + // Makes a copy to avoid an external modification. + this.notInAppFrames = new HashSet(notInAppFrames); + } + /** * Create a fake frame to allow chained exceptions. * From d9064d0da9b8fa16fbf0f39927ba2f8075a0c3d9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 17:12:53 +0100 Subject: [PATCH 0207/2152] Let the Dsn build itself from the env variables Add a new constructor to DSN that will allow it to build itself from the settings in JNDI/Environment variables/System variables. --- .../main/java/net/kencochrane/raven/Dsn.java | 42 +++++++++++++++++++ .../java/net/kencochrane/raven/Raven.java | 37 +--------------- 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Dsn.java b/raven/src/main/java/net/kencochrane/raven/Dsn.java index 22d817b921d..a5c12b77b67 100644 --- a/raven/src/main/java/net/kencochrane/raven/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/Dsn.java @@ -2,9 +2,15 @@ import net.kencochrane.raven.exception.InvalidDsnException; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.naming.NoInitialContextException; import java.net.URI; import java.net.URISyntaxException; import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Data Source name allowing a direct connection to a Sentry server. @@ -34,6 +40,7 @@ public class Dsn { * Protocol setting to disable security checks over an SSL connection. */ public static final String NAIVE_PROTOCOL = "naive"; + private static final Logger logger = Logger.getLogger(Raven.class.getCanonicalName()); private String secretKey; private String publicKey; private String projectId; @@ -45,6 +52,11 @@ public class Dsn { private Map options; private URI uri; + + public Dsn() { + this(dsnLookup()); + } + /** * Creates a DS based on a String. * @@ -72,6 +84,36 @@ public Dsn(String dsn) { } } + public static String dsnLookup() { + String dsn = null; + + // Try to obtain the DSN from JNDI + try { + Context c = new InitialContext(); + dsn = (String) c.lookup("java:comp/env/sentry/dsn"); + } catch (NoInitialContextException e) { + logger.log(Level.INFO, "JNDI not configured for sentry (NoInitialContextEx)"); + } catch (NamingException e) { + logger.log(Level.INFO, "No /sentry/dsn in JNDI"); + } catch (RuntimeException ex) { + logger.log(Level.INFO, "Odd RuntimeException while testing for JNDI: " + ex.getMessage()); + } + + // Try to obtain the DSN from a System Environment Variable + if (dsn == null) + dsn = System.getenv(Dsn.DSN_VARIABLE); + + // Try to obtain the DSN from a Java System Property + if (dsn == null) + dsn = System.getProperty(Dsn.DSN_VARIABLE); + + if (dsn != null) { + return dsn; + } else { + throw new InvalidDsnException("Couldn't find a Sentry DSN in either the Java or System environment."); + } + } + private void extractPathInfo(URI uri) { String uriPath = uri.getPath(); if (uriPath == null) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index bf96c1ceb78..b04ad4bffd6 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -6,13 +6,8 @@ import net.kencochrane.raven.connection.UdpConnection; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.LoggedEvent; -import net.kencochrane.raven.exception.InvalidDsnException; import net.kencochrane.raven.marshaller.simplejson.JsonMarshaller; -import javax.naming.Context; -import javax.naming.InitialContext; -import javax.naming.NamingException; -import javax.naming.NoInitialContextException; import java.nio.charset.Charset; import java.util.UUID; import java.util.logging.Level; @@ -25,7 +20,7 @@ public class Raven { private Connection connection; public Raven() { - this(dsnLookup()); + this(new Dsn()); } public Raven(String dsn) { @@ -40,36 +35,6 @@ public Raven(Connection connection) { this.connection = connection; } - private static String dsnLookup() { - String dsn = null; - - // Try to obtain the DSN from JNDI - try { - Context c = new InitialContext(); - dsn = (String) c.lookup("java:comp/env/sentry/dsn"); - } catch (NoInitialContextException e) { - logger.log(Level.INFO, "JNDI not configured for sentry (NoInitialContextEx)"); - } catch (NamingException e) { - logger.log(Level.INFO, "No /sentry/dsn in JNDI"); - } catch (RuntimeException ex) { - logger.log(Level.INFO, "Odd RuntimeException while testing for JNDI: " + ex.getMessage()); - } - - // Try to obtain the DSN from a System Environment Variable - if (dsn == null) - dsn = System.getenv(Dsn.DSN_VARIABLE); - - // Try to obtain the DSN from a Java System Property - if (dsn == null) - dsn = System.getProperty(Dsn.DSN_VARIABLE); - - if (dsn != null) { - return dsn; - } else { - throw new InvalidDsnException("Couldn't find a Sentry DSN in either the Java or System environment."); - } - } - private static Charset determineCharset(Dsn dsn) { String charset = DEFAULT_CHARSET; From a0e0aba97cd58041f37365c52921f71ecfd72f05 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 17:31:16 +0100 Subject: [PATCH 0208/2152] Move the DSN nullcheck to Dsn(String) --- raven/src/main/java/net/kencochrane/raven/Dsn.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Dsn.java b/raven/src/main/java/net/kencochrane/raven/Dsn.java index a5c12b77b67..12d0c0bd45a 100644 --- a/raven/src/main/java/net/kencochrane/raven/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/Dsn.java @@ -63,6 +63,9 @@ public Dsn() { * @param dsn dsn in a string form. */ public Dsn(String dsn) { + if (dsn == null) + throw new InvalidDsnException("The sentry DSN must be provided and not be null"); + options = new HashMap(); protocolSettings = new HashSet(); @@ -107,11 +110,7 @@ public static String dsnLookup() { if (dsn == null) dsn = System.getProperty(Dsn.DSN_VARIABLE); - if (dsn != null) { - return dsn; - } else { - throw new InvalidDsnException("Couldn't find a Sentry DSN in either the Java or System environment."); - } + return dsn; } private void extractPathInfo(URI uri) { From fb990ea46543de04f0889ce93df90d6a1bd4850e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 17:33:28 +0100 Subject: [PATCH 0209/2152] Avoid unnecessary shadowing of the uri field --- .../main/java/net/kencochrane/raven/Dsn.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Dsn.java b/raven/src/main/java/net/kencochrane/raven/Dsn.java index 12d0c0bd45a..d2c694789f9 100644 --- a/raven/src/main/java/net/kencochrane/raven/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/Dsn.java @@ -113,8 +113,8 @@ public static String dsnLookup() { return dsn; } - private void extractPathInfo(URI uri) { - String uriPath = uri.getPath(); + private void extractPathInfo(URI dsnUri) { + String uriPath = dsnUri.getPath(); if (uriPath == null) return; int projectIdStart = uriPath.lastIndexOf("/") + 1; @@ -122,13 +122,13 @@ private void extractPathInfo(URI uri) { projectId = uriPath.substring(projectIdStart); } - private void extractHostInfo(URI uri) { - host = uri.getHost(); - port = uri.getPort(); + private void extractHostInfo(URI dsnUri) { + host = dsnUri.getHost(); + port = dsnUri.getPort(); } - private void extractProtocolInfo(URI uri) { - String scheme = uri.getScheme(); + private void extractProtocolInfo(URI dsnUri) { + String scheme = dsnUri.getScheme(); if (scheme == null) return; String[] schemeDetails = scheme.split("\\+"); @@ -136,8 +136,8 @@ private void extractProtocolInfo(URI uri) { protocol = schemeDetails[schemeDetails.length - 1]; } - private void extractUserKeys(URI uri) { - String userInfo = uri.getUserInfo(); + private void extractUserKeys(URI dsnUri) { + String userInfo = dsnUri.getUserInfo(); if (userInfo == null) return; String[] userDetails = userInfo.split(":"); @@ -146,8 +146,8 @@ private void extractUserKeys(URI uri) { secretKey = userDetails[1]; } - private void extractOptions(URI uri) { - String query = uri.getQuery(); + private void extractOptions(URI dsnUrgi) { + String query = dsnUri.getQuery(); if (query == null) return; String[] optionPairs = query.split("&"); From b8ca9813c3120317ed69267d3d2fd087a5ced628 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 17:34:52 +0100 Subject: [PATCH 0210/2152] Document methods in Dsn --- .../main/java/net/kencochrane/raven/Dsn.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/Dsn.java b/raven/src/main/java/net/kencochrane/raven/Dsn.java index d2c694789f9..bed0f53bc72 100644 --- a/raven/src/main/java/net/kencochrane/raven/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/Dsn.java @@ -61,6 +61,7 @@ public Dsn() { * Creates a DS based on a String. * * @param dsn dsn in a string form. + * @throws InvalidDsnException the given DSN isn't usable. */ public Dsn(String dsn) { if (dsn == null) @@ -87,6 +88,11 @@ public Dsn(String dsn) { } } + /** + * Looks for a DSN configuration within JNDI, the System environment or Java properties. + * + * @return a DSN configuration or null if nothing could be found. + */ public static String dsnLookup() { String dsn = null; @@ -113,6 +119,11 @@ public static String dsnLookup() { return dsn; } + /** + * Extracts the path and the project ID from the DSN provided as an {@code URI}. + * + * @param dsnUri DSN as an URI. + */ private void extractPathInfo(URI dsnUri) { String uriPath = dsnUri.getPath(); if (uriPath == null) @@ -122,11 +133,21 @@ private void extractPathInfo(URI dsnUri) { projectId = uriPath.substring(projectIdStart); } + /** + * Extracts the hostname and port of the Sentry server from the DSN provided as an {@code URI}. + * + * @param dsnUri DSN as an URI. + */ private void extractHostInfo(URI dsnUri) { host = dsnUri.getHost(); port = dsnUri.getPort(); } + /** + * Extracts the scheme and additional protocol options from the DSN provided as an {@code URI}. + * + * @param dsnUri DSN as an URI. + */ private void extractProtocolInfo(URI dsnUri) { String scheme = dsnUri.getScheme(); if (scheme == null) @@ -136,6 +157,11 @@ private void extractProtocolInfo(URI dsnUri) { protocol = schemeDetails[schemeDetails.length - 1]; } + /** + * Extracts the public and secret keys from the DSN provided as an {@code URI}. + * + * @param dsnUri DSN as an URI. + */ private void extractUserKeys(URI dsnUri) { String userInfo = dsnUri.getUserInfo(); if (userInfo == null) @@ -146,6 +172,11 @@ private void extractUserKeys(URI dsnUri) { secretKey = userDetails[1]; } + /** + * Extracts the DSN options from the DSN provided as an {@code URI}. + * + * @param dsnUri DSN as an URI. + */ private void extractOptions(URI dsnUrgi) { String query = dsnUri.getQuery(); if (query == null) @@ -157,12 +188,21 @@ private void extractOptions(URI dsnUrgi) { } } + /** + * Makes protocol and dsn options immutable to allow an external usage. + */ private void makeOptionsImmutable() { // Make the options immutable options = Collections.unmodifiableMap(options); protocolSettings = Collections.unmodifiableSet(protocolSettings); } + /** + * Validates internally the DSN, and check for mandatory elements. + *

    + * Mandatory elements are the {@link #host}, {@link #publicKey}, {@link #secretKey} and {@link #projectId}. + *

    + */ private void validate() { List missingElements = new LinkedList(); if (host == null) From 53186e283d38a34b7b70cea8e102755eaa9fe601 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 18:23:39 +0100 Subject: [PATCH 0211/2152] Fix typo --- raven/src/main/java/net/kencochrane/raven/Dsn.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Dsn.java b/raven/src/main/java/net/kencochrane/raven/Dsn.java index bed0f53bc72..7819724cd8a 100644 --- a/raven/src/main/java/net/kencochrane/raven/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/Dsn.java @@ -177,7 +177,7 @@ private void extractUserKeys(URI dsnUri) { * * @param dsnUri DSN as an URI. */ - private void extractOptions(URI dsnUrgi) { + private void extractOptions(URI dsnUri) { String query = dsnUri.getQuery(); if (query == null) return; From cd9da232e2d1aaaf20c7eb5eb4e4a78830a33ff2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 18:23:46 +0100 Subject: [PATCH 0212/2152] Delegate hashCode and equals from ImmutableThrowable It's bad in most cases, but this could actually be helpful --- .../raven/event/interfaces/ImmutableThrowable.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java index 43e30b3c135..ecf1e820100 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java @@ -79,4 +79,14 @@ public StackTraceElement[] getStackTrace() { public void setStackTrace(StackTraceElement[] stackTrace) { throw new UnsupportedOperationException(); } + + @Override + public int hashCode() { + return actualThrowable.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return actualThrowable.equals(obj); + } } From 976242fe2e0a5c898f4bdc5be3132421854efbc4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 18:24:24 +0100 Subject: [PATCH 0213/2152] Reorganise imports --- raven/src/test/java/net/kencochrane/raven/RavenTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index 8abdcd91bf1..d0cb155a946 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -3,7 +3,6 @@ import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.LoggedEvent; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -13,9 +12,7 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; public class RavenTest { @Mock From 37602d31c516753624c98c51b792df5a752bf8ee Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 18:24:43 +0100 Subject: [PATCH 0214/2152] Add tests for the JUL handler --- .../raven/jul/SentryHandlerTest.java | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java new file mode 100644 index 00000000000..8b0278a52c8 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java @@ -0,0 +1,120 @@ +package net.kencochrane.raven.jul; + +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.MessageInterface; +import net.kencochrane.raven.event.interfaces.SentryInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; + +public class SentryHandlerTest { + @Mock + private Raven mockRaven; + private Logger logger; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + logger = Logger.getAnonymousLogger(); + logger.setLevel(Level.ALL); + logger.addHandler(new SentryHandler(mockRaven)); + } + + @Test + public void testSimpleMessageLogging() { + ArgumentCaptor eventBuilderCaptor = ArgumentCaptor.forClass(EventBuilder.class); + LoggedEvent event; + + String message = UUID.randomUUID().toString(); + logger.log(Level.INFO, message); + + verify(mockRaven).sendEvent(eventBuilderCaptor.capture()); + event = eventBuilderCaptor.getValue().build(); + + assertEquals(logger.getName(), event.getLogger()); + assertEquals(message, event.getMessage()); + } + + @Test + public void testLogLevelConversions() { + assertLevelConverted(LoggedEvent.Level.DEBUG, Level.FINEST); + assertLevelConverted(LoggedEvent.Level.DEBUG, Level.FINER); + assertLevelConverted(LoggedEvent.Level.DEBUG, Level.FINE); + assertLevelConverted(LoggedEvent.Level.DEBUG, Level.CONFIG); + assertLevelConverted(LoggedEvent.Level.INFO, Level.INFO); + assertLevelConverted(LoggedEvent.Level.WARNING, Level.WARNING); + assertLevelConverted(LoggedEvent.Level.ERROR, Level.SEVERE); + } + + private void assertLevelConverted(LoggedEvent.Level expectedLevel, Level level){ + reset(mockRaven); + ArgumentCaptor eventBuilderCaptor = ArgumentCaptor.forClass(EventBuilder.class); + + logger.log(level, null); + verify(mockRaven).sendEvent(eventBuilderCaptor.capture()); + assertEquals(expectedLevel, eventBuilderCaptor.getValue().build().getLevel()); + } + + @Test + public void testLogException() { + ArgumentCaptor eventBuilderCaptor = ArgumentCaptor.forClass(EventBuilder.class); + Exception exception = new Exception(); + LoggedEvent event; + + logger.log(Level.SEVERE, "message", exception); + + verify(mockRaven).sendEvent(eventBuilderCaptor.capture()); + event = eventBuilderCaptor.getValue().build(); + SentryInterface exceptionInterface = event.getSentryInterfaces().get(ExceptionInterface.EXCEPTION_INTERFACE); + assertThat(exceptionInterface, instanceOf(ExceptionInterface.class)); + + // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. + // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. + assertTrue(((ExceptionInterface) exceptionInterface).getThrowable().equals(exception)); + + + SentryInterface stackTraceInterface = event.getSentryInterfaces().get(StackTraceInterface.STACKTRACE_INTERFACE); + assertThat(stackTraceInterface, instanceOf(StackTraceInterface.class)); + + // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. + // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. + assertTrue(((StackTraceInterface) stackTraceInterface).getThrowable().equals(exception)); + } + + @Test + public void testLogParametrisedMessage() { + ArgumentCaptor eventBuilderCaptor = ArgumentCaptor.forClass(EventBuilder.class); + String message = UUID.randomUUID().toString(); + List parameters = Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()); + LoggedEvent event; + + logger.log(Level.INFO, message, parameters.toArray()); + + verify(mockRaven).sendEvent(eventBuilderCaptor.capture()); + event = eventBuilderCaptor.getValue().build(); + SentryInterface exceptionInterface = event.getSentryInterfaces().get(MessageInterface.MESSAGE_INTERFACE); + assertThat(exceptionInterface, instanceOf(MessageInterface.class)); + + assertEquals(message, ((MessageInterface) exceptionInterface).getMessage()); + assertEquals(parameters, ((MessageInterface) exceptionInterface).getParams()); + } +} From 7244c02d2725fa48a2809768aedc7c84fa18646c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 22:05:54 +0100 Subject: [PATCH 0215/2152] Add Hamcrest as a dependency during tests --- pom.xml | 6 ++++++ raven/pom.xml | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/pom.xml b/pom.xml index db9d4008ef9..52ac18469e5 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ 1.6 1.1.1 3.0.1 + 1.3 @@ -126,6 +127,11 @@ junit ${junit.version}
    + + org.hamcrest + hamcrest-core + ${hamcrest.version} +
    diff --git a/raven/pom.xml b/raven/pom.xml index 26cf45ad7a3..8e0f75590ec 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -38,5 +38,10 @@ junit test + + org.hamcrest + hamcrest-core + test + From 474ea4c7d6207f2b743e6dbc1706a6baae4b5de4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Apr 2013 22:06:07 +0100 Subject: [PATCH 0216/2152] Disable parentHandlers during tests --- .../test/java/net/kencochrane/raven/jul/SentryHandlerTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java index 8b0278a52c8..4fc53df074a 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java @@ -35,6 +35,7 @@ public class SentryHandlerTest { public void setUp() { MockitoAnnotations.initMocks(this); logger = Logger.getAnonymousLogger(); + logger.setUseParentHandlers(false); logger.setLevel(Level.ALL); logger.addHandler(new SentryHandler(mockRaven)); } From cacbbb2d107fa2190642faa9e4407a1fc886c1b1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 9 Apr 2013 10:18:05 +0100 Subject: [PATCH 0217/2152] Add the jackson library --- pom.xml | 6 ++++++ raven/pom.xml | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/pom.xml b/pom.xml index 52ac18469e5..20547c6baaf 100644 --- a/pom.xml +++ b/pom.xml @@ -32,6 +32,7 @@ 1.2.17 1.6 1.1.1 + 2.1.4 3.0.1 1.3 @@ -97,6 +98,11 @@ json-simple ${json-simple.version} + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + javax.servlet javax.servlet-api diff --git a/raven/pom.xml b/raven/pom.xml index 8e0f75590ec..bbe136b7027 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -23,6 +23,10 @@ com.googlecode.json-simple json-simple + + com.fasterxml.jackson.core + jackson-core + javax.servlet javax.servlet-api From 9a14d519636dd844920b8396e40948d43959909a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 9 Apr 2013 16:52:04 +0100 Subject: [PATCH 0218/2152] Remove unnecessary dot --- raven/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/pom.xml b/raven/pom.xml index bbe136b7027..c97c1215a99 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -12,7 +12,7 @@ raven jar The Java Raven client - Java Raven client. + Java Raven client From 8cbd6e8f772a276e0cc7c44d9bc5af7807bde893 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 9 Apr 2013 16:52:33 +0100 Subject: [PATCH 0219/2152] Create a marshaller using Jackson streaming API --- .../json/ExceptionInterfaceBinding.java | 24 ++ .../marshaller/json/HttpInterfaceBinding.java | 94 +++++++ .../marshaller/json/InterfaceBinding.java | 10 + .../raven/marshaller/json/JsonMarshaller.java | 254 ++++++++++++++++++ .../json/MessageInterfaceBinding.java | 23 ++ .../json/StackTraceInterfaceBinding.java | 110 ++++++++ 6 files changed, 515 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java create mode 100644 raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java create mode 100644 raven/src/main/java/net/kencochrane/raven/marshaller/json/InterfaceBinding.java create mode 100644 raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java create mode 100644 raven/src/main/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBinding.java create mode 100644 raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java new file mode 100644 index 00000000000..386d2f10e9f --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java @@ -0,0 +1,24 @@ +package net.kencochrane.raven.marshaller.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.ImmutableThrowable; + +import java.io.IOException; + +public class ExceptionInterfaceBinding implements InterfaceBinding { + private static final String TYPE_PARAMETER = "type"; + private static final String VALUE_PARAMETER = "value"; + private static final String MODULE_PARAMETER = "module"; + + @Override + public void writeInterface(JsonGenerator generator, ExceptionInterface exceptionInterface) throws IOException { + ImmutableThrowable throwable = exceptionInterface.getThrowable(); + + generator.writeStartObject(); + generator.writeStringField(TYPE_PARAMETER, throwable.getActualClass().getSimpleName()); + generator.writeStringField(VALUE_PARAMETER, throwable.getMessage()); + generator.writeStringField(MODULE_PARAMETER, throwable.getActualClass().getPackage().getName()); + generator.writeEndObject(); + } +} diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java new file mode 100644 index 00000000000..4189f23ef91 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java @@ -0,0 +1,94 @@ +package net.kencochrane.raven.marshaller.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import net.kencochrane.raven.event.interfaces.HttpInterface; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +public class HttpInterfaceBinding implements InterfaceBinding { + private static final String URL = "url"; + private static final String METHOD = "method"; + private static final String DATA = "data"; + private static final String QUERY_STRING = "query_string"; + private static final String COOKIES = "cookies"; + private static final String HEADERS = "headers"; + private static final String ENVIRONMENT = "env"; + private static final String REMOTE_ADDR = "REMOTE_ADDR"; + private static final String SERVER_NAME = "SERVER_NAME"; + private static final String SERVER_PORT = "SERVER_PORT"; + private static final String SERVER_PROTOCOL = "SERVER_PROTOCOL"; + + @Override + public void writeInterface(JsonGenerator generator, HttpInterface httpInterface) throws IOException { + HttpServletRequest request = httpInterface.getRequest(); + + generator.writeStartObject(); + generator.writeStringField(URL, request.getRequestURL().toString()); + generator.writeStringField(METHOD, request.getMethod()); + generator.writeFieldName(DATA); + writeData(generator, request.getParameterMap()); + generator.writeStringField(QUERY_STRING, request.getQueryString()); + generator.writeFieldName(COOKIES); + writeCookies(generator, request.getCookies()); + generator.writeFieldName(HEADERS); + writeHeaders(generator, request); + generator.writeFieldName(ENVIRONMENT); + writeEnvironment(generator, request); + generator.writeEndObject(); + } + + private void writeEnvironment(JsonGenerator generator, HttpServletRequest request) throws IOException{ + generator.writeStartObject(); + generator.writeStringField(REMOTE_ADDR, request.getRemoteAddr()); + generator.writeStringField(SERVER_NAME, request.getServerName()); + generator.writeNumberField(SERVER_PORT, request.getServerPort()); + generator.writeStringField(SERVER_PROTOCOL, request.getProtocol()); + generator.writeEndObject(); + } + + private void writeHeaders(JsonGenerator generator, HttpServletRequest request) throws IOException { + generator.writeStartObject(); + for (String header : Collections.list(request.getHeaderNames())) { + generator.writeArrayFieldStart(header); + for (String headerValue : request.getParameterValues(header)) { + generator.writeString(headerValue); + } + generator.writeEndArray(); + } + generator.writeEndObject(); + } + + private void writeCookies(JsonGenerator generator, Cookie[] cookies) throws IOException { + if (cookies == null) { + generator.writeNull(); + return; + } + + generator.writeStartObject(); + for (Cookie cookie : cookies) { + generator.writeStringField(cookie.getName(), cookie.getValue()); + } + generator.writeStartObject(); + } + + private void writeData(JsonGenerator generator, Map parameterMap) throws IOException { + if (parameterMap == null) { + generator.writeNull(); + return; + } + + generator.writeStartObject(); + for (Map.Entry parameter : parameterMap.entrySet()) { + generator.writeArrayFieldStart(parameter.getKey()); + for (String parameterValue : parameter.getValue()) { + generator.writeString(parameterValue); + } + generator.writeEndArray(); + } + generator.writeEndObject(); + } +} diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/InterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/InterfaceBinding.java new file mode 100644 index 00000000000..dee49030a1a --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/InterfaceBinding.java @@ -0,0 +1,10 @@ +package net.kencochrane.raven.marshaller.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import net.kencochrane.raven.event.interfaces.SentryInterface; + +import java.io.IOException; + +public interface InterfaceBinding { + void writeInterface(JsonGenerator generator, T sentryInterface) throws IOException; +} diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java new file mode 100644 index 00000000000..34cc5b60d2c --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -0,0 +1,254 @@ +package net.kencochrane.raven.marshaller.json; + +import com.fasterxml.jackson.core.JsonEncoding; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.event.interfaces.*; +import net.kencochrane.raven.marshaller.Marshaller; +import org.apache.commons.codec.binary.Base64OutputStream; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.DeflaterOutputStream; + +public class JsonMarshaller implements Marshaller { + /** + * Hexadecimal string representing a uuid4 value. + */ + public static final String EVENT_ID = "event_id"; + /** + * User-readable representation of this event. + */ + public static final String MESSAGE = "message"; + /** + * Indicates when the logging record was created. + */ + public static final String TIMESTAMP = "timestamp"; + /** + * The record severity. + */ + public static final String LEVEL = "level"; + /** + * The name of the logger which created the record. + */ + public static final String LOGGER = "logger"; + /** + * A string representing the platform the client is submitting from. + */ + public static final String PLATFORM = "platform"; + /** + * Function call which was the primary perpetrator of this event. + */ + public static final String CULPRIT = "culprit"; + /** + * A map or list of tags for this event. + */ + public static final String TAGS = "tags"; + /** + * Identifies the host client from which the event was recorded. + */ + public static final String SERVER_NAME = "server_name"; + /** + * A list of relevant modules and their versions. + */ + public static final String MODULES = "modules"; + /** + * An arbitrary mapping of additional metadata to store with the event. + */ + public static final String EXTRA = "extra"; + /** + * Checksum for the event, allowing to group events with a similar checksum. + */ + public static final String CHECKSUM = "checksum"; + /** + * Maximum length for a message. + */ + public static final int MAX_MESSAGE_LENGTH = 1000; + /** + * Date format for ISO 8601. + */ + private static final DateFormat ISO_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ"); + private static final Logger logger = Logger.getLogger(JsonMarshaller.class.getCanonicalName()); + private final JsonFactory jsonFactory = new JsonFactory(); + private final Map, InterfaceBinding> interfaceBindings = + new HashMap, InterfaceBinding>(); + /** + * Enables disables the compression of JSON. + */ + private boolean compression = true; + /** + * Charset used to send Json, by default UTF-8 will be used. + */ + private JsonEncoding charset = JsonEncoding.UTF8; + + public JsonMarshaller() { + addInterfaceBinding(ExceptionInterface.class, new ExceptionInterfaceBinding()); + addInterfaceBinding(StackTraceInterface.class, new StackTraceInterfaceBinding()); + addInterfaceBinding(MessageInterface.class, new MessageInterfaceBinding()); + addInterfaceBinding(HttpInterface.class, new HttpInterfaceBinding()); + } + + @Override + public void marshall(LoggedEvent event, OutputStream destination) { + + if (compression) + destination = new Base64OutputStream(new DeflaterOutputStream(destination)); + + JsonGenerator generator = null; + try { + generator = jsonFactory.createGenerator(destination, charset); + writeContent(generator, event); + } catch (IOException e) { + logger.log(Level.SEVERE, "An exception occurred while serialising the event.", e); + } finally { + try { + if (generator != null) + generator.close(); + } catch (IOException e) { + logger.log(Level.SEVERE, "An exception occurred while closing the json stream.", e); + } + } + } + + private void writeContent(JsonGenerator generator, LoggedEvent event) throws IOException { + generator.writeStartObject(); + + generator.writeStringField(EVENT_ID, formatId(event.getId())); + generator.writeStringField(MESSAGE, formatMessage(event.getMessage())); + generator.writeStringField(TIMESTAMP, formatTimestamp(event.getTimestamp())); + generator.writeStringField(LEVEL, formatLevel(event.getLevel())); + generator.writeStringField(LOGGER, event.getLogger()); + generator.writeStringField(PLATFORM, event.getPlatform()); + generator.writeStringField(CULPRIT, event.getCulprit()); + writeTags(generator, event.getTags()); + generator.writeStringField(SERVER_NAME, event.getServerName()); + writeExtras(generator, event.getExtra()); + generator.writeStringField(CHECKSUM, event.getChecksum()); + + for (Map.Entry interfaceEntry : event.getSentryInterfaces().entrySet()) { + SentryInterface sentryInterface = interfaceEntry.getValue(); + + if (interfaceBindings.containsKey(sentryInterface.getClass())) { + generator.writeFieldName(interfaceEntry.getKey()); + interfaceBindings.get(sentryInterface.getClass()).writeInterface(generator, sentryInterface); + } else { + logger.log(Level.SEVERE, "Couldn't parse the content of '" + interfaceEntry.getKey() + "' " + + "provided in " + sentryInterface + "."); + } + } + + generator.writeEndObject(); + } + + private void writeExtras(JsonGenerator generator, Map extras) throws IOException { + generator.writeObjectFieldStart(EXTRA); + for (Map.Entry extra : extras.entrySet()) { + generator.writeObjectField(extra.getKey(), extra.getValue()); + } + generator.writeEndObject(); + } + + private void writeTags(JsonGenerator generator, Map> tags) throws IOException { + generator.writeObjectFieldStart(TAGS); + for (Map.Entry> tag : tags.entrySet()) { + generator.writeArrayFieldStart(tag.getKey()); + for (String tagValue : tag.getValue()) { + generator.writeString(tagValue); + } + generator.writeEndArray(); + } + generator.writeEndObject(); + } + + /** + * Formats a message, ensuring that the maximum length {@link #MAX_MESSAGE_LENGTH} isn't reached. + * + * @param message message to format. + * @return formatted message (shortened if necessary). + */ + private String formatMessage(String message) { + if (message == null) + return null; + else if (message.length() > MAX_MESSAGE_LENGTH) + return message.substring(0, MAX_MESSAGE_LENGTH); + else return message; + } + + /** + * Formats the {@code UUID} to send only the 32 necessary characters. + * + * @param id uuid to format. + * @return a {@code UUID} stripped from the "-" characters. + */ + private String formatId(UUID id) { + return id.toString().replaceAll("-", ""); + } + + /** + * Formats a log level into one of the accepted string representation of a log level. + * + * @param level log level to format. + * @return log level as a String. + */ + private String formatLevel(LoggedEvent.Level level) { + if (level == null) + return null; + + switch (level) { + case DEBUG: + return "debug"; + case FATAL: + return "fatal"; + case WARNING: + return "warning"; + case INFO: + return "info"; + case ERROR: + return "error"; + default: + return null; + } + } + + /** + * Formats a timestamp in the ISO-8601 format without timezone. + * + * @param timestamp date to format. + * @return timestamp as a formatted String. + */ + private String formatTimestamp(Date timestamp) { + return ISO_FORMAT.format(timestamp); + } + + public void addInterfaceBinding(Class sentryInterfaceClass, + InterfaceBinding binding) { + this.interfaceBindings.put(sentryInterfaceClass, binding); + } + + /** + * Enables the JSON compression with deflate. + * + * @param compression state of the compression. + */ + public void setCompression(boolean compression) { + this.compression = compression; + } + + public void setCharset(Charset charset) { + for (JsonEncoding jsonEncoding : JsonEncoding.values()) { + if (jsonEncoding.getJavaName().equals(charset.name())) { + this.charset = jsonEncoding; + return; + } + } + throw new IllegalArgumentException("Couldn't set the charset to " + charset + ". " + + "The supported charsets are '" + JsonEncoding.values() + "'"); + } +} diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBinding.java new file mode 100644 index 00000000000..2bb972d9ca3 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBinding.java @@ -0,0 +1,23 @@ +package net.kencochrane.raven.marshaller.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import net.kencochrane.raven.event.interfaces.MessageInterface; + +import java.io.IOException; + +public class MessageInterfaceBinding implements InterfaceBinding { + private static final String MESSAGE_PARAMETER = "message"; + private static final String PARAMS_PARAMETER = "params"; + + @Override + public void writeInterface(JsonGenerator generator, MessageInterface messageInterface) throws IOException { + generator.writeStartObject(); + generator.writeStringField(MESSAGE_PARAMETER, messageInterface.getMessage()); + generator.writeArrayFieldStart(PARAMS_PARAMETER); + for (String parameter : messageInterface.getParams()) { + generator.writeString(parameter); + } + generator.writeEndArray(); + generator.writeEndObject(); + } +} diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java new file mode 100644 index 00000000000..34a13a6c14e --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java @@ -0,0 +1,110 @@ +package net.kencochrane.raven.marshaller.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import net.kencochrane.raven.event.interfaces.ImmutableThrowable; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; + +import java.io.IOException; +import java.util.*; + +public class StackTraceInterfaceBinding implements InterfaceBinding { + private static final String FRAMES_PARAMETER = "frames"; + private static final String FILENAME_PARAMETER = "filename"; + private static final String FUNCTION_PARAMETER = "function"; + private static final String MODULE_PARAMETER = "module"; + private static final String LINE_NO_PARAMETER = "lineno"; + private static final String ABSOLUTE_PATH_PARAMETER = "abs_path"; + private static final String CONTEXT_LINE_PARAMETER = "context_line"; + private static final String PRE_CONTEXT_PARAMETER = "pre_context"; + private static final String POST_CONTEXT_PARAMETER = "post_context"; + private static final String IN_APP_PARAMETER = "in_app"; + private static final String VARIABLES_PARAMETER = "vars"; + private final Set notInAppFrames; + + public StackTraceInterfaceBinding() { + notInAppFrames = new HashSet(); + notInAppFrames.add("com.sun."); + notInAppFrames.add("java."); + notInAppFrames.add("javax."); + notInAppFrames.add("org.omg."); + notInAppFrames.add("sun."); + notInAppFrames.add("junit."); + notInAppFrames.add("com.intellij.rt."); + } + + public StackTraceInterfaceBinding(Set notInAppFrames) { + // Makes a copy to avoid an external modification. + this.notInAppFrames = new HashSet(notInAppFrames); + } + + /** + * Writes a fake frame to allow chained exceptions. + * + * @param throwable Exception for which a fake frame should be created + */ + private void writeFakeFrame(JsonGenerator generator, ImmutableThrowable throwable) throws IOException { + String message = "Caused by: " + throwable.getActualClass().getName(); + if (throwable.getMessage() != null) + message += " (\"" + throwable.getMessage() + "\")"; + + generator.writeStartObject(); + generator.writeStringField(MODULE_PARAMETER, message); + generator.writeBooleanField(IN_APP_PARAMETER, true); + generator.writeEndObject(); + } + + /** + * Writes a single frame based on a {@code StackTraceElement}. + * + * @param stackTraceElement current frame in the stackTrace. + */ + private void writeFrame(JsonGenerator generator, StackTraceElement stackTraceElement) throws IOException { + generator.writeStartObject(); + // Do not display the file name (irrelevant) as it replaces the module in the sentry interface. + //generator.writeStringField(FILENAME_PARAMETER, stackTraceElement.getFileName()); + generator.writeStringField(MODULE_PARAMETER, stackTraceElement.getClassName()); + generator.writeBooleanField(IN_APP_PARAMETER, isFrameInApp(stackTraceElement)); + generator.writeStringField(FUNCTION_PARAMETER, stackTraceElement.getMethodName()); + generator.writeNumberField(LINE_NO_PARAMETER, stackTraceElement.getLineNumber()); + generator.writeEndObject(); + } + + private boolean isFrameInApp(StackTraceElement stackTraceElement) { + //TODO: A set is absolutely not performant here, a Trie could be a better solution. + for (String notInAppFrame : notInAppFrames) { + if (stackTraceElement.getClassName().startsWith(notInAppFrame)) { + return false; + } + } + return true; + } + + @Override + public void writeInterface(JsonGenerator generator, StackTraceInterface stackTraceInterface) throws IOException { + ImmutableThrowable currentThrowable = stackTraceInterface.getThrowable(); + Deque throwableStack = new LinkedList(); + + //Inverse the chain of exceptions to get the first exception thrown first. + while (currentThrowable != null) { + throwableStack.push(currentThrowable); + currentThrowable = currentThrowable.getCause(); + } + + generator.writeStartObject(); + generator.writeArrayFieldStart(FRAMES_PARAMETER); + while (!throwableStack.isEmpty()) { + currentThrowable = throwableStack.pop(); + StackTraceElement[] stackFrames = currentThrowable.getStackTrace(); + + // Go through the stackTrace frames from the first call to the last + for (int i = currentThrowable.getStackTrace().length - 1; i >= 0; i--) { + writeFrame(generator, stackFrames[i]); + } + + if (!throwableStack.isEmpty()) + writeFakeFrame(generator, currentThrowable); + } + generator.writeEndArray(); + generator.writeEndObject(); + } +} From 285dd649e715eed56aedfe5ab12d2b3fe64aa99d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 9 Apr 2013 16:53:13 +0100 Subject: [PATCH 0220/2152] Add unit tests for the new JSON marshaller --- pom.xml | 5 ++ raven/pom.xml | 5 ++ .../json/AbstractTestInterfaceBinding.java | 35 ++++++++++++++ .../json/TestExceptionInterfaceBinding.java | 41 ++++++++++++++++ .../json/TestMessageInterfaceBinding.java | 42 ++++++++++++++++ .../json/TestStackTraceInterfaceBinding.java | 48 +++++++++++++++++++ 6 files changed, 176 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractTestInterfaceBinding.java create mode 100644 raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java create mode 100644 raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java create mode 100644 raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java diff --git a/pom.xml b/pom.xml index 20547c6baaf..45d8cff9620 100644 --- a/pom.xml +++ b/pom.xml @@ -103,6 +103,11 @@ jackson-core ${jackson.version} + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + javax.servlet javax.servlet-api diff --git a/raven/pom.xml b/raven/pom.xml index c97c1215a99..d4ddda03eba 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -32,6 +32,11 @@ javax.servlet-api provided + + com.fasterxml.jackson.core + jackson-databind + test + org.mockito mockito-core diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractTestInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractTestInterfaceBinding.java new file mode 100644 index 00000000000..05d0bb4a867 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractTestInterfaceBinding.java @@ -0,0 +1,35 @@ +package net.kencochrane.raven.marshaller.json; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Before; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public abstract class AbstractTestInterfaceBinding { + private JsonFactory jsonFactory; + private ObjectMapper mapper; + private ByteArrayOutputStream jsonContentStream; + + @Before + protected void setUp() throws Exception{ + jsonFactory = new JsonFactory(); + mapper = new ObjectMapper(); + } + + protected JsonGenerator getJsonGenerator() throws IOException { + jsonContentStream = new ByteArrayOutputStream(); + return jsonFactory.createJsonGenerator(jsonContentStream); + } + + protected JsonParser getJsonParser() throws IOException { + return jsonFactory.createJsonParser(jsonContentStream.toByteArray()); + } + + protected ObjectMapper getMapper() { + return mapper; + } +} diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java new file mode 100644 index 00000000000..30fcb69b09d --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java @@ -0,0 +1,41 @@ +package net.kencochrane.raven.marshaller.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonNode; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.ImmutableThrowable; +import org.junit.Before; +import org.junit.Test; + +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TestExceptionInterfaceBinding extends AbstractTestInterfaceBinding { + private ExceptionInterfaceBinding interfaceBinding; + + @Before + public void setUp() throws Exception { + super.setUp(); + interfaceBinding = new ExceptionInterfaceBinding(); + } + + @Test + public void testSimpleException() throws Exception { + ExceptionInterface exceptionInterface = mock(ExceptionInterface.class); + String message = UUID.randomUUID().toString(); + Throwable throwable = new IllegalStateException(message); + when(exceptionInterface.getThrowable()).thenReturn(new ImmutableThrowable(throwable)); + + JsonGenerator jsonGenerator = getJsonGenerator(); + interfaceBinding.writeInterface(jsonGenerator, exceptionInterface); + jsonGenerator.close(); + + JsonNode rootNode = getMapper().readValue(getJsonParser(), JsonNode.class); + assertEquals(throwable.getClass().getPackage().getName(), rootNode.get("module").asText()); + assertEquals(throwable.getClass().getSimpleName(), rootNode.get("type").asText()); + assertEquals(message, rootNode.get("value").asText()); + } +} diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java new file mode 100644 index 00000000000..136485577af --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java @@ -0,0 +1,42 @@ +package net.kencochrane.raven.marshaller.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonNode; +import net.kencochrane.raven.event.interfaces.MessageInterface; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TestMessageInterfaceBinding extends AbstractTestInterfaceBinding { + private MessageInterfaceBinding interfaceBinding; + + @Before + public void setUp() throws Exception { + super.setUp(); + interfaceBinding = new MessageInterfaceBinding(); + } + + @Test + public void testSimpleMessage() throws Exception { + String message = UUID.randomUUID().toString(); + List parameters = Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()); + MessageInterface messageInterface = mock(MessageInterface.class); + when(messageInterface.getMessage()).thenReturn(message); + when(messageInterface.getParams()).thenReturn(parameters); + + JsonGenerator jSonGenerator = getJsonGenerator(); + interfaceBinding.writeInterface(jSonGenerator, messageInterface); + jSonGenerator.close(); + + JsonNode rootNode = getMapper().readValue(getJsonParser(), JsonNode.class); + assertEquals(message, rootNode.get("message").asText()); + assertEquals(parameters, getMapper().convertValue(rootNode.get("params"), List.class)); + } +} diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java new file mode 100644 index 00000000000..f8393ca7a37 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java @@ -0,0 +1,48 @@ +package net.kencochrane.raven.marshaller.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonNode; +import net.kencochrane.raven.event.interfaces.ImmutableThrowable; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TestStackTraceInterfaceBinding extends AbstractTestInterfaceBinding { + private StackTraceInterfaceBinding interfaceBinding; + + @Before + public void setUp() throws Exception { + super.setUp(); + interfaceBinding = new StackTraceInterfaceBinding(); + } + + @Test + public void testSingleStackFrame() throws Exception { + String methodName = UUID.randomUUID().toString(); + String className = UUID.randomUUID().toString(); + int lineNumber = 1; + Throwable exception = mock(Throwable.class); + StackTraceElement stackTraceElement = new StackTraceElement(className, methodName, null, lineNumber); + StackTraceInterface stackTraceInterface = mock(StackTraceInterface.class, RETURNS_DEEP_STUBS); + when(stackTraceInterface.getThrowable()).thenReturn(new ImmutableThrowable(exception)); + when(exception.getStackTrace()).thenReturn(new StackTraceElement[]{stackTraceElement}); + + JsonGenerator jSonGenerator = getJsonGenerator(); + interfaceBinding.writeInterface(jSonGenerator, stackTraceInterface); + jSonGenerator.close(); + + JsonNode frames = getMapper().readValue(getJsonParser(), JsonNode.class).get("frames"); + assertEquals(1, frames.size()); + assertEquals(className, frames.get(0).get("module").asText()); + assertEquals(methodName, frames.get(0).get("function").asText()); + assertEquals(lineNumber, frames.get(0).get("lineno").asInt()); + } +} From e1310ff339f5e3fa5f5ee1fca04a8defb98cc604 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 9 Apr 2013 16:56:17 +0100 Subject: [PATCH 0221/2152] Remove the Simple-JSON code --- pom.xml | 6 - raven/pom.xml | 4 - .../java/net/kencochrane/raven/Raven.java | 2 +- .../raven/connection/HttpConnection.java | 2 +- .../raven/connection/UdpConnection.java | 2 +- .../simplejson/ExceptionMarshaller.java | 28 --- .../marshaller/simplejson/HttpMarshaller.java | 19 -- .../simplejson/InterfaceMarshaller.java | 8 - .../marshaller/simplejson/JsonMarshaller.java | 226 ------------------ .../simplejson/MessageMarshaller.java | 24 -- .../simplejson/StackTraceMarshaller.java | 114 --------- 11 files changed, 3 insertions(+), 432 deletions(-) delete mode 100644 raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/ExceptionMarshaller.java delete mode 100644 raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/HttpMarshaller.java delete mode 100644 raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/InterfaceMarshaller.java delete mode 100644 raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/JsonMarshaller.java delete mode 100644 raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/MessageMarshaller.java delete mode 100644 raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/StackTraceMarshaller.java diff --git a/pom.xml b/pom.xml index 45d8cff9620..55d606703f3 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,6 @@ 1.0.11 1.2.17 1.6 - 1.1.1 2.1.4 3.0.1 1.3 @@ -93,11 +92,6 @@ commons-codec ${commons-codec.version} - - com.googlecode.json-simple - json-simple - ${json-simple.version} - com.fasterxml.jackson.core jackson-core diff --git a/raven/pom.xml b/raven/pom.xml index d4ddda03eba..f5556c659a7 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -19,10 +19,6 @@ commons-codec commons-codec - - com.googlecode.json-simple - json-simple - com.fasterxml.jackson.core jackson-core diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index b04ad4bffd6..dbba949d755 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -6,7 +6,7 @@ import net.kencochrane.raven.connection.UdpConnection; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.LoggedEvent; -import net.kencochrane.raven.marshaller.simplejson.JsonMarshaller; +import net.kencochrane.raven.marshaller.json.JsonMarshaller; import java.nio.charset.Charset; import java.util.UUID; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 89c17967132..0aeb80719a0 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -4,7 +4,7 @@ import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.marshaller.Marshaller; -import net.kencochrane.raven.marshaller.simplejson.JsonMarshaller; +import net.kencochrane.raven.marshaller.json.JsonMarshaller; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index 07051523d89..920f86373c7 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -3,7 +3,7 @@ import net.kencochrane.raven.Dsn; import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.marshaller.Marshaller; -import net.kencochrane.raven.marshaller.simplejson.JsonMarshaller; +import net.kencochrane.raven.marshaller.json.JsonMarshaller; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/ExceptionMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/ExceptionMarshaller.java deleted file mode 100644 index 2e0a27e058f..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/ExceptionMarshaller.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.kencochrane.raven.marshaller.simplejson; - -import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.ImmutableThrowable; -import net.kencochrane.raven.event.interfaces.SentryInterface; -import org.json.simple.JSONObject; - -class ExceptionMarshaller implements InterfaceMarshaller { - private static final String TYPE_PARAMETER = "type"; - private static final String VALUE_PARAMETER = "value"; - private static final String MODULE_PARAMETER = "module"; - - @Override - public JSONObject serialiseInterface(SentryInterface sentryInterface) { - if (!(sentryInterface instanceof ExceptionInterface)) { - //TODO: Do something better here! - throw new IllegalArgumentException(); - } - - ExceptionInterface messageInterface = (ExceptionInterface) sentryInterface; - JSONObject jsonObject = new JSONObject(); - ImmutableThrowable throwable = messageInterface.getThrowable(); - jsonObject.put(TYPE_PARAMETER, throwable.getActualClass().getSimpleName()); - jsonObject.put(VALUE_PARAMETER, throwable.getMessage()); - jsonObject.put(MODULE_PARAMETER, throwable.getActualClass().getPackage().getName()); - return jsonObject; - } -} diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/HttpMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/HttpMarshaller.java deleted file mode 100644 index 4ea33dcd9da..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/HttpMarshaller.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.kencochrane.raven.marshaller.simplejson; - -import net.kencochrane.raven.event.interfaces.HttpInterface; -import net.kencochrane.raven.event.interfaces.SentryInterface; -import org.json.simple.JSONObject; - -class HttpMarshaller implements InterfaceMarshaller { - @Override - public JSONObject serialiseInterface(SentryInterface sentryInterface) { - if (!(sentryInterface instanceof HttpInterface)) { - //TODO: Do something better here! - throw new IllegalArgumentException(); - } - - JSONObject jsonObject = new JSONObject(); - //TODO: make an actual implementation! - return jsonObject; - } -} diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/InterfaceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/InterfaceMarshaller.java deleted file mode 100644 index 92281c32982..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/InterfaceMarshaller.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.kencochrane.raven.marshaller.simplejson; - -import net.kencochrane.raven.event.interfaces.SentryInterface; -import org.json.simple.JSONObject; - -interface InterfaceMarshaller { - JSONObject serialiseInterface(SentryInterface sentryInterface); -} diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/JsonMarshaller.java deleted file mode 100644 index f4792237a18..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/JsonMarshaller.java +++ /dev/null @@ -1,226 +0,0 @@ -package net.kencochrane.raven.marshaller.simplejson; - -import net.kencochrane.raven.event.LoggedEvent; -import net.kencochrane.raven.event.interfaces.*; -import net.kencochrane.raven.marshaller.Marshaller; -import org.apache.commons.codec.binary.Base64OutputStream; -import org.json.simple.JSONObject; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.nio.charset.Charset; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.zip.DeflaterOutputStream; - -/** - * Marshaller allowing to transform a simple {@link LoggedEvent} into a compressed JSON String send over a stream. - */ -public class JsonMarshaller implements Marshaller { - /** - * Hexadecimal string representing a uuid4 value. - */ - public static final String EVENT_ID = "event_id"; - /** - * User-readable representation of this event. - */ - public static final String MESSAGE = "message"; - /** - * Indicates when the logging record was created. - */ - public static final String TIMESTAMP = "timestamp"; - /** - * The record severity. - */ - public static final String LEVEL = "level"; - /** - * The name of the logger which created the record. - */ - public static final String LOGGER = "logger"; - /** - * A string representing the platform the client is submitting from. - */ - public static final String PLATFORM = "platform"; - /** - * Function call which was the primary perpetrator of this event. - */ - public static final String CULPRIT = "culprit"; - /** - * A map or list of tags for this event. - */ - public static final String TAGS = "tags"; - /** - * Identifies the host client from which the event was recorded. - */ - public static final String SERVER_NAME = "server_name"; - /** - * A list of relevant modules and their versions. - */ - public static final String MODULES = "modules"; - /** - * An arbitrary mapping of additional metadata to store with the event. - */ - public static final String EXTRA = "extra"; - /** - * Checksum for the event, allowing to group events with a similar checksum. - */ - public static final String CHECKSUM = "checksum"; - /** - * Maximum length for a message. - */ - public static final int MAX_MESSAGE_LENGTH = 1000; - private static final Logger logger = Logger.getLogger(JsonMarshaller.class.getCanonicalName()); - /** - * Date format for ISO 8601. - */ - private static final DateFormat ISO_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ"); - private final Map, InterfaceMarshaller> interfaceMarshallers = - new HashMap, InterfaceMarshaller>(); - /** - * Enables disables the compression of JSON. - */ - private boolean compression = true; - /** - * Charset used to transmit data. - */ - private Charset charset = Charset.defaultCharset(); - - public JsonMarshaller() { - interfaceMarshallers.put(ExceptionInterface.class, new ExceptionMarshaller()); - interfaceMarshallers.put(HttpInterface.class, new HttpMarshaller()); - interfaceMarshallers.put(MessageInterface.class, new MessageMarshaller()); - interfaceMarshallers.put(StackTraceInterface.class, new StackTraceMarshaller()); - } - - @Override - public void marshall(LoggedEvent event, OutputStream destination) { - OutputStream outputStream = new Base64OutputStream(destination); - if (compression) - outputStream = new DeflaterOutputStream(outputStream); - - Writer writer = new OutputStreamWriter(outputStream, charset); - JSONObject jsonObject = encodeToJSONObject(event); - try { - jsonObject.writeJSONString(writer); - } catch (Exception e) { - logger.log(Level.SEVERE, "An exception occurred serialising the event.", e); - } finally { - try { - writer.close(); - } catch (IOException e) { - } - } - } - - @SuppressWarnings("unchecked") - private JSONObject encodeToJSONObject(LoggedEvent event) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put(EVENT_ID, formatId(event.getId())); - jsonObject.put(MESSAGE, formatMessage(event.getMessage())); - jsonObject.put(TIMESTAMP, formatTimestamp(event.getTimestamp())); - jsonObject.put(LEVEL, formatLevel(event.getLevel())); - jsonObject.put(LOGGER, event.getLogger()); - jsonObject.put(PLATFORM, event.getPlatform()); - jsonObject.put(CULPRIT, event.getCulprit()); - jsonObject.put(TAGS, event.getTags()); - jsonObject.put(SERVER_NAME, event.getServerName()); - jsonObject.put(EXTRA, event.getExtra()); - jsonObject.put(CHECKSUM, event.getChecksum()); - - for (Map.Entry sentryInterfaceEntry : event.getSentryInterfaces().entrySet()) { - jsonObject.put(sentryInterfaceEntry.getKey(), formatInterface(sentryInterfaceEntry.getValue())); - } - - return jsonObject; - } - - private JSONObject formatInterface(SentryInterface sentryInterface) { - InterfaceMarshaller interfaceMarshaller = interfaceMarshallers.get(sentryInterface.getClass()); - if (interfaceMarshaller != null) { - return interfaceMarshaller.serialiseInterface(sentryInterface); - } else { - return new JSONObject(); - } - } - - /** - * Formats a message, ensuring that the maximum length {@link #MAX_MESSAGE_LENGTH} isn't reached. - * - * @param message message to format. - * @return formatted message (shortened if necessary). - */ - private String formatMessage(String message) { - if (message == null) - return null; - else if (message.length() > MAX_MESSAGE_LENGTH) - return message.substring(0, MAX_MESSAGE_LENGTH); - else return message; - } - - /** - * Formats the {@code UUID} to send only the 32 necessary characters. - * - * @param id uuid to format. - * @return a {@code UUID} stripped from the "-" characters. - */ - private String formatId(UUID id) { - return id.toString().replaceAll("-", ""); - } - - /** - * Formats a log level into one of the accepted string representation of a log level. - * - * @param level log level to format. - * @return log level as a String. - */ - private String formatLevel(LoggedEvent.Level level) { - if (level == null) - return null; - - switch (level) { - case DEBUG: - return "debug"; - case FATAL: - return "fatal"; - case WARNING: - return "warning"; - case INFO: - return "info"; - case ERROR: - return "error"; - default: - return null; - } - } - - /** - * Formats a timestamp in the ISO-8601 format without timezone. - * - * @param timestamp date to format. - * @return timestamp as a formatted String. - */ - private String formatTimestamp(Date timestamp) { - return ISO_FORMAT.format(timestamp); - } - - /** - * Enables the JSON compression with GZip. - * - * @param compression state of the compression. - */ - public void setCompression(boolean compression) { - this.compression = compression; - } - - public void setCharset(Charset charset) { - this.charset = charset; - } -} diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/MessageMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/MessageMarshaller.java deleted file mode 100644 index c5036d2b0ee..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/MessageMarshaller.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.kencochrane.raven.marshaller.simplejson; - -import net.kencochrane.raven.event.interfaces.MessageInterface; -import net.kencochrane.raven.event.interfaces.SentryInterface; -import org.json.simple.JSONObject; - -class MessageMarshaller implements InterfaceMarshaller { - private static final String MESSAGE_PARAMETER = "message"; - private static final String PARAMS_PARAMETER = "params"; - - @Override - public JSONObject serialiseInterface(SentryInterface sentryInterface) { - if (!(sentryInterface instanceof MessageInterface)) { - //TODO: Do something better here! - throw new IllegalArgumentException(); - } - - MessageInterface messageInterface = (MessageInterface) sentryInterface; - JSONObject jsonObject = new JSONObject(); - jsonObject.put(MESSAGE_PARAMETER, messageInterface.getMessage()); - jsonObject.put(PARAMS_PARAMETER, messageInterface.getParams()); - return jsonObject; - } -} diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/StackTraceMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/StackTraceMarshaller.java deleted file mode 100644 index e0c08cd1a41..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/simplejson/StackTraceMarshaller.java +++ /dev/null @@ -1,114 +0,0 @@ -package net.kencochrane.raven.marshaller.simplejson; - -import net.kencochrane.raven.event.interfaces.ImmutableThrowable; -import net.kencochrane.raven.event.interfaces.SentryInterface; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -class StackTraceMarshaller implements InterfaceMarshaller { - private static final String FRAMES_PARAMETER = "frames"; - private static final String FILENAME_PARAMETER = "filename"; - private static final String FUNCTION_PARAMETER = "function"; - private static final String MODULE_PARAMETER = "module"; - private static final String LINE_NO_PARAMETER = "lineno"; - private static final String ABSOLUTE_PATH_PARAMETER = "abs_path"; - private static final String CONTEXT_LINE_PARAMETER = "context_line"; - private static final String PRE_CONTEXT_PARAMETER = "pre_context"; - private static final String POST_CONTEXT_PARAMETER = "post_context"; - private static final String IN_APP_PARAMETER = "in_app"; - private static final String VARIABLES_PARAMETER = "vars"; - private final Set notInAppFrames; - - public StackTraceMarshaller() { - notInAppFrames = new HashSet(); - notInAppFrames.add("com.sun."); - notInAppFrames.add("java."); - notInAppFrames.add("javax."); - notInAppFrames.add("org.omg."); - notInAppFrames.add("sun."); - notInAppFrames.add("junit."); - notInAppFrames.add("com.intellij.rt."); - } - - public StackTraceMarshaller(Set notInAppFrames) { - // Makes a copy to avoid an external modification. - this.notInAppFrames = new HashSet(notInAppFrames); - } - - /** - * Create a fake frame to allow chained exceptions. - * - * @param throwable Exception for which a fake frame should be created - * @return a fake frame allowing to chain exceptions smoothly in Sentry. - */ - private JSONObject createFakeFrame(ImmutableThrowable throwable) { - JSONObject fakeFrame = new JSONObject(); - String message = "Caused by: " + throwable.getActualClass().getName(); - if (throwable.getMessage() != null) - message += " (\"" + throwable.getMessage() + "\")"; - fakeFrame.put(MODULE_PARAMETER, message); - fakeFrame.put(IN_APP_PARAMETER, true); - return fakeFrame; - } - - /** - * Creates a single frame based on a {@code StackTraceElement}. - * - * @param stackTraceElement current frame in the stackTrace. - * @return frame extracted from the stackTraceElement. - */ - private JSONObject createFrame(StackTraceElement stackTraceElement) { - JSONObject currentFrame = new JSONObject(); - // Do not display the file name (irrelevant) as it replaces the module in the sentry interface. - //currentFrame.put(FILENAME_PARAMETER, stackTraceElement.getFileName()); - currentFrame.put(MODULE_PARAMETER, stackTraceElement.getClassName()); - currentFrame.put(IN_APP_PARAMETER, isFrameInApp(stackTraceElement)); - currentFrame.put(FUNCTION_PARAMETER, stackTraceElement.getMethodName()); - currentFrame.put(LINE_NO_PARAMETER, stackTraceElement.getLineNumber()); - return currentFrame; - } - - private boolean isFrameInApp(StackTraceElement stackTraceElement) { - //TODO: A set is absolutely not performant here, a Trie could be a better solution. - for (String notInAppFrame : notInAppFrames) { - if (stackTraceElement.getClassName().startsWith(notInAppFrame)) { - return false; - } - } - return true; - } - - @Override - public JSONObject serialiseInterface(SentryInterface sentryInterface) { - if (!(sentryInterface instanceof StackTraceInterface)) { - //TODO: Do something better here! - throw new IllegalArgumentException(); - } - - StackTraceInterface stackTraceInterface = (StackTraceInterface) sentryInterface; - JSONObject jsonObject = new JSONObject(); - JSONArray frames = new JSONArray(); - ImmutableThrowable currentThrowable = stackTraceInterface.getThrowable(); - boolean firstFrame = true; - while (currentThrowable != null) { - if (firstFrame) { - firstFrame = false; - } else { - frames.add(createFakeFrame(currentThrowable)); - } - for (StackTraceElement stackTraceElement : currentThrowable.getStackTrace()) { - frames.add(createFrame(stackTraceElement)); - } - currentThrowable = currentThrowable.getCause(); - } - Collections.reverse(frames); - jsonObject.put(FRAMES_PARAMETER, frames); - - return jsonObject; - } -} From db63ecace07cb9b21713203c763f82fe55795f3b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 9 Apr 2013 17:15:45 +0100 Subject: [PATCH 0222/2152] Deflate before encoding in base64 --- .../net/kencochrane/raven/marshaller/json/JsonMarshaller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 34cc5b60d2c..26a300db582 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -99,7 +99,7 @@ public JsonMarshaller() { public void marshall(LoggedEvent event, OutputStream destination) { if (compression) - destination = new Base64OutputStream(new DeflaterOutputStream(destination)); + destination = new DeflaterOutputStream(new Base64OutputStream(destination)); JsonGenerator generator = null; try { From cf752f01ef49fe6f26789ad3220863f1bd37a44f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 9 Apr 2013 18:26:56 +0100 Subject: [PATCH 0223/2152] Set a maximum size for the Message interface --- .../json/MessageInterfaceBinding.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBinding.java index 2bb972d9ca3..143a60f418e 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBinding.java @@ -6,13 +6,31 @@ import java.io.IOException; public class MessageInterfaceBinding implements InterfaceBinding { + /** + * Maximum length for a message. + */ + public static final int MAX_MESSAGE_LENGTH = 1000; private static final String MESSAGE_PARAMETER = "message"; private static final String PARAMS_PARAMETER = "params"; + /** + * Formats a message, ensuring that the maximum length {@link #MAX_MESSAGE_LENGTH} isn't reached. + * + * @param message message to format. + * @return formatted message (shortened if necessary). + */ + private String formatMessage(String message) { + if (message == null) + return null; + else if (message.length() > MAX_MESSAGE_LENGTH) + return message.substring(0, MAX_MESSAGE_LENGTH); + else return message; + } + @Override public void writeInterface(JsonGenerator generator, MessageInterface messageInterface) throws IOException { generator.writeStartObject(); - generator.writeStringField(MESSAGE_PARAMETER, messageInterface.getMessage()); + generator.writeStringField(MESSAGE_PARAMETER, formatMessage(messageInterface.getMessage())); generator.writeArrayFieldStart(PARAMS_PARAMETER); for (String parameter : messageInterface.getParams()) { generator.writeString(parameter); From 094235c63f360b68f5aa0d81ec457f97ad45fcdd Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 09:05:36 +0100 Subject: [PATCH 0224/2152] Create a constant for the Log4j-NDC extra variable --- .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index ff3ce981f8e..d8413485278 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -14,6 +14,7 @@ import java.util.Set; public class SentryAppender extends AppenderSkeleton { + private static final String LOG4J_NDC = "Log4J-NDC"; private final Raven raven; public SentryAppender() { @@ -59,7 +60,7 @@ protected void append(LoggingEvent loggingEvent) { } if (loggingEvent.getNDC() != null) - eventBuilder.addExtra("Log4J-NDC", loggingEvent.getNDC()); + eventBuilder.addExtra(LOG4J_NDC, loggingEvent.getNDC()); for (Map.Entry mdcEntry : (Set) loggingEvent.getProperties().entrySet()) eventBuilder.addExtra(mdcEntry.getKey().toString(), mdcEntry.getValue()); From 88b9368303635aadc1628d3308b63ac7d06da5a5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 09:06:17 +0100 Subject: [PATCH 0225/2152] Generate the checksum only when it's possible --- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 3 ++- .../net/kencochrane/raven/logback/SentryAppender.java | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index d8413485278..f91a7a22054 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -56,7 +56,8 @@ protected void append(LoggingEvent loggingEvent) { } else { // When it's a message try to rely on the position of the log (the same message can be logged from // different places, or a same place can log a message in different ways. - eventBuilder.generateChecksum(loggingEvent.getLocationInformation().fullInfo); + if (loggingEvent.getLocationInformation().fullInfo != null) + eventBuilder.generateChecksum(loggingEvent.getLocationInformation().fullInfo); } if (loggingEvent.getNDC() != null) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index e2ddf0ef050..64b7ee9c9ad 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -60,9 +60,12 @@ protected void append(ILoggingEvent iLoggingEvent) { Throwable throwable = ((ThrowableProxy) iLoggingEvent.getThrowableProxy()).getThrowable(); eventBuilder.addSentryInterface(new ExceptionInterface(throwable)) .addSentryInterface(new StackTraceInterface(throwable)); - } else if (iLoggingEvent.getArgumentArray() != null) { - eventBuilder.addSentryInterface(new MessageInterface(iLoggingEvent.getMessage(), - formatArguments(iLoggingEvent.getArgumentArray()))); + } else { + if (iLoggingEvent.getArgumentArray() != null) + eventBuilder.addSentryInterface(new MessageInterface(iLoggingEvent.getMessage(), + formatArguments(iLoggingEvent.getArgumentArray()))); + // When it's a message try to rely on the position of the log (the same message can be logged from + // different places, or a same place can log a message in different ways. if (iLoggingEvent.getCallerData().length > 0) eventBuilder.generateChecksum(getEventPosition(iLoggingEvent)); } From e20b7ae37bbdaf51cf11e819baa65b4380e9f595 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 14:03:26 +0100 Subject: [PATCH 0226/2152] Reorganise properties for lib versions --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 55d606703f3..089e5bfd7c4 100644 --- a/pom.xml +++ b/pom.xml @@ -26,13 +26,13 @@ 6 - 4.11 - 1.9.5 - 1.0.11 - 1.2.17 1.6 2.1.4 3.0.1 + 1.0.11 + 1.2.17 + 4.11 + 1.9.5 1.3 From e55d3d7218bf31a687c643123841e0df1d182311 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 14:04:29 +0100 Subject: [PATCH 0227/2152] Create Helpers to run with the EventBuilder --- .../kencochrane/raven/event/helper/EventBuilderHelper.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/event/helper/EventBuilderHelper.java diff --git a/raven/src/main/java/net/kencochrane/raven/event/helper/EventBuilderHelper.java b/raven/src/main/java/net/kencochrane/raven/event/helper/EventBuilderHelper.java new file mode 100644 index 00000000000..397a18047a7 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/event/helper/EventBuilderHelper.java @@ -0,0 +1,7 @@ +package net.kencochrane.raven.event.helper; + +import net.kencochrane.raven.event.EventBuilder; + +public interface EventBuilderHelper { + void helpBuildingEvent(EventBuilder eventBuilder); +} From a59d90192ae937563c21efa49cc7ff714410867f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 14:07:10 +0100 Subject: [PATCH 0228/2152] Add builder helpers to the Raven instance --- .../java/net/kencochrane/raven/Raven.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index dbba949d755..afc5f9b68dd 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -6,10 +6,13 @@ import net.kencochrane.raven.connection.UdpConnection; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.event.helper.EventBuilderHelper; import net.kencochrane.raven.marshaller.json.JsonMarshaller; import java.nio.charset.Charset; import java.util.UUID; +import java.util.Collections; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -18,6 +21,7 @@ public class Raven { public static final String NAME = "Raven-Java/3.0"; private static final Logger logger = Logger.getLogger(Raven.class.getCanonicalName()); private Connection connection; + private Set builderHelpers; public Raven() { this(new Dsn()); @@ -79,6 +83,12 @@ public UUID sendEvent(EventBuilder eventBuilder) { return event.getId(); } + public void runBuilderHelpers(EventBuilder eventBuilder) { + for (EventBuilderHelper builderHelper : builderHelpers) { + builderHelper.helpBuildingEvent(eventBuilder); + } + } + public void sendEvent(LoggedEvent event) { try { connection.send(event); @@ -90,4 +100,16 @@ public void sendEvent(LoggedEvent event) { public void setConnection(Connection connection) { this.connection = connection; } + + public Set getBuilderHelpers() { + return Collections.unmodifiableSet(builderHelpers); + } + + public void removeBuilderHelper(EventBuilderHelper builderHelper) { + builderHelpers.remove(builderHelper); + } + + public void addBuilderHelper(EventBuilderHelper builderHelper) { + builderHelpers.add(builderHelper); + } } From a2578a84014968a4c5c49e5c2c958029b4b69145 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 14:08:56 +0100 Subject: [PATCH 0229/2152] Remove the method sendEvent(EventBuilder) This method is counter intuitive and breaks the idea of having an immutable Event sent to Raven --- .../raven/log4j/SentryAppender.java | 2 +- .../raven/logback/SentryAppender.java | 2 +- .../java/net/kencochrane/raven/Raven.java | 7 ------ .../kencochrane/raven/jul/SentryHandler.java | 2 +- .../java/net/kencochrane/raven/RavenTest.java | 14 ----------- .../raven/jul/SentryHandlerTest.java | 24 +++++++++---------- 6 files changed, 15 insertions(+), 36 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index f91a7a22054..d55a2bb542c 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -66,7 +66,7 @@ protected void append(LoggingEvent loggingEvent) { for (Map.Entry mdcEntry : (Set) loggingEvent.getProperties().entrySet()) eventBuilder.addExtra(mdcEntry.getKey().toString(), mdcEntry.getValue()); - raven.sendEvent(eventBuilder); + raven.sendEvent(eventBuilder.build()); } @Override diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 64b7ee9c9ad..57aec38ff40 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -74,7 +74,7 @@ protected void append(ILoggingEvent iLoggingEvent) { eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); } - raven.sendEvent(eventBuilder); + raven.sendEvent(eventBuilder.build()); } private String getEventPosition(ILoggingEvent iLoggingEvent) { diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index afc5f9b68dd..4b277aced6b 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -10,7 +10,6 @@ import net.kencochrane.raven.marshaller.json.JsonMarshaller; import java.nio.charset.Charset; -import java.util.UUID; import java.util.Collections; import java.util.Set; import java.util.logging.Level; @@ -77,12 +76,6 @@ private static Connection determineConnection(Dsn dsn) { return connection; } - public UUID sendEvent(EventBuilder eventBuilder) { - LoggedEvent event = eventBuilder.build(); - sendEvent(event); - return event.getId(); - } - public void runBuilderHelpers(EventBuilder eventBuilder) { for (EventBuilderHelper builderHelper : builderHelpers) { builderHelper.helpBuildingEvent(eventBuilder); diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 15ed23082d1..42d74129ba5 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -65,7 +65,7 @@ public void publish(LogRecord record) { else eventBuilder.setMessage(record.getMessage()); - raven.sendEvent(eventBuilder); + raven.sendEvent(eventBuilder.build()); } @Override diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index d0cb155a946..4398eb70702 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -32,18 +32,4 @@ public void testSendEvent() { verify(mockConnection).send(loggedEvent); } - - @Test - public void testSendEventBuilder() { - EventBuilder eventBuilder = mock(EventBuilder.class); - LoggedEvent loggedEvent = mock(LoggedEvent.class); - UUID mockUuid = UUID.randomUUID(); - when(eventBuilder.build()).thenReturn(loggedEvent); - when(loggedEvent.getId()).thenReturn(mockUuid); - UUID uuid = raven.sendEvent(eventBuilder); - - verify(eventBuilder).build(); - verify(mockConnection).send(any(LoggedEvent.class)); - assertEquals(mockUuid, uuid); - } } diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java index 4fc53df074a..955695002ae 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java @@ -42,14 +42,14 @@ public void setUp() { @Test public void testSimpleMessageLogging() { - ArgumentCaptor eventBuilderCaptor = ArgumentCaptor.forClass(EventBuilder.class); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(LoggedEvent.class); LoggedEvent event; String message = UUID.randomUUID().toString(); logger.log(Level.INFO, message); - verify(mockRaven).sendEvent(eventBuilderCaptor.capture()); - event = eventBuilderCaptor.getValue().build(); + verify(mockRaven).sendEvent(eventCaptor.capture()); + event = eventCaptor.getValue(); assertEquals(logger.getName(), event.getLogger()); assertEquals(message, event.getMessage()); @@ -68,23 +68,23 @@ public void testLogLevelConversions() { private void assertLevelConverted(LoggedEvent.Level expectedLevel, Level level){ reset(mockRaven); - ArgumentCaptor eventBuilderCaptor = ArgumentCaptor.forClass(EventBuilder.class); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(LoggedEvent.class); logger.log(level, null); - verify(mockRaven).sendEvent(eventBuilderCaptor.capture()); - assertEquals(expectedLevel, eventBuilderCaptor.getValue().build().getLevel()); + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertEquals(expectedLevel, eventCaptor.getValue().getLevel()); } @Test public void testLogException() { - ArgumentCaptor eventBuilderCaptor = ArgumentCaptor.forClass(EventBuilder.class); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(LoggedEvent.class); Exception exception = new Exception(); LoggedEvent event; logger.log(Level.SEVERE, "message", exception); - verify(mockRaven).sendEvent(eventBuilderCaptor.capture()); - event = eventBuilderCaptor.getValue().build(); + verify(mockRaven).sendEvent(eventCaptor.capture()); + event = eventCaptor.getValue(); SentryInterface exceptionInterface = event.getSentryInterfaces().get(ExceptionInterface.EXCEPTION_INTERFACE); assertThat(exceptionInterface, instanceOf(ExceptionInterface.class)); @@ -103,15 +103,15 @@ public void testLogException() { @Test public void testLogParametrisedMessage() { - ArgumentCaptor eventBuilderCaptor = ArgumentCaptor.forClass(EventBuilder.class); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(LoggedEvent.class); String message = UUID.randomUUID().toString(); List parameters = Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()); LoggedEvent event; logger.log(Level.INFO, message, parameters.toArray()); - verify(mockRaven).sendEvent(eventBuilderCaptor.capture()); - event = eventBuilderCaptor.getValue().build(); + verify(mockRaven).sendEvent(eventCaptor.capture()); + event = eventCaptor.getValue(); SentryInterface exceptionInterface = event.getSentryInterfaces().get(MessageInterface.MESSAGE_INTERFACE); assertThat(exceptionInterface, instanceOf(MessageInterface.class)); From ecc964289a668bf1923c2539ef4a8883611da5ee Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 14:10:14 +0100 Subject: [PATCH 0230/2152] Run builderHelpers before building the event --- .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 2 ++ .../main/java/net/kencochrane/raven/logback/SentryAppender.java | 2 ++ .../src/main/java/net/kencochrane/raven/jul/SentryHandler.java | 2 ++ 3 files changed, 6 insertions(+) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index d55a2bb542c..3125c5f1397 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -66,6 +66,8 @@ protected void append(LoggingEvent loggingEvent) { for (Map.Entry mdcEntry : (Set) loggingEvent.getProperties().entrySet()) eventBuilder.addExtra(mdcEntry.getKey().toString(), mdcEntry.getValue()); + raven.runBuilderHelpers(eventBuilder); + raven.sendEvent(eventBuilder.build()); } diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 57aec38ff40..cb258e7a4d7 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -74,6 +74,8 @@ protected void append(ILoggingEvent iLoggingEvent) { eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); } + raven.runBuilderHelpers(eventBuilder); + raven.sendEvent(eventBuilder.build()); } diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 42d74129ba5..e3578316ea1 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -65,6 +65,8 @@ public void publish(LogRecord record) { else eventBuilder.setMessage(record.getMessage()); + raven.runBuilderHelpers(eventBuilder); + raven.sendEvent(eventBuilder.build()); } From a761affd561d34671520656a55d760cb7ac73580 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 14:10:36 +0100 Subject: [PATCH 0231/2152] Add a builder helper to extract HttpRequests Use a ServletRequestListener to capture an HttpServletRequest and a BuilderHelper to extract the ThreadLocal content when building the event. --- .../event/helper/HttpEventBuilderHelper.java | 17 +++++++++++++ .../servlet/RavenServletRequestListener.java | 24 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java create mode 100644 raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java diff --git a/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java b/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java new file mode 100644 index 00000000000..9bd877fe7df --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java @@ -0,0 +1,17 @@ +package net.kencochrane.raven.event.helper; + +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.interfaces.HttpInterface; +import net.kencochrane.raven.servlet.RavenServletRequestListener; + +import javax.servlet.http.HttpServletRequest; + +public class HttpEventBuilderHelper implements EventBuilderHelper { + @Override + public void helpBuildingEvent(EventBuilder eventBuilder) { + HttpServletRequest servletRequest = RavenServletRequestListener.getServletRequest(); + if (servletRequest != null) { + eventBuilder.addSentryInterface(new HttpInterface(servletRequest)); + } + } +} diff --git a/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java b/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java new file mode 100644 index 00000000000..d72db999e0d --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java @@ -0,0 +1,24 @@ +package net.kencochrane.raven.servlet; + +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.http.HttpServletRequest; + +public class RavenServletRequestListener implements ServletRequestListener { + private static final ThreadLocal THREAD_REQUEST = new ThreadLocal(); + + public static HttpServletRequest getServletRequest() { + return THREAD_REQUEST.get(); + } + + @Override + public void requestDestroyed(ServletRequestEvent servletRequestEvent) { + THREAD_REQUEST.remove(); + } + + @Override + public void requestInitialized(ServletRequestEvent servletRequestEvent) { + if (servletRequestEvent instanceof HttpServletRequest) + THREAD_REQUEST.set((HttpServletRequest) servletRequestEvent.getServletRequest()); + } +} From 3aace8ad00ac9d89cfff40118d7b7812f4292b68 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 14:13:05 +0100 Subject: [PATCH 0232/2152] Initialise BuilderHelpers --- raven/src/main/java/net/kencochrane/raven/Raven.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 4b277aced6b..cb7c66fffa5 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -11,6 +11,7 @@ import java.nio.charset.Charset; import java.util.Collections; +import java.util.HashSet; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -19,8 +20,8 @@ public class Raven { public static final String DEFAULT_CHARSET = "UTF-8"; public static final String NAME = "Raven-Java/3.0"; private static final Logger logger = Logger.getLogger(Raven.class.getCanonicalName()); + private final Set builderHelpers = new HashSet(); private Connection connection; - private Set builderHelpers; public Raven() { this(new Dsn()); From b4c084d5000e7da71988ba28f08892753627583c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 14:24:59 +0100 Subject: [PATCH 0233/2152] Rename LoggedEvent in Event --- .../raven/log4j/SentryAppender.java | 14 ++++---- .../raven/logback/SentryAppender.java | 12 +++---- .../java/net/kencochrane/raven/Raven.java | 4 +-- .../raven/connection/AsyncConnection.java | 14 ++++---- .../raven/connection/Connection.java | 4 +-- .../raven/connection/HttpConnection.java | 4 +-- .../raven/connection/UdpConnection.java | 4 +-- .../event/{LoggedEvent.java => Event.java} | 14 ++++---- .../kencochrane/raven/event/EventBuilder.java | 22 ++++++------- .../kencochrane/raven/jul/SentryHandler.java | 12 +++---- .../raven/marshaller/Marshaller.java | 6 ++-- .../raven/marshaller/json/JsonMarshaller.java | 8 ++--- .../java/net/kencochrane/raven/RavenTest.java | 8 ++--- .../raven/jul/SentryHandlerTest.java | 32 +++++++++---------- 14 files changed, 79 insertions(+), 79 deletions(-) rename raven/src/main/java/net/kencochrane/raven/event/{LoggedEvent.java => Event.java} (91%) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 3125c5f1397..97616bb25cb 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -1,8 +1,8 @@ package net.kencochrane.raven.log4j; import net.kencochrane.raven.Raven; +import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; -import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.log4j.AppenderSkeleton; @@ -25,17 +25,17 @@ public SentryAppender(Raven raven) { this.raven = raven; } - private static LoggedEvent.Level formatLevel(LoggingEvent loggingEvent) { + private static Event.Level formatLevel(LoggingEvent loggingEvent) { if (loggingEvent.getLevel().isGreaterOrEqual(Level.FATAL)) { - return LoggedEvent.Level.FATAL; + return Event.Level.FATAL; } else if (loggingEvent.getLevel().isGreaterOrEqual(Level.ERROR)) { - return LoggedEvent.Level.ERROR; + return Event.Level.ERROR; } else if (loggingEvent.getLevel().isGreaterOrEqual(Level.WARN)) { - return LoggedEvent.Level.WARNING; + return Event.Level.WARNING; } else if (loggingEvent.getLevel().isGreaterOrEqual(Level.INFO)) { - return LoggedEvent.Level.INFO; + return Event.Level.INFO; } else if (loggingEvent.getLevel().isGreaterOrEqual(Level.ALL)) { - return LoggedEvent.Level.DEBUG; + return Event.Level.DEBUG; } else return null; } diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index cb258e7a4d7..74dd002dd9b 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -5,8 +5,8 @@ import ch.qos.logback.classic.spi.ThrowableProxy; import ch.qos.logback.core.AppenderBase; import net.kencochrane.raven.Raven; +import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; -import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; @@ -35,15 +35,15 @@ private static List formatArguments(Object[] argumentArray) { return arguments; } - private static LoggedEvent.Level formatLevel(ILoggingEvent iLoggingEvent) { + private static Event.Level formatLevel(ILoggingEvent iLoggingEvent) { if (iLoggingEvent.getLevel().isGreaterOrEqual(Level.ERROR)) { - return LoggedEvent.Level.ERROR; + return Event.Level.ERROR; } else if (iLoggingEvent.getLevel().isGreaterOrEqual(Level.WARN)) { - return LoggedEvent.Level.WARNING; + return Event.Level.WARNING; } else if (iLoggingEvent.getLevel().isGreaterOrEqual(Level.INFO)) { - return LoggedEvent.Level.INFO; + return Event.Level.INFO; } else if (iLoggingEvent.getLevel().isGreaterOrEqual(Level.ALL)) { - return LoggedEvent.Level.DEBUG; + return Event.Level.DEBUG; } else return null; } diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index cb7c66fffa5..6b5d1358979 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -5,7 +5,7 @@ import net.kencochrane.raven.connection.HttpConnection; import net.kencochrane.raven.connection.UdpConnection; import net.kencochrane.raven.event.EventBuilder; -import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.helper.EventBuilderHelper; import net.kencochrane.raven.marshaller.json.JsonMarshaller; @@ -83,7 +83,7 @@ public void runBuilderHelpers(EventBuilder eventBuilder) { } } - public void sendEvent(LoggedEvent event) { + public void sendEvent(Event event) { try { connection.send(event); } catch (Exception e) { diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index c32fa3d0ebf..215f1354ee7 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -1,6 +1,6 @@ package net.kencochrane.raven.connection; -import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.event.Event; import java.util.List; import java.util.concurrent.ExecutorService; @@ -58,9 +58,9 @@ public void run() { } @Override - public void send(LoggedEvent event) { + public void send(Event event) { // TODO: Consider adding an option to wait when it's full? - executorService.execute(new LoggedEventSubmitter(event)); + executorService.execute(new EventSubmitter(event)); } private static final class DaemonThreadFactory implements ThreadFactory { @@ -86,13 +86,13 @@ public Thread newThread(Runnable r) { } /** - * Simple runnable using the {@link #send(net.kencochrane.raven.event.LoggedEvent)} method of the + * Simple runnable using the {@link #send(net.kencochrane.raven.event.Event)} method of the * {@link #actualConnection}. */ - private final class LoggedEventSubmitter implements Runnable { - private final LoggedEvent event; + private final class EventSubmitter implements Runnable { + private final Event event; - private LoggedEventSubmitter(LoggedEvent event) { + private EventSubmitter(Event event) { this.event = event; } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/Connection.java b/raven/src/main/java/net/kencochrane/raven/connection/Connection.java index e01fa88cfee..e84085d4044 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/Connection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/Connection.java @@ -1,6 +1,6 @@ package net.kencochrane.raven.connection; -import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.event.Event; /** * Connection to a Sentry server, allowing to send captured events. @@ -11,5 +11,5 @@ public interface Connection { * * @param event captured event to add in Sentry. */ - void send(LoggedEvent event); + void send(Event event); } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 0aeb80719a0..e8d445e3457 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -2,7 +2,7 @@ import net.kencochrane.raven.Dsn; import net.kencochrane.raven.Raven; -import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.event.Event; import net.kencochrane.raven.marshaller.Marshaller; import net.kencochrane.raven.marshaller.json.JsonMarshaller; @@ -79,7 +79,7 @@ private HttpURLConnection getConnection() { } @Override - public void send(LoggedEvent event) { + public void send(Event event) { HttpURLConnection connection = getConnection(); try { connection.connect(); diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index 920f86373c7..211d8b48d61 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -1,7 +1,7 @@ package net.kencochrane.raven.connection; import net.kencochrane.raven.Dsn; -import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.event.Event; import net.kencochrane.raven.marshaller.Marshaller; import net.kencochrane.raven.marshaller.json.JsonMarshaller; @@ -31,7 +31,7 @@ public UdpConnection(Dsn dsn) { } @Override - public void send(LoggedEvent event) { + public void send(Event event) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); writeHeader(baos); diff --git a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java b/raven/src/main/java/net/kencochrane/raven/event/Event.java similarity index 91% rename from raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java rename to raven/src/main/java/net/kencochrane/raven/event/Event.java index e84d2995f48..68ddef3be22 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/LoggedEvent.java +++ b/raven/src/main/java/net/kencochrane/raven/event/Event.java @@ -14,17 +14,17 @@ * Notes to developers: *
      *
    • - * In order to ensure that a LoggedEvent can't be modified externally, the setters should have a package visibility. + * In order to ensure that a Event can't be modified externally, the setters should have a package visibility. *
    • *
    • * A proper immutable Object should only contain immutable Objects and primitives, this must be ensured before - * publishing the LoggedEvent.
      + * publishing the Event.
      * There is one exception, the {@link #extra} section can't be transformed to be completely immutable. *
    • *
    *

    */ -public class LoggedEvent { +public class Event { /** * Unique identifier of the event. */ @@ -75,11 +75,11 @@ public class LoggedEvent { private Map sentryInterfaces = new HashMap(); /** - * Creates a new LoggedEvent (should be called only through {@link EventBuilder} with the specified identifier. + * Creates a new Event (should be called only through {@link EventBuilder} with the specified identifier. * * @param id unique identifier of the event. */ - LoggedEvent(UUID id) { + Event(UUID id) { if (id == null) throw new IllegalArgumentException("The id can't be null"); this.id = id; @@ -182,7 +182,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - return id.equals(((LoggedEvent) o).id); + return id.equals(((Event) o).id); } @@ -193,7 +193,7 @@ public int hashCode() { @Override public String toString() { - return "LoggedEvent{" + + return "Event{" + "level=" + level + ", message='" + message + '\'' + ", logger='" + logger + '\'' + diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 5c47f63c9e8..2c74edf626c 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -9,7 +9,7 @@ import java.util.zip.Checksum; /** - * Builder to assist the creation of {@link LoggedEvent}s. + * Builder to assist the creation of {@link Event}s. */ public class EventBuilder { /** @@ -20,11 +20,11 @@ public class EventBuilder { * Default hostname if it isn't set manually (or can't be determined). */ public static final String DEFAULT_HOSTNAME = "unavailable"; - private final LoggedEvent event; + private final Event event; private boolean alreadyBuilt = false; /** - * Creates a new EventBuilder to prepare a new {@link LoggedEvent}. + * Creates a new EventBuilder to prepare a new {@link Event}. *

    * Automatically generates the id of the new event. *

    @@ -34,12 +34,12 @@ public EventBuilder() { } /** - * Creates a new EventBuilder to prepare a new {@link LoggedEvent}. + * Creates a new EventBuilder to prepare a new {@link Event}. * * @param eventId unique identifier for the new event. */ public EventBuilder(UUID eventId) { - this.event = new LoggedEvent(eventId); + this.event = new Event(eventId); } /** @@ -75,7 +75,7 @@ private static String getHostname() { * * @param event currently handled event. */ - private static void autoSetMissingValues(LoggedEvent event) { + private static void autoSetMissingValues(Event event) { // Ensure that a timestamp is set (to now at least!) if (event.getTimestamp() == null) event.setTimestamp(new Date()); @@ -90,11 +90,11 @@ private static void autoSetMissingValues(LoggedEvent event) { } /** - * Ensures that every field in the {@code LoggedEvent} are immutable to avoid confusion later. + * Ensures that every field in the {@code Event} are immutable to avoid confusion later. * * @param event event to make immutable. */ - private static void makeImmutable(LoggedEvent event) { + private static void makeImmutable(Event event) { // Make the tags unmodifiable Map> unmodifiablesTags = new HashMap>(event.getTags().size()); for (Map.Entry> tag : event.getTags().entrySet()) { @@ -158,7 +158,7 @@ public EventBuilder setTimestamp(Date timestamp) { * @param level log level of the event. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder setLevel(LoggedEvent.Level level) { + public EventBuilder setLevel(Event.Level level) { event.setLevel(level); return this; } @@ -293,14 +293,14 @@ public EventBuilder addSentryInterface(SentryInterface sentryInterface) { } /** - * Finalises the {@link LoggedEvent} and returns it. + * Finalises the {@link Event} and returns it. *

    * This operations will automatically set the missing values and make the mutable values immutable. *

    * * @return an immutable event. */ - public LoggedEvent build() { + public Event build() { if (alreadyBuilt) throw new IllegalStateException("A message can't be built twice"); diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index e3578316ea1..33ca1f05442 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -1,8 +1,8 @@ package net.kencochrane.raven.jul; import net.kencochrane.raven.Raven; +import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; -import net.kencochrane.raven.event.LoggedEvent; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; @@ -25,15 +25,15 @@ public SentryHandler(Raven raven) { this.raven = raven; } - private static LoggedEvent.Level getLevel(Level level) { + private static Event.Level getLevel(Level level) { if (level.intValue() >= Level.SEVERE.intValue()) - return LoggedEvent.Level.ERROR; + return Event.Level.ERROR; else if (level.intValue() >= Level.WARNING.intValue()) - return LoggedEvent.Level.WARNING; + return Event.Level.WARNING; else if (level.intValue() >= Level.INFO.intValue()) - return LoggedEvent.Level.INFO; + return Event.Level.INFO; else if (level.intValue() >= Level.ALL.intValue()) - return LoggedEvent.Level.DEBUG; + return Event.Level.DEBUG; else return null; } diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java index 0268902b4ac..81c5865dbea 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java @@ -1,11 +1,11 @@ package net.kencochrane.raven.marshaller; -import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.event.Event; import java.io.OutputStream; /** - * Marshaller allows to serialise a {@link LoggedEvent} and sends over a stream. + * Marshaller allows to serialise a {@link Event} and sends over a stream. */ public interface Marshaller { /** @@ -18,5 +18,5 @@ public interface Marshaller { * @param event event to serialise. * @param destination destination stream. */ - void marshall(LoggedEvent event, OutputStream destination); + void marshall(Event event, OutputStream destination); } diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 26a300db582..d79dd461a59 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.core.JsonEncoding; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; -import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.interfaces.*; import net.kencochrane.raven.marshaller.Marshaller; import org.apache.commons.codec.binary.Base64OutputStream; @@ -96,7 +96,7 @@ public JsonMarshaller() { } @Override - public void marshall(LoggedEvent event, OutputStream destination) { + public void marshall(Event event, OutputStream destination) { if (compression) destination = new DeflaterOutputStream(new Base64OutputStream(destination)); @@ -117,7 +117,7 @@ public void marshall(LoggedEvent event, OutputStream destination) { } } - private void writeContent(JsonGenerator generator, LoggedEvent event) throws IOException { + private void writeContent(JsonGenerator generator, Event event) throws IOException { generator.writeStartObject(); generator.writeStringField(EVENT_ID, formatId(event.getId())); @@ -197,7 +197,7 @@ private String formatId(UUID id) { * @param level log level to format. * @return log level as a String. */ - private String formatLevel(LoggedEvent.Level level) { + private String formatLevel(Event.Level level) { if (level == null) return null; diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index 4398eb70702..c96efc4dfa3 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -2,7 +2,7 @@ import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.event.EventBuilder; -import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.event.Event; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -27,9 +27,9 @@ public void setUp() { @Test public void testSendEvent() { - LoggedEvent loggedEvent = mock(LoggedEvent.class); - raven.sendEvent(loggedEvent); + Event event = mock(Event.class); + raven.sendEvent(event); - verify(mockConnection).send(loggedEvent); + verify(mockConnection).send(event); } } diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java index 955695002ae..b8af7a6a9a4 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java @@ -2,7 +2,7 @@ import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.EventBuilder; -import net.kencochrane.raven.event.LoggedEvent; +import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.SentryInterface; @@ -42,8 +42,8 @@ public void setUp() { @Test public void testSimpleMessageLogging() { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(LoggedEvent.class); - LoggedEvent event; + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + Event event; String message = UUID.randomUUID().toString(); logger.log(Level.INFO, message); @@ -57,18 +57,18 @@ public void testSimpleMessageLogging() { @Test public void testLogLevelConversions() { - assertLevelConverted(LoggedEvent.Level.DEBUG, Level.FINEST); - assertLevelConverted(LoggedEvent.Level.DEBUG, Level.FINER); - assertLevelConverted(LoggedEvent.Level.DEBUG, Level.FINE); - assertLevelConverted(LoggedEvent.Level.DEBUG, Level.CONFIG); - assertLevelConverted(LoggedEvent.Level.INFO, Level.INFO); - assertLevelConverted(LoggedEvent.Level.WARNING, Level.WARNING); - assertLevelConverted(LoggedEvent.Level.ERROR, Level.SEVERE); + assertLevelConverted(Event.Level.DEBUG, Level.FINEST); + assertLevelConverted(Event.Level.DEBUG, Level.FINER); + assertLevelConverted(Event.Level.DEBUG, Level.FINE); + assertLevelConverted(Event.Level.DEBUG, Level.CONFIG); + assertLevelConverted(Event.Level.INFO, Level.INFO); + assertLevelConverted(Event.Level.WARNING, Level.WARNING); + assertLevelConverted(Event.Level.ERROR, Level.SEVERE); } - private void assertLevelConverted(LoggedEvent.Level expectedLevel, Level level){ + private void assertLevelConverted(Event.Level expectedLevel, Level level){ reset(mockRaven); - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(LoggedEvent.class); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); logger.log(level, null); verify(mockRaven).sendEvent(eventCaptor.capture()); @@ -77,9 +77,9 @@ private void assertLevelConverted(LoggedEvent.Level expectedLevel, Level level){ @Test public void testLogException() { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(LoggedEvent.class); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); Exception exception = new Exception(); - LoggedEvent event; + Event event; logger.log(Level.SEVERE, "message", exception); @@ -103,10 +103,10 @@ public void testLogException() { @Test public void testLogParametrisedMessage() { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(LoggedEvent.class); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); String message = UUID.randomUUID().toString(); List parameters = Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()); - LoggedEvent event; + Event event; logger.log(Level.INFO, message, parameters.toArray()); From f2bf2b00dd571355bdb06f9b22ab154bddf199b8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 14:25:46 +0100 Subject: [PATCH 0234/2152] Use the naming convention when naming constants --- .../net/kencochrane/raven/connection/AsyncConnection.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 215f1354ee7..88df41e2ae1 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -64,7 +64,7 @@ public void send(Event event) { } private static final class DaemonThreadFactory implements ThreadFactory { - private static final AtomicInteger poolNumber = new AtomicInteger(1); + private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1); private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; @@ -72,7 +72,7 @@ private static final class DaemonThreadFactory implements ThreadFactory { private DaemonThreadFactory() { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); - namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; + namePrefix = "pool-" + POOL_NUMBER.getAndIncrement() + "-thread-"; } public Thread newThread(Runnable r) { From 79808bf0594eb4ebafce78b9b3bab73627fff4be Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 14:26:12 +0100 Subject: [PATCH 0235/2152] Make the timeout a constant in AsyncConnection --- .../java/net/kencochrane/raven/connection/AsyncConnection.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 88df41e2ae1..7ac65721687 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -20,6 +20,7 @@ */ public class AsyncConnection implements Connection { private static final Logger logger = Logger.getLogger(AsyncConnection.class.getCanonicalName()); + private static final int TIMEOUT = 1000; /** * Connection used to actually send the events. */ @@ -44,7 +45,7 @@ public void run() { logger.log(Level.INFO, "Gracefully shutdown sentry threads."); executorService.shutdown(); try { - if (!executorService.awaitTermination(1000, TimeUnit.MILLISECONDS)) { + if (!executorService.awaitTermination(TIMEOUT, TimeUnit.MILLISECONDS)) { logger.log(Level.WARNING, "Graceful shutdown took too much time, forcing the shutdown."); List tasks = executorService.shutdownNow(); logger.log(Level.INFO, tasks.size() + " tasks failed to execute before the shutdown."); From 0cafb0f0097b14ed4bd4d130782bff751b7b62ef Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 14:27:21 +0100 Subject: [PATCH 0236/2152] Fix indentation and import order --- raven-log4j/pom.xml | 6 +++--- raven/src/main/java/net/kencochrane/raven/Raven.java | 2 +- .../net/kencochrane/raven/connection/AsyncConnection.java | 1 + .../raven/marshaller/json/HttpInterfaceBinding.java | 2 +- .../raven/marshaller/json/StackTraceInterfaceBinding.java | 5 ++++- raven/src/test/java/net/kencochrane/raven/RavenTest.java | 8 ++------ .../java/net/kencochrane/raven/jul/SentryHandlerTest.java | 7 ++----- .../marshaller/json/AbstractTestInterfaceBinding.java | 2 +- .../marshaller/json/TestStackTraceInterfaceBinding.java | 5 +---- 9 files changed, 16 insertions(+), 22 deletions(-) diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index d500cec24c8..5139bc737c1 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -24,9 +24,9 @@ log4j
    - org.mockito - mockito-core - test + org.mockito + mockito-core + test junit diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 6b5d1358979..c40e7bf4212 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -4,8 +4,8 @@ import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.connection.HttpConnection; import net.kencochrane.raven.connection.UdpConnection; -import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.helper.EventBuilderHelper; import net.kencochrane.raven.marshaller.json.JsonMarshaller; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 7ac65721687..24d8ebb3016 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -76,6 +76,7 @@ private DaemonThreadFactory() { namePrefix = "pool-" + POOL_NUMBER.getAndIncrement() + "-thread-"; } + @Override public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); if (!t.isDaemon()) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java index 4189f23ef91..01aa7c1b6a6 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java @@ -41,7 +41,7 @@ public void writeInterface(JsonGenerator generator, HttpInterface httpInterface) generator.writeEndObject(); } - private void writeEnvironment(JsonGenerator generator, HttpServletRequest request) throws IOException{ + private void writeEnvironment(JsonGenerator generator, HttpServletRequest request) throws IOException { generator.writeStartObject(); generator.writeStringField(REMOTE_ADDR, request.getRemoteAddr()); generator.writeStringField(SERVER_NAME, request.getServerName()); diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java index 34a13a6c14e..74a1ebd46c0 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java @@ -5,7 +5,10 @@ import net.kencochrane.raven.event.interfaces.StackTraceInterface; import java.io.IOException; -import java.util.*; +import java.util.Deque; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; public class StackTraceInterfaceBinding implements InterfaceBinding { private static final String FRAMES_PARAMETER = "frames"; diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index c96efc4dfa3..ce46ab6c245 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -1,18 +1,14 @@ package net.kencochrane.raven; import net.kencochrane.raven.connection.Connection; -import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.Event; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.UUID; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; public class RavenTest { @Mock diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java index b8af7a6a9a4..70d40e4b0bb 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java @@ -1,7 +1,6 @@ package net.kencochrane.raven.jul; import net.kencochrane.raven.Raven; -import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; @@ -20,9 +19,7 @@ import java.util.logging.Logger; import static org.hamcrest.CoreMatchers.instanceOf; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; @@ -66,7 +63,7 @@ public void testLogLevelConversions() { assertLevelConverted(Event.Level.ERROR, Level.SEVERE); } - private void assertLevelConverted(Event.Level expectedLevel, Level level){ + private void assertLevelConverted(Event.Level expectedLevel, Level level) { reset(mockRaven); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractTestInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractTestInterfaceBinding.java index 05d0bb4a867..cc8707d9adb 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractTestInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractTestInterfaceBinding.java @@ -15,7 +15,7 @@ public abstract class AbstractTestInterfaceBinding { private ByteArrayOutputStream jsonContentStream; @Before - protected void setUp() throws Exception{ + protected void setUp() throws Exception { jsonFactory = new JsonFactory(); mapper = new ObjectMapper(); } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java index f8393ca7a37..aa7d690149c 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java @@ -7,13 +7,10 @@ import org.junit.Before; import org.junit.Test; -import java.util.List; import java.util.UUID; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.RETURNS_DEEP_STUBS; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; public class TestStackTraceInterfaceBinding extends AbstractTestInterfaceBinding { private StackTraceInterfaceBinding interfaceBinding; From 2008d5a0bb83504ec199dc34ea30883cb53fc2a4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 15:02:43 +0100 Subject: [PATCH 0237/2152] Add documentation to Raven.java --- .../java/net/kencochrane/raven/Raven.java | 93 +++++++++++++++++-- 1 file changed, 85 insertions(+), 8 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index c40e7bf4212..617e20f15a8 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -16,29 +16,76 @@ import java.util.logging.Level; import java.util.logging.Logger; +/** + * Raven is a client for Sentry allowing to send an {@link Event} that will be processed and sent to a Sentry server. + *

    + * A default client will use the protocol defined in the DSN and will send the content in the JSON format + * (optionally compressed and encoded in base64). + *

    + */ public class Raven { + /** + * Default charset used to send content to Sentry. + *

    + * UTF-8 is used as the default since the communication by default is done through JSON + * (which uses UTF-8 by default). + *

    + */ public static final String DEFAULT_CHARSET = "UTF-8"; + /** + * Version of this client, the major version is the current supported Sentry protocol, the minor version changes + * for each release of this project. + */ public static final String NAME = "Raven-Java/3.0"; private static final Logger logger = Logger.getLogger(Raven.class.getCanonicalName()); private final Set builderHelpers = new HashSet(); private Connection connection; + /** + * Builds a default Raven client, trying to figure out which {@link Dsn} can be used. + * + * @see net.kencochrane.raven.Dsn#dsnLookup() + */ public Raven() { this(new Dsn()); } + /** + * Builds a default Raven client using the given DSN. + * + * @param dsn Data Source Name as a String to use to connect to sentry. + */ public Raven(String dsn) { this(new Dsn(dsn)); } + /** + * Builds a default Raven client using the given DSN. + * + * @param dsn Data Source Name as a String to use to connect to sentry. + */ public Raven(Dsn dsn) { this(determineConnection(dsn)); } + /** + * Builds a Raven client using the given connection. + * + * @param connection connection to sentry. + */ public Raven(Connection connection) { this.connection = connection; } + /** + * Obtains the charset setting for the given DSN. + *

    + * Defaults to {@link #DEFAULT_CHARSET} if the DSN doesn't specify a charset. + *

    + * + * @param dsn Data Source Name optionally containing a charset option. + * @return the charset to use with the given DSN. + */ private static Charset determineCharset(Dsn dsn) { String charset = DEFAULT_CHARSET; @@ -48,6 +95,15 @@ private static Charset determineCharset(Dsn dsn) { return Charset.forName(charset); } + /** + * Builds a {@link Connection} based on a {@link Dsn}. + *

    + * Currently supports the protocols HTTP(s) with {@link HttpConnection} and UPD with {@link UdpConnection}. + *

    + * + * @param dsn Data Source Name from which the connection will be generated. + * @return a {@link Connection} allowing to send events to a Sentry server or {@code null} if nothing was found. + */ //TODO: Replace with a factory? private static Connection determineConnection(Dsn dsn) { String protocol = dsn.getProtocol(); @@ -77,12 +133,23 @@ private static Connection determineConnection(Dsn dsn) { return connection; } + /** + * Runs the {@link EventBuilderHelper} against the {@link EventBuilder} to obtain additional information with a + * MDC-like system. + * + * @param eventBuilder event builder containing a not yet finished event. + */ public void runBuilderHelpers(EventBuilder eventBuilder) { for (EventBuilderHelper builderHelper : builderHelpers) { builderHelper.helpBuildingEvent(eventBuilder); } } + /** + * Sends a built {@link Event} to the Sentry server. + * + * @param event event to send to Sentry. + */ public void sendEvent(Event event) { try { connection.send(event); @@ -91,19 +158,29 @@ public void sendEvent(Event event) { } } - public void setConnection(Connection connection) { - this.connection = connection; - } - - public Set getBuilderHelpers() { - return Collections.unmodifiableSet(builderHelpers); - } - + /** + * Removes a builder helper. + * + * @param builderHelper builder helper to remove. + */ public void removeBuilderHelper(EventBuilderHelper builderHelper) { builderHelpers.remove(builderHelper); } + /** + * Adds a builder helper. + * + * @param builderHelper builder helper to add. + */ public void addBuilderHelper(EventBuilderHelper builderHelper) { builderHelpers.add(builderHelper); } + + public void setConnection(Connection connection) { + this.connection = connection; + } + + public Set getBuilderHelpers() { + return Collections.unmodifiableSet(builderHelpers); + } } From 306bd689c7dbc9ed9edb67f4e236270a2335edad Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 15:08:06 +0100 Subject: [PATCH 0238/2152] Improve documentation --- raven/src/main/java/net/kencochrane/raven/Dsn.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Dsn.java b/raven/src/main/java/net/kencochrane/raven/Dsn.java index 7819724cd8a..768fae6e67f 100644 --- a/raven/src/main/java/net/kencochrane/raven/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/Dsn.java @@ -52,13 +52,15 @@ public class Dsn { private Map options; private URI uri; - + /** + * Creates a DSN based on the {@link #dsnLookup()} result. + */ public Dsn() { this(dsnLookup()); } /** - * Creates a DS based on a String. + * Creates a DSN based on a String. * * @param dsn dsn in a string form. * @throws InvalidDsnException the given DSN isn't usable. From 43b5d8afeba93a27f1a1f75169a75e81349a0e60 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 15:13:17 +0100 Subject: [PATCH 0239/2152] Add logs to the Raven client --- raven/src/main/java/net/kencochrane/raven/Raven.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 617e20f15a8..1af35d03087 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -114,10 +114,12 @@ private static Connection determineConnection(Dsn dsn) { marshaller.setCharset(charset); if (protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https")) { + logger.log(Level.INFO, "Using an HTTP connection to Sentry."); HttpConnection httpConnection = new HttpConnection(dsn); httpConnection.setMarshaller(marshaller); connection = httpConnection; } else if (protocol.equalsIgnoreCase("udp")) { + logger.log(Level.INFO, "Using an UDP connection to Sentry."); UdpConnection udpConnection = new UdpConnection(dsn); udpConnection.setCharset(charset); udpConnection.setMarshaller(marshaller); @@ -164,6 +166,7 @@ public void sendEvent(Event event) { * @param builderHelper builder helper to remove. */ public void removeBuilderHelper(EventBuilderHelper builderHelper) { + logger.log(Level.INFO, "Removes '" + builderHelper + "' to the list of builder helpers."); builderHelpers.remove(builderHelper); } @@ -173,6 +176,7 @@ public void removeBuilderHelper(EventBuilderHelper builderHelper) { * @param builderHelper builder helper to add. */ public void addBuilderHelper(EventBuilderHelper builderHelper) { + logger.log(Level.INFO, "Adding '" + builderHelper + "' to the list of builder helpers."); builderHelpers.add(builderHelper); } From e7a6e4ddb2ad5ea761b3f7dfbf36b5ae060c00ba Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 15:29:58 +0100 Subject: [PATCH 0240/2152] Add Hamcrest library for unit tests --- pom.xml | 5 +++++ raven/pom.xml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/pom.xml b/pom.xml index 089e5bfd7c4..0dc41cd76ed 100644 --- a/pom.xml +++ b/pom.xml @@ -137,6 +137,11 @@ hamcrest-core ${hamcrest.version}
    + + org.hamcrest + hamcrest-library + ${hamcrest.version} +
    diff --git a/raven/pom.xml b/raven/pom.xml index f5556c659a7..f1547d509e2 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -48,5 +48,10 @@ hamcrest-core test
    + + org.hamcrest + hamcrest-library + test + From 81cef77449322df026755bf0d12894066d5bdc3d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 15:30:12 +0100 Subject: [PATCH 0241/2152] Add unit tests for Raven --- .../java/net/kencochrane/raven/RavenTest.java | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index ce46ab6c245..9b92079633e 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -2,13 +2,17 @@ import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.helper.EventBuilderHelper; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.not; +import static org.mockito.Mockito.*; public class RavenTest { @Mock @@ -28,4 +32,44 @@ public void testSendEvent() { verify(mockConnection).send(event); } + + @Test + public void testChangeConnection() { + Event event = mock(Event.class); + Connection mockNewConnection = mock(Connection.class); + + raven.setConnection(mockNewConnection); + raven.sendEvent(event); + + verify(mockConnection, never()).send(event); + verify(mockNewConnection).send(event); + } + + @Test + public void testAddRemoveBuilderHelpers() { + EventBuilderHelper builderHelper = mock(EventBuilderHelper.class); + assertThat(raven.getBuilderHelpers(), not(contains(builderHelper))); + + raven.addBuilderHelper(builderHelper); + assertThat(raven.getBuilderHelpers(), contains(builderHelper)); + raven.removeBuilderHelper(builderHelper); + assertThat(raven.getBuilderHelpers(), not(contains(builderHelper))); + } + + @Test(expected = UnsupportedOperationException.class) + public void testCantModifyBuilderHelpersDirectly() { + EventBuilderHelper builderHelper = mock(EventBuilderHelper.class); + raven.getBuilderHelpers().add(builderHelper); + } + + @Test + public void testRunBuilderHelpers() { + EventBuilder eventBuilder = mock(EventBuilder.class); + EventBuilderHelper builderHelper = mock(EventBuilderHelper.class); + raven.addBuilderHelper(builderHelper); + + raven.runBuilderHelpers(eventBuilder); + + verify(builderHelper).helpBuildingEvent(eventBuilder); + } } From 2aa413d75df15b704cd72e29920e7ccadab72b89 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 16:01:00 +0100 Subject: [PATCH 0242/2152] Change the assertions to the hamcrest system --- .../java/net/kencochrane/raven/DsnTest.java | 39 ++++++++++--------- .../raven/jul/SentryHandlerTest.java | 25 ++++++------ .../json/TestExceptionInterfaceBinding.java | 9 +++-- .../json/TestMessageInterfaceBinding.java | 7 ++-- .../json/TestStackTraceInterfaceBinding.java | 11 +++--- 5 files changed, 50 insertions(+), 41 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/DsnTest.java index 143bb4d7263..a8343b356dd 100644 --- a/raven/src/test/java/net/kencochrane/raven/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/DsnTest.java @@ -3,8 +3,10 @@ import net.kencochrane.raven.exception.InvalidDsnException; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; public class DsnTest { @Test(expected = InvalidDsnException.class) @@ -16,12 +18,12 @@ public void testEmptyDsnInvalid() { public void testSimpleDsnValid() { Dsn dsn = new Dsn("http://publicKey:secretKey@host/9"); - assertEquals("http", dsn.getProtocol()); - assertEquals("publicKey", dsn.getPublicKey()); - assertEquals("secretKey", dsn.getSecretKey()); - assertEquals("host", dsn.getHost()); - assertEquals("/", dsn.getPath()); - assertEquals("9", dsn.getProjectId()); + assertThat(dsn.getProtocol(), is("http")); + assertThat(dsn.getPublicKey(), is("publicKey")); + assertThat(dsn.getSecretKey(), is("secretKey")); + assertThat(dsn.getHost(), is("host")); + assertThat(dsn.getPath(), is("/")); + assertThat(dsn.getProjectId(), is("9")); } @Test(expected = InvalidDsnException.class) @@ -49,16 +51,17 @@ public void testAdvancedDsnValid() { Dsn dsn = new Dsn("naive+udp://1234567890:0987654321@complete.host.name:1234" + "/composed/path/1029384756?option1&option2=valueOption2"); - assertEquals("udp", dsn.getProtocol()); - assertTrue(dsn.getProtocolSettings().contains("naive")); - assertEquals("1234567890", dsn.getPublicKey()); - assertEquals("0987654321", dsn.getSecretKey()); - assertEquals("complete.host.name", dsn.getHost()); - assertEquals(1234, dsn.getPort()); - assertEquals("/composed/path/", dsn.getPath()); - assertEquals("1029384756", dsn.getProjectId()); - assertTrue(dsn.getOptions().containsKey("option1")); - assertEquals("valueOption2", dsn.getOptions().get("option2")); + assertThat(dsn.getProtocol(), is("udp")); + assertThat(dsn.getProtocolSettings(), contains("naive")); + assertThat(dsn.getPublicKey(), is("1234567890")); + assertThat(dsn.getSecretKey(), is("0987654321")); + assertThat(dsn.getHost(), is("complete.host.name")); + assertThat(dsn.getPort(), is(1234)); + assertThat(dsn.getPath(), is("/composed/path/")); + assertThat(dsn.getProjectId(), is("1029384756")); + assertThat(dsn.getOptions(), hasKey("option1")); + assertThat(dsn.getOptions(), hasKey("option2")); + assertThat(dsn.getOptions().get("option2"), is("valueOption2")); } @Test(expected = UnsupportedOperationException.class) diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java index 70d40e4b0bb..9a5026d66d3 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java @@ -6,6 +6,7 @@ import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.SentryInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -18,8 +19,9 @@ import java.util.logging.Level; import java.util.logging.Logger; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.junit.Assert.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; @@ -48,8 +50,8 @@ public void testSimpleMessageLogging() { verify(mockRaven).sendEvent(eventCaptor.capture()); event = eventCaptor.getValue(); - assertEquals(logger.getName(), event.getLogger()); - assertEquals(message, event.getMessage()); + assertThat(event.getLogger(), is(logger.getName())); + assertThat(event.getMessage(), is(message)); } @Test @@ -69,7 +71,7 @@ private void assertLevelConverted(Event.Level expectedLevel, Level level) { logger.log(level, null); verify(mockRaven).sendEvent(eventCaptor.capture()); - assertEquals(expectedLevel, eventCaptor.getValue().getLevel()); + assertThat(eventCaptor.getValue().getLevel(), is(expectedLevel)); } @Test @@ -87,7 +89,7 @@ public void testLogException() { // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. - assertTrue(((ExceptionInterface) exceptionInterface).getThrowable().equals(exception)); + assertThat(((ExceptionInterface) exceptionInterface).getThrowable(), Matchers.equalTo(exception)); SentryInterface stackTraceInterface = event.getSentryInterfaces().get(StackTraceInterface.STACKTRACE_INTERFACE); @@ -95,7 +97,7 @@ public void testLogException() { // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. - assertTrue(((StackTraceInterface) stackTraceInterface).getThrowable().equals(exception)); + assertThat(((StackTraceInterface) stackTraceInterface).getThrowable(), Matchers.equalTo(exception)); } @Test @@ -109,10 +111,11 @@ public void testLogParametrisedMessage() { verify(mockRaven).sendEvent(eventCaptor.capture()); event = eventCaptor.getValue(); - SentryInterface exceptionInterface = event.getSentryInterfaces().get(MessageInterface.MESSAGE_INTERFACE); - assertThat(exceptionInterface, instanceOf(MessageInterface.class)); + assertThat(event.getSentryInterfaces().get(MessageInterface.MESSAGE_INTERFACE), instanceOf(MessageInterface.class)); + MessageInterface messageInterface = + (MessageInterface) event.getSentryInterfaces().get(MessageInterface.MESSAGE_INTERFACE); - assertEquals(message, ((MessageInterface) exceptionInterface).getMessage()); - assertEquals(parameters, ((MessageInterface) exceptionInterface).getParams()); + assertThat(messageInterface.getMessage(), is(message)); + assertThat(messageInterface.getParams(), is(parameters)); } } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java index 30fcb69b09d..1b378dee11f 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java @@ -9,7 +9,8 @@ import java.util.UUID; -import static org.junit.Assert.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -34,8 +35,8 @@ public void testSimpleException() throws Exception { jsonGenerator.close(); JsonNode rootNode = getMapper().readValue(getJsonParser(), JsonNode.class); - assertEquals(throwable.getClass().getPackage().getName(), rootNode.get("module").asText()); - assertEquals(throwable.getClass().getSimpleName(), rootNode.get("type").asText()); - assertEquals(message, rootNode.get("value").asText()); + assertThat(rootNode.get("module").asText(), is(throwable.getClass().getPackage().getName())); + assertThat(rootNode.get("type").asText(), is(throwable.getClass().getSimpleName())); + assertThat(rootNode.get("value").asText(), is(message)); } } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java index 136485577af..4b06692850b 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java @@ -10,7 +10,8 @@ import java.util.List; import java.util.UUID; -import static org.junit.Assert.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -36,7 +37,7 @@ public void testSimpleMessage() throws Exception { jSonGenerator.close(); JsonNode rootNode = getMapper().readValue(getJsonParser(), JsonNode.class); - assertEquals(message, rootNode.get("message").asText()); - assertEquals(parameters, getMapper().convertValue(rootNode.get("params"), List.class)); + assertThat(rootNode.get("message").asText(), is(message)); + assertThat(getMapper().convertValue(rootNode.get("params"), List.class), is(parameters)); } } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java index aa7d690149c..486bca2d555 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java @@ -9,7 +9,8 @@ import java.util.UUID; -import static org.junit.Assert.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.*; public class TestStackTraceInterfaceBinding extends AbstractTestInterfaceBinding { @@ -37,9 +38,9 @@ public void testSingleStackFrame() throws Exception { jSonGenerator.close(); JsonNode frames = getMapper().readValue(getJsonParser(), JsonNode.class).get("frames"); - assertEquals(1, frames.size()); - assertEquals(className, frames.get(0).get("module").asText()); - assertEquals(methodName, frames.get(0).get("function").asText()); - assertEquals(lineNumber, frames.get(0).get("lineno").asInt()); + assertThat(frames.size(), is(1)); + assertThat(frames.get(0).get("module").asText(), is(className)); + assertThat(frames.get(0).get("function").asText(), is(methodName)); + assertThat(frames.get(0).get("lineno").asInt(), is(lineNumber)); } } From 7ebd7d519a43595b7aab60e4c8d9d6572bf3c1e4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 17:29:11 +0100 Subject: [PATCH 0243/2152] Add test to check the dsnLook when it's empty --- raven/src/test/java/net/kencochrane/raven/DsnTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/DsnTest.java index a8343b356dd..178b0bd0f85 100644 --- a/raven/src/test/java/net/kencochrane/raven/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/DsnTest.java @@ -26,6 +26,11 @@ public void testSimpleDsnValid() { assertThat(dsn.getProjectId(), is("9")); } + @Test + public void testDsnLookupWithNothingSet() { + assertThat(Dsn.dsnLookup(), is(nullValue())); + } + @Test(expected = InvalidDsnException.class) public void testMissingSecretKeyInvalid() { new Dsn("http://publicKey:@host/9"); From f2b81dcfd36cfd56e7723156f0d221ff16b2fcd9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 17:31:48 +0100 Subject: [PATCH 0244/2152] Add unit test for DSN lookup through JNDI --- .../java/net/kencochrane/raven/DsnTest.java | 26 +++++++++++++++++++ .../raven/InitialContextMockFactory.java | 15 +++++++++++ 2 files changed, 41 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/InitialContextMockFactory.java diff --git a/raven/src/test/java/net/kencochrane/raven/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/DsnTest.java index 178b0bd0f85..23597e3fab5 100644 --- a/raven/src/test/java/net/kencochrane/raven/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/DsnTest.java @@ -1,14 +1,32 @@ package net.kencochrane.raven; import net.kencochrane.raven.exception.InvalidDsnException; +import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import javax.naming.Context; +import javax.naming.NamingException; +import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.when; public class DsnTest { + @Mock + private Context context; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + System.setProperty("java.naming.factory.initial", InitialContextMockFactory.class.getCanonicalName()); + InitialContextMockFactory.context = context; + } + @Test(expected = InvalidDsnException.class) public void testEmptyDsnInvalid() { new Dsn(""); @@ -31,6 +49,14 @@ public void testDsnLookupWithNothingSet() { assertThat(Dsn.dsnLookup(), is(nullValue())); } + @Test + public void testDsnLookupWithJndi() throws NamingException { + String dsn = UUID.randomUUID().toString(); + when(context.lookup("java:comp/env/sentry/dsn")).thenReturn(dsn); + + assertThat(Dsn.dsnLookup(), is(dsn)); + } + @Test(expected = InvalidDsnException.class) public void testMissingSecretKeyInvalid() { new Dsn("http://publicKey:@host/9"); diff --git a/raven/src/test/java/net/kencochrane/raven/InitialContextMockFactory.java b/raven/src/test/java/net/kencochrane/raven/InitialContextMockFactory.java new file mode 100644 index 00000000000..67a07906c89 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/InitialContextMockFactory.java @@ -0,0 +1,15 @@ +package net.kencochrane.raven; + +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.spi.InitialContextFactory; +import java.util.Hashtable; + +public class InitialContextMockFactory implements InitialContextFactory { + public static Context context; + + @Override + public Context getInitialContext(Hashtable environment) throws NamingException { + return context; + } +} From 399296bbd7de355f1a909a330f2f8cb7c43fa37c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 17:32:28 +0100 Subject: [PATCH 0245/2152] Add unit test for DSN lookup with System property --- raven/src/test/java/net/kencochrane/raven/DsnTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/DsnTest.java index 23597e3fab5..9ebc51b7155 100644 --- a/raven/src/test/java/net/kencochrane/raven/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/DsnTest.java @@ -57,6 +57,16 @@ public void testDsnLookupWithJndi() throws NamingException { assertThat(Dsn.dsnLookup(), is(dsn)); } + @Test + public void testDsnLookupWithSystemProperty() { + String dsn = UUID.randomUUID().toString(); + System.setProperty("SENTRY_DSN", dsn); + + assertThat(Dsn.dsnLookup(), is(dsn)); + + System.clearProperty("SENTRY_DSN"); + } + @Test(expected = InvalidDsnException.class) public void testMissingSecretKeyInvalid() { new Dsn("http://publicKey:@host/9"); From 432b55ac072923d485c1bdf179eaf6516c3af4bf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 17:32:57 +0100 Subject: [PATCH 0246/2152] Add unit test for DSN lookup with Environment Variable --- .../java/net/kencochrane/raven/DsnTest.java | 66 ++++++++++++++++++- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/DsnTest.java index 9ebc51b7155..facabcee561 100644 --- a/raven/src/test/java/net/kencochrane/raven/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/DsnTest.java @@ -8,12 +8,13 @@ import javax.naming.Context; import javax.naming.NamingException; +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.Map; import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.hasKey; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; import static org.mockito.Mockito.when; public class DsnTest { @@ -67,6 +68,65 @@ public void testDsnLookupWithSystemProperty() { System.clearProperty("SENTRY_DSN"); } + @Test + public void testDsnLookupWithEnvironmentVariable() throws Exception { + String dsn = UUID.randomUUID().toString(); + setEnv("SENTRY_DSN", dsn); + + assertThat(Dsn.dsnLookup(), is(dsn)); + + removeEnv("SENTRY_DSN"); + } + + /** + * Sets an environment variable during Unit-Test. + *

    + * DO NOT USE THIS METHOD OUTSIDE OF THE UNIT TESTS! + *

    + * + * @param key name of the environment variable. + * @param value value for the variable. + * @throws Exception if anything goes wrong. + */ + @SuppressWarnings("unchecked") + private void setEnv(String key, String value) throws Exception { + Class[] classes = Collections.class.getDeclaredClasses(); + Map env = System.getenv(); + for (Class cl : classes) { + if (env.getClass().equals(cl)) { + Field field = cl.getDeclaredField("m"); + field.setAccessible(true); + Map map = (Map) field.get(env); + map.put(key, value); + break; + } + } + } + + /** + * Removes an environment variable during Unit-Test. + *

    + * DO NOT USE THIS METHOD OUTSIDE OF THE UNIT TESTS! + *

    + * + * @param key name of the environment variable. + * @throws Exception if anything goes wrong. + */ + @SuppressWarnings("unchecked") + private void removeEnv(String key) throws Exception { + Class[] classes = Collections.class.getDeclaredClasses(); + Map env = System.getenv(); + for (Class cl : classes) { + if (env.getClass().equals(cl)) { + Field field = cl.getDeclaredField("m"); + field.setAccessible(true); + Map map = (Map) field.get(env); + map.remove(key); + break; + } + } + } + @Test(expected = InvalidDsnException.class) public void testMissingSecretKeyInvalid() { new Dsn("http://publicKey:@host/9"); From d1f41c831f19ce6eae0778ffe7c312cd856486ff Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 17:46:28 +0100 Subject: [PATCH 0247/2152] Allow UdpConnection and HttpConnection to be used without DSN --- .../raven/connection/AbstractConnection.java | 24 +++++++------------ .../raven/connection/HttpConnection.java | 11 ++++++--- .../raven/connection/UdpConnection.java | 20 ++++++++++++---- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index a48c6f7826c..21b382de5cb 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -14,7 +14,8 @@ public abstract class AbstractConnection implements Connection { * Current sentry protocol version. */ public static final String SENTRY_PROTOCOL_VERSION = "3"; - private final Dsn dsn; + private final String publicKey; + private final String secretKey; /** * Creates a connection based on a DSN. @@ -22,9 +23,11 @@ public abstract class AbstractConnection implements Connection { * @param dsn Data Source Name of the sentry server. */ protected AbstractConnection(Dsn dsn) { - if (dsn == null) - throw new IllegalArgumentException("The DSN must be not null for the connection to work"); - this.dsn = dsn; + this(dsn.getPublicKey(), dsn.getSecretKey()); + } + protected AbstractConnection(String publicKey, String secretKey) { + this.publicKey = publicKey; + this.secretKey = secretKey; } /** @@ -37,17 +40,8 @@ protected String getAuthHeader() { StringBuilder header = new StringBuilder(); header.append("Sentry sentry_version=").append(SENTRY_PROTOCOL_VERSION); header.append(",sentry_client=").append(Raven.NAME); - header.append(",sentry_key=").append(dsn.getPublicKey()); - header.append(",sentry_secret=").append(dsn.getSecretKey()); + header.append(",sentry_key=").append(publicKey); + header.append(",sentry_secret=").append(secretKey); return header.toString(); } - - /** - * Get the Data Source Name used by the current connection. - * - * @return DSN for the current connection. - */ - protected Dsn getDsn() { - return dsn; - } } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index e8d445e3457..d95f1ced04e 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -41,7 +41,7 @@ public boolean verify(String hostname, SSLSession sslSession) { public HttpConnection(Dsn dsn) { super(dsn); - sentryUrl = getSentryUrl(); + this.sentryUrl = getSentryUrl(dsn); // Check if a timeout is set if (dsn.getOptions().containsKey(Dsn.TIMEOUT_OPTION)) @@ -52,9 +52,14 @@ public HttpConnection(Dsn dsn) { setBypassSecurity(true); } - private URL getSentryUrl() { + public HttpConnection(URL sentryUrl, String publicKey, String secretKey) { + super(publicKey, secretKey); + this.sentryUrl = sentryUrl; + } + + private URL getSentryUrl(Dsn dsn) { try { - String url = getDsn().getUri().toString() + "api/" + getDsn().getProjectId() + "/store/"; + String url = dsn.getUri().toString() + "api/" + dsn.getProjectId() + "/store/"; return new URL(url); } catch (MalformedURLException e) { throw new IllegalArgumentException("Couldn't get a valid URL from the DSN.", e); diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index 211d8b48d61..ae9704c4030 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -21,13 +21,23 @@ */ public class UdpConnection extends AbstractConnection { private static final Logger logger = Logger.getLogger(UdpConnection.class.getCanonicalName()); + private static final int DEFAULT_UDP_PORT = 9001; private DatagramSocket socket; private Charset charset = Charset.defaultCharset(); private Marshaller marshaller = new JsonMarshaller(); public UdpConnection(Dsn dsn) { super(dsn); - openSocket(); + openSocket(dsn.getHost(), dsn.getPort()); + } + + public UdpConnection(String hostname, String publicKey, String secretKey) { + this(hostname, DEFAULT_UDP_PORT, publicKey, secretKey); + } + + public UdpConnection(String hostname, int port, String publicKey, String secretKey) { + super(publicKey, secretKey); + openSocket(hostname, port); } @Override @@ -51,13 +61,13 @@ private void writeHeader(OutputStream os) throws IOException { os.write("\n\n".getBytes(charset)); } - private void openSocket() { + private void openSocket(String hostname, int port) { try { socket = new DatagramSocket(); - socket.connect(new InetSocketAddress(getDsn().getHost(), getDsn().getPort())); + socket.connect(new InetSocketAddress(hostname, port)); } catch (SocketException e) { - throw new IllegalStateException("The UDP connection couldn't be used, impossible to send anything " + - "to sentry", e); + throw new IllegalStateException("The UDP connection couldn't be used, impossible to send anything " + + "to sentry", e); } } From a845c96f881131dce8807893c858de68d3134469 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 18:12:50 +0100 Subject: [PATCH 0248/2152] Force matcher's type to avoid generic clashes --- .../raven/marshaller/json/TestMessageInterfaceBinding.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java index 4b06692850b..ba9a840ae8b 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonNode; import net.kencochrane.raven.event.interfaces.MessageInterface; +import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; @@ -38,6 +39,6 @@ public void testSimpleMessage() throws Exception { JsonNode rootNode = getMapper().readValue(getJsonParser(), JsonNode.class); assertThat(rootNode.get("message").asText(), is(message)); - assertThat(getMapper().convertValue(rootNode.get("params"), List.class), is(parameters)); + assertThat(getMapper().convertValue(rootNode.get("params"), List.class), Matchers.is(parameters)); } } From 2491b22ef4e305557424cde7270cc3192e47de8e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 18:40:18 +0100 Subject: [PATCH 0249/2152] Format/Simplify the code of SentryAppender for log4j --- .../kencochrane/raven/log4j/SentryAppender.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 97616bb25cb..e3614eed4c7 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -25,16 +25,16 @@ public SentryAppender(Raven raven) { this.raven = raven; } - private static Event.Level formatLevel(LoggingEvent loggingEvent) { - if (loggingEvent.getLevel().isGreaterOrEqual(Level.FATAL)) { + private static Event.Level formatLevel(Level level) { + if (level.isGreaterOrEqual(Level.FATAL)) { return Event.Level.FATAL; - } else if (loggingEvent.getLevel().isGreaterOrEqual(Level.ERROR)) { + } else if (level.isGreaterOrEqual(Level.ERROR)) { return Event.Level.ERROR; - } else if (loggingEvent.getLevel().isGreaterOrEqual(Level.WARN)) { + } else if (level.isGreaterOrEqual(Level.WARN)) { return Event.Level.WARNING; - } else if (loggingEvent.getLevel().isGreaterOrEqual(Level.INFO)) { + } else if (level.isGreaterOrEqual(Level.INFO)) { return Event.Level.INFO; - } else if (loggingEvent.getLevel().isGreaterOrEqual(Level.ALL)) { + } else if (level.isGreaterOrEqual(Level.ALL)) { return Event.Level.DEBUG; } else return null; } @@ -45,7 +45,7 @@ protected void append(LoggingEvent loggingEvent) { .setTimestamp(new Date(loggingEvent.getTimeStamp())) .setMessage(loggingEvent.getRenderedMessage()) .setLogger(loggingEvent.getLoggerName()) - .setLevel(formatLevel(loggingEvent)) + .setLevel(formatLevel(loggingEvent.getLevel())) .setCulprit(loggingEvent.getFQNOfLoggerClass()); if (loggingEvent.getThrowableInformation() != null) { From b890a9542004f74f9fa392b1f7345fa0d9ebd240 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 18:41:02 +0100 Subject: [PATCH 0250/2152] Start a log4j2 project to handle the new log4j --- pom.xml | 7 +++++++ raven-log4j2/pom.xml | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 raven-log4j2/pom.xml diff --git a/pom.xml b/pom.xml index 0dc41cd76ed..717afc96ec4 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,7 @@ 4.11 1.9.5 1.3 + 2.0-beta4 @@ -77,6 +78,7 @@ raven raven-log4j raven-logback + raven-log4j2 @@ -122,6 +124,11 @@ logback-classic ${logback.version} + + org.apache.logging.log4j + log4j-core + ${log4j2.version} + org.mockito mockito-core diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml new file mode 100644 index 00000000000..d6e0eff15ec --- /dev/null +++ b/raven-log4j2/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + + net.kencochrane + raven-all + 3.0-SNAPSHOT + + raven-log4j + jar + raven-log4j + Log4J appender for Raven/Sentry. + + + + ${project.groupId} + raven + + + org.apache.logging.log4j + log4j-core + + + org.mockito + mockito-core + test + + + junit + junit + test + + + From 7af30c470d71b720bba2a92238f36c851f15c91f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 18:41:18 +0100 Subject: [PATCH 0251/2152] Create a basic Appender for log4j2 --- .../raven/log4j2/SentryAppender.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java new file mode 100644 index 00000000000..d61c160d2ca --- /dev/null +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -0,0 +1,87 @@ +package net.kencochrane.raven.log4j2; + +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; + +import java.io.Serializable; +import java.util.Date; +import java.util.Map; + +public class SentryAppender extends AbstractAppender { + public static final String APPENDER_NAME = "raven"; + private static final String LOG4J_NDC = "Log4J-NDC"; + private final Raven raven; + + public SentryAppender() { + this(new Raven()); + } + + public SentryAppender(Raven raven) { + super(APPENDER_NAME, null, null); + this.raven = raven; + } + + private static Event.Level formatLevel(Level level) { + switch (level) { + case FATAL: + return Event.Level.FATAL; + case ERROR: + return Event.Level.ERROR; + case WARN: + return Event.Level.WARNING; + case INFO: + return Event.Level.INFO; + case DEBUG: + case TRACE: + return Event.Level.DEBUG; + default: + return null; + } + } + + @Override + public void append(LogEvent event) { + EventBuilder eventBuilder = new EventBuilder() + .setTimestamp(new Date(event.getMillis())) + .setMessage(event.getMessage().getFormattedMessage()) + .setLogger(event.getLoggerName()) + .setLevel(formatLevel(event.getLevel())) + .setCulprit(formatCulprit(event.getSource())); + + if (event.getThrown() != null) { + Throwable throwable = event.getThrown(); + eventBuilder.addSentryInterface(new ExceptionInterface(throwable)) + .addSentryInterface(new StackTraceInterface(throwable)); + eventBuilder.setCulprit(throwable); + } else { + // When it's a message try to rely on the position of the log (the same message can be logged from + // different places, or a same place can log a message in different ways. + eventBuilder.generateChecksum(formatCulprit(event.getSource())); + } + + if (event.getContextStack() != null) { + eventBuilder.addExtra(LOG4J_NDC, event.getContextStack().asList()); + } + + if (event.getContextMap() != null) { + for (Map.Entry mdcEntry : event.getContextMap().entrySet()) { + eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); + } + } + + raven.runBuilderHelpers(eventBuilder); + + raven.sendEvent(eventBuilder.build()); + } + + private String formatCulprit(StackTraceElement stackTraceElement) { + return stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + + " at line " + stackTraceElement.getLineNumber(); + } +} From 473b1f3f323487c4921c143d46cbc8d95fd70c6b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 21:55:17 +0100 Subject: [PATCH 0252/2152] Add the CloseableInterface to Connection --- .../raven/connection/AsyncConnection.java | 44 ++++++++++++++----- .../raven/connection/Connection.java | 5 ++- .../raven/connection/HttpConnection.java | 4 ++ .../raven/connection/UdpConnection.java | 5 +++ 4 files changed, 47 insertions(+), 11 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 24d8ebb3016..5cd073e8ae0 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -2,6 +2,7 @@ import net.kencochrane.raven.event.Event; +import java.io.IOException; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -27,6 +28,7 @@ public class AsyncConnection implements Connection { private final Connection actualConnection; private final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new DaemonThreadFactory()); + private final boolean propagateClose; /** * Create a connection which will rely on an executor to send events. @@ -34,7 +36,17 @@ public class AsyncConnection implements Connection { * @param actualConnection connection used to send the events. */ public AsyncConnection(Connection actualConnection) { + this(actualConnection, true); + } + + /** + * Create a connection which will rely on an executor to send events. + * + * @param actualConnection connection used to send the events. + */ + public AsyncConnection(Connection actualConnection, boolean propagateClose) { this.actualConnection = actualConnection; + this.propagateClose = propagateClose; addShutdownHook(); } @@ -42,17 +54,10 @@ private void addShutdownHook() { //TODO: JUL loggers are shutdown by an other shutdown hook, it's possible that nothing will get actually logged. Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { - logger.log(Level.INFO, "Gracefully shutdown sentry threads."); - executorService.shutdown(); try { - if (!executorService.awaitTermination(TIMEOUT, TimeUnit.MILLISECONDS)) { - logger.log(Level.WARNING, "Graceful shutdown took too much time, forcing the shutdown."); - List tasks = executorService.shutdownNow(); - logger.log(Level.INFO, tasks.size() + " tasks failed to execute before the shutdown."); - } - logger.log(Level.SEVERE, "Shutdown interrupted."); - } catch (InterruptedException e) { - logger.log(Level.SEVERE, "Shutdown interrupted."); + close(); + } catch (IOException e) { + logger.log(Level.SEVERE, "An exception occurred while closing the connection.", e); } } }); @@ -107,4 +112,23 @@ public void run() { } } } + + @Override + public void close() throws IOException { + logger.log(Level.INFO, "Gracefully shutdown sentry threads."); + executorService.shutdown(); + try { + if (!executorService.awaitTermination(TIMEOUT, TimeUnit.MILLISECONDS)) { + logger.log(Level.WARNING, "Graceful shutdown took too much time, forcing the shutdown."); + List tasks = executorService.shutdownNow(); + logger.log(Level.INFO, tasks.size() + " tasks failed to execute before the shutdown."); + } + logger.log(Level.SEVERE, "Shutdown interrupted."); + } catch (InterruptedException e) { + logger.log(Level.SEVERE, "Shutdown interrupted."); + } finally { + if(propagateClose) + actualConnection.close(); + } + } } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/Connection.java b/raven/src/main/java/net/kencochrane/raven/connection/Connection.java index e84085d4044..892118531f5 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/Connection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/Connection.java @@ -2,10 +2,13 @@ import net.kencochrane.raven.event.Event; +import java.io.Closeable; + /** * Connection to a Sentry server, allowing to send captured events. */ -public interface Connection { +//TODO: Move to an AutoCloseable? +public interface Connection extends Closeable { /** * Sends an event to the sentry server. * diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index d95f1ced04e..8f492ba4b16 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -128,4 +128,8 @@ public void setMarshaller(Marshaller marshaller) { public void setBypassSecurity(boolean bypassSecurity) { this.bypassSecurity = bypassSecurity; } + + @Override + public void close() throws IOException { + } } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index ae9704c4030..3f598ce3c5d 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -78,4 +78,9 @@ public void setMarshaller(Marshaller marshaller) { public void setCharset(Charset charset) { this.charset = charset; } + + @Override + public void close() throws IOException { + socket.close(); + } } From a917a1954c056422d11ae2a0d9246c221b9323c7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 22:04:28 +0100 Subject: [PATCH 0253/2152] Let appenders/handlers close the connection If the close can be propagated, close the Raven connection when the handler is closed. --- .../raven/log4j/SentryAppender.java | 16 ++++++++++++- .../raven/log4j2/SentryAppender.java | 23 ++++++++++++++++++- .../raven/logback/SentryAppender.java | 22 +++++++++++++++++- .../java/net/kencochrane/raven/Raven.java | 4 ++++ .../kencochrane/raven/jul/SentryHandler.java | 16 ++++++++++++- 5 files changed, 77 insertions(+), 4 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index e3614eed4c7..947ffaead94 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -9,6 +9,7 @@ import org.apache.log4j.Level; import org.apache.log4j.spi.LoggingEvent; +import java.io.IOException; import java.util.Date; import java.util.Map; import java.util.Set; @@ -16,13 +17,19 @@ public class SentryAppender extends AppenderSkeleton { private static final String LOG4J_NDC = "Log4J-NDC"; private final Raven raven; + private final boolean propagateClose; public SentryAppender() { - this(new Raven()); + this(new Raven(), true); } public SentryAppender(Raven raven) { + this(raven, false); + } + + public SentryAppender(Raven raven, boolean propagateClose) { this.raven = raven; + this.propagateClose = propagateClose; } private static Event.Level formatLevel(Level level) { @@ -73,6 +80,13 @@ protected void append(LoggingEvent loggingEvent) { @Override public void close() { + try { + if (propagateClose) + raven.getConnection().close(); + } catch (IOException e) { + //TODO: What to do with that exception? + e.printStackTrace(); + } } @Override diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index d61c160d2ca..188b56bdbe4 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -9,6 +9,7 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AbstractAppender; +import java.io.IOException; import java.io.Serializable; import java.util.Date; import java.util.Map; @@ -17,14 +18,21 @@ public class SentryAppender extends AbstractAppender { public static final String APPENDER_NAME = "raven"; private static final String LOG4J_NDC = "Log4J-NDC"; private final Raven raven; + private final boolean propagateClose; public SentryAppender() { - this(new Raven()); + this(new Raven(), true); } + public SentryAppender(Raven raven) { + this(raven, false); + } + + public SentryAppender(Raven raven, boolean propagateClose) { super(APPENDER_NAME, null, null); this.raven = raven; + this.propagateClose = propagateClose; } private static Event.Level formatLevel(Level level) { @@ -84,4 +92,17 @@ private String formatCulprit(StackTraceElement stackTraceElement) { return stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + " at line " + stackTraceElement.getLineNumber(); } + + @Override + public void stop() { + super.stop(); + + try { + if (propagateClose) + raven.getConnection().close(); + } catch (IOException e) { + //TODO: What to do with that exception? + e.printStackTrace(); + } + } } diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 74dd002dd9b..54ece35eaca 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -11,6 +11,7 @@ import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -18,13 +19,19 @@ public class SentryAppender extends AppenderBase { private final Raven raven; + private final boolean propagateClose; public SentryAppender() { - this(new Raven()); + this(new Raven(), true); } public SentryAppender(Raven raven) { + this(raven, false); + } + + public SentryAppender(Raven raven, boolean propagateClose) { this.raven = raven; + this.propagateClose = propagateClose; } private static List formatArguments(Object[] argumentArray) { @@ -88,4 +95,17 @@ private String getEventPosition(ILoggingEvent iLoggingEvent) { } return sb.toString(); } + + @Override + public void stop() { + super.stop(); + + try { + if (propagateClose) + raven.getConnection().close(); + } catch (IOException e) { + //TODO: What to do with that exception? + e.printStackTrace(); + } + } } diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 1af35d03087..942759cac27 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -180,6 +180,10 @@ public void addBuilderHelper(EventBuilderHelper builderHelper) { builderHelpers.add(builderHelper); } + public Connection getConnection() { + return connection; + } + public void setConnection(Connection connection) { this.connection = connection; } diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 33ca1f05442..4ae05631d9d 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -7,6 +7,7 @@ import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -16,13 +17,19 @@ public class SentryHandler extends Handler { private final Raven raven; + private final boolean propagateClose; public SentryHandler() { - this(new Raven()); + this(new Raven(), true); } public SentryHandler(Raven raven) { + this(raven, false); + } + + public SentryHandler(Raven raven, boolean propagateClose) { this.raven = raven; + this.propagateClose = propagateClose; } private static Event.Level getLevel(Level level) { @@ -76,5 +83,12 @@ public void flush() { @Override public void close() throws SecurityException { + try { + if (propagateClose) + raven.getConnection().close(); + } catch (IOException e) { + //TODO: What to do with that exception? + e.printStackTrace(); + } } } From 20064e9adfa12e6a58e47ea13983169d81778ecd Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 22:33:54 +0100 Subject: [PATCH 0254/2152] Change the log4j2 artifactId --- raven-log4j2/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index d6e0eff15ec..fd4fb8bb52f 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -9,7 +9,7 @@ raven-all 3.0-SNAPSHOT - raven-log4j + raven-log4j2 jar raven-log4j Log4J appender for Raven/Sentry. From b88e7ae3d05fc143e3a44117bef65af1e01c0a34 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 22:34:28 +0100 Subject: [PATCH 0255/2152] Make the formatCulpritMethod static --- .../net/kencochrane/raven/log4j2/SentryAppender.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 188b56bdbe4..a928759e7f3 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -53,6 +53,11 @@ private static Event.Level formatLevel(Level level) { } } + private static String formatCulprit(StackTraceElement stackTraceElement) { + return stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + + " at line " + stackTraceElement.getLineNumber(); + } + @Override public void append(LogEvent event) { EventBuilder eventBuilder = new EventBuilder() @@ -88,11 +93,6 @@ public void append(LogEvent event) { raven.sendEvent(eventBuilder.build()); } - private String formatCulprit(StackTraceElement stackTraceElement) { - return stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() - + " at line " + stackTraceElement.getLineNumber(); - } - @Override public void stop() { super.stop(); From 89db687b7104b661aa496bd34b32ead6357c05bf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 22:34:51 +0100 Subject: [PATCH 0256/2152] Follow the log4j2 Appender configuration style --- .../raven/log4j2/SentryAppender.java | 50 +++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index a928759e7f3..e45527ca461 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -6,15 +6,22 @@ import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttr; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.layout.PatternLayout; import java.io.IOException; -import java.io.Serializable; import java.util.Date; import java.util.Map; -public class SentryAppender extends AbstractAppender { +@Plugin(name = "Sentry", type = "Sentry", elementType = "appender") +public class SentryAppender extends AbstractAppender { public static final String APPENDER_NAME = "raven"; private static final String LOG4J_NDC = "Log4J-NDC"; private final Raven raven; @@ -30,11 +37,48 @@ public SentryAppender(Raven raven) { } public SentryAppender(Raven raven, boolean propagateClose) { - super(APPENDER_NAME, null, null); + this(APPENDER_NAME, raven, PatternLayout.createLayout(null, null, null, null), null, true, propagateClose); + } + + private SentryAppender(String name, Raven raven, Layout layout, Filter filter, + boolean handleExceptions, boolean propagateClose) { + super(name, filter, layout, handleExceptions); this.raven = raven; this.propagateClose = propagateClose; } + /** + * Create a Sentry Appender. + * + * @param name The name of the Appender. + * @param dsn Data Source Name to access the Sentry server. + * @param suppress "true" if exceptions should be hidden from the application, "false" otherwise. + * The default is "true". + * @param layout The layout to use to format the event. If no layout is provided the default PatternLayout + * will be used. + * @param filter The filter, if any, to use. + * @return The SentryAppender. + */ + @PluginFactory + public static SentryAppender createAppender(@PluginAttr("name") final String name, + @PluginAttr("dsn") final String dsn, + @PluginAttr("suppressExceptions") final String suppress, + @PluginElement("layout") Layout layout, + @PluginElement("filters") final Filter filter) { + + final boolean handleExceptions = suppress == null ? true : Boolean.valueOf(suppress); + + if (name == null) { + LOGGER.error("No name provided for FileAppender"); + return null; + } + + if (layout == null) { + layout = PatternLayout.createLayout(null, null, null, null); + } + return new SentryAppender(name, new Raven(dsn), layout, filter, handleExceptions, true); + } + private static Event.Level formatLevel(Level level) { switch (level) { case FATAL: From e55fe9c034381c41a1b4b7c0652bc22def5a193d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 22:37:40 +0100 Subject: [PATCH 0257/2152] Make the dsn parameter optional in log4j2 --- .../java/net/kencochrane/raven/log4j2/SentryAppender.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index e45527ca461..099bdceef22 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -68,6 +68,8 @@ public static SentryAppender createAppender(@PluginAttr("name") final String nam final boolean handleExceptions = suppress == null ? true : Boolean.valueOf(suppress); + final Raven raven = dsn == null ? new Raven() : new Raven(dsn); + if (name == null) { LOGGER.error("No name provided for FileAppender"); return null; @@ -76,7 +78,7 @@ public static SentryAppender createAppender(@PluginAttr("name") final String nam if (layout == null) { layout = PatternLayout.createLayout(null, null, null, null); } - return new SentryAppender(name, new Raven(dsn), layout, filter, handleExceptions, true); + return new SentryAppender(name, raven, layout, filter, handleExceptions, true); } private static Event.Level formatLevel(Level level) { From cbbb7cf0c88cc0339eeee473cfe5e8798c2a2b0d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 23:12:07 +0100 Subject: [PATCH 0258/2152] Add hamcrest for log4j tests --- raven-log4j/pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 5139bc737c1..9c3a45d7ff1 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -33,5 +33,15 @@ junit test + + org.hamcrest + hamcrest-core + test + + + org.hamcrest + hamcrest-library + test + From 88bd84e106820ed3aae95edc918f3b57a8b4f108 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Apr 2013 23:12:47 +0100 Subject: [PATCH 0259/2152] Add tests for Log4j appender --- .../raven/log4j/SentryAppenderTest.java | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java new file mode 100644 index 00000000000..d94774f7059 --- /dev/null +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -0,0 +1,98 @@ +package net.kencochrane.raven.log4j; + +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.SentryInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import org.apache.log4j.*; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; + + +public class SentryAppenderTest { + @Mock + private Raven mockRaven; + private Logger logger; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + logger = Logger.getLogger(SentryAppenderTest.class); + logger.setLevel(Level.ALL); + logger.setAdditivity(false); + logger.addAppender(new SentryAppender(mockRaven)); + } + + @Test + public void testSimpleMessageLogging() { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + Event event; + + String message = UUID.randomUUID().toString(); + logger.info(message); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + event = eventCaptor.getValue(); + + assertThat(event.getLogger(), is(logger.getName())); + assertThat(event.getMessage(), is(message)); + } + + @Test + public void testLogLevelConversions() { + assertLevelConverted(Event.Level.DEBUG, Level.TRACE); + assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); + assertLevelConverted(Event.Level.INFO, Level.INFO); + assertLevelConverted(Event.Level.WARNING, Level.WARN); + assertLevelConverted(Event.Level.ERROR, Level.ERROR); + assertLevelConverted(Event.Level.FATAL, Level.FATAL); + } + + private void assertLevelConverted(Event.Level expectedLevel, Level level) { + reset(mockRaven); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + logger.log(level, null); + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getLevel(), is(expectedLevel)); + } + + @Test + public void testLogException() { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + Exception exception = new Exception(); + Event event; + + logger.error("message", exception); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + event = eventCaptor.getValue(); + SentryInterface exceptionInterface = event.getSentryInterfaces().get(ExceptionInterface.EXCEPTION_INTERFACE); + assertThat(exceptionInterface, instanceOf(ExceptionInterface.class)); + + // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. + // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. + assertThat(((ExceptionInterface) exceptionInterface).getThrowable(), Matchers.equalTo(exception)); + + + SentryInterface stackTraceInterface = event.getSentryInterfaces().get(StackTraceInterface.STACKTRACE_INTERFACE); + assertThat(stackTraceInterface, instanceOf(StackTraceInterface.class)); + + // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. + // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. + assertThat(((StackTraceInterface) stackTraceInterface).getThrowable(), Matchers.equalTo(exception)); + } +} From 9ab6f37d260c6ff2aa39549bd9fe8751ef952823 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 08:30:02 +0100 Subject: [PATCH 0260/2152] Add tests for logback appender --- raven-logback/pom.xml | 15 ++ .../raven/logback/SentryAppenderTest.java | 135 ++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 4b9832b96d7..f6226ad9279 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -28,10 +28,25 @@ logback-classic + + org.mockito + mockito-core + test + junit junit test + + org.hamcrest + hamcrest-core + test + + + org.hamcrest + hamcrest-library + test + diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java new file mode 100644 index 00000000000..493a715c87e --- /dev/null +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -0,0 +1,135 @@ +package net.kencochrane.raven.logback; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.MessageInterface; +import net.kencochrane.raven.event.interfaces.SentryInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; + +public class SentryAppenderTest { + @Mock + private Raven mockRaven; + private Logger logger; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + logger = new LoggerContext().getLogger(SentryAppenderTest.class); + logger.setLevel(Level.ALL); + Appender appender = new SentryAppender(mockRaven); + appender.start(); + logger.addAppender(appender); + } + + @Test + public void testSimpleMessageLogging() { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + Event event; + + String message = UUID.randomUUID().toString(); + logger.info(message); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + event = eventCaptor.getValue(); + + assertThat(event.getLogger(), is(logger.getName())); + assertThat(event.getMessage(), is(message)); + } + + @Test + public void testLogLevelConversions() { + assertLevelConverted(Event.Level.DEBUG, Level.TRACE); + assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); + assertLevelConverted(Event.Level.INFO, Level.INFO); + assertLevelConverted(Event.Level.WARNING, Level.WARN); + assertLevelConverted(Event.Level.ERROR, Level.ERROR); + } + + private void assertLevelConverted(Event.Level expectedLevel, Level level) { + reset(mockRaven); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + + if (level.isGreaterOrEqual(Level.ERROR)) { + logger.error("message"); + } else if (level.isGreaterOrEqual(Level.WARN)) { + logger.warn("message"); + } else if (level.isGreaterOrEqual(Level.INFO)) { + logger.info("message"); + } else if (level.isGreaterOrEqual(Level.DEBUG)) { + logger.debug("message"); + } else if (level.isGreaterOrEqual(Level.TRACE)) { + logger.trace("message"); + } + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getLevel(), is(expectedLevel)); + } + + @Test + public void testLogException() { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + Exception exception = new Exception(); + Event event; + + logger.error("message", exception); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + event = eventCaptor.getValue(); + SentryInterface exceptionInterface = event.getSentryInterfaces().get(ExceptionInterface.EXCEPTION_INTERFACE); + assertThat(exceptionInterface, instanceOf(ExceptionInterface.class)); + + // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. + // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. + assertThat(((ExceptionInterface) exceptionInterface).getThrowable(), Matchers.equalTo(exception)); + + + SentryInterface stackTraceInterface = event.getSentryInterfaces().get(StackTraceInterface.STACKTRACE_INTERFACE); + assertThat(stackTraceInterface, instanceOf(StackTraceInterface.class)); + + // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. + // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. + assertThat(((StackTraceInterface) stackTraceInterface).getThrowable(), Matchers.equalTo(exception)); + } + + @Test + public void testLogParametrisedMessage() { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + String message = UUID.randomUUID().toString(); + List parameters = Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()); + Event event; + + logger.info(message, parameters.toArray()); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + event = eventCaptor.getValue(); + assertThat(event.getSentryInterfaces().get(MessageInterface.MESSAGE_INTERFACE), instanceOf(MessageInterface.class)); + MessageInterface messageInterface = + (MessageInterface) event.getSentryInterfaces().get(MessageInterface.MESSAGE_INTERFACE); + + assertThat(messageInterface.getMessage(), is(message)); + assertThat(messageInterface.getParams(), is(parameters)); + } +} From 670de3f35efe7db086ca330ed18143650cddcb00 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 08:54:45 +0100 Subject: [PATCH 0261/2152] Create an abstract unit testsclass for loggers --- .../kencochrane/raven/AbstractLoggerTest.java | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java new file mode 100644 index 00000000000..cf3a3a61ca6 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java @@ -0,0 +1,116 @@ +package net.kencochrane.raven; + +import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.MessageInterface; +import net.kencochrane.raven.event.interfaces.SentryInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; + +public abstract class AbstractLoggerTest { + @Mock + private Raven mockRaven; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testSimpleMessageLogging() { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + Event event; + + String message = UUID.randomUUID().toString(); + logAnyLevel(message); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + event = eventCaptor.getValue(); + + assertThat(event.getLogger(), is(getCurrentLoggerName())); + assertThat(event.getMessage(), is(message)); + } + + protected Raven getMockRaven() { + return mockRaven; + } + + public abstract void logAnyLevel(String message); + + public abstract void logAnyLevel(String message, Throwable exception); + + public abstract void logAnyLevel(String message, List parameters); + + public abstract String getCurrentLoggerName(); + + @Test + public abstract void testLogLevelConversions(); + + protected void assertLogLevel(Event.Level expectedLevel) { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getLevel(), is(expectedLevel)); + reset(mockRaven); + } + + @Test + public void testLogException() { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + Exception exception = new Exception(); + Event event; + + logAnyLevel("message", exception); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + event = eventCaptor.getValue(); + SentryInterface exceptionInterface = event.getSentryInterfaces().get(ExceptionInterface.EXCEPTION_INTERFACE); + assertThat(exceptionInterface, instanceOf(ExceptionInterface.class)); + + // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. + // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. + assertThat(((ExceptionInterface) exceptionInterface).getThrowable(), Matchers.equalTo(exception)); + + + SentryInterface stackTraceInterface = event.getSentryInterfaces().get(StackTraceInterface.STACKTRACE_INTERFACE); + assertThat(stackTraceInterface, instanceOf(StackTraceInterface.class)); + + // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. + // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. + assertThat(((StackTraceInterface) stackTraceInterface).getThrowable(), Matchers.equalTo(exception)); + } + + @Test + public void testLogParametrisedMessage() { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + String message = UUID.randomUUID().toString(); + List parameters = Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()); + Event event; + + logAnyLevel(message, parameters); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + event = eventCaptor.getValue(); + assertThat(event.getSentryInterfaces().get(MessageInterface.MESSAGE_INTERFACE), instanceOf(MessageInterface.class)); + MessageInterface messageInterface = + (MessageInterface) event.getSentryInterfaces().get(MessageInterface.MESSAGE_INTERFACE); + + assertThat(messageInterface.getMessage(), is(message)); + assertThat(messageInterface.getParams(), is(parameters)); + } +} From 190bacbc6c99e640739e8cc7c279d8d974119a59 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 08:55:22 +0100 Subject: [PATCH 0262/2152] Use the AbstractLoggerTest for the JUL tests --- .../raven/jul/SentryHandlerTest.java | 100 ++++-------------- 1 file changed, 22 insertions(+), 78 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java index 9a5026d66d3..e0cb88cd4e1 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java @@ -1,59 +1,52 @@ package net.kencochrane.raven.jul; -import net.kencochrane.raven.Raven; +import net.kencochrane.raven.AbstractLoggerTest; import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.MessageInterface; -import net.kencochrane.raven.event.interfaces.SentryInterface; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; -import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import java.util.Arrays; import java.util.List; -import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; -public class SentryHandlerTest { - @Mock - private Raven mockRaven; +public class SentryHandlerTest extends AbstractLoggerTest { private Logger logger; + @Override @Before public void setUp() { - MockitoAnnotations.initMocks(this); + super.setUp(); logger = Logger.getAnonymousLogger(); logger.setUseParentHandlers(false); logger.setLevel(Level.ALL); - logger.addHandler(new SentryHandler(mockRaven)); + logger.addHandler(new SentryHandler(getMockRaven())); } - @Test - public void testSimpleMessageLogging() { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - Event event; - - String message = UUID.randomUUID().toString(); + @Override + public void logAnyLevel(String message) { logger.log(Level.INFO, message); + } - verify(mockRaven).sendEvent(eventCaptor.capture()); - event = eventCaptor.getValue(); + @Override + public void logAnyLevel(String message, Throwable exception) { + logger.log(Level.SEVERE, message, exception); + } + + @Override + public void logAnyLevel(String message, List parameters) { + logger.log(Level.INFO, message, parameters.toArray()); + } - assertThat(event.getLogger(), is(logger.getName())); - assertThat(event.getMessage(), is(message)); + @Override + public String getCurrentLoggerName() { + return logger.getName(); } + @Override @Test public void testLogLevelConversions() { assertLevelConverted(Event.Level.DEBUG, Level.FINEST); @@ -66,56 +59,7 @@ public void testLogLevelConversions() { } private void assertLevelConverted(Event.Level expectedLevel, Level level) { - reset(mockRaven); - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - logger.log(level, null); - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getLevel(), is(expectedLevel)); - } - - @Test - public void testLogException() { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - Exception exception = new Exception(); - Event event; - - logger.log(Level.SEVERE, "message", exception); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - event = eventCaptor.getValue(); - SentryInterface exceptionInterface = event.getSentryInterfaces().get(ExceptionInterface.EXCEPTION_INTERFACE); - assertThat(exceptionInterface, instanceOf(ExceptionInterface.class)); - - // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. - // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. - assertThat(((ExceptionInterface) exceptionInterface).getThrowable(), Matchers.equalTo(exception)); - - - SentryInterface stackTraceInterface = event.getSentryInterfaces().get(StackTraceInterface.STACKTRACE_INTERFACE); - assertThat(stackTraceInterface, instanceOf(StackTraceInterface.class)); - - // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. - // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. - assertThat(((StackTraceInterface) stackTraceInterface).getThrowable(), Matchers.equalTo(exception)); - } - - @Test - public void testLogParametrisedMessage() { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - String message = UUID.randomUUID().toString(); - List parameters = Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()); - Event event; - - logger.log(Level.INFO, message, parameters.toArray()); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - event = eventCaptor.getValue(); - assertThat(event.getSentryInterfaces().get(MessageInterface.MESSAGE_INTERFACE), instanceOf(MessageInterface.class)); - MessageInterface messageInterface = - (MessageInterface) event.getSentryInterfaces().get(MessageInterface.MESSAGE_INTERFACE); - - assertThat(messageInterface.getMessage(), is(message)); - assertThat(messageInterface.getParams(), is(parameters)); + assertLogLevel(expectedLevel); } } From 970f185a3a8549945009264951d671cf90ac54fa Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 08:56:24 +0100 Subject: [PATCH 0263/2152] Package test files in a test-jar --- pom.xml | 6 ++++++ raven/pom.xml | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/pom.xml b/pom.xml index 717afc96ec4..9796b32a026 100644 --- a/pom.xml +++ b/pom.xml @@ -88,6 +88,12 @@ raven ${project.version} + + ${project.groupId} + raven + ${project.version} + test-jar + commons-codec diff --git a/raven/pom.xml b/raven/pom.xml index f1547d509e2..af83934e75e 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -54,4 +54,21 @@ test + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + test-jar + + + + + + From ec34f8250b279b87bd2e6cff82055e02f1221eea Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 08:56:52 +0100 Subject: [PATCH 0264/2152] Use AbstractLoggerTest for log4j tests --- raven-log4j/pom.xml | 6 ++ .../raven/log4j/SentryAppenderTest.java | 88 ++++++------------- 2 files changed, 34 insertions(+), 60 deletions(-) diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 9c3a45d7ff1..4ea073cf1c6 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -23,6 +23,12 @@ log4j log4j + + ${project.groupId} + raven + test-jar + test + org.mockito mockito-core diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index d94774f7059..96d8efe876d 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -1,56 +1,49 @@ package net.kencochrane.raven.log4j; -import net.kencochrane.raven.Raven; +import net.kencochrane.raven.AbstractLoggerTest; import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.SentryInterface; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; -import org.apache.log4j.*; -import org.hamcrest.Matchers; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; import org.junit.Before; import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import java.util.UUID; +import java.util.List; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; - -public class SentryAppenderTest { - @Mock - private Raven mockRaven; +public class SentryAppenderTest extends AbstractLoggerTest { private Logger logger; @Before + @Override public void setUp() { - MockitoAnnotations.initMocks(this); + super.setUp(); logger = Logger.getLogger(SentryAppenderTest.class); logger.setLevel(Level.ALL); logger.setAdditivity(false); - logger.addAppender(new SentryAppender(mockRaven)); + logger.addAppender(new SentryAppender(getMockRaven())); } - @Test - public void testSimpleMessageLogging() { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - Event event; - - String message = UUID.randomUUID().toString(); + @Override + public void logAnyLevel(String message) { logger.info(message); + } - verify(mockRaven).sendEvent(eventCaptor.capture()); - event = eventCaptor.getValue(); + @Override + public void logAnyLevel(String message, Throwable exception) { + logger.error(message, exception); + } + + @Override + public void logAnyLevel(String message, List parameters) { + throw new UnsupportedOperationException(); + } - assertThat(event.getLogger(), is(logger.getName())); - assertThat(event.getMessage(), is(message)); + @Override + public String getCurrentLoggerName() { + return logger.getName(); } + @Override @Test public void testLogLevelConversions() { assertLevelConverted(Event.Level.DEBUG, Level.TRACE); @@ -62,37 +55,12 @@ public void testLogLevelConversions() { } private void assertLevelConverted(Event.Level expectedLevel, Level level) { - reset(mockRaven); - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - logger.log(level, null); - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getLevel(), is(expectedLevel)); + assertLogLevel(expectedLevel); } - @Test - public void testLogException() { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - Exception exception = new Exception(); - Event event; - - logger.error("message", exception); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - event = eventCaptor.getValue(); - SentryInterface exceptionInterface = event.getSentryInterfaces().get(ExceptionInterface.EXCEPTION_INTERFACE); - assertThat(exceptionInterface, instanceOf(ExceptionInterface.class)); - - // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. - // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. - assertThat(((ExceptionInterface) exceptionInterface).getThrowable(), Matchers.equalTo(exception)); - - - SentryInterface stackTraceInterface = event.getSentryInterfaces().get(StackTraceInterface.STACKTRACE_INTERFACE); - assertThat(stackTraceInterface, instanceOf(StackTraceInterface.class)); - - // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. - // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. - assertThat(((StackTraceInterface) stackTraceInterface).getThrowable(), Matchers.equalTo(exception)); + @Override + public void testLogParametrisedMessage() { + // Parametrised messages aren't supported } } From 401ea014e26954dbc4617197e5680733a679374f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 08:57:22 +0100 Subject: [PATCH 0265/2152] Use AbstractLoggerTest for logback tests --- raven-logback/pom.xml | 6 + .../raven/logback/SentryAppenderTest.java | 105 ++++-------------- 2 files changed, 28 insertions(+), 83 deletions(-) diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index f6226ad9279..6a8a02ff034 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -19,6 +19,12 @@ ${project.groupId} raven + + ${project.groupId} + raven + test-jar + test + ch.qos.logback logback-core diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java index 493a715c87e..ac60f2718cb 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -5,60 +5,49 @@ import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; -import net.kencochrane.raven.Raven; +import net.kencochrane.raven.AbstractLoggerTest; import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.MessageInterface; -import net.kencochrane.raven.event.interfaces.SentryInterface; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; -import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import java.util.Arrays; import java.util.List; -import java.util.UUID; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; - -public class SentryAppenderTest { - @Mock - private Raven mockRaven; +public class SentryAppenderTest extends AbstractLoggerTest { private Logger logger; @Before + @Override public void setUp() { - MockitoAnnotations.initMocks(this); + super.setUp(); logger = new LoggerContext().getLogger(SentryAppenderTest.class); logger.setLevel(Level.ALL); - Appender appender = new SentryAppender(mockRaven); + Appender appender = new SentryAppender(getMockRaven()); appender.start(); logger.addAppender(appender); } - @Test - public void testSimpleMessageLogging() { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - Event event; - - String message = UUID.randomUUID().toString(); + @Override + public void logAnyLevel(String message) { logger.info(message); + } - verify(mockRaven).sendEvent(eventCaptor.capture()); - event = eventCaptor.getValue(); + @Override + public void logAnyLevel(String message, Throwable exception) { + logger.error(message, exception); + } - assertThat(event.getLogger(), is(logger.getName())); - assertThat(event.getMessage(), is(message)); + @Override + public void logAnyLevel(String message, List parameters) { + logger.info(message, parameters.toArray()); + } + + @Override + public String getCurrentLoggerName() { + return logger.getName(); } @Test + @Override public void testLogLevelConversions() { assertLevelConverted(Event.Level.DEBUG, Level.TRACE); assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); @@ -68,10 +57,6 @@ public void testLogLevelConversions() { } private void assertLevelConverted(Event.Level expectedLevel, Level level) { - reset(mockRaven); - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - if (level.isGreaterOrEqual(Level.ERROR)) { logger.error("message"); } else if (level.isGreaterOrEqual(Level.WARN)) { @@ -84,52 +69,6 @@ private void assertLevelConverted(Event.Level expectedLevel, Level level) { logger.trace("message"); } - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getLevel(), is(expectedLevel)); - } - - @Test - public void testLogException() { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - Exception exception = new Exception(); - Event event; - - logger.error("message", exception); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - event = eventCaptor.getValue(); - SentryInterface exceptionInterface = event.getSentryInterfaces().get(ExceptionInterface.EXCEPTION_INTERFACE); - assertThat(exceptionInterface, instanceOf(ExceptionInterface.class)); - - // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. - // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. - assertThat(((ExceptionInterface) exceptionInterface).getThrowable(), Matchers.equalTo(exception)); - - - SentryInterface stackTraceInterface = event.getSentryInterfaces().get(StackTraceInterface.STACKTRACE_INTERFACE); - assertThat(stackTraceInterface, instanceOf(StackTraceInterface.class)); - - // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. - // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. - assertThat(((StackTraceInterface) stackTraceInterface).getThrowable(), Matchers.equalTo(exception)); - } - - @Test - public void testLogParametrisedMessage() { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - String message = UUID.randomUUID().toString(); - List parameters = Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()); - Event event; - - logger.info(message, parameters.toArray()); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - event = eventCaptor.getValue(); - assertThat(event.getSentryInterfaces().get(MessageInterface.MESSAGE_INTERFACE), instanceOf(MessageInterface.class)); - MessageInterface messageInterface = - (MessageInterface) event.getSentryInterfaces().get(MessageInterface.MESSAGE_INTERFACE); - - assertThat(messageInterface.getMessage(), is(message)); - assertThat(messageInterface.getParams(), is(parameters)); + assertLogLevel(expectedLevel); } } From 7bb03be8ce7620bf02d21798e464fa9a2db118e0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 08:57:52 +0100 Subject: [PATCH 0266/2152] Reorder properties --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9796b32a026..5aa6c19bde0 100644 --- a/pom.xml +++ b/pom.xml @@ -31,10 +31,10 @@ 3.0.1 1.0.11 1.2.17 + 2.0-beta4 4.11 1.9.5 1.3 - 2.0-beta4 From 44ef5bfd103286947652fe80964d6a77c650b0c0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 09:37:20 +0100 Subject: [PATCH 0267/2152] Change name and description of raven-log4j2 --- raven-log4j2/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index fd4fb8bb52f..6bc9eddb9f8 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -11,8 +11,8 @@ raven-log4j2 jar - raven-log4j - Log4J appender for Raven/Sentry. + raven-log4j2 + Log4J2 appender for Raven/Sentry. From 4cdae3b73d98677689940c2d668efde0201ec206 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 09:37:32 +0100 Subject: [PATCH 0268/2152] Remove TODO, large strings shouldn't be used. There is no real reason to send a significantly large string to the calculateChecksum method, any problem will be the user's responsibility --- .../src/main/java/net/kencochrane/raven/event/EventBuilder.java | 1 - 1 file changed, 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 2c74edf626c..96b48c4c27c 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -49,7 +49,6 @@ public EventBuilder(UUID eventId) { * @return a checksum allowing two events with the same properties to be grouped later. */ private static String calculateChecksum(String string) { - // TODO: Large strings will be poorly handled byte[] bytes = string.getBytes(); Checksum checksum = new CRC32(); checksum.update(bytes, 0, bytes.length); From 5005f9bed364869b781cc226ba59f8d70c33c87e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 09:42:34 +0100 Subject: [PATCH 0269/2152] Make used transitive dependencies explicit --- pom.xml | 5 +++++ raven-log4j2/pom.xml | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/pom.xml b/pom.xml index 5aa6c19bde0..cd69adefe4b 100644 --- a/pom.xml +++ b/pom.xml @@ -130,6 +130,11 @@ logback-classic ${logback.version} + + org.apache.logging.log4j + log4j-api + ${log4j2.version} + org.apache.logging.log4j log4j-core diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 6bc9eddb9f8..a60af7732e3 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -19,6 +19,10 @@ ${project.groupId} raven + + org.apache.logging.log4j + log4j-api + org.apache.logging.log4j log4j-core From ce85fc78bec2d38f9b1bb194d30280e6798dff16 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 14:35:15 +0100 Subject: [PATCH 0270/2152] Allow the Async connection to set some options. The priority and number of threads can now be set, either through the DSN configuration or by manually providing those settings in the constructor. By default the threads priority will be the lowest possible. --- .../java/net/kencochrane/raven/Raven.java | 2 +- .../raven/connection/AsyncConnection.java | 131 +++++++++++++----- 2 files changed, 101 insertions(+), 32 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 942759cac27..cb32615a0ef 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -130,7 +130,7 @@ private static Connection determineConnection(Dsn dsn) { } if (dsn.getOptions().containsKey(Dsn.ASYNC_OPTION)) - connection = new AsyncConnection(connection); + connection = new AsyncConnection(connection, dsn); return connection; } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 5cd073e8ae0..f8f0b6c8e0a 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -1,5 +1,6 @@ package net.kencochrane.raven.connection; +import net.kencochrane.raven.Dsn; import net.kencochrane.raven.event.Event; import java.io.IOException; @@ -20,38 +21,90 @@ *

    */ public class AsyncConnection implements Connection { + /** + * DSN option for the number of threads assigned for the connection. + */ + public static final String MAX_THREADS_OPTION = "raven.async.threads"; + /** + * DSN option for the priority of threads assigned for the connection. + */ + public static final String PRIORITY_OPTION = "raven.async.priority"; private static final Logger logger = Logger.getLogger(AsyncConnection.class.getCanonicalName()); + /** + * Timeout of the {@link #executorService}. + */ private static final int TIMEOUT = 1000; + /** + * Number of threads dedicated to the connection usage by default (Number of processors available). + */ + private static final int DEFAULT_MAX_THREADS = Runtime.getRuntime().availableProcessors(); + /** + * Default threads priority. + */ + private static final int DEFAULT_PRIORITY = Thread.MIN_PRIORITY; /** * Connection used to actually send the events. */ private final Connection actualConnection; - private final ExecutorService executorService = - Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new DaemonThreadFactory()); + /** + * Executor service in charge of running the connection in separate threads. + */ + private final ExecutorService executorService; + /** + * Option to disable the propagation of the {@link #close()} operation to the actual connection. + */ private final boolean propagateClose; /** - * Create a connection which will rely on an executor to send events. + * Creates a connection which will rely on an executor to send events. + *

    + * Will propagate the {@link #close()} operation and use {@link #DEFAULT_MAX_THREADS} and {@link #DEFAULT_PRIORITY}. + *

    * * @param actualConnection connection used to send the events. */ public AsyncConnection(Connection actualConnection) { - this(actualConnection, true); + this(actualConnection, true, DEFAULT_MAX_THREADS, DEFAULT_PRIORITY); } /** - * Create a connection which will rely on an executor to send events. + * Creates a connection which will rely on an executor to send events. + *

    + * Will propagate the {@link #close()} operation and + *

    * - * @param actualConnection connection used to send the events. + * @param actualConnection + * @param dsn */ - public AsyncConnection(Connection actualConnection, boolean propagateClose) { + public AsyncConnection(Connection actualConnection, Dsn dsn) { + this(actualConnection, true, getMaxThreads(dsn), getPriority(dsn)); + } + + public AsyncConnection(Connection actualConnection, boolean propagateClose, int maxThreads, int priority) { this.actualConnection = actualConnection; this.propagateClose = propagateClose; + executorService = Executors.newFixedThreadPool(maxThreads, new DaemonThreadFactory(priority)); addShutdownHook(); } + private static int getMaxThreads(Dsn dsn) { + if (dsn.getOptions().containsKey(MAX_THREADS_OPTION)) { + return Integer.parseInt(dsn.getOptions().get(MAX_THREADS_OPTION)); + } else { + return DEFAULT_MAX_THREADS; + } + } + + private static int getPriority(Dsn dsn) { + if (dsn.getOptions().containsKey(PRIORITY_OPTION)) { + return Integer.parseInt(dsn.getOptions().get(PRIORITY_OPTION)); + } else { + return DEFAULT_PRIORITY; + } + } + private void addShutdownHook() { - //TODO: JUL loggers are shutdown by an other shutdown hook, it's possible that nothing will get actually logged. + // JUL loggers are shutdown by an other shutdown hook, it's possible that nothing will get actually logged. Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { try { @@ -65,20 +118,55 @@ public void run() { @Override public void send(Event event) { - // TODO: Consider adding an option to wait when it's full? executorService.execute(new EventSubmitter(event)); } + /** + * {@inheritDoc}. + *

    + * Closing the {@link AsyncConnection} will attempt a graceful shutdown of the {@link #executorService} with a + * timeout of {@link #TIMEOUT}, allowing the current events to be submitted while new events will be rejected.
    + * If the shutdown times out, the {@code executorService} will be forced to shutdown. + *

    + */ + @Override + public void close() throws IOException { + logger.log(Level.INFO, "Gracefully shutdown sentry threads."); + executorService.shutdown(); + try { + if (!executorService.awaitTermination(TIMEOUT, TimeUnit.MILLISECONDS)) { + logger.log(Level.WARNING, "Graceful shutdown took too much time, forcing the shutdown."); + List tasks = executorService.shutdownNow(); + logger.log(Level.INFO, tasks.size() + " tasks failed to execute before the shutdown."); + } + logger.log(Level.SEVERE, "Shutdown interrupted."); + } catch (InterruptedException e) { + logger.log(Level.SEVERE, "Shutdown interrupted."); + } finally { + if (propagateClose) + actualConnection.close(); + } + } + + /** + * Thread factory generating daemon threads with a custom priority. + *

    + * Those (usually) low priority threads will allow to send event details to sentry concurrently without slowing + * down the main application. + *

    + */ private static final class DaemonThreadFactory implements ThreadFactory { private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1); private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; + private final int priority; - private DaemonThreadFactory() { + private DaemonThreadFactory(int priority) { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); namePrefix = "pool-" + POOL_NUMBER.getAndIncrement() + "-thread-"; + this.priority = priority; } @Override @@ -86,8 +174,8 @@ public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); if (!t.isDaemon()) t.setDaemon(true); - if (t.getPriority() != Thread.NORM_PRIORITY) - t.setPriority(Thread.NORM_PRIORITY); + if (t.getPriority() != priority) + t.setPriority(priority); return t; } } @@ -112,23 +200,4 @@ public void run() { } } } - - @Override - public void close() throws IOException { - logger.log(Level.INFO, "Gracefully shutdown sentry threads."); - executorService.shutdown(); - try { - if (!executorService.awaitTermination(TIMEOUT, TimeUnit.MILLISECONDS)) { - logger.log(Level.WARNING, "Graceful shutdown took too much time, forcing the shutdown."); - List tasks = executorService.shutdownNow(); - logger.log(Level.INFO, tasks.size() + " tasks failed to execute before the shutdown."); - } - logger.log(Level.SEVERE, "Shutdown interrupted."); - } catch (InterruptedException e) { - logger.log(Level.SEVERE, "Shutdown interrupted."); - } finally { - if(propagateClose) - actualConnection.close(); - } - } } From 5ce98a6e96fce5b8681a6b51e549ccdcb4bd5dde Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 14:36:59 +0100 Subject: [PATCH 0271/2152] Fix style and indentation --- .../raven/connection/AbstractConnection.java | 1 + .../main/java/net/kencochrane/raven/event/Event.java | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 21b382de5cb..bd3f4a1fca5 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -25,6 +25,7 @@ public abstract class AbstractConnection implements Connection { protected AbstractConnection(Dsn dsn) { this(dsn.getPublicKey(), dsn.getSecretKey()); } + protected AbstractConnection(String publicKey, String secretKey) { this.publicKey = publicKey; this.secretKey = secretKey; diff --git a/raven/src/main/java/net/kencochrane/raven/event/Event.java b/raven/src/main/java/net/kencochrane/raven/event/Event.java index 68ddef3be22..13e59f2e847 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/Event.java +++ b/raven/src/main/java/net/kencochrane/raven/event/Event.java @@ -193,11 +193,11 @@ public int hashCode() { @Override public String toString() { - return "Event{" + - "level=" + level + - ", message='" + message + '\'' + - ", logger='" + logger + '\'' + - '}'; + return "Event{" + + "level=" + level + + ", message='" + message + '\'' + + ", logger='" + logger + '\'' + + '}'; } public static enum Level { From 940912b1e9571fa17c2088eaf13f54f6fbd99253 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 14:37:24 +0100 Subject: [PATCH 0272/2152] Update the project name description and URL --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index cd69adefe4b..847c12f5c1e 100644 --- a/pom.xml +++ b/pom.xml @@ -13,9 +13,9 @@ raven-all 3.0-SNAPSHOT pom - raven-all - Java Raven client and loggers. - http://sentry.readthedocs.org/en/latest/client/index.html + Java-Raven + Java Raven client and loggers + https://github.com/kencochrane/raven-java UTF-8 From bfb89f8cb04d3d1689d4c5b902cab22346d587f6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 14:37:58 +0100 Subject: [PATCH 0273/2152] Move the properties after the project description --- pom.xml | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/pom.xml b/pom.xml index 847c12f5c1e..bb67f61aaa3 100644 --- a/pom.xml +++ b/pom.xml @@ -17,25 +17,6 @@ Java Raven client and loggers https://github.com/kencochrane/raven-java - - UTF-8 - UTF-8 - - - 6 - 6 - - - 1.6 - 2.1.4 - 3.0.1 - 1.0.11 - 1.2.17 - 2.0-beta4 - 4.11 - 1.9.5 - 1.3 - @@ -74,6 +55,26 @@ + + UTF-8 + UTF-8 + + + 6 + 6 + + + 1.6 + 2.1.4 + 3.0.1 + 1.0.11 + 1.2.17 + 2.0-beta4 + 4.11 + 1.9.5 + 1.3 + + raven raven-log4j From e5a49705b1cd153089bf703c8ae734fe03ead7f0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 14:39:55 +0100 Subject: [PATCH 0274/2152] Add details on the source/issue/distribution/CI management --- pom.xml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bb67f61aaa3..d7e9cbb3b88 100644 --- a/pom.xml +++ b/pom.xml @@ -27,10 +27,24 @@ - git@github.com:kencochrane/raven-java.git + https://github.com:kencochrane/raven-java scm:git:git://github.com/kencochrane/raven-java.git scm:git:git@github.com:kencochrane/raven-java.git + + https://github.com/kencochrane/raven-java/issues + GitHub Issues + + + + raven.java.server + scm:git:git@github.com:kencochrane/raven-java.git + + + + Travis-CI + http://travis-ci.org/kencochrane/raven-java + From fe6f6c0867faf56dc54a66e98c0c723507df09aa Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 14:40:44 +0100 Subject: [PATCH 0275/2152] Enable reporting Enables project info, the javadoc, surefire reports, checkstyle and JXR --- pom.xml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pom.xml b/pom.xml index d7e9cbb3b88..4f3d4a6a7a7 100644 --- a/pom.xml +++ b/pom.xml @@ -177,4 +177,34 @@
    + + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 2.6 + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9 + + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.14 + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.10 + + + org.apache.maven.plugins + maven-jxr-plugin + 2.3 + + + From 0c436c04b7260ed1ce9492488623455baeb152e0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 14:41:53 +0100 Subject: [PATCH 0276/2152] Configure contributors and developers Developers have commit access to the repository while contributors don't --- pom.xml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 4f3d4a6a7a7..442122e4cbb 100644 --- a/pom.xml +++ b/pom.xml @@ -58,16 +58,21 @@ kevin@roam.be - zeeg + ColinHebert + Colin Hebert + hebert.colin@gmail.com + + + + David Cramer contact@email.com - - - griphiam + + Mark Philpot contact@email.com - - + + UTF-8 From 51f02450d3786757f1861421e27e8f6cac960c93 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 14:42:31 +0100 Subject: [PATCH 0277/2152] Enable checkstyle with a local configuration --- pom.xml | 3 + src/checkstyle/checkstyle.xml | 237 ++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100644 src/checkstyle/checkstyle.xml diff --git a/pom.xml b/pom.xml index 442122e4cbb..3ffaaa298cd 100644 --- a/pom.xml +++ b/pom.xml @@ -204,6 +204,9 @@ org.apache.maven.plugins maven-checkstyle-plugin 2.10 + + src/checkstyle/checkstyle.xml + org.apache.maven.plugins diff --git a/src/checkstyle/checkstyle.xml b/src/checkstyle/checkstyle.xml new file mode 100644 index 00000000000..71136abb36d --- /dev/null +++ b/src/checkstyle/checkstyle.xml @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c604664f5405e5ec5d0ac1e9ba5eba203fcdd902 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 14:42:56 +0100 Subject: [PATCH 0278/2152] Add a basic site configuration --- src/site/site.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/site/site.xml diff --git a/src/site/site.xml b/src/site/site.xml new file mode 100644 index 00000000000..5e3b6783ff2 --- /dev/null +++ b/src/site/site.xml @@ -0,0 +1,17 @@ + + + org.apache.maven.skins + maven-fluido-skin + 1.3.0 + + + + + + + + + + + + From dacd908bbe9e5e3d2bd9d87ae50b08deeff08fa8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 15:03:47 +0100 Subject: [PATCH 0279/2152] Fix typos and links --- pom.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 3ffaaa298cd..2cb8471abed 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,6 @@ Java Raven client and loggers https://github.com/kencochrane/raven-java - BSD New @@ -27,7 +26,7 @@ - https://github.com:kencochrane/raven-java + https://github.com/kencochrane/raven-java scm:git:git://github.com/kencochrane/raven-java.git scm:git:git@github.com:kencochrane/raven-java.git @@ -43,7 +42,7 @@ Travis-CI - http://travis-ci.org/kencochrane/raven-java + https://travis-ci.org/kencochrane/raven-java From cb24b6d2002e7726ea97d4adcf40d73e6bd7b16b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 15:04:02 +0100 Subject: [PATCH 0280/2152] Update site's configuration Enable the github ribbon, set-up the pre-release style and display a link to the parent module --- src/site/site.xml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/site/site.xml b/src/site/site.xml index 5e3b6783ff2..ea899b66646 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -5,13 +5,25 @@ 1.3.0 + + + pre-release + true + + kencochrane/raven-java + right + red + + + - - + + + From 39a4a7460d47848013bfd73cba475db976bfc3a6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 15:21:34 +0100 Subject: [PATCH 0281/2152] Use a github.repo property to set up the links Instead of copying the repo name (kencochrane/raven-java) everywhere rely on a property set in the project. --- pom.xml | 16 +++++++++------- src/site/site.xml | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 2cb8471abed..6dfc83eefd5 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ pom Java-Raven Java Raven client and loggers - https://github.com/kencochrane/raven-java + https://github.com/${github.repo} @@ -26,23 +26,23 @@ - https://github.com/kencochrane/raven-java - scm:git:git://github.com/kencochrane/raven-java.git - scm:git:git@github.com:kencochrane/raven-java.git + https://github.com/${github.repo} + scm:git:git://github.com/${github.repo}.git + scm:git:git@github.com:${github.repo}.git - https://github.com/kencochrane/raven-java/issues + https://github.com/${github.repo}/issues GitHub Issues raven.java.server - scm:git:git@github.com:kencochrane/raven-java.git + scm:git:git@github.com:${github.repo}.git Travis-CI - https://travis-ci.org/kencochrane/raven-java + https://travis-ci.org/${github.repo} @@ -77,6 +77,8 @@ UTF-8 UTF-8 + kencochrane/raven-java + 6 6 diff --git a/src/site/site.xml b/src/site/site.xml index ea899b66646..d68a92d2d67 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -10,7 +10,7 @@ pre-release true - kencochrane/raven-java + ${github.repo} right red From 1b3b241619e2bc435ad7a5fbef5991129f4d2018 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 15:27:13 +0100 Subject: [PATCH 0282/2152] Remove fake email addresses, complete contributor list --- pom.xml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 6dfc83eefd5..f8cfd8b1763 100644 --- a/pom.xml +++ b/pom.xml @@ -60,16 +60,21 @@ ColinHebert Colin Hebert hebert.colin@gmail.com + https://github.com/ColinHebert David Cramer - contact@email.com Mark Philpot - contact@email.com + + + Brad Chen + + + ccouturi From b2e888c9f0646246a9f99fbd8793c6351ec80e44 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 16:07:59 +0100 Subject: [PATCH 0283/2152] Add the github plugin to publish maven site --- pom.xml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pom.xml b/pom.xml index f8cfd8b1763..98ab2da2ba4 100644 --- a/pom.xml +++ b/pom.xml @@ -83,6 +83,7 @@ UTF-8 kencochrane/raven-java + github 6 @@ -189,6 +190,30 @@ + + + + com.github.github + site-maven-plugin + 0.7 + false + + Creating site for ${project.name} ${project.version} + true + ${stagingDirectory} + + + + + site + + site-deploy + + + + + + From 60150e1f6edd44b6a3921c3c914a4ed3932ca0bb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 17:02:42 +0100 Subject: [PATCH 0284/2152] Remove unused distributionManagement details The site isn't deployed through the site phase, there is no need to have a ditributionManagement for the site. --- pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pom.xml b/pom.xml index 98ab2da2ba4..2080c059991 100644 --- a/pom.xml +++ b/pom.xml @@ -34,12 +34,6 @@ https://github.com/${github.repo}/issues GitHub Issues - - - raven.java.server - scm:git:git@github.com:${github.repo}.git - - Travis-CI https://travis-ci.org/${github.repo} From a77035a26e64a295c1d14a6fded9ce74ff2e8643 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 17:03:24 +0100 Subject: [PATCH 0285/2152] Deploy the content of the staging folder to gh-pages --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2080c059991..c3f08caa8fb 100644 --- a/pom.xml +++ b/pom.xml @@ -194,7 +194,7 @@ Creating site for ${project.name} ${project.version} true - ${stagingDirectory} + ${project.build.directory}/staging From f622697608def6b5703f7112fcaed7fb4a4fe0cf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 17:03:45 +0100 Subject: [PATCH 0286/2152] Force the site:stage step post-site --- pom.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pom.xml b/pom.xml index c3f08caa8fb..d8770e4627b 100644 --- a/pom.xml +++ b/pom.xml @@ -205,6 +205,19 @@ + + org.apache.maven.plugins + maven-site-plugin + 3.2 + + + + stage + + post-site + + + From 512eb9612276200abbca3f1657c21e20bc015766 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 17:51:40 +0100 Subject: [PATCH 0287/2152] Set up a Legacy project to use the previous API --- pom.xml | 1 + raven-legacy/pom.xml | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 raven-legacy/pom.xml diff --git a/pom.xml b/pom.xml index d8770e4627b..14b137eff5b 100644 --- a/pom.xml +++ b/pom.xml @@ -97,6 +97,7 @@ raven + raven-legacy raven-log4j raven-logback raven-log4j2 diff --git a/raven-legacy/pom.xml b/raven-legacy/pom.xml new file mode 100644 index 00000000000..07d0b7c4b0f --- /dev/null +++ b/raven-legacy/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + 1.1.1 + 2.5 + + + + net.kencochrane + raven-all + 3.0-SNAPSHOT + + raven-legacy + jar + Raven legacy + The legacy client of Raven Java + + + + ${project.groupId} + raven + + + com.googlecode.json-simple + json-simple + ${json-simple.version} + + + commons-lang + commons-lang + ${commons-lang.version} + + + From a1b7dcba11aabcc1b48e7b83d1ab70cc91a7aa5d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 17:52:08 +0100 Subject: [PATCH 0288/2152] Allow regexes in CHECKSTYLE:OFF/ON --- src/checkstyle/checkstyle.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/checkstyle/checkstyle.xml b/src/checkstyle/checkstyle.xml index 71136abb36d..98a3c91648c 100644 --- a/src/checkstyle/checkstyle.xml +++ b/src/checkstyle/checkstyle.xml @@ -33,8 +33,8 @@ - - + + From cc874d63588eaa073d47d75e2654fd7330c950f0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 17:52:47 +0100 Subject: [PATCH 0289/2152] Re-introduce the Utils class (deprecated) --- .../java/net/kencochrane/raven/Utils.java | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 raven-legacy/src/main/java/net/kencochrane/raven/Utils.java diff --git a/raven-legacy/src/main/java/net/kencochrane/raven/Utils.java b/raven-legacy/src/main/java/net/kencochrane/raven/Utils.java new file mode 100644 index 00000000000..f0b90088f53 --- /dev/null +++ b/raven-legacy/src/main/java/net/kencochrane/raven/Utils.java @@ -0,0 +1,125 @@ +//CHECKSTYLE.OFF: .* +package net.kencochrane.raven; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.InflaterOutputStream; + +/** + * Utilities for the Raven client. + */ +@Deprecated +public abstract class Utils { + + @Deprecated + public static final String SENTRY_DSN = "SENTRY_DSN"; + + @Deprecated + private static final Map CACHE = new HashMap(); + + @Deprecated + public interface Client { + @Deprecated + String VERSION = "2.0"; + @Deprecated + String NAME = "Raven-Java " + VERSION; + } + + @Deprecated + @SuppressWarnings("unchecked") + public static String hostname() { + final String cacheKey = "hostname"; + String name = fromCache(cacheKey, 360000); + if (name == null) { + try { + name = InetAddress.getLocalHost().getCanonicalHostName(); + } catch (UnknownHostException e) { + // can't get hostname + name = "unavailable"; + } + CACHE.put(cacheKey, new CacheEntry(name)); + } + return name; + } + + @Deprecated + public static long now() { + return System.currentTimeMillis(); + } + + @Deprecated + public static byte[] toUtf8(String s) { + try { + return s == null ? new byte[0] : s.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + @Deprecated + public static String fromUtf8(byte[] b) { + try { + return new String(b, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + @Deprecated + public static byte[] compress(byte[] input) { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + DeflaterOutputStream output = new DeflaterOutputStream(bytes); + try { + output.write(input); + output.close(); + return bytes.toByteArray(); + } catch (IOException e) { + // Look, if this thing starts throwing IOExceptions, you're on your own. Sorry. + throw new RuntimeException(e); + } + } + + @Deprecated + public static byte[] decompress(byte[] input) { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + InflaterOutputStream output = new InflaterOutputStream(bytes); + try { + output.write(input); + output.close(); + return bytes.toByteArray(); + } catch (IOException e) { + // ... Let things crash if this happens + throw new RuntimeException(e); + } + } + + @Deprecated + @SuppressWarnings("unchecked") + protected static T fromCache(String key, long timeout) { + CacheEntry entry = (CacheEntry) CACHE.get(key); + if (entry == null) { + return null; + } + return (entry.timestamp + timeout > now() ? entry.value : null); + } + + @Deprecated + protected static class CacheEntry { + @Deprecated + public final T value; + @Deprecated + public final long timestamp; + + public CacheEntry(T value) { + this.value = value; + timestamp = now(); + } + } + +} From da0a4f585318ca7ac0c4f97d539e4440f3f7fd68 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 17:53:13 +0100 Subject: [PATCH 0290/2152] Re-introduce the SentryDsn class (deprecated) --- .../java/net/kencochrane/raven/SentryDsn.java | 446 ++++++++++++++++++ 1 file changed, 446 insertions(+) create mode 100644 raven-legacy/src/main/java/net/kencochrane/raven/SentryDsn.java diff --git a/raven-legacy/src/main/java/net/kencochrane/raven/SentryDsn.java b/raven-legacy/src/main/java/net/kencochrane/raven/SentryDsn.java new file mode 100644 index 00000000000..6bb4c30e382 --- /dev/null +++ b/raven-legacy/src/main/java/net/kencochrane/raven/SentryDsn.java @@ -0,0 +1,446 @@ +//CHECKSTYLE.OFF: .* + +package net.kencochrane.raven; + +import org.apache.commons.lang.StringUtils; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.apache.commons.lang.StringUtils.defaultString; + +/** + * The Sentry DSN is the string you can copy-paste from the Sentry client configuration screen. + *

    + * Use this class to extract the interesting parts from the DSN. + *

    + *

    + * To provide flexible configuration, you can specify configuration options though a query string. For example, to + * enable the sending of the (deprecated) HMAC signature to Sentry, your DSN should look a bit like this: + *

    + *
    http://public:private@host:port/path/projectid?raven.includeSignature=true
    + *

    + * The options that are taken into account depend on the selected transport layer. + *

    + * + * @see Transport.Option#INCLUDE_SIGNATURE + * @see Transport.Http.Option#TIMEOUT + */ +@Deprecated +public class SentryDsn { + + /** + * Logger. + */ + @Deprecated + private static final Logger LOG = Logger.getLogger("raven.client"); + + /** + * The scheme, e.g. http, https or udp. + */ + @Deprecated + public final String scheme; + + /** + * The scheme variants, e.g. in case of a full scheme of "naive+https" this will hold "naive". + */ + @Deprecated + public final String[] variants; + + /** + * The Sentry host. + */ + @Deprecated + public final String host; + + /** + * The public key of the client. + */ + @Deprecated + public final String publicKey; + + /** + * The secret, private key of the client. + */ + @Deprecated + public final String secretKey; + + /** + * Optional extra path. + */ + @Deprecated + public final String path; + + /** + * The id of the project to log to in Sentry. + */ + @Deprecated + public final String projectId; + + /** + * The server port. + */ + @Deprecated + public final int port; + + /** + * Extra Raven client options. + */ + @Deprecated + public final Map options; + + /** + * Constructor for your convenience. + *

    + * It's recommended to use one of the {@link #build()} or {@link #buildOptional()} methods instead. + *

    + * + * @param scheme scheme + * @param variants scheme variants (e.g. naive, async) + * @param host host + * @param publicKey public key + * @param secretKey private key + * @param path path + * @param projectId project id + * @param port the port + * @param options miscellaneous options + */ + @Deprecated + public SentryDsn(String scheme, String[] variants, String host, String publicKey, String secretKey, String path, String projectId, int port, Map options) { + this.scheme = scheme; + this.variants = (variants == null ? new String[0] : variants); + this.host = host; + this.publicKey = publicKey; + this.secretKey = secretKey; + this.path = path; + this.projectId = projectId; + this.port = port; + if (options == null) { + this.options = Collections.emptyMap(); + } else { + this.options = Collections.unmodifiableMap(options); + } + } + + /** + * Gets the value of an option as a boolean. + *

    + * In case no option is specified, the default value is returned. + *

    + * + * @param key key of the option + * @param defaultValue default value to return when no matching option value was found + * @return the value of the option or the default value when absent + */ + @Deprecated + public boolean getOptionAsBoolean(String key, boolean defaultValue) { + String value = options.get(key); + if (value == null) { + return defaultValue; + } + return Boolean.parseBoolean(value); + } + + /** + * Gets the value of an option as an int. + * + * @param key key of the option + * @param defaultValue value to return when the option was not specified + * @return the value of the option or the default value when absent + */ + @Deprecated + public int getOptionAsInt(String key, int defaultValue) { + String value = options.get(key); + if (value == null) { + return defaultValue; + } + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new RuntimeException("Expected " + key + " to be a valid int"); + } + } + + /** + * Checks whether the scheme variant is specified in this dsn. + * + * @param variant variant + * @return true when the variant was specified + */ + @Deprecated + public boolean isVariantIncluded(String variant) { + return Arrays.binarySearch(variants, variant) >= 0; + } + + /** + * Gets the full scheme, optionally excluding some parts. + *

    + * This allows the async transport layer to get the actual underlying transport layer to use, ignoring the async + * variant. + *

    + * + * @param excludes variants to exclude from the full scheme + * @return the full scheme + */ + @Deprecated + public String getFullScheme(String... excludes) { + Set excludedSchemes = Collections.emptySet(); + if (excludes != null && excludes.length > 0) { + excludedSchemes = new HashSet(Arrays.asList(excludes)); + } + String full = ""; + List parts = new LinkedList(); + for (String variant : variants) { + if (!excludedSchemes.contains(variant)) { + parts.add(variant); + } + } + parts.add(scheme); + return StringUtils.join(parts, '+'); + } + + /** + * Returns the full dsn or the derived dsn typically used for transport. + * + * @param full whether to generate the full or the derived dsn + * @return the full or derived dsn + */ + @Deprecated + public String toString(boolean full) { + String protocol = (!full || variants.length == 0 ? scheme : StringUtils.join(variants, '+') + "+" + scheme); + String fullHost = (port < 0 ? host : host + ":" + port); + String fullPath = (path == null ? "" : path); + if (!full) { + return String.format("%s://%s%s", protocol, fullHost, fullPath); + } + fullPath += "/" + projectId; + String user = (StringUtils.isBlank(secretKey) ? publicKey : publicKey + ":" + secretKey); + return String.format("%s://%s@%s%s", protocol, user, fullHost, fullPath); + } + + @Override + @Deprecated + public String toString() { + return toString(true); + } + + /** + * Builds the Sentry dsn based on the default lookups as specified by {@link DefaultLookUps}. + *

    + * PaaS providers such as Heroku prefer environment variables. This method will first examine the environment and + * then the system properties to find a Sentry dsn value. + *

    + * + * @return the Sentry dsn when found + */ + @Deprecated + public static SentryDsn build() { + return build(null, DefaultLookUps.values(), null); + } + + /** + * Performs the same logic as {@link #build()} but will catch any {@link InvalidDsnException} thrown and return null + * instead. + * + * @return the Sentry dsn when found and valid or null instead + */ + @Deprecated + public static SentryDsn buildOptional() { + try { + return build(); + } catch (SentryDsn.InvalidDsnException e) { + LOG.log(Level.WARNING, "Could not automatically determine a valid DSN; client will be disabled", e); + return null; + } + } + + /** + * Builds the Sentry dsn. + *

    + * In case a Sentry dsn is specified in the environment or system properties, that value takes precedence over the + * fullDsn parameter. + *

    + * + * @param fullDsn dsn + * @return the dsn found in either the environment, system properties or derived from the parameter fullDsn + */ + @Deprecated + public static SentryDsn build(String fullDsn) { + return build(fullDsn, DefaultLookUps.values(), null); + } + + /** + * Performs the same logic as {@link #build(String)} but will catch any {@link InvalidDsnException} thrown and + * return null instead. + * + * @return the dsn found in either the environment, system properties or derived from the parameter + * fullDsn; if no valid dsn is available, this will return null + */ + @Deprecated + public static SentryDsn buildOptional(String fullDsn) { + try { + return build(fullDsn); + } catch (SentryDsn.InvalidDsnException e) { + LOG.log(Level.WARNING, "Could not automatically determine a valid DSN; client will be disabled", e); + return null; + } + } + + /** + * Builds the dsn. + *

    + * The overrides take precedence over the dsn parameter, while the fallbacks provide a way to look for the dsn when + * the dsn parameter was empty. + *

    + * + * @param fullDsn the supplied dsn + * @param overrides places to check for a dsn value before using the supplied dsn + * @param fallbacks places to check for a dsn value when the supplied dsn is an empty or null string + * @return the built Sentry dsn + */ + @Deprecated + public static SentryDsn build(final String fullDsn, LookUp[] overrides, LookUp[] fallbacks) { + String dsn = defaultString(firstResult(overrides), defaultString(fullDsn, firstResult(fallbacks))); + if (StringUtils.isBlank(dsn)) { + throw new InvalidDsnException("No valid Sentry DSN found"); + } + int schemeEnd = dsn.indexOf("://"); + if (schemeEnd <= 0) { + throw new InvalidDsnException("Expected to discover a scheme in the Sentry DSN"); + } + String fullScheme = dsn.substring(0, schemeEnd); + String[] schemeParts = StringUtils.split(fullScheme, '+'); + String scheme = fullScheme; + String[] variants = null; + if (schemeParts.length > 1) { + variants = Arrays.copyOfRange(schemeParts, 0, schemeParts.length - 1); + scheme = schemeParts[schemeParts.length - 1]; + } + try { + // To prevent us from having to register a handler for for example udp URLs, we'll replace the original + // scheme with "http" and let the URL code of Java handle the parsing. + URL url = new URL("http" + dsn.substring(schemeEnd)); + String[] userParts = url.getUserInfo().split(":"); + String publicKey = userParts[0]; + String secretKey = null; + if (userParts.length > 1) { + secretKey = userParts[1]; + } + String urlPath = url.getPath(); + int lastSlash = urlPath.lastIndexOf('/'); + String path = urlPath.substring(0, lastSlash); + String projectId = urlPath.substring(lastSlash + 1); + Map options = parseQueryString(url.getQuery()); + return new SentryDsn(scheme, variants, url.getHost(), publicKey, secretKey, path, projectId, url.getPort(), options); + } catch (MalformedURLException e) { + // This exception should only be thrown when an unhandled scheme slips in which the above code should + // prevent. Nevertheless: throw something. + throw new InvalidDsnException("Failed to parse " + dsn, e); + } + } + + /** + * See {@link #build(String, net.kencochrane.raven.SentryDsn.LookUp[], net.kencochrane.raven.SentryDsn.LookUp[])}. + * This method will return null when no valid DSN was found instead of throwing a + * {@link InvalidDsnException}. + * + * @param fullDsn the supplied dsn + * @param overrides places to check for a dsn value before using the supplied dsn + * @param fallbacks places to check for a dsn value when the supplied dsn is an empty or null string + * @return the built Sentry dsn or null when no such value was found + */ + @Deprecated + public static SentryDsn buildOptional(final String fullDsn, LookUp[] overrides, LookUp[] fallbacks) { + try { + return build(fullDsn, overrides, fallbacks); + } catch (SentryDsn.InvalidDsnException e) { + LOG.log(Level.WARNING, "Could not automatically determine a valid DSN; client will be disabled", e); + return null; + } + } + + /** + * Parses simple query strings. + *

    + * We don't expect complex query strings - they are only used to pass in Raven options. + *

    + * + * @param q the query string + * @return the key/value pairs in the query string + */ + @Deprecated + protected static Map parseQueryString(String q) { + Map map = new HashMap(); + String[] pairs = StringUtils.split(q, '&'); + if (pairs == null) { + return map; + } + for (String pair : pairs) { + String[] components = StringUtils.split(pair, '='); + String value = (components.length == 1 ? null : StringUtils.join(components, '=', 1, components.length)); + map.put(components[0], value); + } + return map; + } + + @Deprecated + public static String firstResult(LookUp[] lookups) { + if (lookups == null) { + return null; + } + for (LookUp lookup : lookups) { + String dsn = lookup.findDsn(); + if (!StringUtils.isBlank(dsn)) { + return dsn; + } + } + return null; + } + + @Deprecated + public static class InvalidDsnException extends RuntimeException { + + public InvalidDsnException(String message) { + super(message); + } + + public InvalidDsnException(String message, Throwable t) { + super(message, t); + } + + } + + @Deprecated + public interface LookUp { + + @Deprecated + String findDsn(); + + } + + @Deprecated + public enum DefaultLookUps implements LookUp { + + @Deprecated + ENV { + @Override + public String findDsn() { + return System.getenv(Utils.SENTRY_DSN); + } + }, + + @Deprecated + SYSTEM_PROPERTY { + @Override + public String findDsn() { + return System.getProperty(Utils.SENTRY_DSN); + } + } + + + } + +} From 946451210b19be7dadcd5be34b202a0c27330756 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 18:00:29 +0100 Subject: [PATCH 0291/2152] Re-introduce Client (deprecated) --- .../java/net/kencochrane/raven/Client.java | 447 ++++++++++++++++++ 1 file changed, 447 insertions(+) create mode 100644 raven-legacy/src/main/java/net/kencochrane/raven/Client.java diff --git a/raven-legacy/src/main/java/net/kencochrane/raven/Client.java b/raven-legacy/src/main/java/net/kencochrane/raven/Client.java new file mode 100644 index 00000000000..29faf86f582 --- /dev/null +++ b/raven-legacy/src/main/java/net/kencochrane/raven/Client.java @@ -0,0 +1,447 @@ +//CHECKSTYLE.OFF: .* + +package net.kencochrane.raven; + +import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.helper.EventBuilderHelper; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.lang.time.DateFormatUtils; +import org.json.simple.JSONObject; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.zip.CRC32; +import java.util.zip.Checksum; + +import static org.apache.commons.codec.binary.Base64.encodeBase64String; + +/** + * Raven client for Java, allowing sending of messages to Sentry. + *

    + * Clients will typically automatically start the underlying transport layer once instantiated. The default client + * configuration relies on the following mapping of schemes to transport classes: + *

    + *
      + *
    • http:
    • + *
    • https:
    • + *
    • naive+https
    • + *
    • udp
    • + *
    + *

    + *

    + */ +@Deprecated +public class Client { + + private final Raven raven; + + @Deprecated + public interface Default { + @Deprecated + String LOGGER = "root"; + @Deprecated + int LOG_LEVEL = 5000; + @Deprecated + String EMPTY_MESSAGE = "(empty)"; + } + + /** + * Async transport layers require some extra work when instantiating. + */ + @Deprecated + public static final String VARIANT_ASYNC = "async"; + + /** + * The registry mapping schemes to transport layer classes. + */ + @Deprecated + protected static final Map TRANSPORT_REGISTRY = new HashMap(); + + /** + * The dsn used by this client. + */ + @Deprecated + public final SentryDsn dsn = null; + + /** + * Whether messages should be compressed or not - defaults to true. + */ + @Deprecated + protected boolean messageCompressionEnabled = true; + + /** + * Default, easy constructor. + *

    + * A client instance instantiated through this constructor will use the Sentry DSN returned by + * {@link SentryDsn#buildOptional()} and perform an automatic start. + *

    + */ + @Deprecated + public Client() { + raven = new Raven(); + } + + /** + * Extension of the easy constructor {@link #Client()} that allows you to turn off the autostart behavior. + * + * @param autoStart whether to start the underlying transport automatically or not + */ + @Deprecated + public Client(boolean autoStart) { + raven = new Raven(); + } + + /** + * Constructor that performs an autostart using the transport determined by the supplied dsn. + *

    + * Watch out: this constructor will always use the supplied dsn and not look for a Sentry DSN in other locations + * such as an environment variable or system property. + *

    + * + * @param dsn dsn to use + */ + @Deprecated + public Client(SentryDsn dsn) { + raven = new Raven(dsn.toString(true)); + } + + /** + * Constructor using the transport determined by the supplied dsn. + *

    + * Watch out: this constructor will always use the supplied dsn and not look for a Sentry DSN in other locations + * such as an environment variable or system property. + *

    + * + * @param dsn dsn to use + * @param autoStart whether to start the underlying transport layer automatically + */ + @Deprecated + public Client(SentryDsn dsn, boolean autoStart) { + raven = new Raven(dsn.toString(true)); + } + + /** + * Set the processors to be used by this client. Instances from the list are + * copied over. + * + * @param processors a list of processors to be used by this client + */ + @Deprecated + public synchronized void setJSONProcessors(List processors) { + //NOOP + } + + @Deprecated + public boolean isMessageCompressionEnabled() { + //NOOP + return false; + } + + @Deprecated + public void setMessageCompressionEnabled(boolean messageCompressionEnabled) { + //NOOP + } + + private String buildAndSendEvent(EventBuilder eventBuilder){ + for (EventBuilderHelper builderHelper: raven.getBuilderHelpers()){ + builderHelper.helpBuildingEvent(eventBuilder); + } + Event event = eventBuilder.build(); + raven.sendEvent(event); + return event.getId().toString().replaceAll("-", ""); + + } + + private static Event.Level convertLevel(Integer logLevel){ + if(logLevel == null) + return null; + + switch (logLevel){ + case 1: + return Event.Level.DEBUG; + case 2: + return Event.Level.INFO; + case 3: + return Event.Level.WARNING; + case 4: + return Event.Level.ERROR; + case 5: + return Event.Level.FATAL; + default: + return null; + } + } + + + @Deprecated + public String captureMessage(String msg) { + EventBuilder eventBuilder = new EventBuilder().setMessage(msg); + return buildAndSendEvent(eventBuilder); + } + + @Deprecated + public String captureMessage(String msg, Map tags) { + EventBuilder eventBuilder = new EventBuilder().setMessage(msg); + for(Map.Entry tag: tags.entrySet()){ + eventBuilder.addTag(tag.getKey(), tag.getValue().toString()); + } + return buildAndSendEvent(eventBuilder); + } + + @Deprecated + public String captureMessage(String message, Long timestamp, String loggerClass, Integer logLevel, String culprit) { + EventBuilder eventBuilder = new EventBuilder() + .setMessage(message) + .setTimestamp(new Date(timestamp)) + .setLogger(loggerClass) + .setLevel(convertLevel(logLevel)) + .setCulprit(culprit); + + return buildAndSendEvent(eventBuilder); + } + + @Deprecated + public String captureMessage(String message, Long timestamp, String loggerClass, Integer logLevel, String culprit, Map tags) { + EventBuilder eventBuilder = new EventBuilder() + .setMessage(message) + .setTimestamp(new Date(timestamp)) + .setLogger(loggerClass) + .setLevel(convertLevel(logLevel)) + .setCulprit(culprit); + for(Map.Entry tag: tags.entrySet()){ + eventBuilder.addTag(tag.getKey(), tag.getValue().toString()); + } + + return buildAndSendEvent(eventBuilder); + } + + @Deprecated + public String captureException(Throwable exception) { + EventBuilder eventBuilder = new EventBuilder() + .setMessage(exception.getMessage()) + .addSentryInterface(new StackTraceInterface(exception)) + .addSentryInterface(new ExceptionInterface(exception)); + return buildAndSendEvent(eventBuilder); + } + + @Deprecated + public String captureException(Throwable exception, Map tags) { + EventBuilder eventBuilder = new EventBuilder() + .setMessage(exception.getMessage()) + .addSentryInterface(new StackTraceInterface(exception)) + .addSentryInterface(new ExceptionInterface(exception)); + for(Map.Entry tag: tags.entrySet()){ + eventBuilder.addTag(tag.getKey(), tag.getValue().toString()); + } + return buildAndSendEvent(eventBuilder); + } + + @Deprecated + public String captureException(String logMessage, long timestamp, String loggerName, Integer logLevel, String culprit, Throwable exception) { + EventBuilder eventBuilder = new EventBuilder() + .setMessage(logMessage) + .setTimestamp(new Date(timestamp)) + .setLogger(loggerName) + .setLevel(convertLevel(logLevel)) + .setCulprit(culprit) + .addSentryInterface(new StackTraceInterface(exception)) + .addSentryInterface(new ExceptionInterface(exception)); + return buildAndSendEvent(eventBuilder); + } + + @Deprecated + public String captureException(String logMessage, long timestamp, String loggerName, Integer logLevel, String culprit, Throwable exception, Map tags) { + EventBuilder eventBuilder = new EventBuilder() + .setMessage(logMessage) + .setTimestamp(new Date(timestamp)) + .setLogger(loggerName) + .setLevel(convertLevel(logLevel)) + .setCulprit(culprit) + .addSentryInterface(new StackTraceInterface(exception)) + .addSentryInterface(new ExceptionInterface(exception)); + for(Map.Entry tag: tags.entrySet()){ + eventBuilder.addTag(tag.getKey(), tag.getValue().toString()); + } + return buildAndSendEvent(eventBuilder); + } + + @Deprecated + public synchronized void start() { + //NOOP + } + + @Deprecated + public boolean isStarted() { + return true; + } + + @Deprecated + public synchronized void stop() { + try { + raven.getConnection().close(); + } catch (IOException e) { + //TODO: Handle this properly + e.printStackTrace(); + } + } + + @Deprecated + public boolean isDisabled() { + return false; + } + + @Deprecated + protected Message buildMessage(String message, String timestamp, String loggerClass, Integer logLevel, String culprit, Throwable exception, Map tags) { + throw new UnsupportedOperationException(); + } + + @Deprecated + protected void send(Message message, long timestamp) { + throw new UnsupportedOperationException(); + } + + /** + * Generates a unique event id. + * + * @return hexadecimal UUID4 String + */ + @Deprecated + protected String generateEventId() { + // If we keep the -'s in the uuid, it is too long, remove them + return UUID.randomUUID().toString().replaceAll("-", ""); + } + + /** + * Formats a timestamp in the format expected by Sentry. + * + * @param timestamp timestamp to format + * @return formatted timestamp + */ + @Deprecated + protected String formatTimestamp(long timestamp) { + return DateFormatUtils.formatUTC(timestamp, DateFormatUtils.ISO_DATETIME_FORMAT.getPattern()); + } + + /** + * Registers the transport class for the given scheme. + * + * @param scheme scheme to register for + * @param transportClass transport class to register for the scheme + * @return the previously registered transport class for the scheme, if any + */ + @Deprecated + public static Class register(String scheme, Class transportClass) { + return TRANSPORT_REGISTRY.put(scheme, transportClass); + } + + /** + * Registers the default transport classes. + */ + @Deprecated + public static void registerDefaults() { + //NOOP + } + + /** + * Builds the HMAC sentry signature. + *

    + * The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, + * and an arbitrary client version string. + *

    + * The client version should be something distinct to your client, and is simply for reporting purposes. + * To generate the HMAC signature, take the following example (in Python): + *

    + * hmac.new(public_key, '%s %s' % (timestamp, message), hashlib.sha1).hexdigest() + * + * @param message the error message to send to sentry + * @param timestamp the timestamp for when the message was created + * @param key sentry public key + * @return SHA1-signed HMAC string + */ + @Deprecated + public static String sign(String message, long timestamp, String key) { + final String algo = "HmacSHA1"; + try { + SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), algo); + Mac mac = Mac.getInstance(algo); + mac.init(signingKey); + byte[] rawHmac = mac.doFinal((timestamp + " " + message).getBytes()); + return new String(Hex.encodeHex(rawHmac)); + } catch (NoSuchAlgorithmException e) { + throw new InvalidConfig("Could not sign message: " + e.getMessage(), e); + } catch (InvalidKeyException e) { + throw new InvalidConfig("Could not sign message: " + e.getMessage(), e); + } + } + + /** + * An almost-unique hash identifying the this event to improve aggregation. + * + * @param message The message we are sending to sentry + * @return CRC32 Checksum string + */ + @Deprecated + public static String calculateChecksum(String message) { + byte bytes[] = message.getBytes(); + Checksum checksum = new CRC32(); + checksum.update(bytes, 0, bytes.length); + return String.valueOf(checksum.getValue()); + } + + @Deprecated + public static class InvalidConfig extends RuntimeException { + + public InvalidConfig(String msg) { + super(msg); + } + + public InvalidConfig(String msg, Throwable t) { + super(msg, t); + } + + } + + @Deprecated + public static class Message { + + @Deprecated + public static final Message NONE = new Message(null, "-1", false); + + @Deprecated + public final JSONObject json; + @Deprecated + public final String eventId; + @Deprecated + public final boolean compress; + + @Deprecated + public Message(JSONObject json, String eventId, boolean compress) { + this.json = json; + this.eventId = eventId; + this.compress = compress; + } + + @Deprecated + public String encoded() { + byte[] raw = Utils.toUtf8(json.toJSONString()); + if (compress) { + raw = Utils.compress(raw); + } + return encodeBase64String(raw); + } + + @Override + @Deprecated + public String toString() { + return json.toJSONString(); + } + } + +} From fc59bfa5b6b62c12d0e4cd5b76ba69e0ad98bec4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 18:04:54 +0100 Subject: [PATCH 0292/2152] Reorder properties in legacy --- raven-legacy/pom.xml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/raven-legacy/pom.xml b/raven-legacy/pom.xml index 07d0b7c4b0f..9511c361d8a 100644 --- a/raven-legacy/pom.xml +++ b/raven-legacy/pom.xml @@ -3,10 +3,6 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - 1.1.1 - 2.5 - net.kencochrane @@ -18,6 +14,11 @@ Raven legacy The legacy client of Raven Java + + 1.1.1 + 2.5 + + ${project.groupId} From 91706482a74a0a938067e0958b007c33a74bc17e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 18:06:48 +0100 Subject: [PATCH 0293/2152] Add documentation regarding the deprecation --- raven-legacy/src/main/java/net/kencochrane/raven/Client.java | 2 ++ .../src/main/java/net/kencochrane/raven/SentryDsn.java | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/raven-legacy/src/main/java/net/kencochrane/raven/Client.java b/raven-legacy/src/main/java/net/kencochrane/raven/Client.java index 29faf86f582..41e4649c6bf 100644 --- a/raven-legacy/src/main/java/net/kencochrane/raven/Client.java +++ b/raven-legacy/src/main/java/net/kencochrane/raven/Client.java @@ -36,6 +36,8 @@ * *

    *

    + * + * @deprecated Use {@link Raven} instead. */ @Deprecated public class Client { diff --git a/raven-legacy/src/main/java/net/kencochrane/raven/SentryDsn.java b/raven-legacy/src/main/java/net/kencochrane/raven/SentryDsn.java index 6bb4c30e382..f06fc50e7a3 100644 --- a/raven-legacy/src/main/java/net/kencochrane/raven/SentryDsn.java +++ b/raven-legacy/src/main/java/net/kencochrane/raven/SentryDsn.java @@ -26,8 +26,7 @@ * The options that are taken into account depend on the selected transport layer. *

    * - * @see Transport.Option#INCLUDE_SIGNATURE - * @see Transport.Http.Option#TIMEOUT + * @deprecated Use {@link Dsn} instead. */ @Deprecated public class SentryDsn { From 3269e2a90d2a4dbc39ab9d6531f2663869dfa9b6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 21:07:53 +0100 Subject: [PATCH 0294/2152] Fix indentation & imports --- pom.xml | 26 +++++++++---------- .../java/net/kencochrane/raven/Client.java | 18 ++++++------- .../raven/jul/SentryHandlerTest.java | 4 --- src/checkstyle/checkstyle.xml | 15 ++++++----- 4 files changed, 30 insertions(+), 33 deletions(-) diff --git a/pom.xml b/pom.xml index 14b137eff5b..be24c916b08 100644 --- a/pom.xml +++ b/pom.xml @@ -206,19 +206,19 @@ - - org.apache.maven.plugins - maven-site-plugin - 3.2 - - - - stage - - post-site - - - + + org.apache.maven.plugins + maven-site-plugin + 3.2 + + + + stage + + post-site + + + diff --git a/raven-legacy/src/main/java/net/kencochrane/raven/Client.java b/raven-legacy/src/main/java/net/kencochrane/raven/Client.java index 41e4649c6bf..9ed680c22c7 100644 --- a/raven-legacy/src/main/java/net/kencochrane/raven/Client.java +++ b/raven-legacy/src/main/java/net/kencochrane/raven/Client.java @@ -151,8 +151,8 @@ public void setMessageCompressionEnabled(boolean messageCompressionEnabled) { //NOOP } - private String buildAndSendEvent(EventBuilder eventBuilder){ - for (EventBuilderHelper builderHelper: raven.getBuilderHelpers()){ + private String buildAndSendEvent(EventBuilder eventBuilder) { + for (EventBuilderHelper builderHelper : raven.getBuilderHelpers()) { builderHelper.helpBuildingEvent(eventBuilder); } Event event = eventBuilder.build(); @@ -161,11 +161,11 @@ private String buildAndSendEvent(EventBuilder eventBuilder){ } - private static Event.Level convertLevel(Integer logLevel){ - if(logLevel == null) + private static Event.Level convertLevel(Integer logLevel) { + if (logLevel == null) return null; - switch (logLevel){ + switch (logLevel) { case 1: return Event.Level.DEBUG; case 2: @@ -191,7 +191,7 @@ public String captureMessage(String msg) { @Deprecated public String captureMessage(String msg, Map tags) { EventBuilder eventBuilder = new EventBuilder().setMessage(msg); - for(Map.Entry tag: tags.entrySet()){ + for (Map.Entry tag : tags.entrySet()) { eventBuilder.addTag(tag.getKey(), tag.getValue().toString()); } return buildAndSendEvent(eventBuilder); @@ -217,7 +217,7 @@ public String captureMessage(String message, Long timestamp, String loggerClass, .setLogger(loggerClass) .setLevel(convertLevel(logLevel)) .setCulprit(culprit); - for(Map.Entry tag: tags.entrySet()){ + for (Map.Entry tag : tags.entrySet()) { eventBuilder.addTag(tag.getKey(), tag.getValue().toString()); } @@ -239,7 +239,7 @@ public String captureException(Throwable exception, Map tags) { .setMessage(exception.getMessage()) .addSentryInterface(new StackTraceInterface(exception)) .addSentryInterface(new ExceptionInterface(exception)); - for(Map.Entry tag: tags.entrySet()){ + for (Map.Entry tag : tags.entrySet()) { eventBuilder.addTag(tag.getKey(), tag.getValue().toString()); } return buildAndSendEvent(eventBuilder); @@ -268,7 +268,7 @@ public String captureException(String logMessage, long timestamp, String loggerN .setCulprit(culprit) .addSentryInterface(new StackTraceInterface(exception)) .addSentryInterface(new ExceptionInterface(exception)); - for(Map.Entry tag: tags.entrySet()){ + for (Map.Entry tag : tags.entrySet()) { eventBuilder.addTag(tag.getKey(), tag.getValue().toString()); } return buildAndSendEvent(eventBuilder); diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java index e0cb88cd4e1..0e826c3ca6b 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java @@ -9,10 +9,6 @@ import java.util.logging.Level; import java.util.logging.Logger; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.verify; - public class SentryHandlerTest extends AbstractLoggerTest { private Logger logger; diff --git a/src/checkstyle/checkstyle.xml b/src/checkstyle/checkstyle.xml index 98a3c91648c..08078a0f87d 100644 --- a/src/checkstyle/checkstyle.xml +++ b/src/checkstyle/checkstyle.xml @@ -86,7 +86,7 @@
    - + @@ -108,7 +108,8 @@ - + + @@ -116,7 +117,7 @@ - + @@ -219,18 +220,18 @@ - - + + - + - + From 9b69616b798c146805f62e8171071d8b4276e701 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 22:02:16 +0100 Subject: [PATCH 0295/2152] No reason to move to autocloseable ever --- .../main/java/net/kencochrane/raven/connection/Connection.java | 1 - 1 file changed, 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/Connection.java b/raven/src/main/java/net/kencochrane/raven/connection/Connection.java index 892118531f5..fe87cf8b888 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/Connection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/Connection.java @@ -7,7 +7,6 @@ /** * Connection to a Sentry server, allowing to send captured events. */ -//TODO: Move to an AutoCloseable? public interface Connection extends Closeable { /** * Sends an event to the sentry server. From 59439f3b8453c13486c068c9e213411c35fb4e23 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 22:02:51 +0100 Subject: [PATCH 0296/2152] Reorder constants --- .../java/net/kencochrane/raven/connection/HttpConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 8f492ba4b16..58e735bd7d8 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -23,8 +23,8 @@ * Basic connection to a Sentry server, using HTTP and HTTPS. */ public class HttpConnection extends AbstractConnection { - private static final String USER_AGENT = "User-Agent"; private static final Logger logger = Logger.getLogger(HttpConnection.class.getCanonicalName()); + private static final String USER_AGENT = "User-Agent"; private static final String SENTRY_AUTH = "X-Sentry-Auth"; private static final int DEFAULT_TIMEOUT = 10000; private static final HostnameVerifier NAIVE_VERIFIER = new HostnameVerifier() { From 661c2eb67b18d9ac01f5e8833648fb03257a03fd Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 22:03:59 +0100 Subject: [PATCH 0297/2152] Fix typo --- .../main/java/net/kencochrane/raven/event/EventBuilder.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 96b48c4c27c..a1ce52727fb 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -95,11 +95,11 @@ private static void autoSetMissingValues(Event event) { */ private static void makeImmutable(Event event) { // Make the tags unmodifiable - Map> unmodifiablesTags = new HashMap>(event.getTags().size()); + Map> unmodifiableTags = new HashMap>(event.getTags().size()); for (Map.Entry> tag : event.getTags().entrySet()) { - unmodifiablesTags.put(tag.getKey(), Collections.unmodifiableSet(tag.getValue())); + unmodifiableTags.put(tag.getKey(), Collections.unmodifiableSet(tag.getValue())); } - event.setTags(Collections.unmodifiableMap(unmodifiablesTags)); + event.setTags(Collections.unmodifiableMap(unmodifiableTags)); // Make the extra properties unmodifiable (everything in it is still mutable though) event.setExtra(Collections.unmodifiableMap(event.getExtra())); From 445cda9a028af58d21e84f9ea0064ee15e6caf36 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 22:04:15 +0100 Subject: [PATCH 0298/2152] Add xsd to site.xml --- src/site/site.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/site/site.xml b/src/site/site.xml index d68a92d2d67..7dde67dce06 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -1,4 +1,6 @@ - + org.apache.maven.skins maven-fluido-skin From 4742ff1dc4d9ed8676537667cd301b7f81c0fac5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 22:04:28 +0100 Subject: [PATCH 0299/2152] Display array with Arrays.toString() --- .../net/kencochrane/raven/marshaller/json/JsonMarshaller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index d79dd461a59..11a50b1a190 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -249,6 +249,6 @@ public void setCharset(Charset charset) { } } throw new IllegalArgumentException("Couldn't set the charset to " + charset + ". " + - "The supported charsets are '" + JsonEncoding.values() + "'"); + "The supported charsets are '" + Arrays.toString(JsonEncoding.values()) + "'"); } } From bf65dfc59dd471f389a35c2aaab4706e97e5f85c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 22:04:45 +0100 Subject: [PATCH 0300/2152] Create a dedicated method to write interfaces in JSon --- .../raven/marshaller/json/JsonMarshaller.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 11a50b1a190..536367e5cf1 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -131,8 +131,14 @@ private void writeContent(JsonGenerator generator, Event event) throws IOExcepti generator.writeStringField(SERVER_NAME, event.getServerName()); writeExtras(generator, event.getExtra()); generator.writeStringField(CHECKSUM, event.getChecksum()); + writeInterfaces(generator, event.getSentryInterfaces()); - for (Map.Entry interfaceEntry : event.getSentryInterfaces().entrySet()) { + generator.writeEndObject(); + } + + @SuppressWarnings("unchecked") + private void writeInterfaces(JsonGenerator generator, Map sentryInterfaces) throws IOException { + for (Map.Entry interfaceEntry : sentryInterfaces.entrySet()) { SentryInterface sentryInterface = interfaceEntry.getValue(); if (interfaceBindings.containsKey(sentryInterface.getClass())) { @@ -143,8 +149,6 @@ private void writeContent(JsonGenerator generator, Event event) throws IOExcepti + "provided in " + sentryInterface + "."); } } - - generator.writeEndObject(); } private void writeExtras(JsonGenerator generator, Map extras) throws IOException { From f55eefb89a002d814b545dcbbdcaad50a56b1cc7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Apr 2013 22:05:05 +0100 Subject: [PATCH 0301/2152] Format line < 120 chars --- .../src/main/java/net/kencochrane/raven/jul/SentryHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 4ae05631d9d..53c0c39e2a8 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -68,7 +68,8 @@ public void publish(LogRecord record) { } if (record.getParameters() != null) - eventBuilder.addSentryInterface(new MessageInterface(record.getMessage(), formatParameters(record.getParameters()))); + eventBuilder.addSentryInterface(new MessageInterface(record.getMessage(), + formatParameters(record.getParameters()))); else eventBuilder.setMessage(record.getMessage()); From 878489893d696bb614a0fed972dbfd33f171b7a2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 09:12:00 +0100 Subject: [PATCH 0302/2152] Add back the distribution management It turns out that the ditribution management is mandatory for site:stage, so this one needs to be added back while the site:deploy execution is skipped. --- pom.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pom.xml b/pom.xml index be24c916b08..2aed3a17970 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,12 @@ https://github.com/${github.repo}/issues GitHub Issues + + + raven.java.server + scm:git:git@github.com:${github.repo}.git + + Travis-CI https://travis-ci.org/${github.repo} @@ -212,11 +218,21 @@ 3.2 + stage stage post-site + + default-deploy + + deploy + + + true + + From 101be3f02706c3597735c6f5e40b153cec199643 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 09:13:10 +0100 Subject: [PATCH 0303/2152] Add documentation to AbstractConnection --- .../kencochrane/raven/connection/AbstractConnection.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index bd3f4a1fca5..177e0e31de4 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -26,6 +26,12 @@ protected AbstractConnection(Dsn dsn) { this(dsn.getPublicKey(), dsn.getSecretKey()); } + /** + * Creates a connection based on the public and secret keys. + * + * @param publicKey public key (identifier) to the Sentry server. + * @param secretKey secret key (password) to the Sentry server. + */ protected AbstractConnection(String publicKey, String secretKey) { this.publicKey = publicKey; this.secretKey = secretKey; From 0d13b9fee4d1e343dbe48675f3fa257c4c40d3bf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 09:13:36 +0100 Subject: [PATCH 0304/2152] Add documentation to AsyncConnection --- .../raven/connection/AsyncConnection.java | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index f8f0b6c8e0a..bbacec10dea 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -70,16 +70,26 @@ public AsyncConnection(Connection actualConnection) { /** * Creates a connection which will rely on an executor to send events. *

    - * Will propagate the {@link #close()} operation and + * Will propagate the {@link #close()} operation and attempt to get the number of Threads and their priority from + * the DSN configuration. *

    * - * @param actualConnection - * @param dsn + * @param actualConnection connection used to send the events. + * @param dsn Data Source Name containing the additional settings for the async connection. */ public AsyncConnection(Connection actualConnection, Dsn dsn) { this(actualConnection, true, getMaxThreads(dsn), getPriority(dsn)); } + /** + * Creates a connection which will rely on an executor to send events. + * + * @param actualConnection connection used to send the events. + * @param propagateClose whether or not the {@link #actualConnection} should be closed + * when this connection closes. + * @param maxThreads number of {@code Thread}s available in the thread pool. + * @param priority priority of the {@code Thread}s. + */ public AsyncConnection(Connection actualConnection, boolean propagateClose, int maxThreads, int priority) { this.actualConnection = actualConnection; this.propagateClose = propagateClose; @@ -87,6 +97,16 @@ public AsyncConnection(Connection actualConnection, boolean propagateClose, int addShutdownHook(); } + /** + * Gets the number of {@code Thread}s that should be available in the pool. + *

    + * Attempts to get the {@link #MAX_THREADS_OPTION} option from the {@code Dsn}, + * defaults to {@link #DEFAULT_MAX_THREADS} if not available. + *

    + * + * @param dsn Data Source Name potentially containing settings for the {@link AsyncConnection}. + * @return the number of threads that should be available in the pool. + */ private static int getMaxThreads(Dsn dsn) { if (dsn.getOptions().containsKey(MAX_THREADS_OPTION)) { return Integer.parseInt(dsn.getOptions().get(MAX_THREADS_OPTION)); @@ -95,6 +115,16 @@ private static int getMaxThreads(Dsn dsn) { } } + /** + * Gets the priority of {@code Thread}s in the pool. + *

    + * Attempts to get the {@link #PRIORITY_OPTION} option from the {@code Dsn}, + * defaults to {@link #DEFAULT_PRIORITY} if not available. + *

    + * + * @param dsn Data Source Name potentially containing settings for the {@link AsyncConnection}. + * @return the priority of threads available in the pool. + */ private static int getPriority(Dsn dsn) { if (dsn.getOptions().containsKey(PRIORITY_OPTION)) { return Integer.parseInt(dsn.getOptions().get(PRIORITY_OPTION)); @@ -103,6 +133,9 @@ private static int getPriority(Dsn dsn) { } } + /** + * Adds a hook to shutdown the {@link #executorService} gracefully when the JVM shuts down. + */ private void addShutdownHook() { // JUL loggers are shutdown by an other shutdown hook, it's possible that nothing will get actually logged. Runtime.getRuntime().addShutdownHook(new Thread() { @@ -116,6 +149,12 @@ public void run() { }); } + /** + * {@inheritDoc} + *

    + * The event will be added to a queue and will be handled by a separate {@code Thread} later on. + *

    + */ @Override public void send(Event event) { executorService.execute(new EventSubmitter(event)); From 454cf14ad0288641026e45b202a6f2c7651bb441 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 09:13:54 +0100 Subject: [PATCH 0305/2152] Add documentation to HttpConnection --- .../raven/connection/HttpConnection.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 58e735bd7d8..4611b6cfd1c 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -21,23 +21,58 @@ /** * Basic connection to a Sentry server, using HTTP and HTTPS. + *

    + * It is possible to enable the "naive mode" through the DSN with {@link Dsn#NAIVE_PROTOCOL} to allow a connection over + * SSL using a certificate with a wildcard.
    + * + *

    */ public class HttpConnection extends AbstractConnection { private static final Logger logger = Logger.getLogger(HttpConnection.class.getCanonicalName()); + /** + * HTTP Header for the user agent. + */ private static final String USER_AGENT = "User-Agent"; + /** + * HTTP Header for the authentication to Sentry. + */ private static final String SENTRY_AUTH = "X-Sentry-Auth"; + /** + * Default timeout of an HTTP connection to Sentry. + */ private static final int DEFAULT_TIMEOUT = 10000; + /** + * HostnameVerifier allowing wildcard certificates to work without adding them to the truststore. + */ private static final HostnameVerifier NAIVE_VERIFIER = new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession sslSession) { return true; } }; + /** + * URL of the Sentry endpoint. + */ private final URL sentryUrl; + /** + * Marshaller used to transform and send the {@link Event} over a stream. + */ private Marshaller marshaller = new JsonMarshaller(); + /** + * Timeout of an HTTP connection to Sentry. + */ private int timeout = DEFAULT_TIMEOUT; + /** + * Setting allowing to bypass the security system which requires wildcard certificates + * to be added to the truststore. + */ private boolean bypassSecurity; + /** + * Creates a connection through HTTP(s) based on the settings in the {@code dsn}. + * + * @param dsn Data Source Name containing details and options for the connection to Sentry. + */ public HttpConnection(Dsn dsn) { super(dsn); From 1f44c0c98fe8c8aba05f6e4b8556342f0c94d1c0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 09:14:10 +0100 Subject: [PATCH 0306/2152] Fix style issues A line was over 120 chars. A line ended with an operator instead of having that operator on the new line. --- .../kencochrane/raven/marshaller/json/JsonMarshaller.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 536367e5cf1..d74df9374f3 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -137,7 +137,8 @@ private void writeContent(JsonGenerator generator, Event event) throws IOExcepti } @SuppressWarnings("unchecked") - private void writeInterfaces(JsonGenerator generator, Map sentryInterfaces) throws IOException { + private void writeInterfaces(JsonGenerator generator, Map sentryInterfaces) + throws IOException { for (Map.Entry interfaceEntry : sentryInterfaces.entrySet()) { SentryInterface sentryInterface = interfaceEntry.getValue(); @@ -252,7 +253,7 @@ public void setCharset(Charset charset) { return; } } - throw new IllegalArgumentException("Couldn't set the charset to " + charset + ". " + - "The supported charsets are '" + Arrays.toString(JsonEncoding.values()) + "'"); + throw new IllegalArgumentException("Couldn't set the charset to " + charset + ". " + + "The supported charsets are '" + Arrays.toString(JsonEncoding.values()) + "'"); } } From efdb6c299c7e903b6094bc66633693af508a9edd Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 22:58:24 +0100 Subject: [PATCH 0307/2152] Fix dateformat (don't send timezone, send seconds) --- .../net/kencochrane/raven/marshaller/json/JsonMarshaller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index d74df9374f3..2c3bcd867d3 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -74,7 +74,7 @@ public class JsonMarshaller implements Marshaller { /** * Date format for ISO 8601. */ - private static final DateFormat ISO_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ"); + private static final DateFormat ISO_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); private static final Logger logger = Logger.getLogger(JsonMarshaller.class.getCanonicalName()); private final JsonFactory jsonFactory = new JsonFactory(); private final Map, InterfaceBinding> interfaceBindings = From b313b3bc1a359bfb5612a874fa5c39ef5ac1bfd2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 22:59:08 +0100 Subject: [PATCH 0308/2152] Set timezone to UTC when formatting timestamp --- .../net/kencochrane/raven/marshaller/json/JsonMarshaller.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 2c3bcd867d3..51f8f4866df 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -88,6 +88,10 @@ public class JsonMarshaller implements Marshaller { */ private JsonEncoding charset = JsonEncoding.UTF8; + static { + ISO_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); + } + public JsonMarshaller() { addInterfaceBinding(ExceptionInterface.class, new ExceptionInterfaceBinding()); addInterfaceBinding(StackTraceInterface.class, new StackTraceInterfaceBinding()); From 788c8b90627599630654f9f8bbb06dc564310ec0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 23:46:52 +0100 Subject: [PATCH 0309/2152] Print line feeds in HTTP error logs --- .../java/net/kencochrane/raven/connection/HttpConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 4611b6cfd1c..8f5cfb01ee3 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -144,7 +144,7 @@ private String getErrorMessageFromStream(InputStream errorStream) { try { String line; while ((line = reader.readLine()) != null) - sb.append(line); + sb.append(line).append("\n"); } catch (Exception e2) { logger.log(Level.SEVERE, "Exception while reading the error message from the connection.", e2); From 325c6f15860bd6950d6d79defb374abef82802c0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 23:47:08 +0100 Subject: [PATCH 0310/2152] Default to 9001 if no port is provided for UDP --- .../java/net/kencochrane/raven/connection/UdpConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index 3f598ce3c5d..d0cabab5c8a 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -28,7 +28,7 @@ public class UdpConnection extends AbstractConnection { public UdpConnection(Dsn dsn) { super(dsn); - openSocket(dsn.getHost(), dsn.getPort()); + openSocket(dsn.getHost(), dsn.getPort() != -1 ? dsn.getPort() : DEFAULT_UDP_PORT); } public UdpConnection(String hostname, String publicKey, String secretKey) { From a69c3df88373e1410d1f9e5a5a27db4a947b0a23 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 13 Apr 2013 13:23:38 +0100 Subject: [PATCH 0311/2152] Get header values instead of parameters --- .../kencochrane/raven/marshaller/json/HttpInterfaceBinding.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java index 01aa7c1b6a6..27226cf2968 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java @@ -54,7 +54,7 @@ private void writeHeaders(JsonGenerator generator, HttpServletRequest request) t generator.writeStartObject(); for (String header : Collections.list(request.getHeaderNames())) { generator.writeArrayFieldStart(header); - for (String headerValue : request.getParameterValues(header)) { + for (String headerValue : Collections.list(request.getHeaders(header))) { generator.writeString(headerValue); } generator.writeEndArray(); From 23129a43b207beef66ce970d4f0431a705123cbc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 13 Apr 2013 13:24:22 +0100 Subject: [PATCH 0312/2152] Fix a writeEndObject in the HttpInterface --- .../kencochrane/raven/marshaller/json/HttpInterfaceBinding.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java index 27226cf2968..68370657303 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java @@ -72,7 +72,7 @@ private void writeCookies(JsonGenerator generator, Cookie[] cookies) throws IOEx for (Cookie cookie : cookies) { generator.writeStringField(cookie.getName(), cookie.getValue()); } - generator.writeStartObject(); + generator.writeEndObject(); } private void writeData(JsonGenerator generator, Map parameterMap) throws IOException { From 26b6d978016b9d429b02b3f67263e2a4b4d56734 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 13 Apr 2013 13:25:03 +0100 Subject: [PATCH 0313/2152] Automatically register RavenServletRequestListener --- .../kencochrane/raven/servlet/RavenServletRequestListener.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java b/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java index d72db999e0d..e0349235531 100644 --- a/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java +++ b/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java @@ -2,8 +2,10 @@ import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; +import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpServletRequest; +@WebListener public class RavenServletRequestListener implements ServletRequestListener { private static final ThreadLocal THREAD_REQUEST = new ThreadLocal(); From 85d8512f5b688c4a34701d63d349378dd2aa76e2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 13 Apr 2013 13:25:32 +0100 Subject: [PATCH 0314/2152] Fix the extraction of the HttpServletRequest --- .../raven/servlet/RavenServletRequestListener.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java b/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java index e0349235531..af0053465b2 100644 --- a/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java +++ b/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java @@ -1,5 +1,6 @@ package net.kencochrane.raven.servlet; +import javax.servlet.ServletRequest; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.annotation.WebListener; @@ -20,7 +21,8 @@ public void requestDestroyed(ServletRequestEvent servletRequestEvent) { @Override public void requestInitialized(ServletRequestEvent servletRequestEvent) { - if (servletRequestEvent instanceof HttpServletRequest) - THREAD_REQUEST.set((HttpServletRequest) servletRequestEvent.getServletRequest()); + ServletRequest servletRequest = servletRequestEvent.getServletRequest(); + if (servletRequest instanceof HttpServletRequest) + THREAD_REQUEST.set((HttpServletRequest) servletRequest); } } From e87a83626d65420eeba2ecd3b20931207d5abf94 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 13 Apr 2013 14:50:30 +0100 Subject: [PATCH 0315/2152] Add more details to the HTTP environment --- .../marshaller/json/HttpInterfaceBinding.java | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java index 68370657303..02b2195f591 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java @@ -17,10 +17,17 @@ public class HttpInterfaceBinding implements InterfaceBinding { private static final String COOKIES = "cookies"; private static final String HEADERS = "headers"; private static final String ENVIRONMENT = "env"; - private static final String REMOTE_ADDR = "REMOTE_ADDR"; - private static final String SERVER_NAME = "SERVER_NAME"; - private static final String SERVER_PORT = "SERVER_PORT"; - private static final String SERVER_PROTOCOL = "SERVER_PROTOCOL"; + private static final String ENV_REMOTE_ADDR = "REMOTE_ADDR"; + private static final String ENV_SERVER_NAME = "SERVER_NAME"; + private static final String ENV_SERVER_PORT = "SERVER_PORT"; + private static final String ENV_LOCAL_ADDR = "LOCAL_ADDR"; + private static final String ENV_LOCAL_NAME = "LOCAL_NAME"; + private static final String ENV_LOCAL_PORT = "LOCAL_PORT"; + private static final String ENV_SERVER_PROTOCOL = "SERVER_PROTOCOL"; + private static final String ENV_REQUEST_SECURE = "REQUEST_SECURE"; + private static final String ENV_REQUEST_ASYNC = "REQUEST_ASYNC"; + private static final String ENV_AUTH_TYPE = "AUTH_TYPE"; + private static final String ENV_REMOTE_USER = "REMOTE_USER"; @Override public void writeInterface(JsonGenerator generator, HttpInterface httpInterface) throws IOException { @@ -43,10 +50,18 @@ public void writeInterface(JsonGenerator generator, HttpInterface httpInterface) private void writeEnvironment(JsonGenerator generator, HttpServletRequest request) throws IOException { generator.writeStartObject(); - generator.writeStringField(REMOTE_ADDR, request.getRemoteAddr()); - generator.writeStringField(SERVER_NAME, request.getServerName()); - generator.writeNumberField(SERVER_PORT, request.getServerPort()); - generator.writeStringField(SERVER_PROTOCOL, request.getProtocol()); + generator.writeStringField(ENV_REMOTE_ADDR, request.getRemoteAddr()); + generator.writeStringField(ENV_SERVER_NAME, request.getServerName()); + generator.writeNumberField(ENV_SERVER_PORT, request.getServerPort()); + generator.writeStringField(ENV_LOCAL_ADDR, request.getLocalAddr()); + generator.writeStringField(ENV_LOCAL_NAME, request.getLocalName()); + generator.writeNumberField(ENV_LOCAL_PORT, request.getLocalPort()); + generator.writeStringField(ENV_SERVER_PROTOCOL, request.getProtocol()); + generator.writeBooleanField(ENV_REQUEST_SECURE, request.isSecure()); + generator.writeBooleanField(ENV_REQUEST_ASYNC, request.isAsyncStarted()); + generator.writeStringField(ENV_AUTH_TYPE, request.getAuthType()); + //TODO: Should that be really displayed here ? Consider the user interface? + generator.writeStringField(ENV_REMOTE_USER, request.getRemoteUser()); generator.writeEndObject(); } From 5865f545ff97bc6c0591970ae340fd3f55f7d72c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 13 Apr 2013 14:53:10 +0100 Subject: [PATCH 0316/2152] Add a warning when the level can't be converted --- .../net/kencochrane/raven/marshaller/json/JsonMarshaller.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 51f8f4866df..4b6a1df1780 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -222,6 +222,8 @@ private String formatLevel(Event.Level level) { case ERROR: return "error"; default: + logger.warning("The level '" + level.name() + "' isn't supported, this should NEVER happen, contact " + + "the developers of Raven-Java"); return null; } } From 83911ec2f2d7ed058b76128d9f7e30ebf1f9edce Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 13 Apr 2013 15:35:06 +0100 Subject: [PATCH 0317/2152] Add a TODO about Servlet before 3.0 --- .../kencochrane/raven/servlet/RavenServletRequestListener.java | 1 + 1 file changed, 1 insertion(+) diff --git a/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java b/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java index af0053465b2..8103e7fb546 100644 --- a/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java +++ b/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java @@ -6,6 +6,7 @@ import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpServletRequest; +//TODO: Consider Servlet < 3? @WebListener public class RavenServletRequestListener implements ServletRequestListener { private static final ThreadLocal THREAD_REQUEST = new ThreadLocal(); From a02a3d6992c28e6e032214d2a869f2843ed72d90 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 13:40:45 +0100 Subject: [PATCH 0318/2152] Add unit tests to check HttpEventBuilderHelper --- .../helper/HttpEventBuilderHelperTest.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java new file mode 100644 index 00000000000..115c61d043e --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java @@ -0,0 +1,55 @@ +package net.kencochrane.raven.event.helper; + +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.interfaces.SentryInterface; +import net.kencochrane.raven.servlet.RavenServletRequestListener; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import javax.servlet.ServletRequestEvent; +import javax.servlet.http.HttpServletRequest; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Mockito.*; + +public class HttpEventBuilderHelperTest { + @Mock + private EventBuilder eventBuilder; + private HttpEventBuilderHelper httpEventBuilderHelper; + + private static void simulateRequest(HttpServletRequest request) { + RavenServletRequestListener ravenServletRequestListener = new RavenServletRequestListener(); + ServletRequestEvent servletRequestEvent = mock(ServletRequestEvent.class); + when(servletRequestEvent.getServletRequest()).thenReturn(request); + ravenServletRequestListener.requestInitialized(servletRequestEvent); + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + httpEventBuilderHelper = new HttpEventBuilderHelper(); + } + + @Test + public void testNoRequest() throws Exception { + httpEventBuilderHelper.helpBuildingEvent(eventBuilder); + + verify(eventBuilder, never()).addSentryInterface(any(SentryInterface.class)); + } + + @Test + public void testWithRequest() throws Exception { + simulateRequest(mock(HttpServletRequest.class)); + ArgumentCaptor interfaceCaptor = ArgumentCaptor.forClass(SentryInterface.class); + + httpEventBuilderHelper.helpBuildingEvent(eventBuilder); + + verify(eventBuilder).addSentryInterface(interfaceCaptor.capture()); + assertThat(interfaceCaptor.getValue(), is(notNullValue())); + } +} From 3b49df32c310014447b06361c3bcfef8e32c755d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 14:03:12 +0100 Subject: [PATCH 0319/2152] Let test methods throw Exception all the time --- .../raven/log4j/SentryAppenderTest.java | 6 ++--- .../raven/logback/SentryAppenderTest.java | 4 +-- .../kencochrane/raven/AbstractLoggerTest.java | 10 +++---- .../java/net/kencochrane/raven/DsnTest.java | 27 +++++++++---------- .../java/net/kencochrane/raven/RavenTest.java | 12 ++++----- .../raven/jul/SentryHandlerTest.java | 4 +-- 6 files changed, 31 insertions(+), 32 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 96d8efe876d..e2a8498636a 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -15,7 +15,7 @@ public class SentryAppenderTest extends AbstractLoggerTest { @Before @Override - public void setUp() { + public void setUp() throws Exception { super.setUp(); logger = Logger.getLogger(SentryAppenderTest.class); logger.setLevel(Level.ALL); @@ -45,7 +45,7 @@ public String getCurrentLoggerName() { @Override @Test - public void testLogLevelConversions() { + public void testLogLevelConversions() throws Exception { assertLevelConverted(Event.Level.DEBUG, Level.TRACE); assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); assertLevelConverted(Event.Level.INFO, Level.INFO); @@ -60,7 +60,7 @@ private void assertLevelConverted(Event.Level expectedLevel, Level level) { } @Override - public void testLogParametrisedMessage() { + public void testLogParametrisedMessage() throws Exception { // Parametrised messages aren't supported } } diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java index ac60f2718cb..59185a11c7c 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -17,7 +17,7 @@ public class SentryAppenderTest extends AbstractLoggerTest { @Before @Override - public void setUp() { + public void setUp() throws Exception { super.setUp(); logger = new LoggerContext().getLogger(SentryAppenderTest.class); logger.setLevel(Level.ALL); @@ -48,7 +48,7 @@ public String getCurrentLoggerName() { @Test @Override - public void testLogLevelConversions() { + public void testLogLevelConversions() throws Exception { assertLevelConverted(Event.Level.DEBUG, Level.TRACE); assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); assertLevelConverted(Event.Level.INFO, Level.INFO); diff --git a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java index cf3a3a61ca6..ab38d059613 100644 --- a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java @@ -27,12 +27,12 @@ public abstract class AbstractLoggerTest { private Raven mockRaven; @Before - public void setUp() { + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); } @Test - public void testSimpleMessageLogging() { + public void testSimpleMessageLogging() throws Exception { ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); Event event; @@ -59,7 +59,7 @@ protected Raven getMockRaven() { public abstract String getCurrentLoggerName(); @Test - public abstract void testLogLevelConversions(); + public abstract void testLogLevelConversions() throws Exception; protected void assertLogLevel(Event.Level expectedLevel) { ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); @@ -70,7 +70,7 @@ protected void assertLogLevel(Event.Level expectedLevel) { } @Test - public void testLogException() { + public void testLogException() throws Exception { ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); Exception exception = new Exception(); Event event; @@ -96,7 +96,7 @@ public void testLogException() { } @Test - public void testLogParametrisedMessage() { + public void testLogParametrisedMessage() throws Exception { ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); String message = UUID.randomUUID().toString(); List parameters = Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()); diff --git a/raven/src/test/java/net/kencochrane/raven/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/DsnTest.java index facabcee561..7fc0e98d937 100644 --- a/raven/src/test/java/net/kencochrane/raven/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/DsnTest.java @@ -7,7 +7,6 @@ import org.mockito.MockitoAnnotations; import javax.naming.Context; -import javax.naming.NamingException; import java.lang.reflect.Field; import java.util.Collections; import java.util.Map; @@ -22,19 +21,19 @@ public class DsnTest { private Context context; @Before - public void setUp() { + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); System.setProperty("java.naming.factory.initial", InitialContextMockFactory.class.getCanonicalName()); InitialContextMockFactory.context = context; } @Test(expected = InvalidDsnException.class) - public void testEmptyDsnInvalid() { + public void testEmptyDsnInvalid() throws Exception { new Dsn(""); } @Test - public void testSimpleDsnValid() { + public void testSimpleDsnValid() throws Exception { Dsn dsn = new Dsn("http://publicKey:secretKey@host/9"); assertThat(dsn.getProtocol(), is("http")); @@ -46,12 +45,12 @@ public void testSimpleDsnValid() { } @Test - public void testDsnLookupWithNothingSet() { + public void testDsnLookupWithNothingSet() throws Exception { assertThat(Dsn.dsnLookup(), is(nullValue())); } @Test - public void testDsnLookupWithJndi() throws NamingException { + public void testDsnLookupWithJndi() throws Exception { String dsn = UUID.randomUUID().toString(); when(context.lookup("java:comp/env/sentry/dsn")).thenReturn(dsn); @@ -59,7 +58,7 @@ public void testDsnLookupWithJndi() throws NamingException { } @Test - public void testDsnLookupWithSystemProperty() { + public void testDsnLookupWithSystemProperty() throws Exception { String dsn = UUID.randomUUID().toString(); System.setProperty("SENTRY_DSN", dsn); @@ -128,27 +127,27 @@ private void removeEnv(String key) throws Exception { } @Test(expected = InvalidDsnException.class) - public void testMissingSecretKeyInvalid() { + public void testMissingSecretKeyInvalid() throws Exception { new Dsn("http://publicKey:@host/9"); } @Test(expected = InvalidDsnException.class) - public void testMissingHostInvalid() { + public void testMissingHostInvalid() throws Exception { new Dsn("http://publicKey:secretKey@/9"); } @Test(expected = InvalidDsnException.class) - public void testMissingPathInvalid() { + public void testMissingPathInvalid() throws Exception { new Dsn("http://publicKey:secretKey@host"); } @Test(expected = InvalidDsnException.class) - public void testMissingProjectIdInvalid() { + public void testMissingProjectIdInvalid() throws Exception { new Dsn("http://publicKey:secretKey@host/"); } @Test - public void testAdvancedDsnValid() { + public void testAdvancedDsnValid() throws Exception { Dsn dsn = new Dsn("naive+udp://1234567890:0987654321@complete.host.name:1234" + "/composed/path/1029384756?option1&option2=valueOption2"); @@ -166,14 +165,14 @@ public void testAdvancedDsnValid() { } @Test(expected = UnsupportedOperationException.class) - public void testOptionsImmutable() { + public void testOptionsImmutable() throws Exception { Dsn dsn = new Dsn("http://publicKey:secretKey@host/9"); dsn.getOptions().put("test", "test"); } @Test(expected = UnsupportedOperationException.class) - public void testProtocolSettingsImmutable() { + public void testProtocolSettingsImmutable() throws Exception { Dsn dsn = new Dsn("http://publicKey:secretKey@host/9"); dsn.getProtocolSettings().add("test"); diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index 9b92079633e..69f58920722 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -20,13 +20,13 @@ public class RavenTest { private Raven raven; @Before - public void setUp() { + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); raven = new Raven(mockConnection); } @Test - public void testSendEvent() { + public void testSendEvent() throws Exception { Event event = mock(Event.class); raven.sendEvent(event); @@ -34,7 +34,7 @@ public void testSendEvent() { } @Test - public void testChangeConnection() { + public void testChangeConnection() throws Exception { Event event = mock(Event.class); Connection mockNewConnection = mock(Connection.class); @@ -46,7 +46,7 @@ public void testChangeConnection() { } @Test - public void testAddRemoveBuilderHelpers() { + public void testAddRemoveBuilderHelpers() throws Exception { EventBuilderHelper builderHelper = mock(EventBuilderHelper.class); assertThat(raven.getBuilderHelpers(), not(contains(builderHelper))); @@ -57,13 +57,13 @@ public void testAddRemoveBuilderHelpers() { } @Test(expected = UnsupportedOperationException.class) - public void testCantModifyBuilderHelpersDirectly() { + public void testCantModifyBuilderHelpersDirectly() throws Exception { EventBuilderHelper builderHelper = mock(EventBuilderHelper.class); raven.getBuilderHelpers().add(builderHelper); } @Test - public void testRunBuilderHelpers() { + public void testRunBuilderHelpers() throws Exception { EventBuilder eventBuilder = mock(EventBuilder.class); EventBuilderHelper builderHelper = mock(EventBuilderHelper.class); raven.addBuilderHelper(builderHelper); diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java index 0e826c3ca6b..f318bd76e7c 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java @@ -14,7 +14,7 @@ public class SentryHandlerTest extends AbstractLoggerTest { @Override @Before - public void setUp() { + public void setUp() throws Exception { super.setUp(); logger = Logger.getAnonymousLogger(); logger.setUseParentHandlers(false); @@ -44,7 +44,7 @@ public String getCurrentLoggerName() { @Override @Test - public void testLogLevelConversions() { + public void testLogLevelConversions() throws Exception { assertLevelConverted(Event.Level.DEBUG, Level.FINEST); assertLevelConverted(Event.Level.DEBUG, Level.FINER); assertLevelConverted(Event.Level.DEBUG, Level.FINE); From fe44133fc01951e6bd20fe764798f61e7c353bfe Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 14:04:33 +0100 Subject: [PATCH 0320/2152] Set the groupId to net.kencochrane.raven --- pom.xml | 2 +- raven-legacy/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 2aed3a17970..5f8acc1b2e4 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ oss-parent 7 - net.kencochrane + net.kencochrane.raven raven-all 3.0-SNAPSHOT pom diff --git a/raven-legacy/pom.xml b/raven-legacy/pom.xml index 9511c361d8a..7442d46e0e6 100644 --- a/raven-legacy/pom.xml +++ b/raven-legacy/pom.xml @@ -5,7 +5,7 @@ 4.0.0 - net.kencochrane + net.kencochrane.raven raven-all 3.0-SNAPSHOT diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 4ea073cf1c6..a3ba45d201e 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ 4.0.0 - net.kencochrane + net.kencochrane.raven raven-all 3.0-SNAPSHOT diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index a60af7732e3..d13eb9db4da 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ 4.0.0 - net.kencochrane + net.kencochrane.raven raven-all 3.0-SNAPSHOT diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 6a8a02ff034..a4ace2468da 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ 4.0.0 - net.kencochrane + net.kencochrane.raven raven-all 3.0-SNAPSHOT diff --git a/raven/pom.xml b/raven/pom.xml index af83934e75e..7f84f889f23 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ 4.0.0 - net.kencochrane + net.kencochrane.raven raven-all 3.0-SNAPSHOT From dacecb14c1ad754dab29a2bff962a8176d183e72 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 14:59:21 +0100 Subject: [PATCH 0321/2152] Set sensible names/descriptions for each module --- pom.xml | 4 ++-- raven-legacy/pom.xml | 4 ++-- raven-log4j/pom.xml | 4 ++-- raven-log4j2/pom.xml | 4 ++-- raven-logback/pom.xml | 4 ++-- raven/pom.xml | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 5f8acc1b2e4..778ea9a6060 100644 --- a/pom.xml +++ b/pom.xml @@ -13,8 +13,8 @@ raven-all 3.0-SNAPSHOT pom - Java-Raven - Java Raven client and loggers + Raven-Java + Sentry client and appenders for diverse logging frameworks. https://github.com/${github.repo} diff --git a/raven-legacy/pom.xml b/raven-legacy/pom.xml index 7442d46e0e6..d0d70d15add 100644 --- a/raven-legacy/pom.xml +++ b/raven-legacy/pom.xml @@ -11,8 +11,8 @@ raven-legacy jar - Raven legacy - The legacy client of Raven Java + Raven-Java legacy client + Legacy interface for the new version of Raven-Java. 1.1.1 diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index a3ba45d201e..f366e6cd353 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -11,8 +11,8 @@ raven-log4j jar - raven-log4j - Log4J appender for Raven/Sentry. + Raven-Java for log4j + log4j appender allowing to send logs to the Raven-Java client. diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index d13eb9db4da..424c3c135c2 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -11,8 +11,8 @@ raven-log4j2 jar - raven-log4j2 - Log4J2 appender for Raven/Sentry. + Raven-Java for log4j2 + log4j2 appender allowing to send logs to the Raven-Java client. diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index a4ace2468da..34a7cbb416a 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -11,8 +11,8 @@ raven-logback jar - raven-logback - Logback appender for Raven/Sentry. + Raven-Java for Logback + Logback appender allowing to send logs to the Raven-Java client. diff --git a/raven/pom.xml b/raven/pom.xml index 7f84f889f23..80779557997 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -11,8 +11,8 @@ raven jar - The Java Raven client - Java Raven client + Raven-Java client + Sentry client written in Java. From 5084366831ce5bd3ddabc09153949b83b3b1f983 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 15:23:40 +0100 Subject: [PATCH 0322/2152] Prefer RunWith(MockitoJUnitRunner) to initMocks() --- .../kencochrane/raven/log4j/SentryAppenderTest.java | 3 --- .../kencochrane/raven/logback/SentryAppenderTest.java | 2 -- .../java/net/kencochrane/raven/AbstractLoggerTest.java | 10 +++------- raven/src/test/java/net/kencochrane/raven/DsnTest.java | 5 +++-- .../src/test/java/net/kencochrane/raven/RavenTest.java | 5 +++-- .../raven/event/helper/HttpEventBuilderHelperTest.java | 5 +++-- .../net/kencochrane/raven/jul/SentryHandlerTest.java | 2 -- .../marshaller/json/TestExceptionInterfaceBinding.java | 3 +++ .../marshaller/json/TestMessageInterfaceBinding.java | 3 +++ .../json/TestStackTraceInterfaceBinding.java | 3 +++ 10 files changed, 21 insertions(+), 20 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index e2a8498636a..8c96bc32f38 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -9,14 +9,11 @@ import java.util.List; - public class SentryAppenderTest extends AbstractLoggerTest { private Logger logger; @Before - @Override public void setUp() throws Exception { - super.setUp(); logger = Logger.getLogger(SentryAppenderTest.class); logger.setLevel(Level.ALL); logger.setAdditivity(false); diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java index 59185a11c7c..893b51be83f 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -16,9 +16,7 @@ public class SentryAppenderTest extends AbstractLoggerTest { private Logger logger; @Before - @Override public void setUp() throws Exception { - super.setUp(); logger = new LoggerContext().getLogger(SentryAppenderTest.class); logger.setLevel(Level.ALL); Appender appender = new SentryAppender(getMockRaven()); diff --git a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java index ab38d059613..094e36ccd55 100644 --- a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java @@ -6,11 +6,11 @@ import net.kencochrane.raven.event.interfaces.SentryInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.hamcrest.Matchers; -import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.runners.MockitoJUnitRunner; import java.util.Arrays; import java.util.List; @@ -22,15 +22,11 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; +@RunWith(MockitoJUnitRunner.class) public abstract class AbstractLoggerTest { @Mock private Raven mockRaven; - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - } - @Test public void testSimpleMessageLogging() throws Exception { ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); diff --git a/raven/src/test/java/net/kencochrane/raven/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/DsnTest.java index 7fc0e98d937..8b62bb1afbf 100644 --- a/raven/src/test/java/net/kencochrane/raven/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/DsnTest.java @@ -3,8 +3,9 @@ import net.kencochrane.raven.exception.InvalidDsnException; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.runners.MockitoJUnitRunner; import javax.naming.Context; import java.lang.reflect.Field; @@ -16,13 +17,13 @@ import static org.hamcrest.Matchers.*; import static org.mockito.Mockito.when; +@RunWith(MockitoJUnitRunner.class) public class DsnTest { @Mock private Context context; @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); System.setProperty("java.naming.factory.initial", InitialContextMockFactory.class.getCanonicalName()); InitialContextMockFactory.context = context; } diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index 69f58920722..1543a43f083 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -6,14 +6,16 @@ import net.kencochrane.raven.event.helper.EventBuilderHelper; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.runners.MockitoJUnitRunner; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.not; import static org.mockito.Mockito.*; +@RunWith(MockitoJUnitRunner.class) public class RavenTest { @Mock private Connection mockConnection; @@ -21,7 +23,6 @@ public class RavenTest { @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); raven = new Raven(mockConnection); } diff --git a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java index 115c61d043e..dba5aed1c50 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java @@ -5,9 +5,10 @@ import net.kencochrane.raven.servlet.RavenServletRequestListener; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.runners.MockitoJUnitRunner; import javax.servlet.ServletRequestEvent; import javax.servlet.http.HttpServletRequest; @@ -17,6 +18,7 @@ import static org.hamcrest.Matchers.notNullValue; import static org.mockito.Mockito.*; +@RunWith(MockitoJUnitRunner.class) public class HttpEventBuilderHelperTest { @Mock private EventBuilder eventBuilder; @@ -31,7 +33,6 @@ private static void simulateRequest(HttpServletRequest request) { @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); httpEventBuilderHelper = new HttpEventBuilderHelper(); } diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java index f318bd76e7c..8c689f7375f 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java @@ -12,10 +12,8 @@ public class SentryHandlerTest extends AbstractLoggerTest { private Logger logger; - @Override @Before public void setUp() throws Exception { - super.setUp(); logger = Logger.getAnonymousLogger(); logger.setUseParentHandlers(false); logger.setLevel(Level.ALL); diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java index 1b378dee11f..c8cc9eb9932 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java @@ -6,6 +6,8 @@ import net.kencochrane.raven.event.interfaces.ImmutableThrowable; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; import java.util.UUID; @@ -14,6 +16,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +@RunWith(MockitoJUnitRunner.class) public class TestExceptionInterfaceBinding extends AbstractTestInterfaceBinding { private ExceptionInterfaceBinding interfaceBinding; diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java index ba9a840ae8b..5a108ed3a44 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java @@ -6,6 +6,8 @@ import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; import java.util.Arrays; import java.util.List; @@ -16,6 +18,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +@RunWith(MockitoJUnitRunner.class) public class TestMessageInterfaceBinding extends AbstractTestInterfaceBinding { private MessageInterfaceBinding interfaceBinding; diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java index 486bca2d555..fec10cee0e9 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java @@ -6,6 +6,8 @@ import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; import java.util.UUID; @@ -13,6 +15,7 @@ import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.*; +@RunWith(MockitoJUnitRunner.class) public class TestStackTraceInterfaceBinding extends AbstractTestInterfaceBinding { private StackTraceInterfaceBinding interfaceBinding; From ccfabfdacb6797320fa5ad02f0328bccf36d3d37 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 15:24:47 +0100 Subject: [PATCH 0323/2152] Prefer @Mock object to temporary mocks --- .../java/net/kencochrane/raven/DsnTest.java | 6 ++-- .../java/net/kencochrane/raven/RavenTest.java | 35 +++++++++---------- .../helper/HttpEventBuilderHelperTest.java | 19 +++++----- .../json/TestExceptionInterfaceBinding.java | 9 ++--- .../json/TestMessageInterfaceBinding.java | 12 ++++--- .../json/TestStackTraceInterfaceBinding.java | 11 +++--- 6 files changed, 48 insertions(+), 44 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/DsnTest.java index 8b62bb1afbf..d1b508bc31b 100644 --- a/raven/src/test/java/net/kencochrane/raven/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/DsnTest.java @@ -20,12 +20,12 @@ @RunWith(MockitoJUnitRunner.class) public class DsnTest { @Mock - private Context context; + private Context mockContext; @Before public void setUp() throws Exception { System.setProperty("java.naming.factory.initial", InitialContextMockFactory.class.getCanonicalName()); - InitialContextMockFactory.context = context; + InitialContextMockFactory.context = mockContext; } @Test(expected = InvalidDsnException.class) @@ -53,7 +53,7 @@ public void testDsnLookupWithNothingSet() throws Exception { @Test public void testDsnLookupWithJndi() throws Exception { String dsn = UUID.randomUUID().toString(); - when(context.lookup("java:comp/env/sentry/dsn")).thenReturn(dsn); + when(mockContext.lookup("java:comp/env/sentry/dsn")).thenReturn(dsn); assertThat(Dsn.dsnLookup(), is(dsn)); } diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index 1543a43f083..0b85aa26632 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -19,6 +19,10 @@ public class RavenTest { @Mock private Connection mockConnection; + @Mock + private EventBuilderHelper mockBuilderHelper; + @Mock + private Event mockEvent; private Raven raven; @Before @@ -28,49 +32,44 @@ public void setUp() throws Exception { @Test public void testSendEvent() throws Exception { - Event event = mock(Event.class); - raven.sendEvent(event); + raven.sendEvent(mockEvent); - verify(mockConnection).send(event); + verify(mockConnection).send(mockEvent); } @Test public void testChangeConnection() throws Exception { - Event event = mock(Event.class); Connection mockNewConnection = mock(Connection.class); raven.setConnection(mockNewConnection); - raven.sendEvent(event); + raven.sendEvent(mockEvent); - verify(mockConnection, never()).send(event); - verify(mockNewConnection).send(event); + verify(mockConnection, never()).send(mockEvent); + verify(mockNewConnection).send(mockEvent); } @Test public void testAddRemoveBuilderHelpers() throws Exception { - EventBuilderHelper builderHelper = mock(EventBuilderHelper.class); - assertThat(raven.getBuilderHelpers(), not(contains(builderHelper))); + assertThat(raven.getBuilderHelpers(), not(contains(mockBuilderHelper))); - raven.addBuilderHelper(builderHelper); - assertThat(raven.getBuilderHelpers(), contains(builderHelper)); - raven.removeBuilderHelper(builderHelper); - assertThat(raven.getBuilderHelpers(), not(contains(builderHelper))); + raven.addBuilderHelper(mockBuilderHelper); + assertThat(raven.getBuilderHelpers(), contains(mockBuilderHelper)); + raven.removeBuilderHelper(mockBuilderHelper); + assertThat(raven.getBuilderHelpers(), not(contains(mockBuilderHelper))); } @Test(expected = UnsupportedOperationException.class) public void testCantModifyBuilderHelpersDirectly() throws Exception { - EventBuilderHelper builderHelper = mock(EventBuilderHelper.class); - raven.getBuilderHelpers().add(builderHelper); + raven.getBuilderHelpers().add(mockBuilderHelper); } @Test public void testRunBuilderHelpers() throws Exception { EventBuilder eventBuilder = mock(EventBuilder.class); - EventBuilderHelper builderHelper = mock(EventBuilderHelper.class); - raven.addBuilderHelper(builderHelper); + raven.addBuilderHelper(mockBuilderHelper); raven.runBuilderHelpers(eventBuilder); - verify(builderHelper).helpBuildingEvent(eventBuilder); + verify(mockBuilderHelper).helpBuildingEvent(eventBuilder); } } diff --git a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java index dba5aed1c50..a1ba5659980 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java @@ -21,14 +21,13 @@ @RunWith(MockitoJUnitRunner.class) public class HttpEventBuilderHelperTest { @Mock - private EventBuilder eventBuilder; + private EventBuilder mockEventBuilder; private HttpEventBuilderHelper httpEventBuilderHelper; - private static void simulateRequest(HttpServletRequest request) { - RavenServletRequestListener ravenServletRequestListener = new RavenServletRequestListener(); + private static void simulateRequest() { ServletRequestEvent servletRequestEvent = mock(ServletRequestEvent.class); - when(servletRequestEvent.getServletRequest()).thenReturn(request); - ravenServletRequestListener.requestInitialized(servletRequestEvent); + when(servletRequestEvent.getServletRequest()).thenReturn(mock(HttpServletRequest.class)); + new RavenServletRequestListener().requestInitialized(servletRequestEvent); } @Before @@ -38,19 +37,19 @@ public void setUp() throws Exception { @Test public void testNoRequest() throws Exception { - httpEventBuilderHelper.helpBuildingEvent(eventBuilder); + httpEventBuilderHelper.helpBuildingEvent(mockEventBuilder); - verify(eventBuilder, never()).addSentryInterface(any(SentryInterface.class)); + verify(mockEventBuilder, never()).addSentryInterface(any(SentryInterface.class)); } @Test public void testWithRequest() throws Exception { - simulateRequest(mock(HttpServletRequest.class)); + simulateRequest(); ArgumentCaptor interfaceCaptor = ArgumentCaptor.forClass(SentryInterface.class); - httpEventBuilderHelper.helpBuildingEvent(eventBuilder); + httpEventBuilderHelper.helpBuildingEvent(mockEventBuilder); - verify(eventBuilder).addSentryInterface(interfaceCaptor.capture()); + verify(mockEventBuilder).addSentryInterface(interfaceCaptor.capture()); assertThat(interfaceCaptor.getValue(), is(notNullValue())); } } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java index c8cc9eb9932..39b2b343924 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java @@ -7,18 +7,20 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class TestExceptionInterfaceBinding extends AbstractTestInterfaceBinding { private ExceptionInterfaceBinding interfaceBinding; + @Mock + private ExceptionInterface mockExceptionInterface; @Before public void setUp() throws Exception { @@ -28,13 +30,12 @@ public void setUp() throws Exception { @Test public void testSimpleException() throws Exception { - ExceptionInterface exceptionInterface = mock(ExceptionInterface.class); String message = UUID.randomUUID().toString(); Throwable throwable = new IllegalStateException(message); - when(exceptionInterface.getThrowable()).thenReturn(new ImmutableThrowable(throwable)); + when(mockExceptionInterface.getThrowable()).thenReturn(new ImmutableThrowable(throwable)); JsonGenerator jsonGenerator = getJsonGenerator(); - interfaceBinding.writeInterface(jsonGenerator, exceptionInterface); + interfaceBinding.writeInterface(jsonGenerator, mockExceptionInterface); jsonGenerator.close(); JsonNode rootNode = getMapper().readValue(getJsonParser(), JsonNode.class); diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java index 5a108ed3a44..68a0423bfde 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java @@ -7,6 +7,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import java.util.Arrays; @@ -15,12 +16,14 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class TestMessageInterfaceBinding extends AbstractTestInterfaceBinding { private MessageInterfaceBinding interfaceBinding; + @Mock + private MessageInterface mockMessageInterface; + @Before public void setUp() throws Exception { @@ -32,12 +35,11 @@ public void setUp() throws Exception { public void testSimpleMessage() throws Exception { String message = UUID.randomUUID().toString(); List parameters = Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()); - MessageInterface messageInterface = mock(MessageInterface.class); - when(messageInterface.getMessage()).thenReturn(message); - when(messageInterface.getParams()).thenReturn(parameters); + when(mockMessageInterface.getMessage()).thenReturn(message); + when(mockMessageInterface.getParams()).thenReturn(parameters); JsonGenerator jSonGenerator = getJsonGenerator(); - interfaceBinding.writeInterface(jSonGenerator, messageInterface); + interfaceBinding.writeInterface(jSonGenerator, mockMessageInterface); jSonGenerator.close(); JsonNode rootNode = getMapper().readValue(getJsonParser(), JsonNode.class); diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java index fec10cee0e9..a040e85c207 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java @@ -7,17 +7,21 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class TestStackTraceInterfaceBinding extends AbstractTestInterfaceBinding { private StackTraceInterfaceBinding interfaceBinding; + @Mock + private StackTraceInterface mockStackTraceInterface; @Before public void setUp() throws Exception { @@ -32,12 +36,11 @@ public void testSingleStackFrame() throws Exception { int lineNumber = 1; Throwable exception = mock(Throwable.class); StackTraceElement stackTraceElement = new StackTraceElement(className, methodName, null, lineNumber); - StackTraceInterface stackTraceInterface = mock(StackTraceInterface.class, RETURNS_DEEP_STUBS); - when(stackTraceInterface.getThrowable()).thenReturn(new ImmutableThrowable(exception)); + when(mockStackTraceInterface.getThrowable()).thenReturn(new ImmutableThrowable(exception)); when(exception.getStackTrace()).thenReturn(new StackTraceElement[]{stackTraceElement}); JsonGenerator jSonGenerator = getJsonGenerator(); - interfaceBinding.writeInterface(jSonGenerator, stackTraceInterface); + interfaceBinding.writeInterface(jSonGenerator, mockStackTraceInterface); jSonGenerator.close(); JsonNode frames = getMapper().readValue(getJsonParser(), JsonNode.class).get("frames"); From 65bc664d1eaf7d195f2ffe892945fdae415fa06e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 15:26:57 +0100 Subject: [PATCH 0324/2152] Tested element should be before the mocks --- raven/src/test/java/net/kencochrane/raven/RavenTest.java | 2 +- .../raven/event/helper/HttpEventBuilderHelperTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index 0b85aa26632..c07160edb20 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -17,13 +17,13 @@ @RunWith(MockitoJUnitRunner.class) public class RavenTest { + private Raven raven; @Mock private Connection mockConnection; @Mock private EventBuilderHelper mockBuilderHelper; @Mock private Event mockEvent; - private Raven raven; @Before public void setUp() throws Exception { diff --git a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java index a1ba5659980..31c4f227613 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java @@ -20,9 +20,9 @@ @RunWith(MockitoJUnitRunner.class) public class HttpEventBuilderHelperTest { + private HttpEventBuilderHelper httpEventBuilderHelper; @Mock private EventBuilder mockEventBuilder; - private HttpEventBuilderHelper httpEventBuilderHelper; private static void simulateRequest() { ServletRequestEvent servletRequestEvent = mock(ServletRequestEvent.class); From 32aa16f85605e9ed000f636871ae73565873f8cc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 20:23:24 +0100 Subject: [PATCH 0325/2152] Create a sentry-stub project --- pom.xml | 1 + sentry-stub/pom.xml | 51 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 sentry-stub/pom.xml diff --git a/pom.xml b/pom.xml index 778ea9a6060..8a60c3fb865 100644 --- a/pom.xml +++ b/pom.xml @@ -107,6 +107,7 @@ raven-log4j raven-logback raven-log4j2 + sentry-stub
    diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml new file mode 100644 index 00000000000..6331adec35b --- /dev/null +++ b/sentry-stub/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + + net.kencochrane.raven + raven-all + 3.0-SNAPSHOT + + sentry-stub + war + Sentry stub + Stub for tests against a Sentry server. + + + + commons-codec + commons-codec + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + javax.servlet + javax.servlet-api + + + + + + + org.eclipse.jetty + jetty-maven-plugin + 9.0.1.v20130408 + + 1 + + /sentry + + + + + + From d7f45c2e2d52ef610e68bebf86ec917468d5cdd7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 20:24:08 +0100 Subject: [PATCH 0326/2152] Add a web.xml file --- sentry-stub/src/main/webapp/WEB-INF/web.xml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 sentry-stub/src/main/webapp/WEB-INF/web.xml diff --git a/sentry-stub/src/main/webapp/WEB-INF/web.xml b/sentry-stub/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..b9436cdd146 --- /dev/null +++ b/sentry-stub/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,5 @@ + + From 3bcbd5a8680d28c7bab094e090fc720cf8b2f7a3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 20:25:24 +0100 Subject: [PATCH 0327/2152] Add a validation class to test Auth headers --- .../raven/sentrystub/auth/AuthValidator.java | 74 +++++++++++++++++++ .../sentrystub/auth/InvalidAuthException.java | 36 +++++++++ 2 files changed, 110 insertions(+) create mode 100644 sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java create mode 100644 sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/InvalidAuthException.java diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java new file mode 100644 index 00000000000..9c6720abdda --- /dev/null +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java @@ -0,0 +1,74 @@ +package net.kencochrane.raven.sentrystub.auth; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class AuthValidator { + private static final Collection SENTRY_PROTOCOL_VERSIONS = Arrays.asList("3"); + private static final String SENTRY_VERSION_PARAMETER = "Sentry sentry_version"; + private static final String PUBLIC_KEY_PARAMETER = "sentry_key"; + private static final String SECRET_KEY_PARAMETER = "sentry_secret"; + private static final String SENTRY_CLIENT_PARAMETER = "sentry_client"; + private final Map publicKeySecretKey = new HashMap(); + private final Map publicKeyProjectId = new HashMap(); + + public void addUser(String publicKey, String secretKey, String projectId) { + if (publicKeySecretKey.containsKey(publicKey) || publicKeyProjectId.containsKey(publicKey)) { + throw new IllegalArgumentException("There is already a user " + publicKey); + } + + publicKeySecretKey.put(publicKey, secretKey); + publicKeyProjectId.put(publicKey, projectId); + } + + public void validateSentryAuth(Map authParameters, String projectId) { + InvalidAuthException invalidAuthException = new InvalidAuthException("The auth parameters weren't valid"); + + validateVersion(authParameters.get(SENTRY_VERSION_PARAMETER), invalidAuthException); + validateKeys(authParameters.get(PUBLIC_KEY_PARAMETER), authParameters.get(SECRET_KEY_PARAMETER), + invalidAuthException); + validateProject(authParameters.get(PUBLIC_KEY_PARAMETER), projectId, invalidAuthException); + validateClient(authParameters.get(SENTRY_CLIENT_PARAMETER), invalidAuthException); + + if (!invalidAuthException.isEmpty()) + throw invalidAuthException; + } + + private void validateVersion(String authSentryVersion, InvalidAuthException invalidAuthException) { + if (authSentryVersion == null || !SENTRY_PROTOCOL_VERSIONS.contains(authSentryVersion)) + invalidAuthException.addDetailedMessage("The version '" + authSentryVersion + "' isn't valid, " + + "only '" + SENTRY_PROTOCOL_VERSIONS + "' are accepted."); + } + + private void validateKeys(String publicKey, String secretKey, + InvalidAuthException invalidAuthException) { + if (publicKey == null) + invalidAuthException.addDetailedMessage("No public key provided"); + else if (!publicKeySecretKey.containsKey(publicKey)) + invalidAuthException.addDetailedMessage("The public key '" + publicKey + "' isn't associated " + + "with a secret key."); + + if (secretKey == null) + invalidAuthException.addDetailedMessage("No secret key provided"); + + if (secretKey != null && publicKey != null && !publicKeySecretKey.get(publicKey).equals(secretKey)) + invalidAuthException.addDetailedMessage("The secret key '" + secretKey + "' " + + "isn't valid for '" + publicKey + "'"); + } + + private void validateProject(String publicKey, String projectId, InvalidAuthException invalidAuthException) { + if (projectId == null) + invalidAuthException.addDetailedMessage("No project ID provided"); + + if (publicKey != null && !publicKeyProjectId.get(publicKey).equals(projectId)) + invalidAuthException.addDetailedMessage("The project '" + projectId + "' " + + "can't be accessed by ' " + publicKey + " '"); + } + + private void validateClient(String client, InvalidAuthException invalidAuthException) { + if (client == null) + invalidAuthException.addDetailedMessage("The client name is mandatory."); + } +} diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/InvalidAuthException.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/InvalidAuthException.java new file mode 100644 index 00000000000..06238acb32c --- /dev/null +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/InvalidAuthException.java @@ -0,0 +1,36 @@ +package net.kencochrane.raven.sentrystub.auth; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; + +public class InvalidAuthException extends RuntimeException { + private final Collection detailedMessages = new LinkedList(); + + public InvalidAuthException() { + } + + public InvalidAuthException(String message) { + super(message); + } + + public InvalidAuthException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidAuthException(Throwable cause) { + super(cause); + } + + public Collection getDetailedMessages() { + return Collections.unmodifiableCollection(detailedMessages); + } + + public void addDetailedMessage(String message) { + detailedMessages.add(message); + } + + public boolean isEmpty(){ + return detailedMessages.isEmpty(); + } +} From 489ec5732fbf2b9926dff495b132535f83edb218 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 20:25:58 +0100 Subject: [PATCH 0328/2152] Add a Class to decode incoming Stream in JSON --- .../raven/sentrystub/JsonDecoder.java | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java new file mode 100644 index 00000000000..cc6ff6a24fb --- /dev/null +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java @@ -0,0 +1,120 @@ +package net.kencochrane.raven.sentrystub; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import org.apache.commons.codec.binary.Base64InputStream; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.InflaterInputStream; + +public class JsonDecoder { + /** + * Attempts to read the content of the stream and determinate if it's compressed, encoded or simple JSON. + *

    + * This method isn't efficient but it isn't really a problem as this part of the project is not about performances. + *

    + * + * @param originalStream origin stream of information that can be compressed or encoded in base64. + * @return a Stream containing pure JSON. + * @throws IOException if it's impossible to read the content of the Stream. + */ + public InputStream decapsulateContent(InputStream originalStream) throws IOException { + //Make it uncloseable to avoid issues with the InflaterInputStream. + originalStream = new Uncloseable(new BufferedInputStream(originalStream)); + //Hopefully the sent content isn't bigger than 1MB... + originalStream.mark(1 << 20); + InputStream inputStream = originalStream; + + if (!isJson(originalStream)) { + inputStream = new Base64InputStream(inputStream); + originalStream.reset(); + if (!isJson(new Base64InputStream(originalStream))) { + inputStream = new InflaterInputStream(inputStream); + originalStream.reset(); + if (!isJson(new InflaterInputStream(new Base64InputStream(originalStream)))) { + throw new IllegalArgumentException("The given Stream is neither JSON, Base64'd JSON " + + "nor Base64'd deflated JSON."); + } + } + } + + originalStream.reset(); + return inputStream; + } + + /** + * Checks that the parsed content is JSON content. + * + * @param inputStream data source. + * @return true if the content is in JSON, false otherwise. + */ + private boolean isJson(InputStream inputStream) { + boolean valid = false; + try { + final JsonParser parser = new JsonFactory().createJsonParser(inputStream); + while (parser.nextToken() != null) { + } + valid = true; + } catch (Exception ignore) { + } + + return valid; + } + + /** + * InputStream that delegates everything but the {@link #close()} method. + */ + private static class Uncloseable extends InputStream { + private final InputStream original; + + private Uncloseable(InputStream original) { + this.original = original; + } + + @Override + public int read() throws IOException { + return original.read(); + } + + @Override + public int read(byte[] b) throws IOException { + return original.read(b); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return original.read(b, off, len); + } + + @Override + public long skip(long n) throws IOException { + return original.skip(n); + } + + @Override + public int available() throws IOException { + return original.available(); + } + + @Override + public void close() throws IOException { + } + + @Override + public void mark(int readlimit) { + original.mark(readlimit); + } + + @Override + public void reset() throws IOException { + original.reset(); + } + + @Override + public boolean markSupported() { + return original.markSupported(); + } + } +} From 7e95781b10d4b072fd4c6ef5b1569fced7cedc62 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 20:26:26 +0100 Subject: [PATCH 0329/2152] Add a listener starting a UDP socket --- .../sentrystub/SentryUdpContextListener.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java new file mode 100644 index 00000000000..bcc6e41ef9a --- /dev/null +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java @@ -0,0 +1,75 @@ +package net.kencochrane.raven.sentrystub; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.annotation.WebListener; +import java.io.ByteArrayInputStream; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.SocketException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@WebListener +public class SentryUdpContextListener implements ServletContextListener { + private static final int DEFAULT_SENTRY_UDP_PORT = 9001; + private static final String SENTRY_UDP_PORT_PARAMETER = "sentryUdpPort"; + private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + private DatagramSocket udpSocket; + + @Override + public void contextInitialized(ServletContextEvent sce) { + String sentryUdpPortParameter = sce.getServletContext().getInitParameter(SENTRY_UDP_PORT_PARAMETER); + int port = DEFAULT_SENTRY_UDP_PORT; + if (sentryUdpPortParameter != null) + port = Integer.parseInt(sentryUdpPortParameter); + try { + udpSocket = new DatagramSocket(port); + new UdpListenerThread().start(); + } catch (SocketException e) { + throw new RuntimeException(e); + } + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + executorService.shutdownNow(); + udpSocket.close(); + } + + private static class UdpRequestHandler implements Runnable { + private final DatagramPacket datagramPacket; + + private UdpRequestHandler(DatagramPacket datagramPacket) { + this.datagramPacket = datagramPacket; + } + + @Override + public void run() { + try { + ByteArrayInputStream bais = new ByteArrayInputStream(datagramPacket.getData(), + datagramPacket.getOffset(), + datagramPacket.getLength()); + //TODO extract the AUTH header validate it, send the rest to the JSon validator + } catch (Exception e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + } + } + + private class UdpListenerThread extends Thread { + @Override + public void run() { + while (!udpSocket.isClosed()) { + try { + // We'll assume that no-one sends a > 65KB datagram (max size allowed on IPV4). + byte[] buffer = new byte[1 << 16]; + DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length); + udpSocket.receive(datagramPacket); + } catch (Exception e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + } + } + } +} From ae4961edf5e50f1699c6407d43fa2c4b5e3b5373 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 20:26:53 +0100 Subject: [PATCH 0330/2152] Add a HTTP Servlet for Sentry API requests --- .../raven/sentrystub/SentryHttpServlet.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java new file mode 100644 index 00000000000..172e1450acd --- /dev/null +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java @@ -0,0 +1,20 @@ +package net.kencochrane.raven.sentrystub; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@WebServlet(name = "SentryHttpServlet", displayName = "SentryHttpServlet", urlPatterns = "/api/*") +public class SentryHttpServlet extends HttpServlet { + public SentryHttpServlet() { + super(); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + System.out.println("test"); + } +} From aded72cb5e3c46bc694a37f219795e0dd6c8e979 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 20:27:16 +0100 Subject: [PATCH 0331/2152] Add a filter checking the Authorisation to access the API --- .../SentryAuthenticationFilter.java | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java new file mode 100644 index 00000000000..719e4e3b142 --- /dev/null +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java @@ -0,0 +1,76 @@ +package net.kencochrane.raven.sentrystub; + +import net.kencochrane.raven.sentrystub.auth.AuthValidator; +import net.kencochrane.raven.sentrystub.auth.InvalidAuthException; + +import javax.servlet.*; +import javax.servlet.annotation.WebFilter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +@WebFilter(servletNames = "SentryHttpServlet") +public class SentryAuthenticationFilter implements Filter { + private static final String SENTRY_AUTH = "X-Sentry-Auth"; + private AuthValidator authValidator; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + authValidator = new AuthValidator(); + //TODO: Load filter config to add users! + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + + //TODO: respond to everything that isn't "https://example.com/sentry/api/project-id/store/" + String pathInfo = req.getPathInfo(); + + if (!validateAuth(req, resp)) return; + + chain.doFilter(request, response); + } + + private boolean validateAuth(HttpServletRequest req, HttpServletResponse resp) throws IOException { + Map sentryAuthDetails = extractSentryAuthDetails(req); + String projectId = req.getPathInfo().substring(1, req.getPathInfo().indexOf('/', 1)); + + try { + authValidator.validateSentryAuth(sentryAuthDetails, projectId); + } catch (InvalidAuthException iae) { + resp.setStatus(HttpServletResponse.SC_FORBIDDEN); + PrintWriter writer = resp.getWriter(); + for (String message : iae.getDetailedMessages()) { + writer.println(message); + } + return false; + } + return true; + } + + private Map extractSentryAuthDetails(HttpServletRequest request) { + String sentryAuth = request.getHeader(SENTRY_AUTH); + if (sentryAuth == null) { + return Collections.emptyMap(); + } + + String[] authParameters = sentryAuth.split(","); + Map authDetails = new HashMap(authParameters.length); + for (String authParameter : authParameters) { + String[] splitParameter = authParameter.split("="); + authDetails.put(splitParameter[0], splitParameter[1]); + } + + return authDetails; + } + + @Override + public void destroy() { + } +} From 02ba7c1535569c38ed4ccf80b5c4cb52f7d0dc65 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 23:41:22 +0100 Subject: [PATCH 0332/2152] Create UdpRequestHandlers for each packet --- .../kencochrane/raven/sentrystub/SentryUdpContextListener.java | 1 + 1 file changed, 1 insertion(+) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java index bcc6e41ef9a..54a907fd396 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java @@ -66,6 +66,7 @@ public void run() { byte[] buffer = new byte[1 << 16]; DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length); udpSocket.receive(datagramPacket); + executorService.execute(new UdpRequestHandler(datagramPacket)); } catch (Exception e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } From 826851399f249fd12a93b82fd7254b8b48ce2ae7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 23:42:36 +0100 Subject: [PATCH 0333/2152] Fix Auth validation to avoid NPE & obscure message --- .../kencochrane/raven/sentrystub/auth/AuthValidator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java index 9c6720abdda..cc4c9dae835 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java @@ -39,7 +39,7 @@ public void validateSentryAuth(Map authParameters, String projec private void validateVersion(String authSentryVersion, InvalidAuthException invalidAuthException) { if (authSentryVersion == null || !SENTRY_PROTOCOL_VERSIONS.contains(authSentryVersion)) invalidAuthException.addDetailedMessage("The version '" + authSentryVersion + "' isn't valid, " + - "only '" + SENTRY_PROTOCOL_VERSIONS + "' are accepted."); + "only those " + SENTRY_PROTOCOL_VERSIONS + " are supported."); } private void validateKeys(String publicKey, String secretKey, @@ -53,7 +53,7 @@ else if (!publicKeySecretKey.containsKey(publicKey)) if (secretKey == null) invalidAuthException.addDetailedMessage("No secret key provided"); - if (secretKey != null && publicKey != null && !publicKeySecretKey.get(publicKey).equals(secretKey)) + if (secretKey != null && publicKey != null && !secretKey.equals(publicKeySecretKey.get(publicKey))) invalidAuthException.addDetailedMessage("The secret key '" + secretKey + "' " + "isn't valid for '" + publicKey + "'"); } @@ -62,7 +62,7 @@ private void validateProject(String publicKey, String projectId, InvalidAuthExce if (projectId == null) invalidAuthException.addDetailedMessage("No project ID provided"); - if (publicKey != null && !publicKeyProjectId.get(publicKey).equals(projectId)) + if (projectId != null && publicKey != null && !projectId.equals(publicKeyProjectId.get(publicKey))) invalidAuthException.addDetailedMessage("The project '" + projectId + "' " + "can't be accessed by ' " + publicKey + " '"); } From 7f9146c26a1797222c1b83bb5d25328981618b39 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 23:44:04 +0100 Subject: [PATCH 0334/2152] Create an event class to auto parse JSon files --- .../raven/sentrystub/event/Event.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java new file mode 100644 index 00000000000..9b950c845e5 --- /dev/null +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java @@ -0,0 +1,35 @@ +package net.kencochrane.raven.sentrystub.event; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Date; +import java.util.Map; + +public class Event { + @JsonProperty(value = "event_id", required = true) + private String eventId; + @JsonProperty(value = "checksum") + private String checksum; + @JsonProperty(value = "message") + private String message; + @JsonProperty(value = "timestamp", required = true) + private Date timestamp; + @JsonProperty(value = "level") + private String level; + @JsonProperty(value = "logger") + private String logger; + @JsonProperty(value = "platform") + private String platform; + @JsonProperty(value = "culprit") + private String culprit; + @JsonProperty(value = "tags") + private Map tags; + @JsonProperty(value = "server_name") + private String serverName; + @JsonProperty(value = "modules") + private Map modules; + @JsonProperty(value = "extra") + private Map extras; +// @JsonProperty(value = "sentry.interfaces.Message") +// private Map messageInterface; +} From 4cb114e51b2b8b847c36063ec0e84c775e6be1e0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Apr 2013 23:44:42 +0100 Subject: [PATCH 0335/2152] Automatically parse Events retreived from HTTP --- .../raven/sentrystub/SentryHttpServlet.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java index 172e1450acd..ac03fd1a0c1 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java @@ -1,20 +1,28 @@ package net.kencochrane.raven.sentrystub; +import com.fasterxml.jackson.databind.ObjectMapper; +import net.kencochrane.raven.sentrystub.event.Event; + import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.io.InputStream; @WebServlet(name = "SentryHttpServlet", displayName = "SentryHttpServlet", urlPatterns = "/api/*") public class SentryHttpServlet extends HttpServlet { + private JsonDecoder jsonDecoder = new JsonDecoder(); + private ObjectMapper om = new ObjectMapper(); + public SentryHttpServlet() { super(); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - System.out.println("test"); + InputStream jsonStream = jsonDecoder.decapsulateContent(req.getInputStream()); + Event e = om.readValue(jsonStream, Event.class); } } From 891a55a3de0a13777829c4d34d70995ad080392e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 15:48:14 +0100 Subject: [PATCH 0336/2152] Do not force the validation of the project ID UDP doesn't provide the project ID at all --- .../raven/sentrystub/auth/AuthValidator.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java index cc4c9dae835..dd866ad67e2 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java @@ -23,19 +23,32 @@ public void addUser(String publicKey, String secretKey, String projectId) { publicKeyProjectId.put(publicKey, projectId); } - public void validateSentryAuth(Map authParameters, String projectId) { + public void validateSentryAuth(Map authParameters) { InvalidAuthException invalidAuthException = new InvalidAuthException("The auth parameters weren't valid"); validateVersion(authParameters.get(SENTRY_VERSION_PARAMETER), invalidAuthException); validateKeys(authParameters.get(PUBLIC_KEY_PARAMETER), authParameters.get(SECRET_KEY_PARAMETER), invalidAuthException); - validateProject(authParameters.get(PUBLIC_KEY_PARAMETER), projectId, invalidAuthException); validateClient(authParameters.get(SENTRY_CLIENT_PARAMETER), invalidAuthException); if (!invalidAuthException.isEmpty()) throw invalidAuthException; } + public void validateSentryAuth(Map authParameters, String projectId) { + InvalidAuthException invalidAuthException = new InvalidAuthException("The auth parameters weren't valid"); + try { + validateSentryAuth(authParameters); + } catch (InvalidAuthException e) { + invalidAuthException = e; + } + + validateProject(authParameters.get(PUBLIC_KEY_PARAMETER), projectId, invalidAuthException); + + if (!invalidAuthException.isEmpty()) + throw invalidAuthException; + } + private void validateVersion(String authSentryVersion, InvalidAuthException invalidAuthException) { if (authSentryVersion == null || !SENTRY_PROTOCOL_VERSIONS.contains(authSentryVersion)) invalidAuthException.addDetailedMessage("The version '" + authSentryVersion + "' isn't valid, " + From 7187a3e19aab934ce424e65ad4ac35320bdb267a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 15:48:48 +0100 Subject: [PATCH 0337/2152] Add documentation to AuthValidator --- .../raven/sentrystub/auth/AuthValidator.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java index dd866ad67e2..42371c9fcc2 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java @@ -5,6 +5,13 @@ import java.util.HashMap; import java.util.Map; +/** + * Validate a Sentry auth Header (in HTTP {@code X-Sentry-Auth}). + *

    + * The validation of a header goes from the validation of the content to the authorisation check for the given public + * and secret keys. + *

    + */ public class AuthValidator { private static final Collection SENTRY_PROTOCOL_VERSIONS = Arrays.asList("3"); private static final String SENTRY_VERSION_PARAMETER = "Sentry sentry_version"; @@ -14,6 +21,13 @@ public class AuthValidator { private final Map publicKeySecretKey = new HashMap(); private final Map publicKeyProjectId = new HashMap(); + /** + * Adds a user to consider as valid of an Auth header. + * + * @param publicKey public key of the user. + * @param secretKey secret key of the user. + * @param projectId identifier of the project on which the user is allowed to push events. + */ public void addUser(String publicKey, String secretKey, String projectId) { if (publicKeySecretKey.containsKey(publicKey) || publicKeyProjectId.containsKey(publicKey)) { throw new IllegalArgumentException("There is already a user " + publicKey); @@ -23,6 +37,11 @@ public void addUser(String publicKey, String secretKey, String projectId) { publicKeyProjectId.put(publicKey, projectId); } + /** + * Validates an auth header. + * + * @param authParameters auth header as a {@code Map}. + */ public void validateSentryAuth(Map authParameters) { InvalidAuthException invalidAuthException = new InvalidAuthException("The auth parameters weren't valid"); @@ -35,6 +54,13 @@ public void validateSentryAuth(Map authParameters) { throw invalidAuthException; } + /** + * Validates an auth header and the access to a project. + * + * @param authParameters auth header as a {@code Map}. + * @param projectId identifier of the project being accessed (isn't a part + * @see #validateSentryAuth(java.util.Map) + */ public void validateSentryAuth(Map authParameters, String projectId) { InvalidAuthException invalidAuthException = new InvalidAuthException("The auth parameters weren't valid"); try { @@ -49,12 +75,32 @@ public void validateSentryAuth(Map authParameters, String projec throw invalidAuthException; } + /** + * Validates the version of the protocol given in the Auth header. + *

    + * The only supported versions are listed in {@link #SENTRY_PROTOCOL_VERSIONS}. + *

    + * + * @param authSentryVersion version of the Sentry protocol given in the auth header. + * @param invalidAuthException exception thrown if the auth header is invalid. + */ private void validateVersion(String authSentryVersion, InvalidAuthException invalidAuthException) { if (authSentryVersion == null || !SENTRY_PROTOCOL_VERSIONS.contains(authSentryVersion)) invalidAuthException.addDetailedMessage("The version '" + authSentryVersion + "' isn't valid, " + "only those " + SENTRY_PROTOCOL_VERSIONS + " are supported."); } + /** + * Validates the public and secret user keys provided in the Auth Header. + *

    + * Valid keys are listed in {@link #publicKeySecretKey}. + *

    + * + * @param publicKey public key used to identify a user. + * @param secretKey secret key used as a password. + * @param invalidAuthException exception thrown if the auth header is invalid. + * @see #addUser(String, String, String) + */ private void validateKeys(String publicKey, String secretKey, InvalidAuthException invalidAuthException) { if (publicKey == null) @@ -71,6 +117,14 @@ else if (!publicKeySecretKey.containsKey(publicKey)) "isn't valid for '" + publicKey + "'"); } + /** + * Validates the project and checks if the given user can indeed access the project. + * + * @param publicKey public key used to identify a user. + * @param projectId identifier of the project on which the user is allowed to push events. + * @param invalidAuthException exception thrown if the auth header is invalid. + * @see #addUser(String, String, String) + */ private void validateProject(String publicKey, String projectId, InvalidAuthException invalidAuthException) { if (projectId == null) invalidAuthException.addDetailedMessage("No project ID provided"); @@ -80,6 +134,15 @@ private void validateProject(String publicKey, String projectId, InvalidAuthExce "can't be accessed by ' " + publicKey + " '"); } + /** + * Validates the client part of the header. + *

    + * The client should always be provided. + *

    + * + * @param client string identifying a client type (such as Java/3.0) + * @param invalidAuthException exception thrown if the auth header is invalid. + */ private void validateClient(String client, InvalidAuthException invalidAuthException) { if (client == null) invalidAuthException.addDetailedMessage("The client name is mandatory."); From 59597d18170ce13d455c67b1f9592ceaada178bb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 16:04:19 +0100 Subject: [PATCH 0338/2152] Log exceptions as fine rather than ignoring them --- .../java/net/kencochrane/raven/sentrystub/JsonDecoder.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java index cc6ff6a24fb..e59792f40d4 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java @@ -7,9 +7,13 @@ import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.zip.InflaterInputStream; public class JsonDecoder { + private static Logger logger = Logger.getLogger(JsonDecoder.class.getCanonicalName()); + /** * Attempts to read the content of the stream and determinate if it's compressed, encoded or simple JSON. *

    @@ -57,7 +61,8 @@ private boolean isJson(InputStream inputStream) { while (parser.nextToken() != null) { } valid = true; - } catch (Exception ignore) { + } catch (Exception e) { + logger.log(Level.FINE, "An exception occurred while trying to parse an allegedly JSON document", e); } return valid; From bc0b6bbbd89ef1d67e43c7ae653acd9e3e98d041 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 16:04:35 +0100 Subject: [PATCH 0339/2152] Avoid making an empty loop --- .../java/net/kencochrane/raven/sentrystub/JsonDecoder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java index e59792f40d4..04ec4b74ad0 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java @@ -58,7 +58,8 @@ private boolean isJson(InputStream inputStream) { boolean valid = false; try { final JsonParser parser = new JsonFactory().createJsonParser(inputStream); - while (parser.nextToken() != null) { + while (parser.hasCurrentToken()) { + parser.nextToken(); } valid = true; } catch (Exception e) { From 0f42c6dbbe22d5e6d31ff05a0a307cf22b6bae97 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 16:04:47 +0100 Subject: [PATCH 0340/2152] Avoid using magic numbers --- .../java/net/kencochrane/raven/sentrystub/JsonDecoder.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java index 04ec4b74ad0..aff803425cd 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java @@ -25,10 +25,11 @@ public class JsonDecoder { * @throws IOException if it's impossible to read the content of the Stream. */ public InputStream decapsulateContent(InputStream originalStream) throws IOException { + //Hopefully the sent content isn't bigger than 1MB... + final int messageSize = 1048576; //Make it uncloseable to avoid issues with the InflaterInputStream. originalStream = new Uncloseable(new BufferedInputStream(originalStream)); - //Hopefully the sent content isn't bigger than 1MB... - originalStream.mark(1 << 20); + originalStream.mark(messageSize); InputStream inputStream = originalStream; if (!isJson(originalStream)) { From 7af8ce7343b37b5dd4b1ed20a6fda7c7a2a62d49 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 16:05:00 +0100 Subject: [PATCH 0341/2152] Make Uncloseable final --- .../main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java index aff803425cd..5454cc3a419 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java @@ -73,7 +73,7 @@ private boolean isJson(InputStream inputStream) { /** * InputStream that delegates everything but the {@link #close()} method. */ - private static class Uncloseable extends InputStream { + private static final class Uncloseable extends InputStream { private final InputStream original; private Uncloseable(InputStream original) { From b318e9a7b83712af672a28fa23609a3b431fd647 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 16:05:17 +0100 Subject: [PATCH 0342/2152] Put operators at the begining of the line --- .../java/net/kencochrane/raven/sentrystub/JsonDecoder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java index 5454cc3a419..a41d1376798 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java @@ -39,8 +39,8 @@ public InputStream decapsulateContent(InputStream originalStream) throws IOExcep inputStream = new InflaterInputStream(inputStream); originalStream.reset(); if (!isJson(new InflaterInputStream(new Base64InputStream(originalStream)))) { - throw new IllegalArgumentException("The given Stream is neither JSON, Base64'd JSON " + - "nor Base64'd deflated JSON."); + throw new IllegalArgumentException("The given Stream is neither JSON, Base64'd JSON " + + "nor Base64'd deflated JSON."); } } } From 1120cac20d2714955785f20143decf38bb72c77b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 16:05:45 +0100 Subject: [PATCH 0343/2152] Add documentation to JsonDecoder --- .../kencochrane/raven/sentrystub/JsonDecoder.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java index a41d1376798..ff3f05d07eb 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java @@ -11,11 +11,22 @@ import java.util.logging.Logger; import java.util.zip.InflaterInputStream; +/** + * Decodes a Stream as a JSON stream. + *

    + * The supported stream formats are: + *

      + *
    • JSON Stream (nothing to do)
    • + *
    • Base 64'd JSON streams (base64 decoded)
    • + *
    • Base 64'd & deflated JSON streams (base64 decoded and inflated)
    • + *
    + *

    + */ public class JsonDecoder { private static Logger logger = Logger.getLogger(JsonDecoder.class.getCanonicalName()); /** - * Attempts to read the content of the stream and determinate if it's compressed, encoded or simple JSON. + * Attempts to read the content of the stream and determine if it's compressed, encoded or simple JSON. *

    * This method isn't efficient but it isn't really a problem as this part of the project is not about performances. *

    From 343d3684d6997aec11c5762e2453c25738f835d1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 16:05:17 +0100 Subject: [PATCH 0344/2152] Put operators at the begining of the line --- .../raven/sentrystub/auth/AuthValidator.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java index 42371c9fcc2..43e345128ee 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java @@ -86,8 +86,8 @@ public void validateSentryAuth(Map authParameters, String projec */ private void validateVersion(String authSentryVersion, InvalidAuthException invalidAuthException) { if (authSentryVersion == null || !SENTRY_PROTOCOL_VERSIONS.contains(authSentryVersion)) - invalidAuthException.addDetailedMessage("The version '" + authSentryVersion + "' isn't valid, " + - "only those " + SENTRY_PROTOCOL_VERSIONS + " are supported."); + invalidAuthException.addDetailedMessage("The version '" + authSentryVersion + "' isn't valid, " + + "only those " + SENTRY_PROTOCOL_VERSIONS + " are supported."); } /** @@ -106,15 +106,15 @@ private void validateKeys(String publicKey, String secretKey, if (publicKey == null) invalidAuthException.addDetailedMessage("No public key provided"); else if (!publicKeySecretKey.containsKey(publicKey)) - invalidAuthException.addDetailedMessage("The public key '" + publicKey + "' isn't associated " + - "with a secret key."); + invalidAuthException.addDetailedMessage("The public key '" + publicKey + "' isn't associated " + + "with a secret key."); if (secretKey == null) invalidAuthException.addDetailedMessage("No secret key provided"); if (secretKey != null && publicKey != null && !secretKey.equals(publicKeySecretKey.get(publicKey))) - invalidAuthException.addDetailedMessage("The secret key '" + secretKey + "' " + - "isn't valid for '" + publicKey + "'"); + invalidAuthException.addDetailedMessage("The secret key '" + secretKey + "' " + + "isn't valid for '" + publicKey + "'"); } /** @@ -130,8 +130,8 @@ private void validateProject(String publicKey, String projectId, InvalidAuthExce invalidAuthException.addDetailedMessage("No project ID provided"); if (projectId != null && publicKey != null && !projectId.equals(publicKeyProjectId.get(publicKey))) - invalidAuthException.addDetailedMessage("The project '" + projectId + "' " + - "can't be accessed by ' " + publicKey + " '"); + invalidAuthException.addDetailedMessage("The project '" + projectId + "' " + + "can't be accessed by ' " + publicKey + " '"); } /** From ff4a03fb7686f52f8ebf55c389e75f04c9584350 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 18:17:21 +0100 Subject: [PATCH 0345/2152] Create unmarshaller to extract an event from a stream --- .../raven/sentrystub/unmarshaller/Unmarshaller.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/Unmarshaller.java diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/Unmarshaller.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/Unmarshaller.java new file mode 100644 index 00000000000..882fc71668a --- /dev/null +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/Unmarshaller.java @@ -0,0 +1,9 @@ +package net.kencochrane.raven.sentrystub.unmarshaller; + +import net.kencochrane.raven.sentrystub.event.Event; + +import java.io.InputStream; + +public interface Unmarshaller { + Event unmarshall(InputStream source); +} From 7e1e2d92c7f0607491681a22d8aa126e6224e5cf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 18:18:37 +0100 Subject: [PATCH 0346/2152] Create a JsonUnmarshaller to handle JSON --- .../raven/sentrystub/SentryHttpServlet.java | 16 +++++------ .../{ => unmarshaller}/JsonDecoder.java | 2 +- .../unmarshaller/JsonUnmarshaller.java | 27 +++++++++++++++++++ 3 files changed, 34 insertions(+), 11 deletions(-) rename sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/{ => unmarshaller}/JsonDecoder.java (98%) create mode 100644 sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonUnmarshaller.java diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java index ac03fd1a0c1..325ffaccbee 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java @@ -1,7 +1,8 @@ package net.kencochrane.raven.sentrystub; -import com.fasterxml.jackson.databind.ObjectMapper; import net.kencochrane.raven.sentrystub.event.Event; +import net.kencochrane.raven.sentrystub.unmarshaller.JsonUnmarshaller; +import net.kencochrane.raven.sentrystub.unmarshaller.Unmarshaller; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; @@ -9,20 +10,15 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.io.InputStream; @WebServlet(name = "SentryHttpServlet", displayName = "SentryHttpServlet", urlPatterns = "/api/*") public class SentryHttpServlet extends HttpServlet { - private JsonDecoder jsonDecoder = new JsonDecoder(); - private ObjectMapper om = new ObjectMapper(); - - public SentryHttpServlet() { - super(); - } + //TODO: Hardcoded now, but later it could be enhanced. + private Unmarshaller unmarshaller = new JsonUnmarshaller(); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - InputStream jsonStream = jsonDecoder.decapsulateContent(req.getInputStream()); - Event e = om.readValue(jsonStream, Event.class); + Event event = unmarshaller.unmarshall(req.getInputStream()); + //TODO: validate event } } diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java similarity index 98% rename from sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java rename to sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java index ff3f05d07eb..d8fb9920bd8 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/JsonDecoder.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven.sentrystub; +package net.kencochrane.raven.sentrystub.unmarshaller; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonUnmarshaller.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonUnmarshaller.java new file mode 100644 index 00000000000..6f03ee0e9f1 --- /dev/null +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonUnmarshaller.java @@ -0,0 +1,27 @@ +package net.kencochrane.raven.sentrystub.unmarshaller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import net.kencochrane.raven.sentrystub.event.Event; + +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class JsonUnmarshaller implements Unmarshaller { + private static final Logger logger = Logger.getLogger(JsonUnmarshaller.class.getCanonicalName()); + private JsonDecoder jsonDecoder = new JsonDecoder(); + private ObjectMapper om = new ObjectMapper(); + + @Override + public Event unmarshall(InputStream source) { + Event event = null; + try { + InputStream jsonStream = jsonDecoder.decapsulateContent(source); + event = om.readValue(jsonStream, Event.class); + } catch (IOException e) { + logger.log(Level.WARNING, "Couldn't parse some JSON content.", e); + } + return event; + } +} From 84cdb774b0118d80f672cc5945241db2f11e3614 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 18:19:29 +0100 Subject: [PATCH 0347/2152] Allow AuthValidator to read its configuration --- .../raven/sentrystub/auth/AuthValidator.java | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java index 43e345128ee..79ea0a36aeb 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java @@ -1,9 +1,9 @@ package net.kencochrane.raven.sentrystub.auth; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.io.IOException; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Validate a Sentry auth Header (in HTTP {@code X-Sentry-Auth}). @@ -13,6 +13,7 @@ *

    */ public class AuthValidator { + private static final Logger logger = Logger.getLogger(AuthValidator.class.getCanonicalName()); private static final Collection SENTRY_PROTOCOL_VERSIONS = Arrays.asList("3"); private static final String SENTRY_VERSION_PARAMETER = "Sentry sentry_version"; private static final String PUBLIC_KEY_PARAMETER = "sentry_key"; @@ -147,4 +148,21 @@ private void validateClient(String client, InvalidAuthException invalidAuthExcep if (client == null) invalidAuthException.addDetailedMessage("The client name is mandatory."); } + + public void loadSentryUsers(String resourceName) { + Properties sentryProperties = new Properties(); + try { + sentryProperties.load(AuthValidator.class.getResourceAsStream(resourceName)); + int userCount = Integer.parseInt(sentryProperties.getProperty("sentry.user.count", "0")); + for (int i = 1; i <= userCount; i++) { + String publicKey = sentryProperties.getProperty("sentry.user." + i + ".publicKey"); + String secretKey = sentryProperties.getProperty("sentry.user." + i + ".secretKey"); + String projectId = sentryProperties.getProperty("sentry.user." + i + ".projectId"); + addUser(publicKey, secretKey, projectId); + } + } catch (IOException e) { + logger.log(Level.SEVERE, "Couldn't load the sentry.properties file", e); + } + } + } From 2916245502af536c086945a5ceaf87d980bdbfa1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 18:20:27 +0100 Subject: [PATCH 0348/2152] Load auth configuration from the Filter --- .../raven/sentrystub/SentryAuthenticationFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java index 719e4e3b142..c89cb7d342e 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java @@ -21,7 +21,7 @@ public class SentryAuthenticationFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { authValidator = new AuthValidator(); - //TODO: Load filter config to add users! + authValidator.loadSentryUsers("/sentry.properties"); } @Override From e71b7a79f6d2119c739c29ec269d3b4edd7562c8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 18:21:09 +0100 Subject: [PATCH 0349/2152] Add an example of sentry configuration --- .../net/kencochrane/raven/sentrystub/sentry.properties | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 sentry-stub/src/main/resources/net/kencochrane/raven/sentrystub/sentry.properties diff --git a/sentry-stub/src/main/resources/net/kencochrane/raven/sentrystub/sentry.properties b/sentry-stub/src/main/resources/net/kencochrane/raven/sentrystub/sentry.properties new file mode 100644 index 00000000000..419280300de --- /dev/null +++ b/sentry-stub/src/main/resources/net/kencochrane/raven/sentrystub/sentry.properties @@ -0,0 +1,5 @@ +sentry.user.count=1 + +sentry.user.1.publicKey=8292bf61d620417282e68a72ae03154a +sentry.user.1.secretKey=e3908e05ad874b24b7a168992bfa3577 +sentry.user.1.projectId=1 From 992f29bcd56d4b6dcaf0c83b137e2870a91829d8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 18:21:48 +0100 Subject: [PATCH 0350/2152] Do not use magic numbers --- .../raven/sentrystub/SentryUdpContextListener.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java index 54a907fd396..4c7253a3543 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java @@ -60,10 +60,11 @@ public void run() { private class UdpListenerThread extends Thread { @Override public void run() { + // We'll assume that no-one sends a > 65KB datagram (max size allowed on IPV4). + final int datagramPacketSize = 65536; while (!udpSocket.isClosed()) { try { - // We'll assume that no-one sends a > 65KB datagram (max size allowed on IPV4). - byte[] buffer = new byte[1 << 16]; + byte[] buffer = new byte[datagramPacketSize]; DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length); udpSocket.receive(datagramPacket); executorService.execute(new UdpRequestHandler(datagramPacket)); From d0a112bb32b890894dd177a7f0435f6df36db4cc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 18:22:53 +0100 Subject: [PATCH 0351/2152] Log exceptions properly --- .../raven/sentrystub/SentryUdpContextListener.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java index 4c7253a3543..41195caff86 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java @@ -9,9 +9,12 @@ import java.net.SocketException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.logging.Level; +import java.util.logging.Logger; @WebListener public class SentryUdpContextListener implements ServletContextListener { + private static final Logger logger = Logger.getLogger(SentryUdpContextListener.class.getCanonicalName()); private static final int DEFAULT_SENTRY_UDP_PORT = 9001; private static final String SENTRY_UDP_PORT_PARAMETER = "sentryUdpPort"; private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); @@ -69,7 +72,7 @@ public void run() { udpSocket.receive(datagramPacket); executorService.execute(new UdpRequestHandler(datagramPacket)); } catch (Exception e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + logger.log(Level.WARNING, "An exception occurred during the reception of a UDP packet.", e); } } } From a4e826ae1aa11a4f5a1d273b39027e62941b1179 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 18:24:05 +0100 Subject: [PATCH 0352/2152] Configure an AuthValidator --- .../raven/sentrystub/SentryUdpContextListener.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java index 41195caff86..c622c765e4f 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java @@ -1,5 +1,6 @@ package net.kencochrane.raven.sentrystub; +import net.kencochrane.raven.sentrystub.auth.AuthValidator; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; @@ -19,6 +20,7 @@ public class SentryUdpContextListener implements ServletContextListener { private static final String SENTRY_UDP_PORT_PARAMETER = "sentryUdpPort"; private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); private DatagramSocket udpSocket; + private AuthValidator authValidator; @Override public void contextInitialized(ServletContextEvent sce) { @@ -26,6 +28,8 @@ public void contextInitialized(ServletContextEvent sce) { int port = DEFAULT_SENTRY_UDP_PORT; if (sentryUdpPortParameter != null) port = Integer.parseInt(sentryUdpPortParameter); + authValidator = new AuthValidator(); + authValidator.loadSentryUsers("/sentry.properties"); try { udpSocket = new DatagramSocket(port); new UdpListenerThread().start(); From a7ce5ef3ab9b0a30f8a09170c70c58d72305ca90 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 18:24:31 +0100 Subject: [PATCH 0353/2152] Create a new method to start the UDP socket --- .../raven/sentrystub/SentryUdpContextListener.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java index c622c765e4f..58c19892d1a 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java @@ -25,11 +25,15 @@ public class SentryUdpContextListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { String sentryUdpPortParameter = sce.getServletContext().getInitParameter(SENTRY_UDP_PORT_PARAMETER); - int port = DEFAULT_SENTRY_UDP_PORT; - if (sentryUdpPortParameter != null) - port = Integer.parseInt(sentryUdpPortParameter); + startUdpSocket(sentryUdpPortParameter != null + ? Integer.parseInt(sentryUdpPortParameter) + : DEFAULT_SENTRY_UDP_PORT); + authValidator = new AuthValidator(); authValidator.loadSentryUsers("/sentry.properties"); + } + + private void startUdpSocket(int port) { try { udpSocket = new DatagramSocket(port); new UdpListenerThread().start(); From d8f9bfd1b4dad5cbe998db1c3839000b077ff07c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 18:26:48 +0100 Subject: [PATCH 0354/2152] Extract the auth header from a UDP packet --- .../sentrystub/SentryUdpContextListener.java | 53 ++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java index 58c19892d1a..34a4d6c2000 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java @@ -1,13 +1,18 @@ package net.kencochrane.raven.sentrystub; import net.kencochrane.raven.sentrystub.auth.AuthValidator; +import net.kencochrane.raven.sentrystub.auth.InvalidAuthException; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Level; @@ -57,14 +62,50 @@ private UdpRequestHandler(DatagramPacket datagramPacket) { @Override public void run() { + InputStream bais = new ByteArrayInputStream(datagramPacket.getData(), + datagramPacket.getOffset(), + datagramPacket.getLength()); + validateAuthHeader(bais); + } + + /** + * Extracts the Auth Header from a binary stream and leave the stream as is once the header is parsed. + * + * @param inputStream + */ + private void validateAuthHeader(InputStream inputStream) { try { - ByteArrayInputStream bais = new ByteArrayInputStream(datagramPacket.getData(), - datagramPacket.getOffset(), - datagramPacket.getLength()); - //TODO extract the AUTH header validate it, send the rest to the JSon validator - } catch (Exception e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + Map authHeader = parseAuthHeader(inputStream); + authValidator.validateSentryAuth(authHeader); + } catch (IOException e) { + throw new InvalidAuthException("Impossible to extract the auth header from an UDP packet", e); + } + } + + private Map parseAuthHeader(InputStream inputStream) throws IOException { + Map authHeader = new HashMap(); + + int i; + StringBuilder sb = new StringBuilder(); + String key = null; + while ((i = inputStream.read()) >= 0) { + if (i == '\n') { + authHeader.put(key, sb.toString().trim()); + //Assume it's the double \n, parse the next once + inputStream.read(); + break; + } else if (i == '=' && key == null) { + key = sb.toString().trim(); + sb = new StringBuilder(); + } else if (i == ',' && key != null) { + authHeader.put(key, sb.toString().trim()); + sb = new StringBuilder(); + key = null; + } else { + sb.append((char) i); + } } + return authHeader; } } From 089fcf23c713f1028ed67b507956195a92697a76 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 18:27:27 +0100 Subject: [PATCH 0355/2152] Extract an event from the UDP packet --- .../raven/sentrystub/SentryUdpContextListener.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java index 34a4d6c2000..20660d7e505 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java @@ -2,6 +2,10 @@ import net.kencochrane.raven.sentrystub.auth.AuthValidator; import net.kencochrane.raven.sentrystub.auth.InvalidAuthException; +import net.kencochrane.raven.sentrystub.event.Event; +import net.kencochrane.raven.sentrystub.unmarshaller.JsonUnmarshaller; +import net.kencochrane.raven.sentrystub.unmarshaller.Unmarshaller; + import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; @@ -26,6 +30,8 @@ public class SentryUdpContextListener implements ServletContextListener { private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); private DatagramSocket udpSocket; private AuthValidator authValidator; + //TODO: Hardcoded now, but later it could be enhanced. + private Unmarshaller unmarshaller = new JsonUnmarshaller(); @Override public void contextInitialized(ServletContextEvent sce) { @@ -66,6 +72,8 @@ public void run() { datagramPacket.getOffset(), datagramPacket.getLength()); validateAuthHeader(bais); + Event event = unmarshaller.unmarshall(bais); + //TODO: validate event } /** From 64e48e73edb4f7fca37582f42d222cf777409347 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 18:28:08 +0100 Subject: [PATCH 0356/2152] Fix indentation --- .../net/kencochrane/raven/sentrystub/auth/AuthValidator.java | 2 +- .../kencochrane/raven/sentrystub/auth/InvalidAuthException.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java index 79ea0a36aeb..06813ce051a 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java @@ -141,7 +141,7 @@ private void validateProject(String publicKey, String projectId, InvalidAuthExce * The client should always be provided. *

    * - * @param client string identifying a client type (such as Java/3.0) + * @param client string identifying a client type (such as Java/3.0) * @param invalidAuthException exception thrown if the auth header is invalid. */ private void validateClient(String client, InvalidAuthException invalidAuthException) { diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/InvalidAuthException.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/InvalidAuthException.java index 06238acb32c..42178aa83cc 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/InvalidAuthException.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/InvalidAuthException.java @@ -30,7 +30,7 @@ public void addDetailedMessage(String message) { detailedMessages.add(message); } - public boolean isEmpty(){ + public boolean isEmpty() { return detailedMessages.isEmpty(); } } From a7de22cfd53adede2f3618eea5fc02d02151277f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 18:28:20 +0100 Subject: [PATCH 0357/2152] Avoid too broad catches --- .../kencochrane/raven/sentrystub/SentryUdpContextListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java index 20660d7e505..9ef0939be3f 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java @@ -128,7 +128,7 @@ public void run() { DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length); udpSocket.receive(datagramPacket); executorService.execute(new UdpRequestHandler(datagramPacket)); - } catch (Exception e) { + } catch (IOException e) { logger.log(Level.WARNING, "An exception occurred during the reception of a UDP packet.", e); } } From c0c24f2f5976154937d5f74e648e1b2ac668dadf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 18:28:35 +0100 Subject: [PATCH 0358/2152] Make the UdpRequestHandler non-static final --- .../kencochrane/raven/sentrystub/SentryUdpContextListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java index 9ef0939be3f..20b647c9a26 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java @@ -59,7 +59,7 @@ public void contextDestroyed(ServletContextEvent sce) { udpSocket.close(); } - private static class UdpRequestHandler implements Runnable { + private final class UdpRequestHandler implements Runnable { private final DatagramPacket datagramPacket; private UdpRequestHandler(DatagramPacket datagramPacket) { From 0a7dbfc82cf2a0d9b0cc84deae83ce20e1288da4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 18:28:56 +0100 Subject: [PATCH 0359/2152] Add some quick documentation --- .../raven/sentrystub/SentryAuthenticationFilter.java | 1 + .../raven/sentrystub/SentryUdpContextListener.java | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java index c89cb7d342e..d3b6999daf4 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java @@ -39,6 +39,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha private boolean validateAuth(HttpServletRequest req, HttpServletResponse resp) throws IOException { Map sentryAuthDetails = extractSentryAuthDetails(req); + //Can throw an exception, but a request which doesn't provide a project ID should fail anyway String projectId = req.getPathInfo().substring(1, req.getPathInfo().indexOf('/', 1)); try { diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java index 20b647c9a26..39b591929c8 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java @@ -22,6 +22,12 @@ import java.util.logging.Level; import java.util.logging.Logger; +/** + * ContextListener stating an UDP socket when the servlet container starts. + *

    + * This listener allows a {@link DatagramSocket} to be started to listen to UDP requests. + *

    + */ @WebListener public class SentryUdpContextListener implements ServletContextListener { private static final Logger logger = Logger.getLogger(SentryUdpContextListener.class.getCanonicalName()); From edf76d032277a25d61ffdf22946068fae80c2e35 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 18:29:12 +0100 Subject: [PATCH 0360/2152] Remove unused configuration, set scanInterval to 10 --- sentry-stub/pom.xml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 6331adec35b..3fa5c0d3423 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -40,10 +40,7 @@ jetty-maven-plugin 9.0.1.v20130408 - 1 - - /sentry - + 10 From f33f62f31340bfb9959ee375620707c4a8884985 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 21:39:16 +0100 Subject: [PATCH 0361/2152] Use sentry_config to load the configuration --- .../raven/sentrystub/SentryAuthenticationFilter.java | 2 +- .../raven/sentrystub/SentryUdpContextListener.java | 3 ++- sentry-stub/src/main/webapp/WEB-INF/web.xml | 4 ++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java index d3b6999daf4..90accb1ae80 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java @@ -21,7 +21,7 @@ public class SentryAuthenticationFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { authValidator = new AuthValidator(); - authValidator.loadSentryUsers("/sentry.properties"); + authValidator.loadSentryUsers(filterConfig.getServletContext().getInitParameter("sentry_config")); } @Override diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java index 39b591929c8..9f32b550c91 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java @@ -41,13 +41,14 @@ public class SentryUdpContextListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { + String sentyConfiguration = sce.getServletContext().getInitParameter("sentry_config"); String sentryUdpPortParameter = sce.getServletContext().getInitParameter(SENTRY_UDP_PORT_PARAMETER); startUdpSocket(sentryUdpPortParameter != null ? Integer.parseInt(sentryUdpPortParameter) : DEFAULT_SENTRY_UDP_PORT); authValidator = new AuthValidator(); - authValidator.loadSentryUsers("/sentry.properties"); + authValidator.loadSentryUsers(sentyConfiguration); } private void startUdpSocket(int port) { diff --git a/sentry-stub/src/main/webapp/WEB-INF/web.xml b/sentry-stub/src/main/webapp/WEB-INF/web.xml index b9436cdd146..a4425f32aab 100644 --- a/sentry-stub/src/main/webapp/WEB-INF/web.xml +++ b/sentry-stub/src/main/webapp/WEB-INF/web.xml @@ -2,4 +2,8 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> + + sentry_config + /net/kencochrane/raven/sentrystub/sentry.properties + From e1534083f206e11c9b8a896f56c22533b096d813 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 21:40:07 +0100 Subject: [PATCH 0362/2152] Catch any exception from the config loader --- .../net/kencochrane/raven/sentrystub/auth/AuthValidator.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java index 06813ce051a..8c863ba2071 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java @@ -1,6 +1,5 @@ package net.kencochrane.raven.sentrystub.auth; -import java.io.IOException; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; @@ -160,7 +159,7 @@ public void loadSentryUsers(String resourceName) { String projectId = sentryProperties.getProperty("sentry.user." + i + ".projectId"); addUser(publicKey, secretKey, projectId); } - } catch (IOException e) { + } catch (Exception e) { logger.log(Level.SEVERE, "Couldn't load the sentry.properties file", e); } } From 5c1ebefe6e66dcd0e81e2bda2060eb47891b0c39 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 21:49:57 +0100 Subject: [PATCH 0363/2152] Replace invalid while loop with a do while At the beginning there is no current token, start by getting the next token. --- .../raven/sentrystub/unmarshaller/JsonDecoder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java index d8fb9920bd8..890f96511ce 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java @@ -70,9 +70,9 @@ private boolean isJson(InputStream inputStream) { boolean valid = false; try { final JsonParser parser = new JsonFactory().createJsonParser(inputStream); - while (parser.hasCurrentToken()) { + do { parser.nextToken(); - } + } while (parser.hasCurrentToken()); valid = true; } catch (Exception e) { logger.log(Level.FINE, "An exception occurred while trying to parse an allegedly JSON document", e); From 2cfc0143cd09163a0034aeb7536e8064a809cf6a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 11:15:58 +0100 Subject: [PATCH 0364/2152] Handle basic interfaces while parsing an event --- .../kencochrane/raven/sentrystub/event/Event.java | 11 +++++++++-- .../event/interfaces/ExceptionInterface.java | 12 ++++++++++++ .../event/interfaces/MessageInterface.java | 8 ++++++++ .../event/interfaces/StackTraceInterface.java | 4 ++++ 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/ExceptionInterface.java create mode 100644 sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/MessageInterface.java create mode 100644 sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/StackTraceInterface.java diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java index 9b950c845e5..d4b5abf2463 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java @@ -1,6 +1,9 @@ package net.kencochrane.raven.sentrystub.event; import com.fasterxml.jackson.annotation.JsonProperty; +import net.kencochrane.raven.sentrystub.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.sentrystub.event.interfaces.MessageInterface; +import net.kencochrane.raven.sentrystub.event.interfaces.StackTraceInterface; import java.util.Date; import java.util.Map; @@ -30,6 +33,10 @@ public class Event { private Map modules; @JsonProperty(value = "extra") private Map extras; -// @JsonProperty(value = "sentry.interfaces.Message") -// private Map messageInterface; + @JsonProperty(value = "sentry.interfaces.Message") + private MessageInterface messageInterface; + @JsonProperty(value = "sentry.interfaces.Exception") + private ExceptionInterface exceptionInterface; + @JsonProperty(value = "sentry.interfaces.StackTrace") + private StackTraceInterface stackTraceInterface; } diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/ExceptionInterface.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/ExceptionInterface.java new file mode 100644 index 00000000000..6e6c27df43a --- /dev/null +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/ExceptionInterface.java @@ -0,0 +1,12 @@ +package net.kencochrane.raven.sentrystub.event.interfaces; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ExceptionInterface { + @JsonProperty(value = "type") + private String type; + @JsonProperty(value = "value") + private String value; + @JsonProperty(value = "module") + private String module; +} diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/MessageInterface.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/MessageInterface.java new file mode 100644 index 00000000000..699d3e930ea --- /dev/null +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/MessageInterface.java @@ -0,0 +1,8 @@ +package net.kencochrane.raven.sentrystub.event.interfaces; + +import java.util.List; + +public class MessageInterface { + private String message; + private List params; +} diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/StackTraceInterface.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/StackTraceInterface.java new file mode 100644 index 00000000000..bd5323f5f39 --- /dev/null +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/StackTraceInterface.java @@ -0,0 +1,4 @@ +package net.kencochrane.raven.sentrystub.event.interfaces; + +public class StackTraceInterface { +} From 65d11e8be010713698550f882f831698fefa83d1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 11:19:12 +0100 Subject: [PATCH 0365/2152] StackTrace now only handle the stacktrace --- .../event/interfaces/StackTraceInterface.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java index 0abdb2d8246..61e324d6e8a 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java @@ -1,14 +1,13 @@ package net.kencochrane.raven.event.interfaces; +import java.util.Arrays; + public class StackTraceInterface implements SentryInterface { public static final String STACKTRACE_INTERFACE = "sentry.interfaces.Stacktrace"; - private final ImmutableThrowable throwable; + private final StackTraceElement[] stackTrace; - //TODO: Base this interface on a unique stacktrace (rather than an entire exception) - //This should be done when the exception system in Sentry will be improved to support chained exception - //For now, a fake stacktrace (containing the parent exceptions and their stacktraces) will be used. - public StackTraceInterface(Throwable throwable) { - this.throwable = new ImmutableThrowable(throwable); + public StackTraceInterface(StackTraceElement[] stackTrace) { + this.stackTrace = Arrays.copyOf(stackTrace, stackTrace.length); } @Override @@ -16,7 +15,7 @@ public String getInterfaceName() { return STACKTRACE_INTERFACE; } - public ImmutableThrowable getThrowable() { - return throwable; + public StackTraceElement[] getStackTrace() { + return Arrays.copyOf(stackTrace, stackTrace.length); } } From fc240aa7ab1d1abdcb7626fa890ac7ac80848f97 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 11:24:09 +0100 Subject: [PATCH 0366/2152] Marshall exceptions with cause and stacktrace --- .../json/ExceptionInterfaceBinding.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java index 386d2f10e9f..a0373445a8c 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.ImmutableThrowable; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; import java.io.IOException; @@ -10,15 +11,25 @@ public class ExceptionInterfaceBinding implements InterfaceBinding Date: Tue, 16 Apr 2013 11:28:24 +0100 Subject: [PATCH 0367/2152] Rewrite the stacktrace marshaller --- .../json/StackTraceInterfaceBinding.java | 41 +++---------------- 1 file changed, 5 insertions(+), 36 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java index 74a1ebd46c0..e0908bc88bf 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java @@ -5,9 +5,7 @@ import net.kencochrane.raven.event.interfaces.StackTraceInterface; import java.io.IOException; -import java.util.Deque; import java.util.HashSet; -import java.util.LinkedList; import java.util.Set; public class StackTraceInterfaceBinding implements InterfaceBinding { @@ -40,22 +38,6 @@ public StackTraceInterfaceBinding(Set notInAppFrames) { this.notInAppFrames = new HashSet(notInAppFrames); } - /** - * Writes a fake frame to allow chained exceptions. - * - * @param throwable Exception for which a fake frame should be created - */ - private void writeFakeFrame(JsonGenerator generator, ImmutableThrowable throwable) throws IOException { - String message = "Caused by: " + throwable.getActualClass().getName(); - if (throwable.getMessage() != null) - message += " (\"" + throwable.getMessage() + "\")"; - - generator.writeStartObject(); - generator.writeStringField(MODULE_PARAMETER, message); - generator.writeBooleanField(IN_APP_PARAMETER, true); - generator.writeEndObject(); - } - /** * Writes a single frame based on a {@code StackTraceElement}. * @@ -84,29 +66,16 @@ private boolean isFrameInApp(StackTraceElement stackTraceElement) { @Override public void writeInterface(JsonGenerator generator, StackTraceInterface stackTraceInterface) throws IOException { - ImmutableThrowable currentThrowable = stackTraceInterface.getThrowable(); - Deque throwableStack = new LinkedList(); - - //Inverse the chain of exceptions to get the first exception thrown first. - while (currentThrowable != null) { - throwableStack.push(currentThrowable); - currentThrowable = currentThrowable.getCause(); - } + StackTraceElement[] stackTrace = stackTraceInterface.getStackTrace(); generator.writeStartObject(); generator.writeArrayFieldStart(FRAMES_PARAMETER); - while (!throwableStack.isEmpty()) { - currentThrowable = throwableStack.pop(); - StackTraceElement[] stackFrames = currentThrowable.getStackTrace(); - // Go through the stackTrace frames from the first call to the last - for (int i = currentThrowable.getStackTrace().length - 1; i >= 0; i--) { - writeFrame(generator, stackFrames[i]); - } - - if (!throwableStack.isEmpty()) - writeFakeFrame(generator, currentThrowable); + // Go through the stackTrace frames from the first call to the last + for (int i = stackTrace.length - 1; i >= 0; i--) { + writeFrame(generator, stackTrace[i]); } + generator.writeEndArray(); generator.writeEndObject(); } From 716f5bc54f78b98ed8ccc3fe3fb58a84c87147a7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 11:39:25 +0100 Subject: [PATCH 0368/2152] Stop adding the stacktrace manually --- raven-legacy/src/main/java/net/kencochrane/raven/Client.java | 4 ---- .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 3 +-- .../java/net/kencochrane/raven/log4j2/SentryAppender.java | 3 +-- .../java/net/kencochrane/raven/logback/SentryAppender.java | 3 +-- .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 3 +-- 5 files changed, 4 insertions(+), 12 deletions(-) diff --git a/raven-legacy/src/main/java/net/kencochrane/raven/Client.java b/raven-legacy/src/main/java/net/kencochrane/raven/Client.java index 9ed680c22c7..b62232b2324 100644 --- a/raven-legacy/src/main/java/net/kencochrane/raven/Client.java +++ b/raven-legacy/src/main/java/net/kencochrane/raven/Client.java @@ -228,7 +228,6 @@ public String captureMessage(String message, Long timestamp, String loggerClass, public String captureException(Throwable exception) { EventBuilder eventBuilder = new EventBuilder() .setMessage(exception.getMessage()) - .addSentryInterface(new StackTraceInterface(exception)) .addSentryInterface(new ExceptionInterface(exception)); return buildAndSendEvent(eventBuilder); } @@ -237,7 +236,6 @@ public String captureException(Throwable exception) { public String captureException(Throwable exception, Map tags) { EventBuilder eventBuilder = new EventBuilder() .setMessage(exception.getMessage()) - .addSentryInterface(new StackTraceInterface(exception)) .addSentryInterface(new ExceptionInterface(exception)); for (Map.Entry tag : tags.entrySet()) { eventBuilder.addTag(tag.getKey(), tag.getValue().toString()); @@ -253,7 +251,6 @@ public String captureException(String logMessage, long timestamp, String loggerN .setLogger(loggerName) .setLevel(convertLevel(logLevel)) .setCulprit(culprit) - .addSentryInterface(new StackTraceInterface(exception)) .addSentryInterface(new ExceptionInterface(exception)); return buildAndSendEvent(eventBuilder); } @@ -266,7 +263,6 @@ public String captureException(String logMessage, long timestamp, String loggerN .setLogger(loggerName) .setLevel(convertLevel(logLevel)) .setCulprit(culprit) - .addSentryInterface(new StackTraceInterface(exception)) .addSentryInterface(new ExceptionInterface(exception)); for (Map.Entry tag : tags.entrySet()) { eventBuilder.addTag(tag.getKey(), tag.getValue().toString()); diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 947ffaead94..1be529d35cd 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -57,8 +57,7 @@ protected void append(LoggingEvent loggingEvent) { if (loggingEvent.getThrowableInformation() != null) { Throwable throwable = loggingEvent.getThrowableInformation().getThrowable(); - eventBuilder.addSentryInterface(new ExceptionInterface(throwable)) - .addSentryInterface(new StackTraceInterface(throwable)); + eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); eventBuilder.setCulprit(throwable); } else { // When it's a message try to rely on the position of the log (the same message can be logged from diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 099bdceef22..a4fa0625b73 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -115,8 +115,7 @@ public void append(LogEvent event) { if (event.getThrown() != null) { Throwable throwable = event.getThrown(); - eventBuilder.addSentryInterface(new ExceptionInterface(throwable)) - .addSentryInterface(new StackTraceInterface(throwable)); + eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); eventBuilder.setCulprit(throwable); } else { // When it's a message try to rely on the position of the log (the same message can be logged from diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 54ece35eaca..9391edc5e43 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -65,8 +65,7 @@ protected void append(ILoggingEvent iLoggingEvent) { if (iLoggingEvent.getThrowableProxy() != null) { Throwable throwable = ((ThrowableProxy) iLoggingEvent.getThrowableProxy()).getThrowable(); - eventBuilder.addSentryInterface(new ExceptionInterface(throwable)) - .addSentryInterface(new StackTraceInterface(throwable)); + eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); } else { if (iLoggingEvent.getArgumentArray() != null) eventBuilder.addSentryInterface(new MessageInterface(iLoggingEvent.getMessage(), diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 53c0c39e2a8..90048401d1a 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -63,8 +63,7 @@ public void publish(LogRecord record) { .setLogger(record.getLoggerName()) .setCulprit(record.getSourceClassName() + "." + record.getSourceMethodName() + "()"); if (record.getThrown() != null) { - eventBuilder.addSentryInterface(new ExceptionInterface(record.getThrown())) - .addSentryInterface(new StackTraceInterface(record.getThrown())); + eventBuilder.addSentryInterface(new ExceptionInterface(record.getThrown())); } if (record.getParameters() != null) From e0fabcaa6b31bbf72894d260c4eb48edd1fb4382 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 11:42:13 +0100 Subject: [PATCH 0369/2152] Update TestStackTraceInterfaceBinding --- .../raven/marshaller/json/TestStackTraceInterfaceBinding.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java index a040e85c207..5e1ca3d38a4 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java @@ -34,10 +34,8 @@ public void testSingleStackFrame() throws Exception { String methodName = UUID.randomUUID().toString(); String className = UUID.randomUUID().toString(); int lineNumber = 1; - Throwable exception = mock(Throwable.class); StackTraceElement stackTraceElement = new StackTraceElement(className, methodName, null, lineNumber); - when(mockStackTraceInterface.getThrowable()).thenReturn(new ImmutableThrowable(exception)); - when(exception.getStackTrace()).thenReturn(new StackTraceElement[]{stackTraceElement}); + when(mockStackTraceInterface.getStackTrace()).thenReturn(new StackTraceElement[]{stackTraceElement}); JsonGenerator jSonGenerator = getJsonGenerator(); interfaceBinding.writeInterface(jSonGenerator, mockStackTraceInterface); From 5f33d5cc95dc1bbba861f1df913fc875122f3f48 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 11:42:53 +0100 Subject: [PATCH 0370/2152] Update TestExceptionInterfaceBinding It nows return an array of exceptions containing stacktraces --- .../marshaller/json/TestExceptionInterfaceBinding.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java index 39b2b343924..21292fc8345 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java @@ -39,8 +39,11 @@ public void testSimpleException() throws Exception { jsonGenerator.close(); JsonNode rootNode = getMapper().readValue(getJsonParser(), JsonNode.class); - assertThat(rootNode.get("module").asText(), is(throwable.getClass().getPackage().getName())); - assertThat(rootNode.get("type").asText(), is(throwable.getClass().getSimpleName())); - assertThat(rootNode.get("value").asText(), is(message)); + assertThat(rootNode.isArray(), is(true)); + JsonNode exceptionNode = rootNode.get(0); + assertThat(exceptionNode.get("module").asText(), is(throwable.getClass().getPackage().getName())); + assertThat(exceptionNode.get("type").asText(), is(throwable.getClass().getSimpleName())); + assertThat(exceptionNode.get("value").asText(), is(message)); + assertThat(exceptionNode.get("stacktrace").isObject(), is(true)); } } From 75ee109c82a57749574788d8a33cd55103c5d882 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 11:43:19 +0100 Subject: [PATCH 0371/2152] Handle the aliases for interfaces in the stub server --- .../net/kencochrane/raven/sentrystub/event/Event.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java index d4b5abf2463..b19738dc804 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java @@ -39,4 +39,14 @@ public class Event { private ExceptionInterface exceptionInterface; @JsonProperty(value = "sentry.interfaces.StackTrace") private StackTraceInterface stackTraceInterface; + + @JsonProperty(value = "exception") + public void setExceptionInterface(ExceptionInterface exceptionInterface) { + this.exceptionInterface = exceptionInterface; + } + + @JsonProperty(value = "stacktrace") + public void setStackTraceInterface(StackTraceInterface stackTraceInterface) { + this.stackTraceInterface = stackTraceInterface; + } } From a4d56099bf203c895e9f51e0261f8459abed8b4b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 11:45:21 +0100 Subject: [PATCH 0372/2152] Increment the version to 4.0 --- pom.xml | 2 +- raven-legacy/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 8a60c3fb865..7d94ebe8b3b 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ net.kencochrane.raven raven-all - 3.0-SNAPSHOT + 4.0-SNAPSHOT pom Raven-Java Sentry client and appenders for diverse logging frameworks. diff --git a/raven-legacy/pom.xml b/raven-legacy/pom.xml index d0d70d15add..9a44208427f 100644 --- a/raven-legacy/pom.xml +++ b/raven-legacy/pom.xml @@ -7,7 +7,7 @@ net.kencochrane.raven raven-all - 3.0-SNAPSHOT + 4.0-SNAPSHOT raven-legacy jar diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index f366e6cd353..82133e47417 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -7,7 +7,7 @@ net.kencochrane.raven raven-all - 3.0-SNAPSHOT + 4.0-SNAPSHOT raven-log4j jar diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 424c3c135c2..635df8135a3 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -7,7 +7,7 @@ net.kencochrane.raven raven-all - 3.0-SNAPSHOT + 4.0-SNAPSHOT raven-log4j2 jar diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 34a7cbb416a..62fe731043b 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -7,7 +7,7 @@ net.kencochrane.raven raven-all - 3.0-SNAPSHOT + 4.0-SNAPSHOT raven-logback jar diff --git a/raven/pom.xml b/raven/pom.xml index 80779557997..9fb36bb4791 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -7,7 +7,7 @@ net.kencochrane.raven raven-all - 3.0-SNAPSHOT + 4.0-SNAPSHOT raven jar diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 3fa5c0d3423..9cb66052fe8 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -7,7 +7,7 @@ net.kencochrane.raven raven-all - 3.0-SNAPSHOT + 4.0-SNAPSHOT sentry-stub war From 0f7a2d50fde59c2c95e53f734f63bd7f400b8326 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 11:45:57 +0100 Subject: [PATCH 0373/2152] Add the protocol v4 to the supported ones in the stub --- .../net/kencochrane/raven/sentrystub/auth/AuthValidator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java index 8c863ba2071..eb258a56fa0 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java @@ -13,7 +13,7 @@ */ public class AuthValidator { private static final Logger logger = Logger.getLogger(AuthValidator.class.getCanonicalName()); - private static final Collection SENTRY_PROTOCOL_VERSIONS = Arrays.asList("3"); + private static final Collection SENTRY_PROTOCOL_VERSIONS = Arrays.asList("3", "4"); private static final String SENTRY_VERSION_PARAMETER = "Sentry sentry_version"; private static final String PUBLIC_KEY_PARAMETER = "sentry_key"; private static final String SECRET_KEY_PARAMETER = "sentry_secret"; From 0b5477f61ffff9f98b7fb885e4eb9b7a5058423b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 11:46:43 +0100 Subject: [PATCH 0374/2152] Increment raven-Java client to 4.0 --- raven/src/main/java/net/kencochrane/raven/Raven.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index cb32615a0ef..ca68f8f4f72 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -36,7 +36,7 @@ public class Raven { * Version of this client, the major version is the current supported Sentry protocol, the minor version changes * for each release of this project. */ - public static final String NAME = "Raven-Java/3.0"; + public static final String NAME = "Raven-Java/4.0"; private static final Logger logger = Logger.getLogger(Raven.class.getCanonicalName()); private final Set builderHelpers = new HashSet(); private Connection connection; From 230a68f3e3e0ba5b902968aeddbc8a4eac245cc3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 11:46:56 +0100 Subject: [PATCH 0375/2152] Update protocol version to 4 --- .../net/kencochrane/raven/connection/AbstractConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 177e0e31de4..d7ed815d8d2 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -13,7 +13,7 @@ public abstract class AbstractConnection implements Connection { /** * Current sentry protocol version. */ - public static final String SENTRY_PROTOCOL_VERSION = "3"; + public static final String SENTRY_PROTOCOL_VERSION = "4"; private final String publicKey; private final String secretKey; From 48401403e3dc9f64c6f5325badb2683bc513c540 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 11:51:13 +0100 Subject: [PATCH 0376/2152] Handle exception as a list of exceptions --- .../java/net/kencochrane/raven/sentrystub/event/Event.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java index b19738dc804..fc77e11e0c6 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java @@ -6,6 +6,7 @@ import net.kencochrane.raven.sentrystub.event.interfaces.StackTraceInterface; import java.util.Date; +import java.util.List; import java.util.Map; public class Event { @@ -36,13 +37,13 @@ public class Event { @JsonProperty(value = "sentry.interfaces.Message") private MessageInterface messageInterface; @JsonProperty(value = "sentry.interfaces.Exception") - private ExceptionInterface exceptionInterface; + private List exceptionInterfaces; @JsonProperty(value = "sentry.interfaces.StackTrace") private StackTraceInterface stackTraceInterface; @JsonProperty(value = "exception") - public void setExceptionInterface(ExceptionInterface exceptionInterface) { - this.exceptionInterface = exceptionInterface; + public void setExceptionInterfaces(List exceptionInterfaces) { + this.exceptionInterfaces = exceptionInterfaces; } @JsonProperty(value = "stacktrace") From a2460e091638c5f432762714c1a1bdf979f1fe43 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 11:51:24 +0100 Subject: [PATCH 0377/2152] Add a stacktrace element to exception --- .../raven/sentrystub/event/interfaces/ExceptionInterface.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/ExceptionInterface.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/ExceptionInterface.java index 6e6c27df43a..3683de81683 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/ExceptionInterface.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/ExceptionInterface.java @@ -9,4 +9,6 @@ public class ExceptionInterface { private String value; @JsonProperty(value = "module") private String module; + @JsonProperty(value = "stacktrace") + private StackTraceInterface stackTraceInterface; } From 1e8f96035365ea5955fd7a1121c79a8d4999a54c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 12:04:49 +0100 Subject: [PATCH 0378/2152] Remove old test --- .../java/net/kencochrane/raven/AbstractLoggerTest.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java index 094e36ccd55..ef288cb00db 100644 --- a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java @@ -81,14 +81,6 @@ public void testLogException() throws Exception { // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. assertThat(((ExceptionInterface) exceptionInterface).getThrowable(), Matchers.equalTo(exception)); - - - SentryInterface stackTraceInterface = event.getSentryInterfaces().get(StackTraceInterface.STACKTRACE_INTERFACE); - assertThat(stackTraceInterface, instanceOf(StackTraceInterface.class)); - - // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. - // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. - assertThat(((StackTraceInterface) stackTraceInterface).getThrowable(), Matchers.equalTo(exception)); } @Test From a885819677ce23565f8d35e60684594fafe69977 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 12:05:16 +0100 Subject: [PATCH 0379/2152] Support property aliases with multiple setters --- .../kencochrane/raven/sentrystub/event/Event.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java index fc77e11e0c6..06ccd67e2e4 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java @@ -36,9 +36,7 @@ public class Event { private Map extras; @JsonProperty(value = "sentry.interfaces.Message") private MessageInterface messageInterface; - @JsonProperty(value = "sentry.interfaces.Exception") private List exceptionInterfaces; - @JsonProperty(value = "sentry.interfaces.StackTrace") private StackTraceInterface stackTraceInterface; @JsonProperty(value = "exception") @@ -50,4 +48,14 @@ public void setExceptionInterfaces(List exceptionInterfaces) public void setStackTraceInterface(StackTraceInterface stackTraceInterface) { this.stackTraceInterface = stackTraceInterface; } + + @JsonProperty(value = "sentry.interfaces.Exception") + public void setExceptionInterfacesLong(List exceptionInterfaces) { + this.exceptionInterfaces = exceptionInterfaces; + } + + @JsonProperty(value = "sentry.interfaces.StackTrace") + public void setStackTraceInterfaceLong(StackTraceInterface stackTraceInterface) { + this.stackTraceInterface = stackTraceInterface; + } } From 317fef7e7259899d806ef790c43bfe7e6b806810 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 12:06:30 +0100 Subject: [PATCH 0380/2152] Remove unused imports --- raven-legacy/src/main/java/net/kencochrane/raven/Client.java | 1 - .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 1 - .../main/java/net/kencochrane/raven/log4j2/SentryAppender.java | 1 - .../main/java/net/kencochrane/raven/logback/SentryAppender.java | 1 - .../src/main/java/net/kencochrane/raven/jul/SentryHandler.java | 1 - .../raven/marshaller/json/StackTraceInterfaceBinding.java | 1 - .../src/test/java/net/kencochrane/raven/AbstractLoggerTest.java | 1 - .../raven/marshaller/json/TestStackTraceInterfaceBinding.java | 2 -- 8 files changed, 9 deletions(-) diff --git a/raven-legacy/src/main/java/net/kencochrane/raven/Client.java b/raven-legacy/src/main/java/net/kencochrane/raven/Client.java index b62232b2324..b0da221faf2 100644 --- a/raven-legacy/src/main/java/net/kencochrane/raven/Client.java +++ b/raven-legacy/src/main/java/net/kencochrane/raven/Client.java @@ -6,7 +6,6 @@ import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.helper.EventBuilderHelper; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang.time.DateFormatUtils; import org.json.simple.JSONObject; diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 1be529d35cd..d1032b4e35a 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -4,7 +4,6 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; import org.apache.log4j.spi.LoggingEvent; diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index a4fa0625b73..8b6a9dfdb0b 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -4,7 +4,6 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 9391edc5e43..e2368111f80 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -9,7 +9,6 @@ import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; import java.io.IOException; import java.util.ArrayList; diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 90048401d1a..399908f7f5a 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -5,7 +5,6 @@ import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; import java.io.IOException; import java.util.ArrayList; diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java index e0908bc88bf..7968dc790bb 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java @@ -1,7 +1,6 @@ package net.kencochrane.raven.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; -import net.kencochrane.raven.event.interfaces.ImmutableThrowable; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import java.io.IOException; diff --git a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java index ef288cb00db..8d4c45d627f 100644 --- a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java @@ -4,7 +4,6 @@ import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.SentryInterface; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java index 5e1ca3d38a4..7ad4f3f079c 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonNode; -import net.kencochrane.raven.event.interfaces.ImmutableThrowable; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.junit.Before; import org.junit.Test; @@ -14,7 +13,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) From b800522c17c4153a78dd930ff6a4210fc803b3fc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 12:06:45 +0100 Subject: [PATCH 0381/2152] Give elements to parse the Message interface --- .../raven/sentrystub/event/interfaces/MessageInterface.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/MessageInterface.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/MessageInterface.java index 699d3e930ea..7d8bb6d17e3 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/MessageInterface.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/MessageInterface.java @@ -1,8 +1,12 @@ package net.kencochrane.raven.sentrystub.event.interfaces; +import com.fasterxml.jackson.annotation.JsonProperty; + import java.util.List; public class MessageInterface { + @JsonProperty(value = "message") private String message; + @JsonProperty(value = "params") private List params; } From 2f078b4db292e9b5f6eedc34b5bce8c41e343148 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 12:07:10 +0100 Subject: [PATCH 0382/2152] Add elements to parse the StackTraceInterface --- .../event/interfaces/StackTraceInterface.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/StackTraceInterface.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/StackTraceInterface.java index bd5323f5f39..ba74fe20de0 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/StackTraceInterface.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/interfaces/StackTraceInterface.java @@ -1,4 +1,35 @@ package net.kencochrane.raven.sentrystub.event.interfaces; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + public class StackTraceInterface { + @JsonProperty(value = "frames") + private List stackFrames; + + public static class StackFrame { + @JsonProperty(value = "filename") + private String fileName; + @JsonProperty(value = "function") + private String function; + @JsonProperty(value = "module") + private String module; + @JsonProperty(value = "lineno") + private int lineno; + @JsonProperty(value = "colno") + private int colno; + @JsonProperty(value = "abs_path") + private String absPath; + @JsonProperty(value = "context_line") + private String contextLine; + @JsonProperty(value = "pre_context") + private List preContext; + @JsonProperty(value = "post_context") + private List postContext; + @JsonProperty(value = "in_app") + private boolean inApp; + @JsonProperty(value = "vars") + private Object vars; + } } From 6ba9eab7030c3112d55066b00105a3ea39b315da Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 14:01:40 +0100 Subject: [PATCH 0383/2152] Fix property name case (Stacktrace not StackTrace) --- .../main/java/net/kencochrane/raven/sentrystub/event/Event.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java index 06ccd67e2e4..0cc0a46e5c8 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/event/Event.java @@ -54,7 +54,7 @@ public void setExceptionInterfacesLong(List exceptionInterfa this.exceptionInterfaces = exceptionInterfaces; } - @JsonProperty(value = "sentry.interfaces.StackTrace") + @JsonProperty(value = "sentry.interfaces.Stacktrace") public void setStackTraceInterfaceLong(StackTraceInterface stackTraceInterface) { this.stackTraceInterface = stackTraceInterface; } From 68b8b308904e4f40a005911b95f0aba8ed227c62 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Apr 2013 21:52:20 +0100 Subject: [PATCH 0384/2152] Add documentation to InitialContextMockFactory --- .../net/kencochrane/raven/InitialContextMockFactory.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/InitialContextMockFactory.java b/raven/src/test/java/net/kencochrane/raven/InitialContextMockFactory.java index 67a07906c89..5d0872e43a0 100644 --- a/raven/src/test/java/net/kencochrane/raven/InitialContextMockFactory.java +++ b/raven/src/test/java/net/kencochrane/raven/InitialContextMockFactory.java @@ -5,6 +5,11 @@ import javax.naming.spi.InitialContextFactory; import java.util.Hashtable; +/** + * JNDI initial context factory allowing to create custom mocked contexts. + * + * @see DsnTest#setUp() + */ public class InitialContextMockFactory implements InitialContextFactory { public static Context context; From c2b696c658dc68959b81b3278ccff0841f040cb2 Mon Sep 17 00:00:00 2001 From: Rob Whelan Date: Wed, 17 Apr 2013 02:54:24 +0300 Subject: [PATCH 0385/2152] Better groupId in pom.xml Changed groupId to "net.kencochrane.raven" to describe project. See http://maven.apache.org/ref/3.0.3/maven-model/maven.html#class_project --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d5ed8b36782..cf7266e64ef 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ 4.0.0 - net.kencochrane + net.kencochrane.raven raven-all 2.0-SNAPSHOT pom From 9e5312e4f0f4da5c2ed0753c5c8ee68183f3886f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 14:47:40 +0100 Subject: [PATCH 0386/2152] SentryHandler should be created without a raven instance --- .../kencochrane/raven/jul/SentryHandler.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 399908f7f5a..d00ba797300 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -15,11 +15,12 @@ import java.util.logging.LogRecord; public class SentryHandler extends Handler { - private final Raven raven; private final boolean propagateClose; + private Raven raven; + private String dsn; public SentryHandler() { - this(new Raven(), true); + propagateClose = true; } public SentryHandler(Raven raven) { @@ -71,9 +72,19 @@ public void publish(LogRecord record) { else eventBuilder.setMessage(record.getMessage()); - raven.runBuilderHelpers(eventBuilder); + getRaven().runBuilderHelpers(eventBuilder); - raven.sendEvent(eventBuilder.build()); + getRaven().sendEvent(eventBuilder.build()); + } + + private Raven getRaven() { + if (raven == null) + raven = (dsn != null) ? new Raven(dsn) : new Raven(); + return raven; + } + + public void setDsn(String dsn) { + this.dsn = dsn; } @Override From 8d086dbe7a09518eb4902df9480ed9316714a947 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 14:47:40 +0100 Subject: [PATCH 0387/2152] SentryAppender should be created without a raven instance --- .../kencochrane/raven/log4j/SentryAppender.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index d1032b4e35a..1e687062733 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -15,11 +15,12 @@ public class SentryAppender extends AppenderSkeleton { private static final String LOG4J_NDC = "Log4J-NDC"; - private final Raven raven; private final boolean propagateClose; + private Raven raven; + private String dsn; public SentryAppender() { - this(new Raven(), true); + this.propagateClose = true; } public SentryAppender(Raven raven) { @@ -45,6 +46,12 @@ private static Event.Level formatLevel(Level level) { } else return null; } + @Override + public void activateOptions() { + if (raven == null) + raven = (dsn != null) ? new Raven(dsn) : new Raven(); + } + @Override protected void append(LoggingEvent loggingEvent) { EventBuilder eventBuilder = new EventBuilder() @@ -76,6 +83,10 @@ protected void append(LoggingEvent loggingEvent) { raven.sendEvent(eventBuilder.build()); } + public void setDsn(String dsn) { + this.dsn = dsn; + } + @Override public void close() { try { From 505a6d0f45a571559abbfadb183744ebf4a2ed01 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 14:47:40 +0100 Subject: [PATCH 0388/2152] SentryAppender should be created without a raven instance --- .../raven/logback/SentryAppender.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index e2368111f80..112553b410f 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -17,11 +17,12 @@ import java.util.Map; public class SentryAppender extends AppenderBase { - private final Raven raven; private final boolean propagateClose; + private Raven raven; + private String dsn; public SentryAppender() { - this(new Raven(), true); + propagateClose = true; } public SentryAppender(Raven raven) { @@ -53,6 +54,13 @@ private static Event.Level formatLevel(ILoggingEvent iLoggingEvent) { } else return null; } + @Override + public void start() { + super.start(); + if (raven == null) + raven = (dsn != null) ? new Raven(dsn) : new Raven(); + } + @Override protected void append(ILoggingEvent iLoggingEvent) { EventBuilder eventBuilder = new EventBuilder() @@ -94,6 +102,10 @@ private String getEventPosition(ILoggingEvent iLoggingEvent) { return sb.toString(); } + public void setDsn(String dsn) { + this.dsn = dsn; + } + @Override public void stop() { super.stop(); From 5b3deff4f6f50e5bcce499fc66f76b8421df4d40 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 14:47:40 +0100 Subject: [PATCH 0389/2152] SentryAppender should be created without a raven instance --- .../raven/log4j2/SentryAppender.java | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 8b6a9dfdb0b..ca9d64b5d00 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -20,14 +20,15 @@ import java.util.Map; @Plugin(name = "Sentry", type = "Sentry", elementType = "appender") -public class SentryAppender extends AbstractAppender { +public class SentryAppender extends AbstractAppender { public static final String APPENDER_NAME = "raven"; private static final String LOG4J_NDC = "Log4J-NDC"; - private final Raven raven; private final boolean propagateClose; + private Raven raven; + private String dsn; public SentryAppender() { - this(new Raven(), true); + this(APPENDER_NAME, PatternLayout.createLayout(null, null, null, null), null, true); } @@ -36,39 +37,31 @@ public SentryAppender(Raven raven) { } public SentryAppender(Raven raven, boolean propagateClose) { - this(APPENDER_NAME, raven, PatternLayout.createLayout(null, null, null, null), null, true, propagateClose); + this(APPENDER_NAME, PatternLayout.createLayout(null, null, null, null), null, propagateClose); + this.raven = raven; } - private SentryAppender(String name, Raven raven, Layout layout, Filter filter, - boolean handleExceptions, boolean propagateClose) { - super(name, filter, layout, handleExceptions); - this.raven = raven; + private SentryAppender(String name, Layout layout, Filter filter, boolean propagateClose) { + super(name, filter, layout, true); this.propagateClose = propagateClose; } /** * Create a Sentry Appender. * - * @param name The name of the Appender. - * @param dsn Data Source Name to access the Sentry server. - * @param suppress "true" if exceptions should be hidden from the application, "false" otherwise. - * The default is "true". - * @param layout The layout to use to format the event. If no layout is provided the default PatternLayout - * will be used. - * @param filter The filter, if any, to use. + * @param name The name of the Appender. + * @param dsn Data Source Name to access the Sentry server. + * @param layout The layout to use to format the event. If no layout is provided the default PatternLayout + * will be used. + * @param filter The filter, if any, to use. * @return The SentryAppender. */ @PluginFactory public static SentryAppender createAppender(@PluginAttr("name") final String name, @PluginAttr("dsn") final String dsn, - @PluginAttr("suppressExceptions") final String suppress, - @PluginElement("layout") Layout layout, + @PluginElement("layout") Layout layout, @PluginElement("filters") final Filter filter) { - final boolean handleExceptions = suppress == null ? true : Boolean.valueOf(suppress); - - final Raven raven = dsn == null ? new Raven() : new Raven(dsn); - if (name == null) { LOGGER.error("No name provided for FileAppender"); return null; @@ -77,7 +70,9 @@ public static SentryAppender createAppender(@PluginAttr("name") final String nam if (layout == null) { layout = PatternLayout.createLayout(null, null, null, null); } - return new SentryAppender(name, raven, layout, filter, handleExceptions, true); + SentryAppender sentryAppender = new SentryAppender(name, layout, filter, true); + sentryAppender.setDsn(dsn); + return sentryAppender; } private static Event.Level formatLevel(Level level) { @@ -103,6 +98,12 @@ private static String formatCulprit(StackTraceElement stackTraceElement) { + " at line " + stackTraceElement.getLineNumber(); } + @Override + public void start() { + if (raven == null) + raven = (dsn != null) ? new Raven(dsn) : new Raven(); + } + @Override public void append(LogEvent event) { EventBuilder eventBuilder = new EventBuilder() @@ -137,6 +138,10 @@ public void append(LogEvent event) { raven.sendEvent(eventBuilder.build()); } + public void setDsn(String dsn) { + this.dsn = dsn; + } + @Override public void stop() { super.stop(); From cbeb83355e5ff96da06d3e3c323cba91c6f637cb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 16:11:14 +0100 Subject: [PATCH 0390/2152] Make the depednency to servlet-api optional --- raven/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/raven/pom.xml b/raven/pom.xml index 9fb36bb4791..28dce4a7acb 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -27,6 +27,7 @@ javax.servlet javax.servlet-api provided + true com.fasterxml.jackson.core From 123e6f7e624e033f6138e86bc2ef4d8fc45c396a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 17:00:16 +0100 Subject: [PATCH 0391/2152] Ensure that the sentry-stub is always available --- raven/pom.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/raven/pom.xml b/raven/pom.xml index 28dce4a7acb..914b66d640b 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -54,6 +54,14 @@ hamcrest-library test + + + ${project.groupId} + sentry-stub + test + war + ${project.version} + From f0091e169f3994eadc2d3945eada76b33ab9f0bc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 17:00:42 +0100 Subject: [PATCH 0392/2152] Add plugins to start sentry-stub during IT --- pom.xml | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/pom.xml b/pom.xml index 7d94ebe8b3b..797e656db7b 100644 --- a/pom.xml +++ b/pom.xml @@ -237,6 +237,81 @@ + + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.14.1 + + + + integration-test + verify + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.7 + + + copy-war-for-integration-tests + generate-resources + + copy + + + + + ${project.groupId} + sentry-stub + ${project.version} + war + + + true + true + ${project.build.directory}/webapps + + + + + + org.eclipse.jetty + jetty-maven-plugin + 9.0.1.v20130408 + + 10 + foo + 9999 + ${project.build.directory}/webapps/sentry-stub.war + + + + start-sentry-stub + pre-integration-test + + deploy-war + + + 0 + true + + + + stop-sentry-stub + post-integration-test + + stop + + + + + + From 21d4713208dbf39d5c41b01c1c18ff4a263a41b4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 17:01:20 +0100 Subject: [PATCH 0393/2152] Do not start sentry-stub while building sentry-stub --- sentry-stub/pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 9cb66052fe8..246f01bd2e8 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -42,6 +42,16 @@ 10 + + + start-sentry-stub + none + + + stop-sentry-stub + none + + From 66da15a0b0e5a39a92c2e96c262f4050bfa20b8c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Apr 2013 19:11:09 +0100 Subject: [PATCH 0394/2152] Enable Integration Tests for log4j --- raven-log4j/pom.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 82133e47417..d90b22f0c50 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -50,4 +50,20 @@ test + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.apache.maven.plugins + maven-dependency-plugin + + + org.eclipse.jetty + jetty-maven-plugin + + + From b3568e7377611f02b468dad36d498f6799f1ae97 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 10:56:29 +0100 Subject: [PATCH 0395/2152] Upgrade surefire-report version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 797e656db7b..f9607137110 100644 --- a/pom.xml +++ b/pom.xml @@ -329,7 +329,7 @@ org.apache.maven.plugins maven-surefire-report-plugin - 2.14 + 2.14.1 org.apache.maven.plugins From 715ce6127bcd77034aba038954915db6f04fedda Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 11:07:38 +0100 Subject: [PATCH 0396/2152] Copy the stub war during the package phase --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f9607137110..bea1abea7bb 100644 --- a/pom.xml +++ b/pom.xml @@ -259,7 +259,7 @@ copy-war-for-integration-tests - generate-resources + package copy From ed3fdbe76d5e2ea53c431fda423f143c003f005b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 11:08:03 +0100 Subject: [PATCH 0397/2152] Do not start jetty if the tests are disabled --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index bea1abea7bb..f4bfc33f44e 100644 --- a/pom.xml +++ b/pom.xml @@ -299,6 +299,7 @@ 0 true + ${skipTests} From 9ea832a5861597bfa2847ffb1faf17363a62ca8b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 11:08:50 +0100 Subject: [PATCH 0398/2152] Enable integration tests on every project --- raven-legacy/pom.xml | 17 +++++++++++++++++ raven-log4j2/pom.xml | 17 +++++++++++++++++ raven-logback/pom.xml | 17 +++++++++++++++++ raven/pom.xml | 12 ++++++++++++ 4 files changed, 63 insertions(+) diff --git a/raven-legacy/pom.xml b/raven-legacy/pom.xml index 9a44208427f..7b0745ed439 100644 --- a/raven-legacy/pom.xml +++ b/raven-legacy/pom.xml @@ -35,4 +35,21 @@ ${commons-lang.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.apache.maven.plugins + maven-dependency-plugin + + + org.eclipse.jetty + jetty-maven-plugin + + + diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 635df8135a3..3bbc521f64d 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -38,4 +38,21 @@ test + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.apache.maven.plugins + maven-dependency-plugin + + + org.eclipse.jetty + jetty-maven-plugin + + + diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 62fe731043b..40d6801e3a9 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -55,4 +55,21 @@ test + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.apache.maven.plugins + maven-dependency-plugin + + + org.eclipse.jetty + jetty-maven-plugin + + + diff --git a/raven/pom.xml b/raven/pom.xml index 914b66d640b..42b5f3818d8 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -78,6 +78,18 @@ + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.apache.maven.plugins + maven-dependency-plugin + + + org.eclipse.jetty + jetty-maven-plugin + From c2d15912f97030c0da0492307accae07ebacec54 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 11:09:12 +0100 Subject: [PATCH 0399/2152] Fix the culprit in Log4j --- .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 1e687062733..213480984bb 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -59,7 +59,7 @@ protected void append(LoggingEvent loggingEvent) { .setMessage(loggingEvent.getRenderedMessage()) .setLogger(loggingEvent.getLoggerName()) .setLevel(formatLevel(loggingEvent.getLevel())) - .setCulprit(loggingEvent.getFQNOfLoggerClass()); + .setCulprit(loggingEvent.getLoggerName()); if (loggingEvent.getThrowableInformation() != null) { Throwable throwable = loggingEvent.getThrowableInformation().getThrowable(); From 8e1dc58f11a7298e2695875ed06a65f95c7ca5c1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 11:09:34 +0100 Subject: [PATCH 0400/2152] Use the log fullinfo as the culprit if possible --- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 213480984bb..e06c84d5871 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -65,11 +65,11 @@ protected void append(LoggingEvent loggingEvent) { Throwable throwable = loggingEvent.getThrowableInformation().getThrowable(); eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); eventBuilder.setCulprit(throwable); - } else { + } else if (loggingEvent.getLocationInformation().fullInfo != null) { // When it's a message try to rely on the position of the log (the same message can be logged from - // different places, or a same place can log a message in different ways. - if (loggingEvent.getLocationInformation().fullInfo != null) - eventBuilder.generateChecksum(loggingEvent.getLocationInformation().fullInfo); + // different places, or a same place can log a message in different ways). + eventBuilder.generateChecksum(loggingEvent.getLocationInformation().fullInfo); + eventBuilder.setCulprit(loggingEvent.getLocationInformation().fullInfo); } if (loggingEvent.getNDC() != null) From d4c41bbcb4b4453abb8dc21724143a2ec9bc80f6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 12:15:06 +0100 Subject: [PATCH 0401/2152] Move dependency to sentry-stub in jetty plugin --- pom.xml | 8 ++++++++ raven/pom.xml | 8 -------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index f4bfc33f44e..46d77e3d78a 100644 --- a/pom.xml +++ b/pom.xml @@ -278,6 +278,14 @@ + + + ${project.groupId} + sentry-stub + ${project.version} + war + + org.eclipse.jetty diff --git a/raven/pom.xml b/raven/pom.xml index 42b5f3818d8..df6bea47f65 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -54,14 +54,6 @@ hamcrest-library test - - - ${project.groupId} - sentry-stub - test - war - ${project.version} - From 57705be93a1eeca687a9c0dbedced755fc2d47de Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 12:15:44 +0100 Subject: [PATCH 0402/2152] Remove redundant configuration --- sentry-stub/pom.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 246f01bd2e8..84dba4ec59b 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -38,10 +38,6 @@ org.eclipse.jetty jetty-maven-plugin - 9.0.1.v20130408 - - 10 - start-sentry-stub From a9460b08e83b2d9db9c27aef3072f6b922d8ebe0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 12:16:12 +0100 Subject: [PATCH 0403/2152] Check compatibility with 1.6 with animal sniffer --- pom.xml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pom.xml b/pom.xml index 46d77e3d78a..486e3164ab6 100644 --- a/pom.xml +++ b/pom.xml @@ -236,6 +236,27 @@ + + org.codehaus.mojo + animal-sniffer-maven-plugin + 1.9 + + + org.codehaus.mojo.signature + java16 + 1.1 + + + + + check-compatibility + verify + + check + + + + From 16292218a5e262528555e9482073707fa58713da Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 12:16:52 +0100 Subject: [PATCH 0404/2152] Do not double build (animal sniffer already checks that) --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4db492a319c..dff5f3a5d02 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1 @@ language: java -jdk: - - openjdk7 - - openjdk6 From 252f354a4dec879af5018f2a81449cde1a10031b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 12:17:19 +0100 Subject: [PATCH 0405/2152] Indentation --- raven-log4j/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index d90b22f0c50..9659921fdfc 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -50,6 +50,7 @@ test + From fcc361216e1931843b6f160e136dc5143d386b9f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 17:06:41 +0100 Subject: [PATCH 0406/2152] Force charset to UTF-8 UTF-8 has to be provided by every implementations of the JVM, so there is no reason to rely on that all the time. --- .../main/java/net/kencochrane/raven/Dsn.java | 4 --- .../java/net/kencochrane/raven/Raven.java | 30 ------------------- .../raven/connection/UdpConnection.java | 10 ++----- .../raven/marshaller/json/JsonMarshaller.java | 19 +----------- 4 files changed, 3 insertions(+), 60 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Dsn.java b/raven/src/main/java/net/kencochrane/raven/Dsn.java index 768fae6e67f..f7ea4a8bd9b 100644 --- a/raven/src/main/java/net/kencochrane/raven/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/Dsn.java @@ -32,10 +32,6 @@ public class Dsn { * Option to send events asynchronously. */ public static final String ASYNC_OPTION = "raven.async"; - /** - * Option to set the charset for strings sent to sentry. - */ - public static final String CHARSET_OPTION = "raven.charset"; /** * Protocol setting to disable security checks over an SSL connection. */ diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index ca68f8f4f72..c8bafa2a450 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -9,7 +9,6 @@ import net.kencochrane.raven.event.helper.EventBuilderHelper; import net.kencochrane.raven.marshaller.json.JsonMarshaller; -import java.nio.charset.Charset; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -24,14 +23,6 @@ *

    */ public class Raven { - /** - * Default charset used to send content to Sentry. - *

    - * UTF-8 is used as the default since the communication by default is done through JSON - * (which uses UTF-8 by default). - *

    - */ - public static final String DEFAULT_CHARSET = "UTF-8"; /** * Version of this client, the major version is the current supported Sentry protocol, the minor version changes * for each release of this project. @@ -77,24 +68,6 @@ public Raven(Connection connection) { this.connection = connection; } - /** - * Obtains the charset setting for the given DSN. - *

    - * Defaults to {@link #DEFAULT_CHARSET} if the DSN doesn't specify a charset. - *

    - * - * @param dsn Data Source Name optionally containing a charset option. - * @return the charset to use with the given DSN. - */ - private static Charset determineCharset(Dsn dsn) { - String charset = DEFAULT_CHARSET; - - if (dsn.getOptions().containsKey(Dsn.CHARSET_OPTION)) - charset = dsn.getOptions().get(Dsn.CHARSET_OPTION); - - return Charset.forName(charset); - } - /** * Builds a {@link Connection} based on a {@link Dsn}. *

    @@ -108,10 +81,8 @@ private static Charset determineCharset(Dsn dsn) { private static Connection determineConnection(Dsn dsn) { String protocol = dsn.getProtocol(); Connection connection = null; - Charset charset = determineCharset(dsn); JsonMarshaller marshaller = new JsonMarshaller(); marshaller.setCompression(!dsn.getOptions().containsKey(Dsn.NOCOMPRESSION_OPTION)); - marshaller.setCharset(charset); if (protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https")) { logger.log(Level.INFO, "Using an HTTP connection to Sentry."); @@ -121,7 +92,6 @@ private static Connection determineConnection(Dsn dsn) { } else if (protocol.equalsIgnoreCase("udp")) { logger.log(Level.INFO, "Using an UDP connection to Sentry."); UdpConnection udpConnection = new UdpConnection(dsn); - udpConnection.setCharset(charset); udpConnection.setMarshaller(marshaller); connection = udpConnection; } else { diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index d0cabab5c8a..725fcd73084 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -12,7 +12,6 @@ import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.SocketException; -import java.nio.charset.Charset; import java.util.logging.Level; import java.util.logging.Logger; @@ -23,7 +22,6 @@ public class UdpConnection extends AbstractConnection { private static final Logger logger = Logger.getLogger(UdpConnection.class.getCanonicalName()); private static final int DEFAULT_UDP_PORT = 9001; private DatagramSocket socket; - private Charset charset = Charset.defaultCharset(); private Marshaller marshaller = new JsonMarshaller(); public UdpConnection(Dsn dsn) { @@ -57,8 +55,8 @@ public void send(Event event) { } private void writeHeader(OutputStream os) throws IOException { - os.write(getAuthHeader().getBytes(charset)); - os.write("\n\n".getBytes(charset)); + os.write(getAuthHeader().getBytes("UTF-8")); + os.write("\n\n".getBytes("UTF-8")); } private void openSocket(String hostname, int port) { @@ -75,10 +73,6 @@ public void setMarshaller(Marshaller marshaller) { this.marshaller = marshaller; } - public void setCharset(Charset charset) { - this.charset = charset; - } - @Override public void close() throws IOException { socket.close(); diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 4b6a1df1780..9b9c27016d1 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -1,6 +1,5 @@ package net.kencochrane.raven.marshaller.json; -import com.fasterxml.jackson.core.JsonEncoding; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import net.kencochrane.raven.event.Event; @@ -10,7 +9,6 @@ import java.io.IOException; import java.io.OutputStream; -import java.nio.charset.Charset; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; @@ -83,10 +81,6 @@ public class JsonMarshaller implements Marshaller { * Enables disables the compression of JSON. */ private boolean compression = true; - /** - * Charset used to send Json, by default UTF-8 will be used. - */ - private JsonEncoding charset = JsonEncoding.UTF8; static { ISO_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); @@ -107,7 +101,7 @@ public void marshall(Event event, OutputStream destination) { JsonGenerator generator = null; try { - generator = jsonFactory.createGenerator(destination, charset); + generator = jsonFactory.createGenerator(destination); writeContent(generator, event); } catch (IOException e) { logger.log(Level.SEVERE, "An exception occurred while serialising the event.", e); @@ -251,15 +245,4 @@ public void addInterfaceBinding(Class sentryInter public void setCompression(boolean compression) { this.compression = compression; } - - public void setCharset(Charset charset) { - for (JsonEncoding jsonEncoding : JsonEncoding.values()) { - if (jsonEncoding.getJavaName().equals(charset.name())) { - this.charset = jsonEncoding; - return; - } - } - throw new IllegalArgumentException("Couldn't set the charset to " + charset + ". " - + "The supported charsets are '" + Arrays.toString(JsonEncoding.values()) + "'"); - } } From e9f9821fe85e664f023ab1453d763cf4b7ae1434 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 17:23:59 +0100 Subject: [PATCH 0407/2152] Update README.md --- README.md | 400 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 208 insertions(+), 192 deletions(-) diff --git a/README.md b/README.md index 2d810b3504f..089de209683 100644 --- a/README.md +++ b/README.md @@ -1,230 +1,246 @@ # Raven-Java -Raven-Java is a Java client for [Sentry](https://www.getsentry.com/). Besides a regular client you can use within your application code, Raven-Java also provides the `raven-log4j` package you can use to send logging to Sentry via [log4j](http://logging.apache.org/log4j/) and the `raven-logback` package to do the same through [logback](http://logback.qos.ch/). +Raven-Java is a Java client for [Sentry](https://www.getsentry.com/). +Besides the regular client you can use within your application code, this +project also provides tools allowing the most popular logging frameworks +to send the logs directly to sentry: + + - `java.util.logging` is supported out of the box. + - `raven-log4j` adds the support for [log4j](https://logging.apache.org/log4j/1.2/). + - `raven-log4j2` adds the support for [log4j2](https://logging.apache.org/log4j/2.x/). + - `raven-logback` adds the support for [logback](http://logback.qos.ch/). + +Raven-Java supports both HTTP(S) and UDP transport of events. + +## Sentry Protocol and supported versions +### Sentry Protocol versions +Since the version 3.0, Raven-Java the versionning system is based on the +protocol version of Sentry. This means that Raven-Java 3.0 only supports +the version 3 of Sentry's protocol while Raven-Java 4.0 only supports +the version 4. + +Sentry only supports the last two major releases of the protocol, for this +reason, only the last two major versions of Raven-Java are maintained. + +### Sentry versions + + - Sentry protocol v4 is not yet available (use Raven-Java 4.0) + - Sentry protocol v3 is available since Sentry 5.1 (use Raven-Java 3.0) + - Sentry protocol v2 is available since Sentry 2.0 (use Raven-Java 2.0) + +## Build and Installation +Currently there are 6 modules in the project: + + - `raven`, the core of the project, providing the client and support for JUL + - `raven-legacy`, support of the Raven-Java 2.0 API (it's recommended to move + to the new API as legacy will be removed in the future releases of Raven-Java) + - `raven-log4j`, Appender for log4j + - `raven-log4j2`, Appender for log4j2 + - `raven-logback`, Appander for Logback + - `sentry-stub`, Sentry server stub, allowing to test the protocol + +### Build +It's possible to get the latest version of Raven-Java by building it from the +sources. -Raven-Java supports both HTTP(S) and UDP transport of messages. - -## Sentry Versions Supported -This client supports Sentry protocol version 2.0 (which is Sentry >= 2.0). - -Since version 4.6.0 of Sentry, signed messages have been deprecated. If you still use an earlier version, have a look at the client options below to find out how to enable signed messages. - -* * * - -## Using the client - -````java -import net.kencochrane.raven.Client -import net.kencochrane.raven.SentryDsn - -public class Example { - - public static void main(String[] args) { - // The DSN from Sentry: "http://public:private@host:port/1" - String rawDsn = args[0]; - SentryDsn dsn = SentryDsn.build(rawDsn); - Client client = new Client(dsn); - client.captureMessage("Hello from Raven-Java!"); - } - -} - -```` - -A full example is available in `raven/src/main/test/net/kencochrane/raven/ClientExample`. - -Note that `SentryDsn` will first examine the environment variables and system properties for a variable called `SENTRY_DSN`. If such a variable is available, *that* value will be used instead of the DSN you supply (the `rawDsn` variable in the example above). - -This allows flexible usage of the library on PaaS providers such as Heroku. This also means the above example can be simplified to the following *if* you specify `SENTRY_DSN` in your: - -- environment variables — e.g. `export SENTRY_DSN=yoursentrydsn` or `setenv SENTRYDSN yoursentrydsn`; or -- system properties — `-DSENTRY_DSN=yoursentrydsn` - -````java -import net.kencochrane.raven.Client -import net.kencochrane.raven.SentryDsn - -public class Example { - - public static void main(String[] args) { - // DSN is determined by the client from system properties or env - Client client = new Client(); - client.captureMessage("Hello from Raven-Java!"); - } - -} - -```` - -* * * - -## Using the log4j appender -You can either utilize the `net.kencochrane.raven.log4j.SentryAppender` or `net.kencochrane.raven.log4j.AsyncSentryAppender` as an appender in your log4j configuration just like you would use any other appender. - - log4j.appender.sentry=net.kencochrane.raven.log4j.SentryAppender - log4j.appender.sentry.sentryDsn=http://b4935bdd7862409:7a37d9ad47654281@localhost:8000/1 - -Like the client, these appenders will examine the system properties and environment variables for a better `SENTRY_DSN` candidate. Which log messages are ultimately sent to your Sentry instance, depends on the configuration of your Sentry appender. If you only want to send error messages, use the following: - - log4j.appender.sentry.Threshold=ERROR - -### Asynchronous logging -If you use log4j's XML configuration, you can use its [AsyncAppender](http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/AsyncAppender.html) to wrap Raven-Java's `SentryAppender`. - -But even if you use a properties file to configure log4j, you can log asynchronously by using the `net.kencochrane.raven.log4j.AsyncSentryAppender` instead. - -* * * - -## Using the logback appender -Make sure to use the `raven-logback` artifact and add something like the following to your logback configuration: - - - http://public:private@host:port/project - + $ git clone https://github.com/kencochrane/raven-java.git + $ cd raven-java + $ mvn clean install -DskipTests=true -And don't forget to use the appender. For example: +_Due to a [known issue](https://bugs.eclipse.org/bugs/show_bug.cgi?id=405631) in +jetty, it is currently not possible to run the integration tests from the main +project. They have to be run manually on each module independently._ - - - - - +### Maven dependency +To add raven-java as a dependency, simply add this to your pom.xml: -* * * + + net.kencochrane.raven + raven + 4.0-SNAPSHOT + -## Client configuration -Client configuration is completely driven through the Sentry DSN. The DSN you can copy-paste from your Sentry instance looks like this: +You can add the other modules the same way (replacing the `artifactId`) with the +name of the module. + +### Manual installation + +## Using Raven-Java + +### Manual usage + + import net.kencochrane.raven.Raven; + import net.kencochrane.raven.event.EventBuilder; + + public class Example { + public static void main(String[] args) { + // The DSN from Sentry: "http://public:private@host:port/1" + String rawDsn = args[0]; + Raven client = new Raven(rawDsn); + EventBuilder eventBuilder = new EventBuilder() + .setMessage("Hello from Raven-Java!"); + client.sendEvent(eventBuilder.build()); + } + } + +It is also possible to create a client without directly providing a DSN, +the DSN will then be determined at runtime (when the client is created). + +The client will lookup for the first DSN configuration provided: + + - JNDI in `java:comp/env/sentry/dsn` + - The environment variable `SENTRY_DSN` + (`export SENTRY_DSN=yoursentrydsn` or `setenv SENTRYDSN yoursentrydsn`) + - The system property `SENTRY_DSN` (`-DSENTRY_DSN=yoursentrydsn`) + + import net.kencochrane.raven.Raven; + import net.kencochrane.raven.event.EventBuilder; + + public class Example { + public static void main(String[] args) { + Raven client = new Raven(); + EventBuilder eventBuilder = new EventBuilder() + .setMessage("Hello from Raven-Java!"); + client.sendEvent(eventBuilder.build()); + } + } + +### Using `java.util.logging` +TODO + +### Using log4j +TODO + +#### Asynchronous logging with AsyncAppander +log4j supports asynchronous logging with +[AsyncAppender](http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/AsyncAppender.html). +While this is a common solution to avoid blocking the current thread until the +event is sent to Sentry, it is recommended to use instead the option +`raven.async` to enable asynchronous logging for raven. + +Some + +### Using log4j2 +**TODO** + +### Using logback +**TODO** + +### Capturing the HTTP environment +**TODO** + +## Connection and protocol +It is possible to send events to Sentry over different protocols, depending +on the security and performance requirements. +So far Sentry accepts HTTP(S) and UDP which are both fully supported by +Raven-Java. + +### HTTP +The most common way to access Sentry is through HTTP, this can be done by +using a DSN using this form: http://public:private@host:port/1 + +If not provided, the port will default to `80`. -Changing the behavior of the client is done through the *scheme* and *querystring* of the DSN. - -### Transport protocols - -#### HTTPS -If you're using https, your DSN should look like this: +### HTTPS +It is possible to use an encrypted connection to Sentry using HTTPS: https://public:private@host:port/1 - -#### Naive HTTPS -If you're using https with a wildcard certificate (which most Java versions can't handle) and you're too lazy -to add the certificate to your truststore, you can tell the client to be naive and allow it to ignore the -hostname verification: + +If not provided, the port will default to `443`. +### HTTPS (naive) +If the certificate used over HTTPS is a wildcard certificate (which is not +handled by every version of Java), and the certificate isn't added to the +truststore, it is possible to add a protocol setting to tell the client to be +naive and ignore the hostname verification: naive+https://public:private@host:port/1 -#### UDP -Prefer udp? Change your DSN to this: +### UDP +It is possible to use a DSN with the UDP protocol: udp://public:private@host:port/1 -#### Asynchronous -The client can use a separate thread to actually send the messages to Sentry. To enable this feature, add `async+` to the scheme in your DSN: - - async+http://public:private@host:port/1 - # Or - async+https://public:private@host:port/1 - # Or - async+naive+https://public:private@host:port/1 - # Or - async+udp://public:private@host:port/1 +If not provided the port will default to `9001`. -#### Adding other schemes -You can add your own custom transport and scheme through the `register` method of the client. +While being faster because there is no TCP and HTTP overhead, UDP doesn't wait +for a reply, and if a connection problem occurs, there will be no notification. -### Client options -More client configuration can be specified through the querystring of the DSN like this: +## Options +It is possible to enable some options by adding data to the query string of the +DSN: - http://public:private@host:port/1?optionA=true&optionB=20 + http://public:private@host:port/1?option1=value1&option2&option3=value3 -#### Enabling signed messages -Signed messages have been deprecated in Sentry 4.6.0. If you're using an earlier version, you'll have to tell the client to sign messages through the option `raven.includeSignature`: +Not every option requires a value. - http://public:private@host:port/1?raven.includeSignature=true +### Async connection +In order to avoid performance issues due to a large amout of logs being +generated or a slow connection to the Sentry server, it is recommended to use +the asynchronous connection which will use a low priority thread pool to submit +events to Sentry. -#### HTTP/HTTPS timeout -The default timeout for HTTP/HTTPS transport is set to 10 seconds. If you want to change this value, use the `raven.timeout` option to specify the timeout in milliseconds. - - http://public:private@host:port/1?raven.timeout=10000 +To enable the async mode, add the `raven.async` option to your DSN: -#### Async queue configuration -When using the async transport (this is not the same as using the `AsyncSentryAppender`), you can configure the behavior of the underlying `java.util.concurrent.BlockingQueue` through the `raven.waitWhenFull` and `raven.capacity` options. + http://public:private@host:port/1?raven.async -By default the client will **not** block when the queue is full and will use a queue at maximum capacity. If instead you want to use a blocking queue with a capacity of 20 messages, change your DSN to something like this: +#### Threads count (advanced) +By default the thread pool used by the async connection contains one thread per +processor available to the JVM (more threads wouldn't be useful). - async+http://public:private@host:port/1?raven.waitWhenFull=true&raven.capacity=20 +It's possible to manually set the number of threads (for example if you want +only one Thread) with the option `raven.async.threads`: -#### Enabling ServletJSONProcessor -In a servlet environment, Raven can append request information to logs sent to Sentry when logs are created on request threads. Information sent to Sentry include: + http://public:private@host:port/1?raven.async&raven.async.threads=1 -* Request URL -* POST parameters -* Request headers -* Cookies -* Environment variables, including: - * Remote address - * Server name - * Server port - * Server protocol +#### Threads priority (advanced) +As in most cases sending logs to Sentry isn't as important as an application +running smoothly, the threads have a +[minimal priority](http://docs.oracle.com/javase/6/docs/api/java/lang/Thread.html#MIN_PRIORITY). -Please be aware that sensitive information, such as user passwords or credit card numbers, may potentially be logged. Common security measures, such as protecting the Sentry installation, should be practiced. To enable this support, add the following line to Log4j configuration: +It is possible to customise this value to increase the priority of those threads +with the option `raven.async.priority`: - log4j.appender.sentry.jsonProcessors=net.kencochrane.raven.ext.ServletJSONProcessor + http://public:private@host:port/1?raven.async.priority=10&raven.async -Then, add the following lines to web.xml: +### Inapp classes +**TODO** - - - net.kencochrane.raven.ext.RavenServletRequestListener - - +### Compression +By default the content sent to Sentry is compressed and encoded in base64 before +being sent. +This operation allows to send a smaller amount of data for each event. +However compressing and encoding the data adds a CPU and memory overhead which +might not be useful if the connection to Sentry is fast and reliable. -* * * +Depending on the limitations of the project (ie: a mobile application with a +limited connection, Sentry hosted on an external network), it can be interesting +to compress the data beforehand or not. -## Installation +It's possible to disable the compression with the option `raven.nocompression` -This version isn't available in the Central Maven repository yet. The easiest -way to get started is to clone this repository and install the artifacts into -your local repository or proxy like Nexus: - - $ git clone https://github.com/kencochrane/raven-java.git - $ cd raven-java - $ mvn clean install + http://public:private@host:port/1?raven.nocompression -This will build and test the Raven client and Log4J appender and install it -into your local repository. You shouldn't worry about stacktraces appearing in -the output unless tests are failing; they are *supposed* to be there. +### Timeout (advanced) +To avoid blocking the thread because of a connection taking too much time, a +timeout can be set by the connection. -Then add the correct dependency to your POM file: +By default the connection will set up its own timeout, but it's possible to +manually set one with `raven.timeout` (in milliseconds): - - net.kencochrane - raven - 2.0-SNAPSHOT - - -Or if you simply want to log to Sentry from Log4J: - - - net.kencochrane - raven-log4j - 2.0-SNAPSHOT - - -Or Logback: - - - net.kencochrane - raven-logback - 2.0-SNAPSHOT - - -* * * + http://public:private@host:port/1?raven.timeout=10000 ## History -- 2.0-SNAPSHOT +- 4.0 + - Support of the Sentry protocol V4 +- 3.0 + - Support of the Sentry protocol V3 + - Rewritten + - Added log4j2 appender + - Support of JNDI +- 2.0 - Version increment to reduce confusion about releases - Added Logback appender (thanks to [ccouturi](https://github.com/ccouturi)) - 1.0 @@ -252,12 +268,12 @@ Or Logback: - 0.1 - initial version - ## Contributors -- Ken Cochrane (@KenCochrane) -- Kevin Wetzels (@roambe) -- David Cramer (@zeeg) -- Mark Philpot (@griphiam) -- Brad Chen (@vvasabi) -- @ccouturi +- [Ken Cochrane](https://github.com/kencochrane) +- [Kevin Wetzels](https://github.com/roam) +- [David Cramer](https://github.com/dcramer) +- [Mark Philpot](https://github.com/griphiam) +- [Brad Chen](https://github.com/vvasabi) +- [ccouturi](https://github.com/ccouturi) +- [Colin Hebert](https://github.com/ColinHebert) From 11375c908319a1b54288f96af07fd033506506bd Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 17:30:53 +0100 Subject: [PATCH 0408/2152] Split README and INSTALL --- INSTALL.md | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 145 +++-------------------------------------------------- 2 files changed, 144 insertions(+), 139 deletions(-) create mode 100644 INSTALL.md diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 00000000000..9c067e47208 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,138 @@ +# Installation and setup of Raven-Java + +## Installing Raven-Java +Currently there are 6 modules in the project: + + - `raven`, the core of the project, providing the client and support for JUL + - `raven-legacy`, support of the Raven-Java 2.0 API (it's recommended to move + to the new API as legacy will be removed in the future releases of Raven-Java) + - `raven-log4j`, Appender for log4j + - `raven-log4j2`, Appender for log4j2 + - `raven-logback`, Appander for Logback + - `sentry-stub`, Sentry server stub, allowing to test the protocol + +### Build +It's possible to get the latest version of Raven-Java by building it from the +sources. + + $ git clone https://github.com/kencochrane/raven-java.git + $ cd raven-java + $ mvn clean install -DskipTests=true + +_Due to a [known issue](https://bugs.eclipse.org/bugs/show_bug.cgi?id=405631) in +jetty, it is currently not possible to run the integration tests from the main +project. They have to be run manually on each module independently._ + +### Maven dependency +To add raven-java as a dependency, simply add this to your pom.xml: + + + net.kencochrane.raven + raven + 4.0-SNAPSHOT + + +You can add the other modules the same way (replacing the `artifactId`) with the +name of the module. + +### Manual installation + +## Using Raven-Java + +### Manual usage + + import net.kencochrane.raven.Raven; + import net.kencochrane.raven.event.EventBuilder; + + public class Example { + public static void main(String[] args) { + // The DSN from Sentry: "http://public:private@host:port/1" + String rawDsn = args[0]; + Raven client = new Raven(rawDsn); + EventBuilder eventBuilder = new EventBuilder() + .setMessage("Hello from Raven-Java!"); + client.sendEvent(eventBuilder.build()); + } + } + +It is also possible to create a client without directly providing a DSN, +the DSN will then be determined at runtime (when the client is created). + +The client will lookup for the first DSN configuration provided: + + - JNDI in `java:comp/env/sentry/dsn` + - The environment variable `SENTRY_DSN` + (`export SENTRY_DSN=yoursentrydsn` or `setenv SENTRYDSN yoursentrydsn`) + - The system property `SENTRY_DSN` (`-DSENTRY_DSN=yoursentrydsn`) + + import net.kencochrane.raven.Raven; + import net.kencochrane.raven.event.EventBuilder; + + public class Example { + public static void main(String[] args) { + Raven client = new Raven(); + EventBuilder eventBuilder = new EventBuilder() + .setMessage("Hello from Raven-Java!"); + client.sendEvent(eventBuilder.build()); + } + } + +### Using `java.util.logging` +TODO + +### Using log4j +TODO + +#### Asynchronous logging with AsyncAppander +log4j supports asynchronous logging with +[AsyncAppender](http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/AsyncAppender.html). +While this is a common solution to avoid blocking the current thread until the +event is sent to Sentry, it is recommended to use instead the option +`raven.async` to enable asynchronous logging for raven. + +### Using log4j2 +**TODO** + +### Using logback +**TODO** + +### Capturing the HTTP environment +**TODO** + +## Connection and protocol +It is possible to send events to Sentry over different protocols, depending +on the security and performance requirements. +So far Sentry accepts HTTP(S) and UDP which are both fully supported by +Raven-Java. + +### HTTP +The most common way to access Sentry is through HTTP, this can be done by +using a DSN using this form: + + http://public:private@host:port/1 + +If not provided, the port will default to `80`. + +### HTTPS +It is possible to use an encrypted connection to Sentry using HTTPS: + + https://public:private@host:port/1 + +If not provided, the port will default to `443`. +### HTTPS (naive) +If the certificate used over HTTPS is a wildcard certificate (which is not +handled by every version of Java), and the certificate isn't added to the +truststore, it is possible to add a protocol setting to tell the client to be +naive and ignore the hostname verification: + + naive+https://public:private@host:port/1 + +### UDP +It is possible to use a DSN with the UDP protocol: + + udp://public:private@host:port/1 + +If not provided the port will default to `9001`. + +While being faster because there is no TCP and HTTP overhead, UDP doesn't wait +for a reply, and if a connection problem occurs, there will be no notification. \ No newline at end of file diff --git a/README.md b/README.md index 089de209683..ebf72bf5867 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,10 @@ project also provides tools allowing the most popular logging frameworks to send the logs directly to sentry: - `java.util.logging` is supported out of the box. - - `raven-log4j` adds the support for [log4j](https://logging.apache.org/log4j/1.2/). - - `raven-log4j2` adds the support for [log4j2](https://logging.apache.org/log4j/2.x/). + - `raven-log4j` adds the support for + [log4j](https://logging.apache.org/log4j/1.2/). + - `raven-log4j2` adds the support for + [log4j2](https://logging.apache.org/log4j/2.x/). - `raven-logback` adds the support for [logback](http://logback.qos.ch/). Raven-Java supports both HTTP(S) and UDP transport of events. @@ -29,143 +31,8 @@ reason, only the last two major versions of Raven-Java are maintained. - Sentry protocol v2 is available since Sentry 2.0 (use Raven-Java 2.0) ## Build and Installation -Currently there are 6 modules in the project: - - - `raven`, the core of the project, providing the client and support for JUL - - `raven-legacy`, support of the Raven-Java 2.0 API (it's recommended to move - to the new API as legacy will be removed in the future releases of Raven-Java) - - `raven-log4j`, Appender for log4j - - `raven-log4j2`, Appender for log4j2 - - `raven-logback`, Appander for Logback - - `sentry-stub`, Sentry server stub, allowing to test the protocol - -### Build -It's possible to get the latest version of Raven-Java by building it from the -sources. - - $ git clone https://github.com/kencochrane/raven-java.git - $ cd raven-java - $ mvn clean install -DskipTests=true - -_Due to a [known issue](https://bugs.eclipse.org/bugs/show_bug.cgi?id=405631) in -jetty, it is currently not possible to run the integration tests from the main -project. They have to be run manually on each module independently._ - -### Maven dependency -To add raven-java as a dependency, simply add this to your pom.xml: - - - net.kencochrane.raven - raven - 4.0-SNAPSHOT - - -You can add the other modules the same way (replacing the `artifactId`) with the -name of the module. - -### Manual installation - -## Using Raven-Java - -### Manual usage - - import net.kencochrane.raven.Raven; - import net.kencochrane.raven.event.EventBuilder; - - public class Example { - public static void main(String[] args) { - // The DSN from Sentry: "http://public:private@host:port/1" - String rawDsn = args[0]; - Raven client = new Raven(rawDsn); - EventBuilder eventBuilder = new EventBuilder() - .setMessage("Hello from Raven-Java!"); - client.sendEvent(eventBuilder.build()); - } - } - -It is also possible to create a client without directly providing a DSN, -the DSN will then be determined at runtime (when the client is created). - -The client will lookup for the first DSN configuration provided: - - - JNDI in `java:comp/env/sentry/dsn` - - The environment variable `SENTRY_DSN` - (`export SENTRY_DSN=yoursentrydsn` or `setenv SENTRYDSN yoursentrydsn`) - - The system property `SENTRY_DSN` (`-DSENTRY_DSN=yoursentrydsn`) - - import net.kencochrane.raven.Raven; - import net.kencochrane.raven.event.EventBuilder; - - public class Example { - public static void main(String[] args) { - Raven client = new Raven(); - EventBuilder eventBuilder = new EventBuilder() - .setMessage("Hello from Raven-Java!"); - client.sendEvent(eventBuilder.build()); - } - } - -### Using `java.util.logging` -TODO - -### Using log4j -TODO - -#### Asynchronous logging with AsyncAppander -log4j supports asynchronous logging with -[AsyncAppender](http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/AsyncAppender.html). -While this is a common solution to avoid blocking the current thread until the -event is sent to Sentry, it is recommended to use instead the option -`raven.async` to enable asynchronous logging for raven. - -Some - -### Using log4j2 -**TODO** - -### Using logback -**TODO** - -### Capturing the HTTP environment -**TODO** - -## Connection and protocol -It is possible to send events to Sentry over different protocols, depending -on the security and performance requirements. -So far Sentry accepts HTTP(S) and UDP which are both fully supported by -Raven-Java. - -### HTTP -The most common way to access Sentry is through HTTP, this can be done by -using a DSN using this form: - - http://public:private@host:port/1 - -If not provided, the port will default to `80`. - -### HTTPS -It is possible to use an encrypted connection to Sentry using HTTPS: - - https://public:private@host:port/1 - -If not provided, the port will default to `443`. -### HTTPS (naive) -If the certificate used over HTTPS is a wildcard certificate (which is not -handled by every version of Java), and the certificate isn't added to the -truststore, it is possible to add a protocol setting to tell the client to be -naive and ignore the hostname verification: - - naive+https://public:private@host:port/1 - -### UDP -It is possible to use a DSN with the UDP protocol: - - udp://public:private@host:port/1 - -If not provided the port will default to `9001`. - -While being faster because there is no TCP and HTTP overhead, UDP doesn't wait -for a reply, and if a connection problem occurs, there will be no notification. +**See +[INSTALL.md](https://github.com/kencochrane/raven-java/blob/master/INSTALL.md)** ## Options It is possible to enable some options by adding data to the query string of the From 3b5b9694645fdb65af613e7a968ed94c6dc33468 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 17:32:25 +0100 Subject: [PATCH 0409/2152] Make the JNDI name a constant --- raven/src/main/java/net/kencochrane/raven/Dsn.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Dsn.java b/raven/src/main/java/net/kencochrane/raven/Dsn.java index f7ea4a8bd9b..6b9258aca5c 100644 --- a/raven/src/main/java/net/kencochrane/raven/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/Dsn.java @@ -36,6 +36,10 @@ public class Dsn { * Protocol setting to disable security checks over an SSL connection. */ public static final String NAIVE_PROTOCOL = "naive"; + /** + * Lookup name for the DSN in JNDI. + */ + private static final String JNDI_DSN_NAME = "java:comp/env/sentry/dsn"; private static final Logger logger = Logger.getLogger(Raven.class.getCanonicalName()); private String secretKey; private String publicKey; @@ -97,7 +101,7 @@ public static String dsnLookup() { // Try to obtain the DSN from JNDI try { Context c = new InitialContext(); - dsn = (String) c.lookup("java:comp/env/sentry/dsn"); + dsn = (String) c.lookup(JNDI_DSN_NAME); } catch (NoInitialContextException e) { logger.log(Level.INFO, "JNDI not configured for sentry (NoInitialContextEx)"); } catch (NamingException e) { From 0cac4056983d41d22502b73e343c84e51f4299db Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 17:35:35 +0100 Subject: [PATCH 0410/2152] Add an empty line at the end of INSTALL.md --- INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index 9c067e47208..5c28e016e44 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -135,4 +135,4 @@ It is possible to use a DSN with the UDP protocol: If not provided the port will default to `9001`. While being faster because there is no TCP and HTTP overhead, UDP doesn't wait -for a reply, and if a connection problem occurs, there will be no notification. \ No newline at end of file +for a reply, and if a connection problem occurs, there will be no notification. From 999a86e7502a65275090249891498c5349bdb565 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 17:55:52 +0100 Subject: [PATCH 0411/2152] Change the groupId in the submodules to match the parent --- raven-integration-tests/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/raven-integration-tests/pom.xml b/raven-integration-tests/pom.xml index 8e2e7bcc0b5..810e6c7a452 100644 --- a/raven-integration-tests/pom.xml +++ b/raven-integration-tests/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - net.kencochrane + net.kencochrane.raven raven-integration-tests 2.0-SNAPSHOT jar diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 33cf651eb94..0ca9349f98d 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ 4.0.0 - net.kencochrane + net.kencochrane.raven raven-all 2.0-SNAPSHOT diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 932f1a7a2b2..5eba52a2229 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ 4.0.0 - net.kencochrane + net.kencochrane.raven raven-all 2.0-SNAPSHOT diff --git a/raven/pom.xml b/raven/pom.xml index 7f7efe2dba0..84d07d418a1 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ 4.0.0 - net.kencochrane + net.kencochrane.raven raven-all 2.0-SNAPSHOT From ec2bdcd7c35700914482e2876462330712dfc670 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 18:04:21 +0100 Subject: [PATCH 0412/2152] Remove now deprecated documents --- docs/allclasses-frame.html | 35 - docs/allclasses-noframe.html | 35 - docs/constant-values.html | 142 ---- docs/deprecated-list.html | 142 ---- docs/help-doc.html | 209 ------ docs/index-files/index-1.html | 141 ---- docs/index-files/index-2.html | 150 ----- docs/index-files/index-3.html | 196 ------ docs/index-files/index-4.html | 142 ---- docs/index-files/index-5.html | 138 ---- docs/index-files/index-6.html | 155 ----- docs/index-files/index-7.html | 170 ----- docs/index-files/index-8.html | 141 ---- docs/index-files/index-9.html | 141 ---- docs/index.html | 36 -- docs/net/kencochrane/sentry/RavenConfig.html | 609 ------------------ docs/net/kencochrane/sentry/RavenUtils.html | 509 --------------- .../kencochrane/sentry/SentryAppender.html | 402 ------------ .../net/kencochrane/sentry/SentryExample.html | 252 -------- .../net/kencochrane/sentry/package-frame.html | 36 -- .../kencochrane/sentry/package-summary.html | 168 ----- docs/net/kencochrane/sentry/package-tree.html | 149 ----- docs/overview-tree.html | 151 ----- docs/package-list | 1 - docs/resources/inherit.gif | Bin 57 -> 0 bytes docs/stylesheet.css | 29 - 26 files changed, 4279 deletions(-) delete mode 100644 docs/allclasses-frame.html delete mode 100644 docs/allclasses-noframe.html delete mode 100644 docs/constant-values.html delete mode 100644 docs/deprecated-list.html delete mode 100644 docs/help-doc.html delete mode 100644 docs/index-files/index-1.html delete mode 100644 docs/index-files/index-2.html delete mode 100644 docs/index-files/index-3.html delete mode 100644 docs/index-files/index-4.html delete mode 100644 docs/index-files/index-5.html delete mode 100644 docs/index-files/index-6.html delete mode 100644 docs/index-files/index-7.html delete mode 100644 docs/index-files/index-8.html delete mode 100644 docs/index-files/index-9.html delete mode 100644 docs/index.html delete mode 100644 docs/net/kencochrane/sentry/RavenConfig.html delete mode 100644 docs/net/kencochrane/sentry/RavenUtils.html delete mode 100644 docs/net/kencochrane/sentry/SentryAppender.html delete mode 100644 docs/net/kencochrane/sentry/SentryExample.html delete mode 100644 docs/net/kencochrane/sentry/package-frame.html delete mode 100644 docs/net/kencochrane/sentry/package-summary.html delete mode 100644 docs/net/kencochrane/sentry/package-tree.html delete mode 100644 docs/overview-tree.html delete mode 100644 docs/package-list delete mode 100644 docs/resources/inherit.gif delete mode 100644 docs/stylesheet.css diff --git a/docs/allclasses-frame.html b/docs/allclasses-frame.html deleted file mode 100644 index e7b4c4f89a4..00000000000 --- a/docs/allclasses-frame.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - -All Classes - - - - - - - - - - - -All Classes -
    - - - - - -
    RavenConfig -
    -RavenUtils -
    -SentryAppender -
    -
    - - - diff --git a/docs/allclasses-noframe.html b/docs/allclasses-noframe.html deleted file mode 100644 index 2752d537041..00000000000 --- a/docs/allclasses-noframe.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - -All Classes - - - - - - - - - - - -All Classes -
    - - - - - -
    RavenConfig -
    -RavenUtils -
    -SentryAppender -
    -
    - - - diff --git a/docs/constant-values.html b/docs/constant-values.html deleted file mode 100644 index 537ac550158..00000000000 --- a/docs/constant-values.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - -Constant Field Values - - - - - - - - - - - - -


    - - - - - - - - - - - - - - - -
    - -
    - - - -
    -
    -

    -Constant Field Values

    -
    -
    -Contents
      -
    - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/deprecated-list.html b/docs/deprecated-list.html deleted file mode 100644 index a018fbe56d6..00000000000 --- a/docs/deprecated-list.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - -Deprecated List - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    -
    -

    -Deprecated API

    -
    -
    -Contents
      -
    - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/help-doc.html b/docs/help-doc.html deleted file mode 100644 index f2ea01c3dc4..00000000000 --- a/docs/help-doc.html +++ /dev/null @@ -1,209 +0,0 @@ - - - - - - -API Help - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    -
    -

    -How This API Document Is Organized

    -
    -This API (Application Programming Interface) document has pages corresponding to the items in the navigation bar, described as follows.

    -Package

    -
    - -

    -Each package has a page that contains a list of its classes and interfaces, with a summary for each. This page can contain four categories:

      -
    • Interfaces (italic)
    • Classes
    • Enums
    • Exceptions
    • Errors
    • Annotation Types
    -
    -

    -Class/Interface

    -
    - -

    -Each class, interface, nested class and nested interface has its own separate page. Each of these pages has three sections consisting of a class/interface description, summary tables, and detailed member descriptions:

      -
    • Class inheritance diagram
    • Direct Subclasses
    • All Known Subinterfaces
    • All Known Implementing Classes
    • Class/interface declaration
    • Class/interface description -

      -

    • Nested Class Summary
    • Field Summary
    • Constructor Summary
    • Method Summary -

      -

    • Field Detail
    • Constructor Detail
    • Method Detail
    -Each summary entry contains the first sentence from the detailed description for that item. The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.
    - -

    -Annotation Type

    -
    - -

    -Each annotation type has its own separate page with the following sections:

      -
    • Annotation Type declaration
    • Annotation Type description
    • Required Element Summary
    • Optional Element Summary
    • Element Detail
    -
    - -

    -Enum

    -
    - -

    -Each enum has its own separate page with the following sections:

      -
    • Enum declaration
    • Enum description
    • Enum Constant Summary
    • Enum Constant Detail
    -
    -

    -Tree (Class Hierarchy)

    -
    -There is a Class Hierarchy page for all packages, plus a hierarchy for each package. Each hierarchy page contains a list of classes and a list of interfaces. The classes are organized by inheritance structure starting with java.lang.Object. The interfaces do not inherit from java.lang.Object.
      -
    • When viewing the Overview page, clicking on "Tree" displays the hierarchy for all packages.
    • When viewing a particular package, class or interface page, clicking "Tree" displays the hierarchy for only that package.
    -
    -

    -Deprecated API

    -
    -The Deprecated API page lists all of the API that have been deprecated. A deprecated API is not recommended for use, generally due to improvements, and a replacement API is usually given. Deprecated APIs may be removed in future implementations.
    -

    -Index

    -
    -The Index contains an alphabetic list of all classes, interfaces, constructors, methods, and fields.
    -

    -Prev/Next

    -These links take you to the next or previous class, interface, package, or related page.

    -Frames/No Frames

    -These links show and hide the HTML frames. All pages are available with or without frames. -

    -

    -Serialized Form

    -Each serializable or externalizable class has a description of its serialization fields and methods. This information is of interest to re-implementors, not to developers using the API. While there is no link in the navigation bar, you can get to this information by going to any serialized class and clicking "Serialized Form" in the "See also" section of the class description. -

    -

    -Constant Field Values

    -The Constant Field Values page lists the static final fields and their values. -

    - - -This help file applies to API documentation generated using the standard doclet. - -
    -


    - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/index-files/index-1.html b/docs/index-files/index-1.html deleted file mode 100644 index cfde7705385..00000000000 --- a/docs/index-files/index-1.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - -A-Index - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    -

    -A

    -
    -
    append(LoggingEvent) - -Method in class net.kencochrane.sentry.SentryAppender -
      -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    - - - diff --git a/docs/index-files/index-2.html b/docs/index-files/index-2.html deleted file mode 100644 index 35d190bea13..00000000000 --- a/docs/index-files/index-2.html +++ /dev/null @@ -1,150 +0,0 @@ - - - - - - -C-Index - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    -

    -C

    -
    -
    calculateChecksum(String) - -Static method in class net.kencochrane.sentry.RavenUtils -
    An almost-unique hash identifying the this event to improve aggregation. -
    calculateHMAC(String, String) - -Static method in class net.kencochrane.sentry.RavenUtils -
    Computes RFC 2104-compliant HMAC signature. -
    close() - -Method in class net.kencochrane.sentry.SentryAppender -
      -
    compressAndEncode(String) - -Static method in class net.kencochrane.sentry.RavenUtils -
    Gzip then base64 encode the str value -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    - - - diff --git a/docs/index-files/index-3.html b/docs/index-files/index-3.html deleted file mode 100644 index f7a73534998..00000000000 --- a/docs/index-files/index-3.html +++ /dev/null @@ -1,196 +0,0 @@ - - - - - - -G-Index - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    -

    -G

    -
    -
    getDateAsISO8601String(Date) - -Static method in class net.kencochrane.sentry.RavenUtils -
    Convert a java date into the ISO8601 format YYYYMMDDTHH:mm:ss+HH:00 - note the added colon for the Timezone -
    getHost() - -Method in class net.kencochrane.sentry.RavenConfig -
    The sentry server host -
    getHostname() - -Static method in class net.kencochrane.sentry.RavenUtils -
    Get the hostname for the system throwing the error. -
    getPath() - -Method in class net.kencochrane.sentry.RavenConfig -
    sentry url path -
    getPort() - -Method in class net.kencochrane.sentry.RavenConfig -
    sentry server port -
    getProjectId() - -Method in class net.kencochrane.sentry.RavenConfig -
    Sentry project Id -
    getProtocol() - -Method in class net.kencochrane.sentry.RavenConfig -
    Sentry server protocol http https? -
    getProxy() - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    getProxy() - -Method in class net.kencochrane.sentry.SentryAppender -
      -
    getPublicKey() - -Method in class net.kencochrane.sentry.RavenConfig -
    The Sentry public key -
    getRandomUUID() - -Static method in class net.kencochrane.sentry.RavenUtils -
    Hexadecimal string representing a uuid4 value. -
    getSecretKey() - -Method in class net.kencochrane.sentry.RavenConfig -
    The Sentry secret key -
    getSentry_dsn() - -Method in class net.kencochrane.sentry.SentryAppender -
      -
    getSentryURL() - -Method in class net.kencochrane.sentry.RavenConfig -
    The Sentry server URL that we post the message to. -
    getSignature(String, long, String) - -Static method in class net.kencochrane.sentry.RavenUtils -
    build the HMAC sentry signature -

    - The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, - and an arbitrary client version string. -

    getTimestampLong() - -Static method in class net.kencochrane.sentry.RavenUtils -
    Get the timestamp for right now as a long -
    getTimestampString(long) - -Static method in class net.kencochrane.sentry.RavenUtils -
    Given a long timestamp convert to a ISO8601 formatted date string -
    getTimestampString() - -Static method in class net.kencochrane.sentry.RavenUtils -
    Given the time right now return a ISO8601 formatted date string -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    - - - diff --git a/docs/index-files/index-4.html b/docs/index-files/index-4.html deleted file mode 100644 index 356aa1c1dde..00000000000 --- a/docs/index-files/index-4.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - -H-Index - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    -

    -H

    -
    -
    hexEncode(byte[]) - -Static method in class net.kencochrane.sentry.RavenUtils -
    The byte[] returned by MessageDigest does not have a nice - textual representation, so some form of encoding is usually performed. -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    - - - diff --git a/docs/index-files/index-5.html b/docs/index-files/index-5.html deleted file mode 100644 index 7e13001cf05..00000000000 --- a/docs/index-files/index-5.html +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - -N-Index - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    -

    -N

    -
    -
    net.kencochrane.sentry - package net.kencochrane.sentry
     
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    - - - diff --git a/docs/index-files/index-6.html b/docs/index-files/index-6.html deleted file mode 100644 index 9c15e42b7a3..00000000000 --- a/docs/index-files/index-6.html +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - -R-Index - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    -

    -R

    -
    -
    RavenConfig - Class in net.kencochrane.sentry
    User: ken cochrane - Date: 2/8/12 - Time: 1:16 PM
    RavenConfig(String) - -Constructor for class net.kencochrane.sentry.RavenConfig -
    Takes in a sentryDSN and builds up the configuration -
    RavenConfig(String, String) - -Constructor for class net.kencochrane.sentry.RavenConfig -
    Takes in a sentryDSN and builds up the configuration -
    RavenUtils - Class in net.kencochrane.sentry
    User: ken Cochrane - Date: 2/10/12 - Time: 10:43 AM - Utility class for the Raven client.
    RavenUtils() - -Constructor for class net.kencochrane.sentry.RavenUtils -
      -
    requiresLayout() - -Method in class net.kencochrane.sentry.SentryAppender -
      -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    - - - diff --git a/docs/index-files/index-7.html b/docs/index-files/index-7.html deleted file mode 100644 index 1ea2f3449b7..00000000000 --- a/docs/index-files/index-7.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - -S-Index - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    -

    -S

    -
    -
    SentryAppender - Class in net.kencochrane.sentry
    User: ken cochrane - Date: 2/6/12 - Time: 10:54 AM
    SentryAppender() - -Constructor for class net.kencochrane.sentry.SentryAppender -
      -
    setHost(String) - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    setPath(String) - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    setPort(int) - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    setProjectId(String) - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    setProtocol(String) - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    setProxy(String) - -Method in class net.kencochrane.sentry.SentryAppender -
      -
    setPublicKey(String) - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    setSecretKey(String) - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    setSentry_dsn(String) - -Method in class net.kencochrane.sentry.SentryAppender -
      -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    - - - diff --git a/docs/index-files/index-8.html b/docs/index-files/index-8.html deleted file mode 100644 index 52856f4706b..00000000000 --- a/docs/index-files/index-8.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - -T-Index - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    -

    -T

    -
    -
    toString() - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H N R S T
    - - - diff --git a/docs/index-files/index-9.html b/docs/index-files/index-9.html deleted file mode 100644 index 86c49a23322..00000000000 --- a/docs/index-files/index-9.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - -T-Index - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H M N R S T
    -

    -T

    -
    -
    toString() - -Method in class net.kencochrane.sentry.RavenConfig -
      -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -A C G H M N R S T
    - - - diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 7105375e378..00000000000 --- a/docs/index.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - -Generated Documentation (Untitled) - - - - - - - - -<H2> -Frame Alert</H2> - -<P> -This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client. -<BR> -Link to<A HREF="net/kencochrane/sentry/package-summary.html">Non-frame version.</A> - - - diff --git a/docs/net/kencochrane/sentry/RavenConfig.html b/docs/net/kencochrane/sentry/RavenConfig.html deleted file mode 100644 index ebaf0884315..00000000000 --- a/docs/net/kencochrane/sentry/RavenConfig.html +++ /dev/null @@ -1,609 +0,0 @@ - - - - - - -RavenConfig - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - -
    - -
    - - - -
    - -

    - -net.kencochrane.sentry -
    -Class RavenConfig

    -
    -java.lang.Object
    -  extended by net.kencochrane.sentry.RavenConfig
    -
    -
    -
    -
    public class RavenConfig
    extends java.lang.Object
    - - -

    -User: ken cochrane - Date: 2/8/12 - Time: 1:16 PM -

    - -

    -


    - -

    - - - - - - - - - - - - - - -
    -Constructor Summary
    RavenConfig(java.lang.String sentryDSN) - -
    -          Takes in a sentryDSN and builds up the configuration
    RavenConfig(java.lang.String sentryDSN, - java.lang.String proxy) - -
    -          Takes in a sentryDSN and builds up the configuration
    -  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -Method Summary
    - java.lang.StringgetHost() - -
    -          The sentry server host
    - java.lang.StringgetPath() - -
    -          sentry url path
    - intgetPort() - -
    -          sentry server port
    - java.lang.StringgetProjectId() - -
    -          Sentry project Id
    - java.lang.StringgetProtocol() - -
    -          Sentry server protocol http https?
    - java.net.ProxygetProxy() - -
    -           
    - java.lang.StringgetPublicKey() - -
    -          The Sentry public key
    - java.lang.StringgetSecretKey() - -
    -          The Sentry secret key
    - java.lang.StringgetSentryURL() - -
    -          The Sentry server URL that we post the message to.
    - voidsetHost(java.lang.String host) - -
    -           
    - voidsetPath(java.lang.String path) - -
    -           
    - voidsetPort(int port) - -
    -           
    - voidsetProjectId(java.lang.String projectId) - -
    -           
    - voidsetProtocol(java.lang.String protocol) - -
    -           
    - voidsetPublicKey(java.lang.String publicKey) - -
    -           
    - voidsetSecretKey(java.lang.String secretKey) - -
    -           
    - java.lang.StringtoString() - -
    -           
    - - - - - - - -
    Methods inherited from class java.lang.Object
    clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
    -  -

    - - - - - - - - -
    -Constructor Detail
    - -

    -RavenConfig

    -
    -public RavenConfig(java.lang.String sentryDSN)
    -
    -
    Takes in a sentryDSN and builds up the configuration -

    -

    -
    Parameters:
    sentryDSN - '{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}{PROJECT_ID}'
    -
    -
    - -

    -RavenConfig

    -
    -public RavenConfig(java.lang.String sentryDSN,
    -                   java.lang.String proxy)
    -
    -
    Takes in a sentryDSN and builds up the configuration -

    -

    -
    Parameters:
    sentryDSN - '{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}{PROJECT_ID}'
    proxy - proxy to use for the HTTP connections; blank or null when no proxy is to be used
    -
    - - - - - - - - -
    -Method Detail
    - -

    -getSentryURL

    -
    -public java.lang.String getSentryURL()
    -
    -
    The Sentry server URL that we post the message to. -

    -

    - -
    Returns:
    sentry server url
    -
    -
    -
    - -

    -getProxy

    -
    -public java.net.Proxy getProxy()
    -
    -
    -
    -
    -
    -
    - -

    -getHost

    -
    -public java.lang.String getHost()
    -
    -
    The sentry server host -

    -

    - -
    Returns:
    server host
    -
    -
    -
    - -

    -setHost

    -
    -public void setHost(java.lang.String host)
    -
    -
    -
    -
    -
    -
    - -

    -getProtocol

    -
    -public java.lang.String getProtocol()
    -
    -
    Sentry server protocol http https? -

    -

    - -
    Returns:
    http or https
    -
    -
    -
    - -

    -setProtocol

    -
    -public void setProtocol(java.lang.String protocol)
    -
    -
    -
    -
    -
    -
    - -

    -getPublicKey

    -
    -public java.lang.String getPublicKey()
    -
    -
    The Sentry public key -

    -

    - -
    Returns:
    Sentry public key
    -
    -
    -
    - -

    -setPublicKey

    -
    -public void setPublicKey(java.lang.String publicKey)
    -
    -
    -
    -
    -
    -
    - -

    -getSecretKey

    -
    -public java.lang.String getSecretKey()
    -
    -
    The Sentry secret key -

    -

    - -
    Returns:
    Sentry secret key
    -
    -
    -
    - -

    -setSecretKey

    -
    -public void setSecretKey(java.lang.String secretKey)
    -
    -
    -
    -
    -
    -
    - -

    -getPath

    -
    -public java.lang.String getPath()
    -
    -
    sentry url path -

    -

    - -
    Returns:
    url path
    -
    -
    -
    - -

    -setPath

    -
    -public void setPath(java.lang.String path)
    -
    -
    -
    -
    -
    -
    - -

    -getProjectId

    -
    -public java.lang.String getProjectId()
    -
    -
    Sentry project Id -

    -

    - -
    Returns:
    project Id
    -
    -
    -
    - -

    -setProjectId

    -
    -public void setProjectId(java.lang.String projectId)
    -
    -
    -
    -
    -
    -
    - -

    -getPort

    -
    -public int getPort()
    -
    -
    sentry server port -

    -

    - -
    Returns:
    server port
    -
    -
    -
    - -

    -setPort

    -
    -public void setPort(int port)
    -
    -
    -
    -
    -
    -
    - -

    -toString

    -
    -public java.lang.String toString()
    -
    -
    -
    Overrides:
    toString in class java.lang.Object
    -
    -
    -
    -
    -
    - -
    - - - - - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/net/kencochrane/sentry/RavenUtils.html b/docs/net/kencochrane/sentry/RavenUtils.html deleted file mode 100644 index 086068cd946..00000000000 --- a/docs/net/kencochrane/sentry/RavenUtils.html +++ /dev/null @@ -1,509 +0,0 @@ - - - - - - -RavenUtils - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - -
    - -
    - - - -
    - -

    - -net.kencochrane.sentry -
    -Class RavenUtils

    -
    -java.lang.Object
    -  extended by net.kencochrane.sentry.RavenUtils
    -
    -
    -
    -
    public class RavenUtils
    extends java.lang.Object
    - - -

    -User: ken Cochrane - Date: 2/10/12 - Time: 10:43 AM - Utility class for the Raven client. -

    - -

    -


    - -

    - - - - - - - - - - - -
    -Constructor Summary
    RavenUtils() - -
    -           
    -  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -Method Summary
    -static java.lang.StringcalculateChecksum(java.lang.String message) - -
    -          An almost-unique hash identifying the this event to improve aggregation.
    -static java.lang.StringcalculateHMAC(java.lang.String data, - java.lang.String key) - -
    -          Computes RFC 2104-compliant HMAC signature.
    -static java.lang.StringcompressAndEncode(java.lang.String str) - -
    -          Gzip then base64 encode the str value
    -static java.lang.StringgetDateAsISO8601String(java.util.Date date) - -
    -          Convert a java date into the ISO8601 format YYYYMMDDTHH:mm:ss+HH:00 - note the added colon for the Timezone
    -static java.lang.StringgetHostname() - -
    -          Get the hostname for the system throwing the error.
    -static java.lang.StringgetRandomUUID() - -
    -          Hexadecimal string representing a uuid4 value.
    -static java.lang.StringgetSignature(java.lang.String message, - long timestamp, - java.lang.String key) - -
    -          build the HMAC sentry signature -

    - The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, - and an arbitrary client version string.

    -static longgetTimestampLong() - -
    -          Get the timestamp for right now as a long
    -static java.lang.StringgetTimestampString() - -
    -          Given the time right now return a ISO8601 formatted date string
    -static java.lang.StringgetTimestampString(long timestamp) - -
    -          Given a long timestamp convert to a ISO8601 formatted date string
    -static java.lang.StringhexEncode(byte[] aInput) - -
    -          The byte[] returned by MessageDigest does not have a nice - textual representation, so some form of encoding is usually performed.
    - - - - - - - -
    Methods inherited from class java.lang.Object
    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    -  -

    - - - - - - - - -
    -Constructor Detail
    - -

    -RavenUtils

    -
    -public RavenUtils()
    -
    -
    - - - - - - - - -
    -Method Detail
    - -

    -calculateHMAC

    -
    -public static java.lang.String calculateHMAC(java.lang.String data,
    -                                             java.lang.String key)
    -                                      throws java.security.SignatureException
    -
    -
    Computes RFC 2104-compliant HMAC signature. - Based off of the sample here. http://docs.amazonwebservices.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AuthJavaSampleHMACSignature.html -

    -

    -
    Parameters:
    data - The data to be signed.
    key - The signing key. -
    Returns:
    The hex-encoded RFC 2104-compliant HMAC signature. -
    Throws: -
    java.security.SignatureException - when signature generation fails
    -
    -
    -
    - -

    -getHostname

    -
    -public static java.lang.String getHostname()
    -
    -
    Get the hostname for the system throwing the error. -

    -

    - -
    Returns:
    The hostname for the server
    -
    -
    -
    - -

    -getDateAsISO8601String

    -
    -public static java.lang.String getDateAsISO8601String(java.util.Date date)
    -
    -
    Convert a java date into the ISO8601 format YYYYMMDDTHH:mm:ss+HH:00 - note the added colon for the Timezone -

    -

    -
    Parameters:
    date - the date to convert -
    Returns:
    ISO8601 formatted date as a String
    -
    -
    -
    - -

    -getTimestampLong

    -
    -public static long getTimestampLong()
    -
    -
    Get the timestamp for right now as a long -

    -

    - -
    Returns:
    timestamp for now as long
    -
    -
    -
    - -

    -getTimestampString

    -
    -public static java.lang.String getTimestampString(long timestamp)
    -
    -
    Given a long timestamp convert to a ISO8601 formatted date string -

    -

    -
    Parameters:
    timestamp - the timestamp to convert -
    Returns:
    ISO8601 formatted date string
    -
    -
    -
    - -

    -getTimestampString

    -
    -public static java.lang.String getTimestampString()
    -
    -
    Given the time right now return a ISO8601 formatted date string -

    -

    - -
    Returns:
    ISO8601 formatted date string
    -
    -
    -
    - -

    -getSignature

    -
    -public static java.lang.String getSignature(java.lang.String message,
    -                                            long timestamp,
    -                                            java.lang.String key)
    -
    -
    build the HMAC sentry signature -

    - The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, - and an arbitrary client version string. -

    - The client version should be something distinct to your client, and is simply for reporting purposes. - To generate the HMAC signature, take the following example (in Python): -

    - hmac.new(public_key, '%s %s' % (timestamp, message), hashlib.sha1).hexdigest() -

    -

    -
    Parameters:
    message - the error message to send to sentry
    timestamp - the timestamp for when the message was created
    key - sentry public key -
    Returns:
    SHA1-signed HMAC string
    -
    -
    -
    - -

    -hexEncode

    -
    -public static java.lang.String hexEncode(byte[] aInput)
    -
    -
    The byte[] returned by MessageDigest does not have a nice - textual representation, so some form of encoding is usually performed. -

    - This implementation follows the example of David Flanagan's book - "Java In A Nutshell", and converts a byte array into a String - of hex characters. -

    - Another popular alternative is to use a "Base64" encoding. -

    -

    -
    Parameters:
    aInput - what we are hex encoding -
    Returns:
    hex encoded string.
    -
    -
    -
    - -

    -calculateChecksum

    -
    -public static java.lang.String calculateChecksum(java.lang.String message)
    -
    -
    An almost-unique hash identifying the this event to improve aggregation. -

    -

    -
    Parameters:
    message - The message we are sending to sentry -
    Returns:
    CRC32 Checksum string
    -
    -
    -
    - -

    -getRandomUUID

    -
    -public static java.lang.String getRandomUUID()
    -
    -
    Hexadecimal string representing a uuid4 value. -

    -

    - -
    Returns:
    Hexadecimal UUID4 String
    -
    -
    -
    - -

    -compressAndEncode

    -
    -public static java.lang.String compressAndEncode(java.lang.String str)
    -
    -
    Gzip then base64 encode the str value -

    -

    -
    Parameters:
    str - the value we want to compress and encode. -
    Returns:
    Base64 encoded compressed version of the string passed in.
    -
    -
    - -
    - - - - - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/net/kencochrane/sentry/SentryAppender.html b/docs/net/kencochrane/sentry/SentryAppender.html deleted file mode 100644 index a3237462969..00000000000 --- a/docs/net/kencochrane/sentry/SentryAppender.html +++ /dev/null @@ -1,402 +0,0 @@ - - - - - - -SentryAppender - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - -
    - -
    - - - -
    - -

    - -net.kencochrane.sentry -
    -Class SentryAppender

    -
    -java.lang.Object
    -  extended by org.apache.log4j.AppenderSkeleton
    -      extended by net.kencochrane.sentry.SentryAppender
    -
    -
    -
    All Implemented Interfaces:
    org.apache.log4j.Appender, org.apache.log4j.spi.OptionHandler
    -
    -
    -
    -
    public class SentryAppender
    extends org.apache.log4j.AppenderSkeleton
    - - -

    -User: ken cochrane - Date: 2/6/12 - Time: 10:54 AM -

    - -

    -


    - -

    - - - - - - - -
    -Field Summary
    - - - - - - - -
    Fields inherited from class org.apache.log4j.AppenderSkeleton
    closed, errorHandler, headFilter, layout, name, tailFilter, threshold
    -  - - - - - - - - - - -
    -Constructor Summary
    SentryAppender() - -
    -           
    -  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -Method Summary
    -protected  voidappend(org.apache.log4j.spi.LoggingEvent loggingEvent) - -
    -           
    - voidclose() - -
    -           
    - java.lang.StringgetProxy() - -
    -           
    - java.lang.StringgetSentry_dsn() - -
    -           
    - booleanrequiresLayout() - -
    -           
    - voidsetProxy(java.lang.String proxy) - -
    -           
    - voidsetSentry_dsn(java.lang.String sentry_dsn) - -
    -           
    - - - - - - - -
    Methods inherited from class org.apache.log4j.AppenderSkeleton
    activateOptions, addFilter, clearFilters, doAppend, finalize, getErrorHandler, getFilter, getFirstFilter, getLayout, getName, getThreshold, isAsSevereAsThreshold, setErrorHandler, setLayout, setName, setThreshold
    - - - - - - - -
    Methods inherited from class java.lang.Object
    clone, equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    -  -

    - - - - - - - - -
    -Constructor Detail
    - -

    -SentryAppender

    -
    -public SentryAppender()
    -
    -
    - - - - - - - - -
    -Method Detail
    - -

    -getSentry_dsn

    -
    -public java.lang.String getSentry_dsn()
    -
    -
    -
    -
    -
    -
    - -

    -setSentry_dsn

    -
    -public void setSentry_dsn(java.lang.String sentry_dsn)
    -
    -
    -
    -
    -
    -
    - -

    -getProxy

    -
    -public java.lang.String getProxy()
    -
    -
    -
    -
    -
    -
    - -

    -setProxy

    -
    -public void setProxy(java.lang.String proxy)
    -
    -
    -
    -
    -
    -
    - -

    -append

    -
    -protected void append(org.apache.log4j.spi.LoggingEvent loggingEvent)
    -
    -
    -
    Specified by:
    append in class org.apache.log4j.AppenderSkeleton
    -
    -
    -
    -
    -
    -
    - -

    -close

    -
    -public void close()
    -
    -
    -
    -
    -
    -
    - -

    -requiresLayout

    -
    -public boolean requiresLayout()
    -
    -
    -
    -
    -
    - -
    - - - - - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/net/kencochrane/sentry/SentryExample.html b/docs/net/kencochrane/sentry/SentryExample.html deleted file mode 100644 index de225a0af7e..00000000000 --- a/docs/net/kencochrane/sentry/SentryExample.html +++ /dev/null @@ -1,252 +0,0 @@ - - - - - - -SentryExample - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - -
    - -
    - - - -
    - -

    - -net.kencochrane.sentry -
    -Class SentryExample

    -
    -java.lang.Object
    -  extended by net.kencochrane.sentry.SentryExample
    -
    -
    -
    -
    public class SentryExample
    extends java.lang.Object
    - - -

    -Simple example used to test out the sentry logger. -

    - -

    -


    - -

    - - - - - - - - - - - -
    -Constructor Summary
    SentryExample() - -
    -           
    -  - - - - - - - - - - - -
    -Method Summary
    -static voidmain(java.lang.String[] args) - -
    -           
    - - - - - - - -
    Methods inherited from class java.lang.Object
    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    -  -

    - - - - - - - - -
    -Constructor Detail
    - -

    -SentryExample

    -
    -public SentryExample()
    -
    -
    - - - - - - - - -
    -Method Detail
    - -

    -main

    -
    -public static void main(java.lang.String[] args)
    -
    -
    -
    -
    -
    - -
    - - - - - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/net/kencochrane/sentry/package-frame.html b/docs/net/kencochrane/sentry/package-frame.html deleted file mode 100644 index 22a070b2216..00000000000 --- a/docs/net/kencochrane/sentry/package-frame.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - -net.kencochrane.sentry - - - - - - - - - - - -net.kencochrane.sentry - - - - -
    -Classes  - -
    -RavenConfig -
    -RavenUtils -
    -SentryAppender
    - - - - diff --git a/docs/net/kencochrane/sentry/package-summary.html b/docs/net/kencochrane/sentry/package-summary.html deleted file mode 100644 index 8b3e221fe43..00000000000 --- a/docs/net/kencochrane/sentry/package-summary.html +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - -net.kencochrane.sentry - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    -

    -Package net.kencochrane.sentry -

    - - - - - - - - - - - - - - - - - -
    -Class Summary
    RavenConfigUser: ken cochrane - Date: 2/8/12 - Time: 1:16 PM
    RavenUtilsUser: ken Cochrane - Date: 2/10/12 - Time: 10:43 AM - Utility class for the Raven client.
    SentryAppenderUser: ken cochrane - Date: 2/6/12 - Time: 10:54 AM
    -  - -

    -

    -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/net/kencochrane/sentry/package-tree.html b/docs/net/kencochrane/sentry/package-tree.html deleted file mode 100644 index 05116620130..00000000000 --- a/docs/net/kencochrane/sentry/package-tree.html +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - -net.kencochrane.sentry Class Hierarchy - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    -
    -

    -Hierarchy For Package net.kencochrane.sentry -

    -
    -

    -Class Hierarchy -

    -
      -
    • java.lang.Object
        -
      • org.apache.log4j.AppenderSkeleton (implements org.apache.log4j.Appender, org.apache.log4j.spi.OptionHandler) - -
      • net.kencochrane.sentry.RavenConfig
      • net.kencochrane.sentry.RavenUtils
      -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/overview-tree.html b/docs/overview-tree.html deleted file mode 100644 index 0d8261f2a30..00000000000 --- a/docs/overview-tree.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - -Class Hierarchy - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    -
    -

    -Hierarchy For All Packages

    -
    -
    -
    Package Hierarchies:
    net.kencochrane.sentry
    -
    -

    -Class Hierarchy -

    -
      -
    • java.lang.Object
        -
      • org.apache.log4j.AppenderSkeleton (implements org.apache.log4j.Appender, org.apache.log4j.spi.OptionHandler) - -
      • net.kencochrane.sentry.RavenConfig
      • net.kencochrane.sentry.RavenUtils
      -
    -
    - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - diff --git a/docs/package-list b/docs/package-list deleted file mode 100644 index b263a2d0e9d..00000000000 --- a/docs/package-list +++ /dev/null @@ -1 +0,0 @@ -net.kencochrane.sentry diff --git a/docs/resources/inherit.gif b/docs/resources/inherit.gif deleted file mode 100644 index c814867a13deb0ca7ea2156c6ca1d5a03372af7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57 zcmZ?wbhEHbIIT!9-C*e{wE9>Kx3D)-;0v)C; KYxQGgum%9JOA&7X diff --git a/docs/stylesheet.css b/docs/stylesheet.css deleted file mode 100644 index 6ea9e516161..00000000000 --- a/docs/stylesheet.css +++ /dev/null @@ -1,29 +0,0 @@ -/* Javadoc style sheet */ - -/* Define colors, fonts and other style attributes here to override the defaults */ - -/* Page background color */ -body { background-color: #FFFFFF; color:#000000 } - -/* Headings */ -h1 { font-size: 145% } - -/* Table colors */ -.TableHeadingColor { background: #CCCCFF; color:#000000 } /* Dark mauve */ -.TableSubHeadingColor { background: #EEEEFF; color:#000000 } /* Light mauve */ -.TableRowColor { background: #FFFFFF; color:#000000 } /* White */ - -/* Font used in left-hand frame lists */ -.FrameTitleFont { font-size: 100%; font-family: Helvetica, Arial, sans-serif; color:#000000 } -.FrameHeadingFont { font-size: 90%; font-family: Helvetica, Arial, sans-serif; color:#000000 } -.FrameItemFont { font-size: 90%; font-family: Helvetica, Arial, sans-serif; color:#000000 } - -/* Navigation bar fonts and colors */ -.NavBarCell1 { background-color:#EEEEFF; color:#000000} /* Light mauve */ -.NavBarCell1Rev { background-color:#00008B; color:#FFFFFF} /* Dark Blue */ -.NavBarFont1 { font-family: Arial, Helvetica, sans-serif; color:#000000;color:#000000;} -.NavBarFont1Rev { font-family: Arial, Helvetica, sans-serif; color:#FFFFFF;color:#FFFFFF;} - -.NavBarCell2 { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF; color:#000000} -.NavBarCell3 { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF; color:#000000} - From 83773c4d83481c62208499f96fd855e1edfd8bcf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 18:05:20 +0100 Subject: [PATCH 0413/2152] Remove the old folder (it's versionned) --- old/README.rst | 212 --------- old/pom.xml | 124 ------ .../net/kencochrane/sentry/RavenClient.java | 418 ------------------ .../net/kencochrane/sentry/RavenConfig.java | 213 --------- .../net/kencochrane/sentry/RavenUtils.java | 248 ----------- .../kencochrane/sentry/SentryAppender.java | 106 ----- .../net/kencochrane/sentry/SentryQueue.java | 75 ---- .../net/kencochrane/sentry/SentryWorker.java | 83 ---- .../kencochrane/sentry/PerformanceTest.java | 44 -- .../kencochrane/sentry/RavenConfigTest.java | 26 -- .../kencochrane/sentry/SentryClientTest.java | 73 --- .../net/kencochrane/sentry/SentryExample.java | 61 --- .../test/resources/log4j_configuration.txt | 28 -- 13 files changed, 1711 deletions(-) delete mode 100644 old/README.rst delete mode 100644 old/pom.xml delete mode 100644 old/src/main/java/net/kencochrane/sentry/RavenClient.java delete mode 100644 old/src/main/java/net/kencochrane/sentry/RavenConfig.java delete mode 100644 old/src/main/java/net/kencochrane/sentry/RavenUtils.java delete mode 100644 old/src/main/java/net/kencochrane/sentry/SentryAppender.java delete mode 100644 old/src/main/java/net/kencochrane/sentry/SentryQueue.java delete mode 100644 old/src/main/java/net/kencochrane/sentry/SentryWorker.java delete mode 100644 old/src/test/java/net/kencochrane/sentry/PerformanceTest.java delete mode 100644 old/src/test/java/net/kencochrane/sentry/RavenConfigTest.java delete mode 100644 old/src/test/java/net/kencochrane/sentry/SentryClientTest.java delete mode 100644 old/src/test/java/net/kencochrane/sentry/SentryExample.java delete mode 100644 old/src/test/resources/log4j_configuration.txt diff --git a/old/README.rst b/old/README.rst deleted file mode 100644 index 8bcfc1b062c..00000000000 --- a/old/README.rst +++ /dev/null @@ -1,212 +0,0 @@ -Raven-java -========== -Raven-java is a Java client for Sentry. It is a basic log4j appender that will send your log messages to a sentry server of your choice. - -It is officially recognized as production-ready by Sentry: http://sentry.readthedocs.org/en/latest/client/index.html - -The log4j appender is asyncronous by design so there is no need to put it in a AsyncAppender. - -Current Status --------------- -.. image:: https://secure.travis-ci.org/kencochrane/raven-java.png - :target: http://travis-ci.org/kencochrane/raven-java - - -Installation ------------- - -Download Jars -~~~~~~~~~~~~~ -Precompiled jars are available for download directly from github. - - https://github.com/kencochrane/raven-java/downloads - -Build Jars yourself -~~~~~~~~~~~~~~~~~~~ -You'll need Maven 2 to build the project:: - - $ cd raven-java - $ mvn package -Dmaven.test.skip - -The last step will build the standalone raven-java jar file but also a jar file containing raven-java and all dependencies, which -you'll find in the target directory of the project. - -**Option 1**: add raven-java as a dependency when you're using Maven:: - - - net.kencochrane - raven-java - 0.6-SNAPSHOT - - -**Option 2**: add the plain jar and the jar files of all dependencies to your classpath - -**Option 3**: add the self contained jar file to your classpath - -Configuration -------------- - -Log4J configuration -~~~~~~~~~~~~~~~~~~~ -Check out ``src/test/java/resources/log4j_configuration.txt`` where you can see an example log4j config file. - -You will need to add the SentryAppender and the sentry_dsn properties. - -sentry_dsn -^^^^^^^^^^ -You will get this value from the "Projects / default / Manage / Member: " page. It will be under "Client DSN". -Don't put quotes around it, because it will mess it up, just put it like you see it below. - -Log4j Config example:: - - log4j.appender.sentry=net.kencochrane.sentry.SentryAppender - log4j.appender.sentry.sentry_dsn=http://b4935bdd7862409:7a37d9ad47654281@localhost:8000/1 - -Proxy -^^^^^ -If you need to use a proxy for HTTP transport, you can configure it as well:: - - log4j.appender.sentry.proxy=HTTP:proxyhost:proxyport - -Queue Size -^^^^^^^^^^ -By default, the appender is configured with a queue of 1000 events. To tune this parameter:: - - log4j.appender.sentry.queue_size=100 - -Blocking -^^^^^^^^ -By default, the appender is non-blocking. If the queue is filled then log events will be written to Standard Error. -If you want to make sure log events always reach sentry, you can turn blocking on:: - - log4j.appender.sentry.blocking=true - -WARNING: By setting blocking true, you will effectively lock up the thread doing the logging! Use with care. - -Naive SSL -^^^^^^^^^ -If you're using Java 6 or earlier, you might encounter errors when connecting to a Sentry instance using a TLS (https) -connection since Server Name Indication (SNI) support has only been available in Java since Java 7. You can either add -the corresponding certificate to your keystore (recommended!) or enable ``naiveSsl`` on the Sentry appender. This will -make the Raven client use a custom hostname verifier that *will* allow the JVM to connect with the host - in fact it -will let the Raven client connect to any host even if the certificate is invalid. Use at your own risk:: - - log4j.appender.sentry.naiveSsl=true - - -SENTRY_DSN Environment Variable -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Raven-Java will first look to see if there is an environment variable called ``SENTRY_DSN`` set before it looks at the log4j config. If the environment variable is set, it will use that value instead of the one set in ``log4j.appender.sentry.sentry_dsn``. This is for a few reasons. - -1. Some hosting providers (Heroku, etc) have http://GetSentry.com plugins and they expose your Sentry settings via an environment variable. -2. This allows you to specify your Sentry config by server. You could put a development Sentry server in your log4j properties file and then on each server override those values to point to different sentry servers if you need to. -3. It allows you to use the RavenClient directly outside of log4J, without having to hard code the Sentry DSN in your source code. - -Linux example:: - - # put this in your profile, or add it to a shell script that calls your java program. - - $ export SENTRY_DSN=http://b4935bdd78624092a:7a37d9ad47654281803f@localhost:8000/1 - -Usage ------ - -Log4J -~~~~~ - -If you configure log4j to only error messages to Sentry, it will ignore all other message levels and only send the logger.error() messages - -Example:: - - // configure log4j the normal way, and then just use it like you normally would. - - logger.debug("Debug example"); // ignored - logger.error("Error example"); // sent to sentry - logger.info("info Example"); // ignored - - try { - throw new RuntimeException("Uh oh!"); - } catch (RuntimeException e) { - logger.error("Error example with stacktrace", e); //sent to sentry - } - - -RavenClient -~~~~~~~~~~~ -Set the SENTRY_DSN Environment Variable with your sentry DSN. - -Create an instance of the client:: - - RavenClient client = new RavenClient(); - -Now call out to the raven client to capture events:: - - // record a simple message - client.captureMessage("hello world!"); - - // capture an exception - try { - throw new RuntimeException("Uh oh!"); - } - catch (Throwable e) { - client.captureException(e); - } - - -Sentry Versions Supported -------------------------- -This client supports Sentry protocol version 2.0 (which is Sentry >= 2.0) - -Other ------ -If you want to generate the javadocs for this project, simply run ``mvn javadoc:javadoc`` and you'll be able to browse the -docs from the target directory of the project. - -Running Tests -------------- -We are using maven, so all that you need to do in order to run the test is run the following:: - - $ cd raven-java - $ mvn test - -TODO ----- -- Create better documentation -- Add more unit tests -- Add more examples -- Get compression to work on message body, it isn't working now, not sure if it is sentry server or raven-java. Might be incompatible versions of zlib Java->python. - - -History -------- -- 0.6 - - Added support for sending messages through UDP -- 0.5 - - Added async support - - Fixed issue with parsing of path and port in DSN -- 0.4 - - Added the ability to get the SENTRY_DSN from the ENV - - Added RavenClient.captureMessage - - Added RavenClient.captureException -- 0.3 - - Added Maven support - - Merged with log4sentry project by Kevin Wetzels - - Added Proxy support - - Added full stack trace to logs - -- 0.2 - - code refactor and cleanup - -- 0.1 - - initial version - -Contributors ------------- -- Ken Cochrane (@KenCochrane) -- Kevin Wetzels (@roambe) -- David Cramer (@zeeg) -- Mark Philpot (@griphiam) - -License -------- -We are using the same license as the Sentry master project which is a BSD license. For more information see the LICENSE file, or follow this link: http://en.wikipedia.org/wiki/BSD_licenses diff --git a/old/pom.xml b/old/pom.xml deleted file mode 100644 index cc3af1de8c6..00000000000 --- a/old/pom.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - 4.0.0 - - net.kencochrane - raven-java - 0.6-SNAPSHOT - jar - raven-java - Java Raven client and log4j appender. - - - - commons-lang - commons-lang - 2.5 - - - commons-codec - commons-codec - 1.6 - - - com.googlecode.json-simple - json-simple - 1.1 - - - log4j - log4j - 1.2.16 - - - com.googlecode.jmockit - jmockit - 0.999.12 - test - - - junit - junit - 4.8.1 - test - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - UTF-8 - - - - org.apache.maven.plugins - maven-resources-plugin - 2.5 - - UTF-8 - - - - - maven-assembly-plugin - 2.3 - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - - - - - ${basedir}/src/main/java - - **/*.json - **/*.yml - - - - ${basedir}/src/main/resources - - **/*.* - - - - - - - ${basedir}/src/test/java - - **/*.json - **/*.yml - **/*.txt - - - - ${basedir}/src/test/resources - - **/*.* - - - - - - \ No newline at end of file diff --git a/old/src/main/java/net/kencochrane/sentry/RavenClient.java b/old/src/main/java/net/kencochrane/sentry/RavenClient.java deleted file mode 100644 index 7398b94072c..00000000000 --- a/old/src/main/java/net/kencochrane/sentry/RavenClient.java +++ /dev/null @@ -1,418 +0,0 @@ -package net.kencochrane.sentry; - -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSession; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.*; - -import static org.apache.commons.codec.binary.Base64.encodeBase64String; - -/** - * User: ken cochrane - * Date: 2/6/12 - * Time: 11:59 AM - */ - -public class RavenClient { - - public static final String RAVEN_JAVA_VERSION = "Raven-Java 0.6"; - private RavenConfig config; - private String sentryDSN; - private String lastID; - private MessageSender messageSender; - - public RavenClient() { - this.sentryDSN = System.getenv("SENTRY_DSN"); - if (this.sentryDSN == null || this.sentryDSN.length() == 0) { - throw new RuntimeException("You must provide a DSN to RavenClient"); - } - setConfig(new RavenConfig(this.sentryDSN)); - } - - public RavenClient(String sentryDSN) { - this.sentryDSN = sentryDSN; - setConfig(new RavenConfig(sentryDSN)); - } - - public RavenClient(String sentryDSN, String proxy, boolean naiveSsl) { - this.sentryDSN = sentryDSN; - setConfig(new RavenConfig(sentryDSN, proxy, naiveSsl)); - } - - public RavenConfig getConfig() { - return config; - } - - public void setConfig(RavenConfig config) { - this.config = config; - try { - String protocol = config.getProtocol(); - if ("udp".equals(protocol)) { - messageSender = new UdpMessageSender(config, null); - } else { - URL endpoint = new URL(config.getSentryURL()); - if (config.isNaiveSsl() && "https".equals(protocol)) { - messageSender = new NaiveHttpsMessageSender(config, endpoint); - } else { - messageSender = new MessageSender(config, endpoint); - } - } - } catch (MalformedURLException e) { - throw new RuntimeException("Sentry URL is malformed", e); - } - } - - public String getSentryDSN() { - return sentryDSN; - } - - public void setSentryDSN(String sentryDSN) { - this.sentryDSN = sentryDSN; - } - - public void setLastID(String lastID) { - this.lastID = lastID; - } - - public String getLastID() { - return lastID; - } - - /** - * Build up the JSON body for the POST that is sent to sentry - * - * @param message The log message - * @param timestamp ISO8601 formatted date string - * @param loggerClass The class associated with the log message - * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.) - * @param culprit Who we think caused the problem. - * @return JSON String of message body - */ - private String buildJSON(String message, String timestamp, String loggerClass, int logLevel, String culprit, Throwable exception) { - JSONObject obj = new JSONObject(); - String lastID = RavenUtils.getRandomUUID(); - obj.put("event_id", lastID); //Hexadecimal string representing a uuid4 value. - obj.put("checksum", RavenUtils.calculateChecksum(message)); - if (exception == null) { - obj.put("culprit", culprit); - } else { - obj.put("culprit", determineCulprit(exception)); - obj.put("sentry.interfaces.Exception", buildException(exception)); - obj.put("sentry.interfaces.Stacktrace", buildStacktrace(exception)); - } - obj.put("timestamp", timestamp); - obj.put("message", message); - obj.put("project", getConfig().getProjectId()); - obj.put("level", logLevel); - obj.put("logger", loggerClass); - obj.put("server_name", RavenUtils.getHostname()); - setLastID(lastID); - return obj.toJSONString(); - } - - /** - * Determines the class and method name where the root cause exception occurred. - * - * @param exception exception - * @return the culprit - */ - private String determineCulprit(Throwable exception) { - Throwable cause = exception; - String culprit = null; - while (cause != null) { - StackTraceElement[] elements = cause.getStackTrace(); - if (elements.length > 0) { - StackTraceElement trace = elements[0]; - culprit = trace.getClassName() + "." + trace.getMethodName(); - } - cause = cause.getCause(); - } - return culprit; - } - - private JSONObject buildException(Throwable exception) { - JSONObject json = new JSONObject(); - json.put("type", exception.getClass().getSimpleName()); - json.put("value", exception.getMessage()); - json.put("module", exception.getClass().getPackage().getName()); - return json; - } - - private JSONObject buildStacktrace(Throwable exception) { - JSONArray array = new JSONArray(); - Throwable cause = exception; - while (cause != null) { - StackTraceElement[] elements = cause.getStackTrace(); - for (int index = 0; index < elements.length; ++index) { - if (index == 0) { - JSONObject causedByFrame = new JSONObject(); - String msg = "Caused by: " + cause.getClass().getName(); - if (cause.getMessage() != null) { - msg += " (\"" + cause.getMessage() + "\")"; - } - causedByFrame.put("filename", msg); - causedByFrame.put("lineno", -1); - array.add(causedByFrame); - } - StackTraceElement element = elements[index]; - JSONObject frame = new JSONObject(); - frame.put("filename", element.getClassName()); - frame.put("function", element.getMethodName()); - frame.put("lineno", element.getLineNumber()); - array.add(frame); - } - cause = cause.getCause(); - } - JSONObject stacktrace = new JSONObject(); - stacktrace.put("frames", array); - return stacktrace; - } - - /** - * Take the raw message body and get it ready for sending. Encode and compress it. - * - * @param jsonMessage the message we want to prepare - * @return Encode and compressed version of the jsonMessage - */ - private String buildMessageBody(String jsonMessage) { - //need to zip and then base64 encode the message. - // compressing doesn't work right now, sentry isn't decompressing correctly. - // come back to it later. - //return compressAndEncode(jsonMessage); - - // in the meantime just base64 encode it. - return encodeBase64String(jsonMessage.getBytes()); - - } - - /** - * Build up the JSON body and then Encode and compress it. - * - * @param message The log message - * @param timestamp ISO8601 formatted date string - * @param loggerClass The class associated with the log message - * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.) - * @param culprit Who we think caused the problem. - * @param exception exception causing the problem - * @return Encode and compressed version of the JSON Message body - */ - private String buildMessage(String message, String timestamp, String loggerClass, int logLevel, String culprit, Throwable exception) { - // get the json version of the body - String jsonMessage = buildJSON(message, timestamp, loggerClass, logLevel, culprit, exception); - - // compress and encode the json message. - return buildMessageBody(jsonMessage); - } - - /** - * Send the message to the sentry server. - * - * @param messageBody the encoded json message we are sending to the sentry server - * @param timestamp the timestamp of the message - */ - private void sendMessage(String messageBody, long timestamp) { - try { - messageSender.send(messageBody, timestamp); - } catch (IOException e) { - // Eat the errors, we don't want to cause problems if there are major issues. - e.printStackTrace(); - } - } - - /** - * Send the log message to the sentry server. - *

    - * This method is deprecated. You should use captureMessage or captureException instead. - * - * @param theLogMessage The log message - * @param timestamp unix timestamp - * @param loggerClass The class associated with the log message - * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.) - * @param culprit Who we think caused the problem. - * @param exception exception that occurred - * @deprecated - */ - public void logMessage(String theLogMessage, long timestamp, String loggerClass, int logLevel, String culprit, Throwable exception) { - String message = buildMessage(theLogMessage, RavenUtils.getTimestampString(timestamp), loggerClass, logLevel, culprit, exception); - sendMessage(message, timestamp); - } - - - /** - * Send the log message to the sentry server. - * - * @param message The log message - * @param timestamp unix timestamp - * @param loggerClass The class associated with the log message - * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.) - * @param culprit Who we think caused the problem. - * @return lastID The ID for the last message. - */ - public String captureMessage(String message, long timestamp, String loggerClass, int logLevel, String culprit) { - String body = buildMessage(message, RavenUtils.getTimestampString(timestamp), loggerClass, logLevel, culprit, null); - sendMessage(body, timestamp); - return getLastID(); - } - - /** - * Send the log message to the sentry server. - * - * @param message The log message - * @return lastID The ID for the last message. - */ - public String captureMessage(String message) { - return captureMessage(message, RavenUtils.getTimestampLong(), "root", 50, null); - } - - /** - * Send the exception to the sentry server. - * - * @param message The log message - * @param timestamp unix timestamp - * @param loggerClass The class associated with the log message - * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.) - * @param culprit Who we think caused the problem. - * @param exception exception that occurred - * @return lastID The ID for the last message. - */ - public String captureException(String message, long timestamp, String loggerClass, int logLevel, String culprit, Throwable exception) { - String body = buildMessage(message, RavenUtils.getTimestampString(timestamp), loggerClass, logLevel, culprit, exception); - sendMessage(body, timestamp); - return getLastID(); - } - - /** - * Send an exception to the sentry server. - * - * @param exception exception that occurred - * @return lastID The ID for the last message. - */ - public String captureException(Throwable exception) { - return captureException(exception.getMessage(), RavenUtils.getTimestampLong(), "root", 50, null, exception); - } - - public static class MessageSender { - - public final RavenConfig config; - public final URL endpoint; - - public MessageSender(RavenConfig config, URL endpoint) { - this.config = config; - this.endpoint = endpoint; - } - - public void send(String messageBody, long timestamp) throws IOException { - // get the hmac Signature for the header - String hmacSignature = RavenUtils.getSignature(messageBody, timestamp, config.getSecretKey()); - - // get the auth header - String authHeader = buildAuthHeader(hmacSignature, timestamp, config.getPublicKey()); - - doSend(messageBody, authHeader); - } - - protected void doSend(String messageBody, String authHeader) throws IOException { - HttpURLConnection connection = getConnection(); - connection.setRequestMethod("POST"); - connection.setDoOutput(true); - connection.setReadTimeout(10000); - connection.setRequestProperty("X-Sentry-Auth", authHeader); - OutputStream output = connection.getOutputStream(); - output.write(messageBody.getBytes()); - output.close(); - connection.connect(); - InputStream input = connection.getInputStream(); - input.close(); - } - - /** - * Build up the sentry auth header in the following format. - *

    - * The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, and an - * arbitrary client version string. The client version should be something distinct to your client, - * and is simply for reporting purposes. - *

    - * X-Sentry-Auth: Sentry sentry_version=2.0, - * sentry_signature=, - * sentry_timestamp=[, - * sentry_key=,[ - * sentry_client=]] - * - * @param hmacSignature SHA1-signed HMAC - * @param timestamp is the timestamp of which this message was generated - * @param publicKey is either the public_key or the shared global key between client and server. - * @return String version of the sentry auth header - */ - protected String buildAuthHeader(String hmacSignature, long timestamp, String publicKey) { - StringBuilder header = new StringBuilder(); - header.append("Sentry sentry_version=2.0,sentry_signature="); - header.append(hmacSignature); - header.append(",sentry_timestamp="); - header.append(timestamp); - header.append(",sentry_key="); - header.append(publicKey); - header.append(",sentry_client="); - header.append(RAVEN_JAVA_VERSION); - - return header.toString(); - } - - protected HttpURLConnection getConnection() throws IOException { - return (HttpURLConnection) endpoint.openConnection(config.getProxy()); - } - } - - public static class NaiveHttpsMessageSender extends MessageSender { - - public final HostnameVerifier hostnameVerifier; - - public NaiveHttpsMessageSender(RavenConfig config, URL endpoint) { - super(config, endpoint); - this.hostnameVerifier = new AcceptAllHostnameVerifier(); - } - - @Override - protected HttpURLConnection getConnection() throws IOException { - HttpsURLConnection connection = (HttpsURLConnection) endpoint.openConnection(config.getProxy()); - connection.setHostnameVerifier(hostnameVerifier); - return connection; - } - } - - public static class UdpMessageSender extends MessageSender { - - private final DatagramSocket socket; - - public UdpMessageSender(RavenConfig config, URL endpoint) { - super(config, endpoint); - try { - socket = new DatagramSocket(); - socket.connect(new InetSocketAddress(config.getHost(), config.getPort())); - } catch (SocketException e) { - throw new IllegalStateException(e); - } - } - - @Override - protected void doSend(String messageBody, String authHeader) throws IOException { - byte[] message = (authHeader + "\n\n" + messageBody).getBytes("UTF-8"); - DatagramPacket packet = new DatagramPacket(message, message.length); - socket.send(packet); - } - - } - - public static class AcceptAllHostnameVerifier implements HostnameVerifier { - @Override - public boolean verify(String hostname, SSLSession sslSession) { - return true; - } - } - -} \ No newline at end of file diff --git a/old/src/main/java/net/kencochrane/sentry/RavenConfig.java b/old/src/main/java/net/kencochrane/sentry/RavenConfig.java deleted file mode 100644 index 56e4a0bea54..00000000000 --- a/old/src/main/java/net/kencochrane/sentry/RavenConfig.java +++ /dev/null @@ -1,213 +0,0 @@ -package net.kencochrane.sentry; - -import java.net.*; - -/** - * User: ken cochrane - * Date: 2/8/12 - * Time: 1:16 PM - */ -public class RavenConfig { - - private String host, protocol, publicKey, secretKey, path, projectId; - private int port; - private String proxyType, proxyHost; - private int proxyPort; - private boolean naiveSsl; - - /** - * Takes in a sentryDSN and builds up the configuration - * - * @param sentryDSN '{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}/{PROJECT_ID}' - */ - public RavenConfig(String sentryDSN) { - this(sentryDSN, null, false); - } - - /** - * Takes in a sentryDSN and builds up the configuration - * - * @param sentryDSN '{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}/{PROJECT_ID}' - * @param proxy proxy to use for the HTTP connections; blank or null when no proxy is to be used - * @param naiveSsl use a hostname verifier for SSL connections that allows all connections - */ - public RavenConfig(String sentryDSN, String proxy, boolean naiveSsl) { - this.naiveSsl = naiveSsl; - try { - boolean udp = sentryDSN.startsWith("udp://"); - if (udp) { - // So either we have to start registering protocol handlers which is a PITA to do decently in Java - // without causing problems for the actual application, or we hack our way around it. - sentryDSN = sentryDSN.replace("udp://", "http://"); - } - URL url = new URL(sentryDSN); - this.host = url.getHost(); - this.protocol = udp ? "udp" : url.getProtocol(); - String urlPath = url.getPath(); - - int lastSlash = urlPath.lastIndexOf("/"); - this.path = urlPath.substring(0, lastSlash); - // ProjectId is the integer after the last slash in the path - this.projectId = urlPath.substring(lastSlash + 1); - - String userInfo = url.getUserInfo(); - String[] userParts = userInfo.split(":"); - - this.secretKey = userParts[1]; - this.publicKey = userParts[0]; - - this.port = url.getPort(); - - if (proxy != null && !proxy.isEmpty()) { - String[] proxyParts = proxy.split(":"); - this.proxyType = proxyParts[0]; - this.proxyHost = proxyParts[1]; - this.proxyPort = Integer.parseInt(proxyParts[2]); - } - - } catch (MalformedURLException e) { - e.printStackTrace(); - } - - } - - /** - * The Sentry server URL that we post the message to. - * - * @return sentry server url - */ - public String getSentryURL() { - StringBuilder serverUrl = new StringBuilder(); - serverUrl.append(getProtocol()); - serverUrl.append("://"); - serverUrl.append(getHost()); - if ((getPort() != 0) && (getPort() != 80) && getPort() != -1) { - serverUrl.append(":").append(getPort()); - } - serverUrl.append(getPath()); - serverUrl.append("/api/store/"); - return serverUrl.toString(); - } - - public Proxy getProxy() { - if (proxyType == null || Proxy.Type.DIRECT.name().equals(proxyType)) { - return Proxy.NO_PROXY; - } - SocketAddress proxyAddress = new InetSocketAddress(proxyHost, proxyPort); - return new Proxy(Proxy.Type.valueOf(proxyType), proxyAddress); - } - - /** - * The sentry server host - * - * @return server host - */ - public String getHost() { - return host; - } - - public void setHost(String host) { - this.host = host; - } - - /** - * Sentry server protocol http https? - * - * @return http or https - */ - public String getProtocol() { - return protocol; - } - - public void setProtocol(String protocol) { - this.protocol = protocol; - } - - /** - * The Sentry public key - * - * @return Sentry public key - */ - public String getPublicKey() { - return publicKey; - } - - public void setPublicKey(String publicKey) { - this.publicKey = publicKey; - } - - /** - * The Sentry secret key - * - * @return Sentry secret key - */ - public String getSecretKey() { - return secretKey; - } - - public void setSecretKey(String secretKey) { - this.secretKey = secretKey; - } - - /** - * sentry url path - * - * @return url path - */ - public String getPath() { - return path; - } - - public void setPath(String path) { - this.path = path; - } - - /** - * Sentry project Id - * - * @return project Id - */ - public String getProjectId() { - return projectId; - } - - public void setProjectId(String projectId) { - this.projectId = projectId; - } - - /** - * sentry server port - * - * @return server port - */ - public int getPort() { - return port; - } - - public void setPort(int port) { - this.port = port; - } - - public boolean isNaiveSsl() { - return naiveSsl; - } - - public void setNaiveSsl(boolean naiveSsl) { - this.naiveSsl = naiveSsl; - } - - @Override - public String toString() { - return "RavenConfig{" + - "host='" + host + '\'' + - ", protocol='" + protocol + '\'' + - ", publicKey='" + publicKey + '\'' + - ", secretKey='" + secretKey + '\'' + - ", path='" + path + '\'' + - ", projectId='" + projectId + '\'' + - ", naiveSsl='" + naiveSsl + '\'' + - ", SentryUrl='" + getSentryURL() + '\'' + - '}'; - } - -} diff --git a/old/src/main/java/net/kencochrane/sentry/RavenUtils.java b/old/src/main/java/net/kencochrane/sentry/RavenUtils.java deleted file mode 100644 index e280afbb6e2..00000000000 --- a/old/src/main/java/net/kencochrane/sentry/RavenUtils.java +++ /dev/null @@ -1,248 +0,0 @@ -package net.kencochrane.sentry; - -import org.apache.commons.lang.time.DateFormatUtils; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.security.SignatureException; -import java.util.Date; -import java.util.UUID; -import java.util.zip.CRC32; -import java.util.zip.Checksum; -import java.util.zip.GZIPOutputStream; - -import static org.apache.commons.codec.binary.Base64.encodeBase64String; - -/** - * User: ken Cochrane - * Date: 2/10/12 - * Time: 10:43 AM - * Utility class for the Raven client. - */ -public class RavenUtils { - - private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; - - /** - * Computes RFC 2104-compliant HMAC signature. - * Based off of the sample here. http://docs.amazonwebservices.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AuthJavaSampleHMACSignature.html - * - * @param data The data to be signed. - * @param key The signing key. - * @return The hex-encoded RFC 2104-compliant HMAC signature. - * @throws java.security.SignatureException - * when signature generation fails - */ - public static String calculateHMAC(String data, String key) throws java.security.SignatureException { - String result; - try { - - // get an hmac_sha1 key from the raw key bytes - SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM); - - // get an hmac_sha1 Mac instance and initialize with the signing key - Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); - mac.init(signingKey); - - // compute the hmac on input data bytes - byte[] rawHmac = mac.doFinal(data.getBytes()); - - result = hexEncode(rawHmac); - - } catch (Exception e) { - throw new SignatureException("Failed to generate HMAC : " + e.getMessage()); - } - return result; - } - - /** - * Get the hostname for the system throwing the error. - * - * @return The hostname for the server - */ - public static String getHostname() { - String hostname; - try { - hostname = InetAddress.getLocalHost().getCanonicalHostName(); - } catch (UnknownHostException e) { - // can't get hostname - hostname = "unavailable"; - } - return hostname; - } - - /** - * Convert a java date into the ISO8601 format YYYYMMDDTHH:mm:ss - * in timezone UTC - * - * @param date the date to convert - * @return ISO8601 formatted date as a String - */ - public static String getDateAsISO8601String(Date date) { - return DateFormatUtils.formatUTC(date, DateFormatUtils.ISO_DATETIME_FORMAT.getPattern()); - } - - /** - * Get the timestamp for right now as a long - * - * @return timestamp for now as long - */ - public static long getTimestampLong() { - return System.currentTimeMillis(); - } - - /** - * Given a long timestamp convert to a ISO8601 formatted date string - * - * @param timestamp the timestamp to convert - * @return ISO8601 formatted date string - */ - public static String getTimestampString(long timestamp) { - - java.util.Date date = new java.util.Date(timestamp); - return getDateAsISO8601String(date); - } - - /** - * Given the time right now return a ISO8601 formatted date string - * - * @return ISO8601 formatted date string - */ - public static String getTimestampString() { - - java.util.Date date = new java.util.Date(); - return getDateAsISO8601String(date); - } - - /** - * build the HMAC sentry signature - *

    - * The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, - * and an arbitrary client version string. - *

    - * The client version should be something distinct to your client, and is simply for reporting purposes. - * To generate the HMAC signature, take the following example (in Python): - *

    - * hmac.new(public_key, '%s %s' % (timestamp, message), hashlib.sha1).hexdigest() - * - * @param message the error message to send to sentry - * @param timestamp the timestamp for when the message was created - * @param key sentry public key - * @return SHA1-signed HMAC string - */ - public static String getSignature(String message, long timestamp, String key) { - String full_message = timestamp + " " + message; - String hmac = null; - try { - hmac = calculateHMAC(full_message, key); - - } catch (SignatureException e) { - e.printStackTrace(); - } - return hmac; - } - - - /** - * The byte[] returned by MessageDigest does not have a nice - * textual representation, so some form of encoding is usually performed. - *

    - * This implementation follows the example of David Flanagan's book - * "Java In A Nutshell", and converts a byte array into a String - * of hex characters. - *

    - * Another popular alternative is to use a "Base64" encoding. - * - * @param aInput what we are hex encoding - * @return hex encoded string. - */ - public static String hexEncode(byte[] aInput) { - StringBuilder result = new StringBuilder(); - char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - for (byte b : aInput) { - result.append(digits[(b & 0xf0) >> 4]); - result.append(digits[b & 0x0f]); - } - return result.toString(); - } - - /** - * An almost-unique hash identifying the this event to improve aggregation. - * - * @param message The message we are sending to sentry - * @return CRC32 Checksum string - */ - public static String calculateChecksum(String message) { - - // get bytes from string - byte bytes[] = message.getBytes(); - - Checksum checksum = new CRC32(); - - // update the current checksum with the specified array of bytes - checksum.update(bytes, 0, bytes.length); - - // get the current checksum value - long checksumValue = checksum.getValue(); - return String.valueOf(checksumValue); - } - - /** - * Hexadecimal string representing a uuid4 value. - * - * @return Hexadecimal UUID4 String - */ - public static String getRandomUUID() { - UUID uuid = UUID.randomUUID(); - String uuid_string = uuid.toString(); - // if we keep the -'s in the uuid, it is too long, remove them - uuid_string = uuid_string.replaceAll("-", ""); - return uuid_string; - } - - /** - * Gzip then base64 encode the str value - * - * @param str the value we want to compress and encode. - * @return Base64 encoded compressed version of the string passed in. - */ - public static String compressAndEncode(String str) { - if (str == null || str.length() == 0) { - return str; - } - ByteArrayOutputStream out = new ByteArrayOutputStream(); - GZIPOutputStream gzip; - try { - gzip = new GZIPOutputStream(out); - - gzip.write(str.getBytes()); - gzip.close(); - } catch (IOException e) { - e.printStackTrace(); - //todo do something here better then this. - } - return encodeBase64String(out.toByteArray()); - } - - public static byte[] toUtf8(String s) { - try { - return s == null ? new byte[0] : s.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - public static String fromUtf8(byte[] b) { - try { - return new String(b, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - -} diff --git a/old/src/main/java/net/kencochrane/sentry/SentryAppender.java b/old/src/main/java/net/kencochrane/sentry/SentryAppender.java deleted file mode 100644 index 71b176a5707..00000000000 --- a/old/src/main/java/net/kencochrane/sentry/SentryAppender.java +++ /dev/null @@ -1,106 +0,0 @@ -package net.kencochrane.sentry; - -import org.apache.log4j.AppenderSkeleton; -import org.apache.log4j.spi.LoggingEvent; - -/** - * User: ken cochrane - * Date: 2/6/12 - * Time: 10:54 AM - */ -public class SentryAppender extends AppenderSkeleton { - - private String sentry_dsn; - private String proxy; - private int queue_size; - private boolean blocking; - private boolean naiveSsl; - - public SentryAppender() - { - queue_size = 1000; - blocking = false; - } - - public String getSentry_dsn() { - return sentry_dsn; - } - - public void setSentry_dsn(String sentry_dsn) { - this.sentry_dsn = sentry_dsn; - } - - public String getProxy() { - return proxy; - } - - public void setProxy(String proxy) { - this.proxy = proxy; - } - - public int getQueue_size() { - return queue_size; - } - - public void setQueue_size(int queue_size) { - this.queue_size = queue_size; - } - - public boolean getBlocking() { - return blocking; - } - - public void setBlocking(boolean blocking) { - this.blocking = blocking; - } - - public boolean isNaiveSsl() { - return naiveSsl; - } - - public void setNaiveSsl(boolean naiveSsl) { - this.naiveSsl = naiveSsl; - } - - /** - * Look for the ENV variable first, and if it isn't there, then look in the log4j properties - * - */ - private String findSentryDSN(){ - String sentryDSN = System.getenv("SENTRY_DSN"); - if (sentryDSN == null || sentryDSN.length() == 0) { - sentryDSN = getSentry_dsn(); - if (sentryDSN == null) { - throw new RuntimeException("ERROR: You do not have a Sentry DSN configured! make sure you add sentry_dsn to your log4j properties, or have set SENTRY_DSN as an envirornment variable."); - } - } - return sentryDSN; - } - - @Override - protected void append(LoggingEvent loggingEvent) { - - //find the sentry DSN. - String sentryDSN = findSentryDSN(); - - synchronized (this) - { - if(!SentryQueue.getInstance().isSetup()) - { - SentryQueue.getInstance().setup(sentryDSN, getProxy(), queue_size, blocking, naiveSsl); - } - } - - SentryQueue.getInstance().addEvent(loggingEvent); - } - - public void close() - { - SentryQueue.getInstance().shutdown(); - } - - public boolean requiresLayout() - { - return false; - } -} diff --git a/old/src/main/java/net/kencochrane/sentry/SentryQueue.java b/old/src/main/java/net/kencochrane/sentry/SentryQueue.java deleted file mode 100644 index 7842755a6fb..00000000000 --- a/old/src/main/java/net/kencochrane/sentry/SentryQueue.java +++ /dev/null @@ -1,75 +0,0 @@ -package net.kencochrane.sentry; - -import org.apache.log4j.spi.LoggingEvent; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; - -/** - * User: mphilpot - * Date: 3/29/12 - */ -public class SentryQueue -{ - private static SentryQueue ourInstance = new SentryQueue(); - private static BlockingQueue queue; - - private SentryWorker worker; - private boolean blocking; - - public static SentryQueue getInstance() - { - return ourInstance; - } - - private SentryQueue() - { - queue = null; - - worker = null; - - } - - public void shutdown() - { - worker.shutdown(); - worker.interrupt(); - } - - public synchronized boolean isSetup() - { - return (queue != null); - } - - public synchronized void setup(String sentryDSN, String proxy, int queueSize, boolean blocking, boolean naiveSsl) - { - queue = new LinkedBlockingQueue(queueSize); - this.blocking = blocking; - - worker = new SentryWorker(queue, sentryDSN, proxy, naiveSsl); - worker.start(); - } - - public void addEvent(LoggingEvent le) - { - try - { - if(blocking) - { - queue.put(le); - } - else - { - queue.add(le); - } - } - catch(IllegalStateException e) - { - System.err.println("Sentry Queue Full :: " + le); - } - catch(InterruptedException e) - { - System.err.println("Sentry Queue Interrupted :: "+ le); - } - } -} diff --git a/old/src/main/java/net/kencochrane/sentry/SentryWorker.java b/old/src/main/java/net/kencochrane/sentry/SentryWorker.java deleted file mode 100644 index c36e4d71575..00000000000 --- a/old/src/main/java/net/kencochrane/sentry/SentryWorker.java +++ /dev/null @@ -1,83 +0,0 @@ -package net.kencochrane.sentry; - -import org.apache.log4j.spi.LoggingEvent; -import org.apache.log4j.spi.ThrowableInformation; - -import java.util.concurrent.BlockingQueue; - -/** - * User: mphilpot - * Date: 3/29/12 - */ -public class SentryWorker extends Thread -{ - private boolean shouldShutdown; - - private RavenClient client; - - private BlockingQueue queue; - - public SentryWorker(BlockingQueue queue, String sentryDSN, String proxy, boolean naiveSsl) - { - this.shouldShutdown = false; - this.queue = queue; - this.client = new RavenClient(sentryDSN, proxy, naiveSsl); - } - - @Override - public void run() - { - while(!shouldShutdown) - { - try - { - LoggingEvent le = queue.take(); - - sendToSentry(le); - } - catch (InterruptedException e) - { - // Thread interrupted... probably shutting down - } - } - } - - public void shutdown() - { - shouldShutdown = true; - } - - public void sendToSentry(LoggingEvent loggingEvent) - { - synchronized (this) - { - try - { - // get timestamp and timestamp in correct string format. - long timestamp = loggingEvent.getTimeStamp(); - - // get the log and info about the log. - String logMessage = loggingEvent.getRenderedMessage(); - String loggingClass = loggingEvent.getLogger().getName(); - int logLevel = (loggingEvent.getLevel().toInt() / 1000); //Need to divide by 1000 to keep consistent with sentry - String culprit = loggingEvent.getLoggerName(); - - // is it an exception? - ThrowableInformation throwableInformation = loggingEvent.getThrowableInformation(); - - // send the message to the sentry server - if (throwableInformation == null){ - client.captureMessage(logMessage, timestamp, loggingClass, logLevel, culprit); - }else{ - client.captureException(logMessage, timestamp, loggingClass, logLevel, culprit, throwableInformation.getThrowable()); - } - - } catch (Exception e) - { - // Can we tell if there is another logger to send the event to? - System.err.println(e); - } - } - } - -} diff --git a/old/src/test/java/net/kencochrane/sentry/PerformanceTest.java b/old/src/test/java/net/kencochrane/sentry/PerformanceTest.java deleted file mode 100644 index 8df5204ba1f..00000000000 --- a/old/src/test/java/net/kencochrane/sentry/PerformanceTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package net.kencochrane.sentry; - -import org.apache.log4j.Logger; -import org.apache.log4j.PropertyConfigurator; -import org.junit.Test; - -import java.util.Date; - -/** - * User: mphilpot - * Date: 3/29/12 - */ -public class PerformanceTest -{ - static final Logger log = Logger.getLogger(PerformanceTest.class); - - @Test - public void testPerformance() throws InterruptedException - { - PropertyConfigurator.configure(getClass().getResource("/log4j_configuration.txt")); - - Date start = new Date(); - for(int i = 0; i < 100; i++) - { - log.info("Simple log message w/ no exception"); - } - Date end = new Date(); - - System.out.println(String.format("Simple test :: %d ms", end.getTime() - start.getTime())); - - Date startE = new Date(); - Exception e = new Exception("Test Exception"); - for(int i = 0; i < 100; i++ ) - { - log.warn("Log message w/ exception", e); - } - Date endE = new Date(); - - System.out.println(String.format("Exception test :: %d ms", endE.getTime() - startE.getTime())); - - // To see the messages get sent to the server - //TimeUnit.SECONDS.sleep(30); - } -} diff --git a/old/src/test/java/net/kencochrane/sentry/RavenConfigTest.java b/old/src/test/java/net/kencochrane/sentry/RavenConfigTest.java deleted file mode 100644 index db99232acb9..00000000000 --- a/old/src/test/java/net/kencochrane/sentry/RavenConfigTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.kencochrane.sentry; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class RavenConfigTest -{ - @Test - public void testDSNComponents() { - - // Without Port - RavenClient client = new RavenClient("http://public:secret@example.com/path/sentry/1"); - assertEquals("http://public:secret@example.com/path/sentry/1", client.getSentryDSN()); - assertEquals("example.com", client.getConfig().getHost()); - assertEquals("/path/sentry", client.getConfig().getPath()); - assertEquals(-1, client.getConfig().getPort()); - assertEquals("1", client.getConfig().getProjectId()); - assertEquals("http://example.com/path/sentry/api/store/", client.getConfig().getSentryURL()); - - // With Port - client = new RavenClient("http://public:secret@example.com:9000/path/sentry/1"); - assertEquals(9000, client.getConfig().getPort()); - assertEquals("http://example.com:9000/path/sentry/api/store/", client.getConfig().getSentryURL()); - } -} diff --git a/old/src/test/java/net/kencochrane/sentry/SentryClientTest.java b/old/src/test/java/net/kencochrane/sentry/SentryClientTest.java deleted file mode 100644 index 651bb016a0d..00000000000 --- a/old/src/test/java/net/kencochrane/sentry/SentryClientTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package net.kencochrane.sentry; - -import mockit.Mocked; -import mockit.Expectations; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class SentryClientTest { - public void triggerRuntimeException() { - try { - triggerNullPointer(); - } catch (Exception e) { - throw new RuntimeException("Error triggering null pointer", e); - } - } - - public String triggerNullPointer() { - String c = null; - return c.toLowerCase(); - } - - @Test(expected=RuntimeException.class) - public void testMissingDSN() { - RavenClient client = new RavenClient(); - } - - @Test - public void testConfigureFromDSN() { - RavenClient client = new RavenClient("http://public:secret@example.com/path/1"); - assertEquals(client.getSentryDSN(), "http://public:secret@example.com/path/1"); - } - - @Test - public void testConfigureFromEnvironment() { - new Expectations() - { - @Mocked("getenv") System mockedSystem; - - { - System.getenv("SENTRY_DSN"); returns("http://public:secret@example.com/path/1"); - } - }; - RavenClient client = new RavenClient(); - assertEquals(client.getSentryDSN(), "http://public:secret@example.com/path/1"); - } - - @Test - public void testCaptureExceptionWithOnlyThrowable() { - RavenClient client = new RavenClient("http://public:secret@example.com/path/1"); - - new Expectations() - { - - @Mocked("getRandomUUID") RavenUtils mockedRavenUtils; - - { - RavenUtils.getRandomUUID(); returns("1234567890"); - } - - // TODO: this should be mocked, somehow - // RavenClient.sendMessage(); minTimes = 1; maxTimes = 1; - }; - - - try { - triggerRuntimeException(); - } catch (RuntimeException e) { - String ident = client.captureException(e); - assertEquals(ident, "1234567890"); - } - } -} \ No newline at end of file diff --git a/old/src/test/java/net/kencochrane/sentry/SentryExample.java b/old/src/test/java/net/kencochrane/sentry/SentryExample.java deleted file mode 100644 index d7f65d0e5a9..00000000000 --- a/old/src/test/java/net/kencochrane/sentry/SentryExample.java +++ /dev/null @@ -1,61 +0,0 @@ -package net.kencochrane.sentry; - -/** - * User: ken cochrane - * Date: 2/6/12 - * Time: 11:35 AM - */ - -// Import log4j classes. - -import org.apache.log4j.Logger; -import org.apache.log4j.PropertyConfigurator; -import org.junit.Test; - -/** - * Simple example used to test out the sentry logger. - */ -public class SentryExample { - - // Define a static logger variable so that it references the - // Logger instance named "MyApp". - static final Logger logger = Logger.getLogger(SentryExample.class); - - public void triggerRuntimeException() { - try { - triggerNullPointer(); - } catch (Exception e) { - throw new RuntimeException("Error triggering null pointer", e); - } - } - - public String triggerNullPointer() { - String c = null; - return c.toLowerCase(); - } - - @Test - public void test_simple() { - - // PropertyConfigurator. - PropertyConfigurator.configure(getClass().getResource("/log4j_configuration.txt")); - - logger.debug("Debug example"); - logger.error("Error example"); - logger.trace("Trace Example"); - logger.fatal("Fatal Example"); - logger.info("info Example"); - logger.warn("Warn Example"); - try { - triggerRuntimeException(); - } catch (RuntimeException e) { - logger.error("Error example with stacktrace", e); - } - // This really shouldn't be necessary - try { - Thread.sleep(5000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } -} diff --git a/old/src/test/resources/log4j_configuration.txt b/old/src/test/resources/log4j_configuration.txt deleted file mode 100644 index a3e61fb9fa2..00000000000 --- a/old/src/test/resources/log4j_configuration.txt +++ /dev/null @@ -1,28 +0,0 @@ -log4j.rootLogger=info, stdout, R, sentry - -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout - -# Pattern to output the caller's file name and line number. -log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n - -log4j.appender.R=org.apache.log4j.RollingFileAppender -log4j.appender.R.File=example.log - -log4j.appender.R.MaxFileSize=100KB -# Keep one backup file -log4j.appender.R.MaxBackupIndex=1 - -log4j.appender.R.layout=org.apache.log4j.PatternLayout -log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n - -# These are for Sentry and Raven-java -log4j.appender.sentry=net.kencochrane.sentry.SentryAppender -log4j.appender.sentry.sentry_dsn=http://7e4dff58960645adb2ade337e6d53425:81fe140206d7464e911b89cd93e2a5a4@localhost:9000/2 - -# If you're using Java 6 or earlier, you might encounter errors when connecting to a Sentry instance using a TLS (https) -# connection since Server Name Indication support has only been available in Java since Java 7. You can either add -# the corresponding certificate to your keystore (recommended!) or enable naiveSsl on the Sentry appender. This will -# make the Raven client use a custom hostname verifier that *will* allow the JVM to connect with the host - in fact it -# will let the Raven client connect to any host even if the certificate is invalid. Use at your own risk. -# log4j.appender.sentry.naiveSsl=true From aa62437611162d727d5550fb4a8150303c4fa5fc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 18:16:37 +0100 Subject: [PATCH 0414/2152] Add maven 3 as a prerequisite --- pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pom.xml b/pom.xml index 486e3164ab6..9107c5d0dd5 100644 --- a/pom.xml +++ b/pom.xml @@ -78,6 +78,10 @@ + + 3.0 + + UTF-8 UTF-8 From 685d21dd43ecb96a0a3f8fa290212372b0d75224 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 18:21:32 +0100 Subject: [PATCH 0415/2152] Reorganise maven details to match the XSD See https://maven.apache.org/ref/3.0.3/maven-model/maven.html --- pom.xml | 63 ++++++++++++++++++++++--------------------- raven-legacy/pom.xml | 2 ++ raven-log4j/pom.xml | 2 ++ raven-log4j2/pom.xml | 2 ++ raven-logback/pom.xml | 2 ++ raven/pom.xml | 2 ++ sentry-stub/pom.xml | 2 ++ 7 files changed, 45 insertions(+), 30 deletions(-) diff --git a/pom.xml b/pom.xml index 9107c5d0dd5..8deea251240 100644 --- a/pom.xml +++ b/pom.xml @@ -9,14 +9,16 @@ oss-parent 7 + net.kencochrane.raven raven-all 4.0-SNAPSHOT pom + Raven-Java Sentry client and appenders for diverse logging frameworks. https://github.com/${github.repo} - + 2012 BSD New @@ -25,26 +27,6 @@ - - https://github.com/${github.repo} - scm:git:git://github.com/${github.repo}.git - scm:git:git@github.com:${github.repo}.git - - - https://github.com/${github.repo}/issues - GitHub Issues - - - - raven.java.server - scm:git:git@github.com:${github.repo}.git - - - - Travis-CI - https://travis-ci.org/${github.repo} - - kencochrane @@ -82,6 +64,36 @@ 3.0 + + raven + raven-legacy + raven-log4j + raven-logback + raven-log4j2 + sentry-stub + + + + https://github.com/${github.repo} + scm:git:git://github.com/${github.repo}.git + scm:git:git@github.com:${github.repo}.git + + + https://github.com/${github.repo}/issues + GitHub Issues + + + Travis-CI + https://travis-ci.org/${github.repo} + + + + + raven.java.server + scm:git:git@github.com:${github.repo}.git + + + UTF-8 UTF-8 @@ -105,15 +117,6 @@ 1.3 - - raven - raven-legacy - raven-log4j - raven-logback - raven-log4j2 - sentry-stub - - diff --git a/raven-legacy/pom.xml b/raven-legacy/pom.xml index 7b0745ed439..87c2b9aa6cc 100644 --- a/raven-legacy/pom.xml +++ b/raven-legacy/pom.xml @@ -9,8 +9,10 @@ raven-all 4.0-SNAPSHOT + raven-legacy jar + Raven-Java legacy client Legacy interface for the new version of Raven-Java. diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 9659921fdfc..d80307825c7 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -9,8 +9,10 @@ raven-all 4.0-SNAPSHOT + raven-log4j jar + Raven-Java for log4j log4j appender allowing to send logs to the Raven-Java client. diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 3bbc521f64d..9b3b55f675e 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -9,8 +9,10 @@ raven-all 4.0-SNAPSHOT + raven-log4j2 jar + Raven-Java for log4j2 log4j2 appender allowing to send logs to the Raven-Java client. diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 40d6801e3a9..1ad26bcc8d3 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -9,8 +9,10 @@ raven-all 4.0-SNAPSHOT + raven-logback jar + Raven-Java for Logback Logback appender allowing to send logs to the Raven-Java client. diff --git a/raven/pom.xml b/raven/pom.xml index df6bea47f65..b1c6602a55c 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -9,8 +9,10 @@ raven-all 4.0-SNAPSHOT + raven jar + Raven-Java client Sentry client written in Java. diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 84dba4ec59b..63d9746ece0 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -9,8 +9,10 @@ raven-all 4.0-SNAPSHOT + sentry-stub war + Sentry stub Stub for tests against a Sentry server. From 0db47263ba58867d4190af112518f091d77d754d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 18:27:14 +0100 Subject: [PATCH 0416/2152] Remove old README --- raven-logback/README.md | 73 ----------------------------------------- 1 file changed, 73 deletions(-) delete mode 100644 raven-logback/README.md diff --git a/raven-logback/README.md b/raven-logback/README.md deleted file mode 100644 index e5c0b97d674..00000000000 --- a/raven-logback/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# Raven-Logback -A [logback](http://logback.qos.ch/) appender passing messages along to [Sentry](http://www.getsentry.com/). - -## Maven dependencies -````xml - - - org.slf4j - slf4j-api - 1.7.2 - - - - ch.qos.logback - logback-classic - 1.0.7 - - - - net.kencochrane - raven-logback - 2.0-SNAPSHOT - - -```` - -## Using the logback appender - -````java -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class Example { - public static Logger logger = LoggerFactory.getLogger(Example.class); - - public static void main(String[] args) { - logger.info("Hello World"); - logger.trace("Hello World!"); - logger.debug("How are you today?"); - logger.info("I am fine."); - logger.warn("I love programming."); - logger.error("I am programming."); - } - -} -```` - -## Appender configuration -Example of configuration in src/test/resources/sentryappender.logback.xml - -````xml - - - - - - %d{HH:mm:ss.SSS} [%thread] %boldRed(%-5level) %logger{36} - %msg%n - - - - - http://2d0fd0e8c0d546279c7115b563bdf60f:3429a634a51b4b5b937be06d83bc6c97@localhost:9000/1 - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - -```` From 71a96d700806a15fea6beff7a791ad345f8ff1d9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Apr 2013 20:38:12 +0100 Subject: [PATCH 0417/2152] Configure github to deploy properly every module --- pom.xml | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/pom.xml b/pom.xml index 8deea251240..8465c4cc2dd 100644 --- a/pom.xml +++ b/pom.xml @@ -89,8 +89,9 @@ - raven.java.server - scm:git:git@github.com:${github.repo}.git + github-pages-site + Deployment through GitHub's site deployment plugin + site/${project.version} @@ -205,14 +206,14 @@ com.github.github site-maven-plugin 0.7 - false - Creating site for ${project.name} ${project.version} + Creating site for ${project.artifactId} ${project.version} + ${project.distributionManagement.site.url} true - ${project.build.directory}/staging + github-site site @@ -224,24 +225,9 @@ org.apache.maven.plugins maven-site-plugin 3.2 - - - stage - - stage - - post-site - - - default-deploy - - deploy - - - true - - - + + true + org.codehaus.mojo From 4a311d08d58937a33ef5ce89f32f00e5fdb39aef Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 18 Apr 2013 17:06:16 +0100 Subject: [PATCH 0418/2152] Backoff when an exception occurs in the connection --- .../raven/connection/AbstractConnection.java | 66 ++++++++++++++++++- .../raven/connection/HttpConnection.java | 10 +-- .../raven/connection/UdpConnection.java | 12 ++-- .../raven/exception/ConnectionException.java | 19 ++++++ 4 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 raven/src/main/java/net/kencochrane/raven/exception/ConnectionException.java diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index d7ed815d8d2..6de1a45dd3b 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -2,11 +2,19 @@ import net.kencochrane.raven.Dsn; import net.kencochrane.raven.Raven; +import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.exception.ConnectionException; + +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Abstract connection to a Sentry server. *

    - * Provide the basic tools to submit events to the server (authentication header, dsn). + * Provide the basic tools to submit events to the server (authentication header, dsn).
    + * To avoid spamming the network if and when Sentry is down, automatically lock the connection each time a + * {@link ConnectionException} is caught. *

    */ public abstract class AbstractConnection implements Connection { @@ -14,8 +22,19 @@ public abstract class AbstractConnection implements Connection { * Current sentry protocol version. */ public static final String SENTRY_PROTOCOL_VERSION = "4"; + /** + * At most wait 5 minutes if the connection failed too many times. + */ + private static final long MAX_WAITING_TIME = 300000; + /** + * When the first exception occurs, wait 10 millis before trying again. + */ + private static final long BASE_WAITING_TIME = 10; + private static final Logger logger = Logger.getLogger(Raven.class.getCanonicalName()); private final String publicKey; private final String secretKey; + private final ReentrantLock lock = new ReentrantLock(); + private long waitingTime = BASE_WAITING_TIME; /** * Creates a connection based on a DSN. @@ -51,4 +70,49 @@ protected String getAuthHeader() { header.append(",sentry_secret=").append(secretKey); return header.toString(); } + + @Override + public final void send(Event event) { + try { + if (!lock.isLocked()) { + doSend(event); + waitingTime = BASE_WAITING_TIME; + } else { + logger.info("The event '" + event + "' hasn't been sent to the server due to a lockdown."); + } + } catch (ConnectionException e) { + lock.tryLock(); + logger.log(Level.WARNING, "An exception due to the connection occurred, a lockdown will be initiated.", e); + } finally { + if (lock.isHeldByCurrentThread()) + lockDown(); + } + } + + /** + * Initiates a lockdown for {@link #waitingTime}ms and release the lock once the lockdown ends. + */ + private void lockDown() { + try { + logger.log(Level.WARNING, "Lockdown started for " + waitingTime + "ms."); + Thread.sleep(waitingTime); + + // Double the wait until the maximum is reached + if (waitingTime > MAX_WAITING_TIME) + waitingTime <<= 1; + } catch (Exception e) { + logger.log(Level.SEVERE, "An exception occurred during the lockdown.", e); + } finally { + lock.unlock(); + logger.log(Level.WARNING, "Lockdown ended."); + } + } + + /** + * Sends an event to the sentry server. + * + * @param event captured event to add in Sentry. + * @throws ConnectionException whenever a temporary exception due to the connection happened. + */ + protected abstract void doSend(Event event) throws ConnectionException; } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 8f5cfb01ee3..d3519546409 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -3,6 +3,7 @@ import net.kencochrane.raven.Dsn; import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.exception.ConnectionException; import net.kencochrane.raven.marshaller.Marshaller; import net.kencochrane.raven.marshaller.json.JsonMarshaller; @@ -23,8 +24,7 @@ * Basic connection to a Sentry server, using HTTP and HTTPS. *

    * It is possible to enable the "naive mode" through the DSN with {@link Dsn#NAIVE_PROTOCOL} to allow a connection over - * SSL using a certificate with a wildcard.
    - * + * SSL using a certificate with a wildcard. *

    */ public class HttpConnection extends AbstractConnection { @@ -119,7 +119,7 @@ private HttpURLConnection getConnection() { } @Override - public void send(Event event) { + public void doSend(Event event) { HttpURLConnection connection = getConnection(); try { connection.connect(); @@ -130,8 +130,8 @@ public void send(Event event) { if (connection.getErrorStream() != null) { logger.log(Level.SEVERE, getErrorMessageFromStream(connection.getErrorStream()), e); } else { - logger.log(Level.SEVERE, - "An exception occurred while submitting the event to the sentry server.", e); + throw new ConnectionException("An exception occurred while submitting the event to the sentry server." + , e); } } finally { connection.disconnect(); diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index 725fcd73084..3972d3b5003 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -2,6 +2,7 @@ import net.kencochrane.raven.Dsn; import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.exception.ConnectionException; import net.kencochrane.raven.marshaller.Marshaller; import net.kencochrane.raven.marshaller.json.JsonMarshaller; @@ -12,14 +13,11 @@ import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.SocketException; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Connection to a Sentry server through an UDP connection. */ public class UdpConnection extends AbstractConnection { - private static final Logger logger = Logger.getLogger(UdpConnection.class.getCanonicalName()); private static final int DEFAULT_UDP_PORT = 9001; private DatagramSocket socket; private Marshaller marshaller = new JsonMarshaller(); @@ -39,7 +37,7 @@ public UdpConnection(String hostname, int port, String publicKey, String secretK } @Override - public void send(Event event) { + public void doSend(Event event) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); writeHeader(baos); @@ -49,8 +47,8 @@ public void send(Event event) { DatagramPacket packet = new DatagramPacket(message, message.length); socket.send(packet); } catch (IOException e) { - logger.log(Level.SEVERE, - "An exception occurred while trying to establish a connection to the sentry server"); + throw new ConnectionException( + "An exception occurred while trying to establish a connection to the sentry server", e); } } @@ -64,7 +62,7 @@ private void openSocket(String hostname, int port) { socket = new DatagramSocket(); socket.connect(new InetSocketAddress(hostname, port)); } catch (SocketException e) { - throw new IllegalStateException("The UDP connection couldn't be used, impossible to send anything " + throw new ConnectionException("The UDP connection couldn't be used, impossible to send anything " + "to sentry", e); } } diff --git a/raven/src/main/java/net/kencochrane/raven/exception/ConnectionException.java b/raven/src/main/java/net/kencochrane/raven/exception/ConnectionException.java new file mode 100644 index 00000000000..1c29a076db2 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/exception/ConnectionException.java @@ -0,0 +1,19 @@ +package net.kencochrane.raven.exception; + +public class ConnectionException extends RuntimeException { + + public ConnectionException() { + } + + public ConnectionException(String message) { + super(message); + } + + public ConnectionException(String message, Throwable cause) { + super(message, cause); + } + + public ConnectionException(Throwable cause) { + super(cause); + } +} From d577dad1434487c50134c638c9f9ee14b7fd7ac5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 18 Apr 2013 17:18:48 +0100 Subject: [PATCH 0419/2152] No point in adding the signature back --- .../net/kencochrane/raven/connection/AbstractConnection.java | 1 - 1 file changed, 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 6de1a45dd3b..bf1c8629aef 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -62,7 +62,6 @@ protected AbstractConnection(String publicKey, String secretKey) { * @return an authentication header as a String. */ protected String getAuthHeader() { - //TODO: Consider adding back signature? Not a priority, probably not worth it. StringBuilder header = new StringBuilder(); header.append("Sentry sentry_version=").append(SENTRY_PROTOCOL_VERSION); header.append(",sentry_client=").append(Raven.NAME); From 4a35811c9daa1551f40fba1024eafc7bb539e511 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 18 Apr 2013 17:19:07 +0100 Subject: [PATCH 0420/2152] Define the stacktrace binding properly in ExceptionBinding --- .../raven/marshaller/json/ExceptionInterfaceBinding.java | 7 +++++-- .../kencochrane/raven/marshaller/json/JsonMarshaller.java | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java index a0373445a8c..a5ee6e398c9 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java @@ -12,8 +12,11 @@ public class ExceptionInterfaceBinding implements InterfaceBinding stackTraceInterfaceBinding; + + public ExceptionInterfaceBinding(InterfaceBinding stackTraceInterfaceBinding) { + this.stackTraceInterfaceBinding = stackTraceInterfaceBinding; + } @Override public void writeInterface(JsonGenerator generator, ExceptionInterface exceptionInterface) throws IOException { diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 9b9c27016d1..28893d3c4fe 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -87,8 +87,10 @@ public class JsonMarshaller implements Marshaller { } public JsonMarshaller() { - addInterfaceBinding(ExceptionInterface.class, new ExceptionInterfaceBinding()); - addInterfaceBinding(StackTraceInterface.class, new StackTraceInterfaceBinding()); + StackTraceInterfaceBinding stackTraceBinding = new StackTraceInterfaceBinding(); + + addInterfaceBinding(StackTraceInterface.class, stackTraceBinding); + addInterfaceBinding(ExceptionInterface.class, new ExceptionInterfaceBinding(stackTraceBinding)); addInterfaceBinding(MessageInterface.class, new MessageInterfaceBinding()); addInterfaceBinding(HttpInterface.class, new HttpInterfaceBinding()); } From edc8d9b5b5e94801399fe9bb6e7000a77cff3089 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 19 Apr 2013 11:36:06 +0100 Subject: [PATCH 0421/2152] Make the call to AsyncConnection.close explicit Instead of simply calling close(), specify explicitly that the parent object's close() method should be called. --- .../java/net/kencochrane/raven/connection/AsyncConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index bbacec10dea..61155368ddb 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -141,7 +141,7 @@ private void addShutdownHook() { Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { try { - close(); + AsyncConnection.this.close(); } catch (IOException e) { logger.log(Level.SEVERE, "An exception occurred while closing the connection.", e); } From 6299e8eb8704669642a543c3fa26aef09808dd9d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 19 Apr 2013 11:37:48 +0100 Subject: [PATCH 0422/2152] Do not close the stream from the marshaller It could be the case in the future that some content should be sent after the content provided by the marshaller. This promotes the responsibility to close the stream at the same level as it was opened. --- .../raven/connection/HttpConnection.java | 10 ++--- .../raven/connection/UdpConnection.java | 1 + .../raven/marshaller/Marshaller.java | 40 ++++++++++++++++++- .../raven/marshaller/json/JsonMarshaller.java | 2 + 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index d3519546409..39febdbb90d 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -10,10 +10,7 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSession; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.*; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; @@ -123,8 +120,9 @@ public void doSend(Event event) { HttpURLConnection connection = getConnection(); try { connection.connect(); - marshaller.marshall(event, connection.getOutputStream()); - connection.getOutputStream().close(); + OutputStream outputStream = connection.getOutputStream(); + marshaller.marshall(event, outputStream); + outputStream.close(); connection.getInputStream().close(); } catch (IOException e) { if (connection.getErrorStream() != null) { diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index 3972d3b5003..8ab9a3d91dd 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -42,6 +42,7 @@ public void doSend(Event event) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); writeHeader(baos); marshaller.marshall(event, baos); + baos.close(); byte[] message = baos.toByteArray(); DatagramPacket packet = new DatagramPacket(message, message.length); diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java index 81c5865dbea..97d86e9f737 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java @@ -2,6 +2,7 @@ import net.kencochrane.raven.event.Event; +import java.io.IOException; import java.io.OutputStream; /** @@ -11,12 +12,47 @@ public interface Marshaller { /** * Serialise an event and sends it through an {@code OutputStream}. *

    - * The marshaller will close the given stream once it's done sending content. - * If it should stay open, use a wrapper that will intercept the call to {@code OutputStream#close()}. + * The marshaller should not close the given stream, use {@link UncloseableOutputStream} to prevent automatic calls + * to {@link OutputStream#close()}. *

    * * @param event event to serialise. * @param destination destination stream. */ void marshall(Event event, OutputStream destination); + + /** + * OutputStream delegating every call except for {@link #close()} to an other OutputStream. + */ + final class UncloseableOutputStream extends OutputStream { + private final OutputStream originalStream; + + public UncloseableOutputStream(OutputStream originalStream) { + this.originalStream = originalStream; + } + + @Override + public void write(int b) throws IOException { + originalStream.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + originalStream.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + originalStream.write(b, off, len); + } + + @Override + public void flush() throws IOException { + originalStream.flush(); + } + + @Override + public void close() throws IOException { + } + } } diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 28893d3c4fe..be25c9112dc 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -97,6 +97,8 @@ public JsonMarshaller() { @Override public void marshall(Event event, OutputStream destination) { + // Prevent the stream from being closed automatically + destination = new UncloseableOutputStream(destination); if (compression) destination = new DeflaterOutputStream(new Base64OutputStream(destination)); From 21a0348a0e75c4227848714f70a73b084ca0d136 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 19 Apr 2013 11:39:49 +0100 Subject: [PATCH 0423/2152] Fix typo in documentation --- .../main/java/net/kencochrane/raven/marshaller/Marshaller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java index 97d86e9f737..40643dfdb43 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java @@ -10,7 +10,7 @@ */ public interface Marshaller { /** - * Serialise an event and sends it through an {@code OutputStream}. + * Serialises an event and sends it through an {@code OutputStream}. *

    * The marshaller should not close the given stream, use {@link UncloseableOutputStream} to prevent automatic calls * to {@link OutputStream#close()}. From 6824429cbb4dca88f2e1b8b93a2e1aae124c381c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 22 Apr 2013 10:49:54 +0100 Subject: [PATCH 0424/2152] Rename DSN options to make the code clearer --- .../raven/connection/AsyncConnection.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 61155368ddb..c1ecea2e5f8 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -24,11 +24,11 @@ public class AsyncConnection implements Connection { /** * DSN option for the number of threads assigned for the connection. */ - public static final String MAX_THREADS_OPTION = "raven.async.threads"; + public static final String DSN_MAX_THREADS_OPTION = "raven.async.threads"; /** * DSN option for the priority of threads assigned for the connection. */ - public static final String PRIORITY_OPTION = "raven.async.priority"; + public static final String DSN_PRIORITY_OPTION = "raven.async.priority"; private static final Logger logger = Logger.getLogger(AsyncConnection.class.getCanonicalName()); /** * Timeout of the {@link #executorService}. @@ -100,7 +100,7 @@ public AsyncConnection(Connection actualConnection, boolean propagateClose, int /** * Gets the number of {@code Thread}s that should be available in the pool. *

    - * Attempts to get the {@link #MAX_THREADS_OPTION} option from the {@code Dsn}, + * Attempts to get the {@link #DSN_MAX_THREADS_OPTION} option from the {@code Dsn}, * defaults to {@link #DEFAULT_MAX_THREADS} if not available. *

    * @@ -108,8 +108,8 @@ public AsyncConnection(Connection actualConnection, boolean propagateClose, int * @return the number of threads that should be available in the pool. */ private static int getMaxThreads(Dsn dsn) { - if (dsn.getOptions().containsKey(MAX_THREADS_OPTION)) { - return Integer.parseInt(dsn.getOptions().get(MAX_THREADS_OPTION)); + if (dsn.getOptions().containsKey(DSN_MAX_THREADS_OPTION)) { + return Integer.parseInt(dsn.getOptions().get(DSN_MAX_THREADS_OPTION)); } else { return DEFAULT_MAX_THREADS; } @@ -118,7 +118,7 @@ private static int getMaxThreads(Dsn dsn) { /** * Gets the priority of {@code Thread}s in the pool. *

    - * Attempts to get the {@link #PRIORITY_OPTION} option from the {@code Dsn}, + * Attempts to get the {@link #DSN_PRIORITY_OPTION} option from the {@code Dsn}, * defaults to {@link #DEFAULT_PRIORITY} if not available. *

    * @@ -126,8 +126,8 @@ private static int getMaxThreads(Dsn dsn) { * @return the priority of threads available in the pool. */ private static int getPriority(Dsn dsn) { - if (dsn.getOptions().containsKey(PRIORITY_OPTION)) { - return Integer.parseInt(dsn.getOptions().get(PRIORITY_OPTION)); + if (dsn.getOptions().containsKey(DSN_PRIORITY_OPTION)) { + return Integer.parseInt(dsn.getOptions().get(DSN_PRIORITY_OPTION)); } else { return DEFAULT_PRIORITY; } From bbb62150d9cba2ffc82d953c94ac66b1f0dc821d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 22 Apr 2013 11:22:38 +0100 Subject: [PATCH 0425/2152] Mock the stackTraceBinding for ExceptionBinding tests --- .../json/TestExceptionInterfaceBinding.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java index 21292fc8345..5590c8c8451 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java @@ -4,16 +4,21 @@ import com.fasterxml.jackson.databind.JsonNode; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.ImmutableThrowable; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) @@ -21,11 +26,27 @@ public class TestExceptionInterfaceBinding extends AbstractTestInterfaceBinding private ExceptionInterfaceBinding interfaceBinding; @Mock private ExceptionInterface mockExceptionInterface; + @Mock + private InterfaceBinding stackTraceInterfaceBinding; @Before public void setUp() throws Exception { super.setUp(); - interfaceBinding = new ExceptionInterfaceBinding(); + interfaceBinding = new ExceptionInterfaceBinding(stackTraceInterfaceBinding); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + JsonGenerator jsonGenerator = (JsonGenerator) invocation.getArguments()[0]; + if (invocation.getArguments()[1] != null) { + jsonGenerator.writeStartObject(); + jsonGenerator.writeEndObject(); + } else { + jsonGenerator.writeNull(); + } + return null; + } + }).when(stackTraceInterfaceBinding).writeInterface(any(JsonGenerator.class), any(StackTraceInterface.class)); } @Test From f218b2996a31946a04922f100e3346091b9dcf25 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 22 Apr 2013 11:27:15 +0100 Subject: [PATCH 0426/2152] Create SentryStub to delegate sentry operations Instead of using different implementations of the AuthValidation and the Unmarshaller, regroup everything in a singleton SentryStub which will do most of the Sentry operations and will store details on the received events --- .../SentryAuthenticationFilter.java | 7 +-- .../raven/sentrystub/SentryHttpServlet.java | 7 ++- .../raven/sentrystub/SentryStub.java | 51 +++++++++++++++++++ .../sentrystub/SentryUdpContextListener.java | 15 ++---- sentry-stub/src/main/webapp/WEB-INF/web.xml | 4 -- 5 files changed, 60 insertions(+), 24 deletions(-) create mode 100644 sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStub.java diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java index 90accb1ae80..d1935fbb34b 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java @@ -1,6 +1,5 @@ package net.kencochrane.raven.sentrystub; -import net.kencochrane.raven.sentrystub.auth.AuthValidator; import net.kencochrane.raven.sentrystub.auth.InvalidAuthException; import javax.servlet.*; @@ -16,12 +15,10 @@ @WebFilter(servletNames = "SentryHttpServlet") public class SentryAuthenticationFilter implements Filter { private static final String SENTRY_AUTH = "X-Sentry-Auth"; - private AuthValidator authValidator; + private final SentryStub sentryStub = SentryStub.getInstance(); @Override public void init(FilterConfig filterConfig) throws ServletException { - authValidator = new AuthValidator(); - authValidator.loadSentryUsers(filterConfig.getServletContext().getInitParameter("sentry_config")); } @Override @@ -43,7 +40,7 @@ private boolean validateAuth(HttpServletRequest req, HttpServletResponse resp) t String projectId = req.getPathInfo().substring(1, req.getPathInfo().indexOf('/', 1)); try { - authValidator.validateSentryAuth(sentryAuthDetails, projectId); + sentryStub.validateAuth(sentryAuthDetails, projectId); } catch (InvalidAuthException iae) { resp.setStatus(HttpServletResponse.SC_FORBIDDEN); PrintWriter writer = resp.getWriter(); diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java index 325ffaccbee..18e04caffee 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java @@ -13,12 +13,11 @@ @WebServlet(name = "SentryHttpServlet", displayName = "SentryHttpServlet", urlPatterns = "/api/*") public class SentryHttpServlet extends HttpServlet { - //TODO: Hardcoded now, but later it could be enhanced. - private Unmarshaller unmarshaller = new JsonUnmarshaller(); + private SentryStub sentryStub = SentryStub.getInstance(); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - Event event = unmarshaller.unmarshall(req.getInputStream()); - //TODO: validate event + Event event = sentryStub.parseEvent(req.getInputStream()); + sentryStub.addEvent(event); } } diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStub.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStub.java new file mode 100644 index 00000000000..f02332477df --- /dev/null +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStub.java @@ -0,0 +1,51 @@ +package net.kencochrane.raven.sentrystub; + +import net.kencochrane.raven.sentrystub.auth.AuthValidator; +import net.kencochrane.raven.sentrystub.event.Event; +import net.kencochrane.raven.sentrystub.unmarshaller.JsonUnmarshaller; +import net.kencochrane.raven.sentrystub.unmarshaller.Unmarshaller; + +import java.io.InputStream; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.Map; + +public final class SentryStub { + private static SentryStub instance = new SentryStub(); + private final Collection events = new LinkedList(); + private final AuthValidator authValidator = new AuthValidator(); + private final Unmarshaller unmarshaller = new JsonUnmarshaller(); + + private SentryStub() { + authValidator.loadSentryUsers("/net/kencochrane/raven/sentrystub/sentry.properties"); + } + + public static SentryStub getInstance() { + return instance; + } + + public void addEvent(Event event) { + validateEvent(event); + events.add(event); + } + + public void validateEvent(Event event){ + } + + public Event parseEvent(InputStream source) { + return unmarshaller.unmarshall(source); + } + + public Collection getEvents() { + return Collections.unmodifiableCollection(events); + } + + public void validateAuth(Map authHeader, String projectId) { + authValidator.validateSentryAuth(authHeader, projectId); + } + + public void validateAuth(Map authHeader) { + authValidator.validateSentryAuth(authHeader); + } +} diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java index 9f32b550c91..9fcab4ad501 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java @@ -1,6 +1,5 @@ package net.kencochrane.raven.sentrystub; -import net.kencochrane.raven.sentrystub.auth.AuthValidator; import net.kencochrane.raven.sentrystub.auth.InvalidAuthException; import net.kencochrane.raven.sentrystub.event.Event; import net.kencochrane.raven.sentrystub.unmarshaller.JsonUnmarshaller; @@ -35,20 +34,14 @@ public class SentryUdpContextListener implements ServletContextListener { private static final String SENTRY_UDP_PORT_PARAMETER = "sentryUdpPort"; private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); private DatagramSocket udpSocket; - private AuthValidator authValidator; - //TODO: Hardcoded now, but later it could be enhanced. - private Unmarshaller unmarshaller = new JsonUnmarshaller(); + private final SentryStub sentryStub = SentryStub.getInstance(); @Override public void contextInitialized(ServletContextEvent sce) { - String sentyConfiguration = sce.getServletContext().getInitParameter("sentry_config"); String sentryUdpPortParameter = sce.getServletContext().getInitParameter(SENTRY_UDP_PORT_PARAMETER); startUdpSocket(sentryUdpPortParameter != null ? Integer.parseInt(sentryUdpPortParameter) : DEFAULT_SENTRY_UDP_PORT); - - authValidator = new AuthValidator(); - authValidator.loadSentryUsers(sentyConfiguration); } private void startUdpSocket(int port) { @@ -79,8 +72,8 @@ public void run() { datagramPacket.getOffset(), datagramPacket.getLength()); validateAuthHeader(bais); - Event event = unmarshaller.unmarshall(bais); - //TODO: validate event + Event event = sentryStub.parseEvent(bais); + sentryStub.addEvent(event); } /** @@ -91,7 +84,7 @@ public void run() { private void validateAuthHeader(InputStream inputStream) { try { Map authHeader = parseAuthHeader(inputStream); - authValidator.validateSentryAuth(authHeader); + sentryStub.validateAuth(authHeader); } catch (IOException e) { throw new InvalidAuthException("Impossible to extract the auth header from an UDP packet", e); } diff --git a/sentry-stub/src/main/webapp/WEB-INF/web.xml b/sentry-stub/src/main/webapp/WEB-INF/web.xml index a4425f32aab..b9436cdd146 100644 --- a/sentry-stub/src/main/webapp/WEB-INF/web.xml +++ b/sentry-stub/src/main/webapp/WEB-INF/web.xml @@ -2,8 +2,4 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> - - sentry_config - /net/kencochrane/raven/sentrystub/sentry.properties - From 721e9e374f4260353a01ec28d2ece050fb8d1e88 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 22 Apr 2013 16:43:55 +0100 Subject: [PATCH 0427/2152] Mark javax.servlet-api as provided --- sentry-stub/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 63d9746ece0..4b6b88feea4 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -32,6 +32,7 @@ javax.servlet javax.servlet-api + provided From c116ba663d4f7dd13eec754f8cde23eb48691cbc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 22 Apr 2013 16:44:40 +0100 Subject: [PATCH 0428/2152] Add a removeEvents to the stub --- .../java/net/kencochrane/raven/sentrystub/SentryStub.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStub.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStub.java index f02332477df..90f15bbb3a9 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStub.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStub.java @@ -48,4 +48,8 @@ public void validateAuth(Map authHeader, String projectId) { public void validateAuth(Map authHeader) { authValidator.validateSentryAuth(authHeader); } + + public void removeEvents(){ + events.clear(); + } } From 37c19db69a315c002e9f10b7e9b58052c8d7eb46 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 22 Apr 2013 16:45:12 +0100 Subject: [PATCH 0429/2152] Create a servlet to access the details of sentry stub --- .../raven/sentrystub/SentryStubServlet.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStubServlet.java diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStubServlet.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStubServlet.java new file mode 100644 index 00000000000..2b5965b826c --- /dev/null +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStubServlet.java @@ -0,0 +1,52 @@ +package net.kencochrane.raven.sentrystub; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Simple API to access the sentry stub details. + */ +@WebServlet(name = "SentryStubServlet", displayName = "SentryStubServlet", urlPatterns = "/stub/*") +public class SentryStubServlet extends HttpServlet { + private static final String COUNT_OPERATION = "count"; + private static final String CLEANUP_OPERATION = "cleanup"; + private SentryStub sentryStub = SentryStub.getInstance(); + private JsonFactory jsonFactory = new JsonFactory(); + + public void getEventsCounter(JsonGenerator generator) throws IOException { + generator.writeStartObject(); + generator.writeNumberField("count", sentryStub.getEvents().size()); + generator.writeEndObject(); + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String operation = req.getPathInfo().substring(1); + + JsonGenerator jsonGenerator = jsonFactory.createGenerator(resp.getOutputStream()); + + if (COUNT_OPERATION.equals(operation)) { + getEventsCounter(jsonGenerator); + } else { + resp.setStatus(HttpServletResponse.SC_NOT_FOUND); + } + + jsonGenerator.close(); + } + + @Override + protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String operation = req.getPathInfo().substring(1); + + if (CLEANUP_OPERATION.equals(operation)) { + sentryStub.removeEvents(); + } + } +} From d1e5234123c515f2717a828227a9abd12c207734 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 22 Apr 2013 16:45:44 +0100 Subject: [PATCH 0430/2152] Simplify code --- .../raven/sentrystub/SentryAuthenticationFilter.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java index d1935fbb34b..7cf7055005f 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java @@ -26,10 +26,8 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; - //TODO: respond to everything that isn't "https://example.com/sentry/api/project-id/store/" - String pathInfo = req.getPathInfo(); - - if (!validateAuth(req, resp)) return; + if (!validateAuth(req, resp)) + return; chain.doFilter(request, response); } From 6ab7662651bcbaa99dbe6bb2a922242e19ea2904 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 22 Apr 2013 16:45:54 +0100 Subject: [PATCH 0431/2152] Do not log a warning when UDP is closed --- .../kencochrane/raven/sentrystub/SentryUdpContextListener.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java index 9fcab4ad501..6105c4adbb8 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java @@ -129,7 +129,8 @@ public void run() { udpSocket.receive(datagramPacket); executorService.execute(new UdpRequestHandler(datagramPacket)); } catch (IOException e) { - logger.log(Level.WARNING, "An exception occurred during the reception of a UDP packet.", e); + logger.log(Level.FINE, + "An exception occurred during the reception of a UDP packet (server probably closing).", e); } } } From 011dbf634349e28e99c15cee36b38705d963951e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 22 Apr 2013 16:46:13 +0100 Subject: [PATCH 0432/2152] Create a SentryStub client for ITs --- .../net/kencochrane/raven/SentryStub.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/SentryStub.java diff --git a/raven/src/test/java/net/kencochrane/raven/SentryStub.java b/raven/src/test/java/net/kencochrane/raven/SentryStub.java new file mode 100644 index 00000000000..92f0fd58906 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/SentryStub.java @@ -0,0 +1,86 @@ +package net.kencochrane.raven; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class SentryStub { + private static final Logger logger = Logger.getLogger(SentryStub.class.getCanonicalName()); + private static final URL DEFAULT_URL; + private final URL url; + private final ObjectMapper mapper = new ObjectMapper(); + + static { + URL url = null; + try { + url = new URL("http://localhost:8080/stub/"); + } catch (MalformedURLException e) { + logger.log(Level.FINE, "Couldn't create the URL http://localhost:8080/stub", e); + } + + DEFAULT_URL = url; + } + + public SentryStub() { + this(DEFAULT_URL); + } + + public SentryStub(URL url) { + this.url = url; + } + + public int getEventCount() { + try { + HttpURLConnection connection = connnectTo("count"); + connection.setRequestMethod("GET"); + return (Integer) getContent(connection).get("count"); + } catch (Exception e) { + logger.log(Level.SEVERE, "Couldn't get the number of events created.", e); + return -1; + } + } + + public void removeEvents() { + try { + HttpURLConnection connection = connnectTo("cleanup"); + connection.setRequestMethod("DELETE"); + connection.setDoOutput(false); + connection.connect(); + connection.getInputStream().close(); + } catch (Exception e) { + logger.log(Level.SEVERE, "Couldn't remove stub events.", e); + } + } + + private Map getContent(HttpURLConnection connection) { + try { + connection.setDoInput(true); + connection.connect(); + try { + connection.getOutputStream().close(); + } catch (IOException e) { + logger.log(Level.FINE, "Couldn't open and close the outputstream", e); + } + return (Map) mapper.readValue(connection.getInputStream(), Map.class); + } catch (Exception e) { + throw new IllegalStateException( + "Couldn't get the JSON content for the connection '" + connection + "'", e); + } + } + + private HttpURLConnection connnectTo(String path) { + try { + URL test = new URL(url, path); + return (HttpURLConnection) test.openConnection(); + } catch (Exception e) { + throw new IllegalStateException( + "Couldn't open a connection to the path '" + path + "' in '" + url + "'", e); + } + } +} From f3ef08e1e0a9af6d434a963c6c24153329a54c69 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 22 Apr 2013 16:46:38 +0100 Subject: [PATCH 0433/2152] Create integration test for log4j --- raven-log4j/pom.xml | 6 ++++ .../raven/log4j/SentryAppenderIT.java | 32 +++++++++++++++++++ .../src/test/resources/log4j.properties | 7 ++++ 3 files changed, 45 insertions(+) create mode 100644 raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java create mode 100644 raven-log4j/src/test/resources/log4j.properties diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index d80307825c7..e2e999de8cb 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -31,6 +31,12 @@ test-jar test + + + com.fasterxml.jackson.core + jackson-databind + test + org.mockito mockito-core diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java new file mode 100644 index 00000000000..a7eca83b53e --- /dev/null +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java @@ -0,0 +1,32 @@ +package net.kencochrane.raven.log4j; + +import net.kencochrane.raven.SentryStub; +import org.apache.log4j.Logger; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SentryAppenderIT { + private static final Logger logger = Logger.getLogger(SentryAppenderIT.class); + private SentryStub sentryStub; + + @Before + public void setUp(){ + sentryStub = new SentryStub(); + } + + @After + public void tearDown(){ + sentryStub.removeEvents(); + } + + @Test + public void testInfoLog(){ + assertThat(sentryStub.getEventCount(), is(0)); + logger.info("This is a test"); + assertThat(sentryStub.getEventCount(), is(1)); + } +} diff --git a/raven-log4j/src/test/resources/log4j.properties b/raven-log4j/src/test/resources/log4j.properties new file mode 100644 index 00000000000..0e03b7b0214 --- /dev/null +++ b/raven-log4j/src/test/resources/log4j.properties @@ -0,0 +1,7 @@ +log4j.rootLogger=DEBUG, ConsoleAppender, SentryAppender + +log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender +log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender + +log4j.appender.SentryAppender.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1 +log4j.appender.ConsoleAppender.layout=org.apache.log4j.SimpleLayout From f5de9737de230aca947ae425054e33e2d84aeb9f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 22 Apr 2013 17:15:04 +0100 Subject: [PATCH 0434/2152] Run integration-tests (separatly) in travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index dff5f3a5d02..4781ef9fdc5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1 +1,2 @@ language: java +script: mvn test && for module in $(find . -mindepth 2 -name pom.xml -exec dirname '{}' ';'); do; cd $module; mvn integration-test; cd ../; done; From 689033ff04a0a48b21816a5fc8eb234c9a3b3b85 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 22 Apr 2013 17:30:07 +0100 Subject: [PATCH 0435/2152] Manually escape the ; in find --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4781ef9fdc5..0a61b5edec9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,2 +1,2 @@ language: java -script: mvn test && for module in $(find . -mindepth 2 -name pom.xml -exec dirname '{}' ';'); do; cd $module; mvn integration-test; cd ../; done; +script: mvn test && for module in $(find . -mindepth 2 -name pom.xml -exec dirname '{}' \;); do; cd $module; mvn integration-test; cd ../; done; From bfbbfd098e021b00a5b371091ef8149f1f32fa4c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 23 Apr 2013 10:28:46 +0100 Subject: [PATCH 0436/2152] Run multiple integration-test processes --- .travis.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0a61b5edec9..1f404bff871 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,2 +1,9 @@ language: java -script: mvn test && for module in $(find . -mindepth 2 -name pom.xml -exec dirname '{}' \;); do; cd $module; mvn integration-test; cd ../; done; +script: + - mvn test + - cd raven && mvn integration-test + - cd raven-legacy && mvn integration-test + - cd raven-log4j && mvn integration-test + - cd raven-log4j2 && mvn integration-test + - cd raven-logback && mvn integration-test + - cd sentry-stub && mvn integration-test From 6dd3671e17a20d0ae738f976c9462971f3763e3d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 23 Apr 2013 10:40:14 +0100 Subject: [PATCH 0437/2152] Make tests independent --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1f404bff871..9eddb872fd7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: java script: - mvn test - - cd raven && mvn integration-test - - cd raven-legacy && mvn integration-test - - cd raven-log4j && mvn integration-test - - cd raven-log4j2 && mvn integration-test - - cd raven-logback && mvn integration-test - - cd sentry-stub && mvn integration-test + - cd raven; mvn integration-test; cd ../ + - cd raven-legacy; mvn integration-test; cd ../ + - cd raven-log4j; mvn integration-test; cd ../ + - cd raven-log4j2; mvn integration-test; cd ../ + - cd raven-logback; mvn integration-test; cd ../ + - cd sentry-stub; mvn integration-test; cd ../ From 3cb7e103e43d4ffe2622d0f6bc894205c9e488e2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 24 Apr 2013 14:13:52 +0100 Subject: [PATCH 0438/2152] Add Colin Hebert to the developers --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index cf7266e64ef..3d197d6437e 100644 --- a/pom.xml +++ b/pom.xml @@ -53,6 +53,12 @@ Mark Philpot contact@email.com + + ColinHebert + Colin Hebert + hebert.colin@gmail.com + https://github.com/ColinHebert + From 5ff85b641160b0518d08cc6e7c2ea85161f98180 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 24 Apr 2013 14:14:05 +0100 Subject: [PATCH 0439/2152] Set default configuration for the release plugin To avoid any mistake, do not auto commit when doing a release:prepare and use the version as the tag name --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index 3d197d6437e..1e8c908fdbb 100644 --- a/pom.xml +++ b/pom.xml @@ -67,6 +67,11 @@ raven-logback + + ${project.version} + false + + From 17f65208bd2035deffdf1d208a7964b1013f73ac Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 24 Apr 2013 14:20:10 +0100 Subject: [PATCH 0440/2152] Fix tagNameFormat Maven documentation says: The tagNameFormat uses @{ and } as delimiters in order to ensure that the default Maven property interpolation does not substitute the values before the version transformation has taken place. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1e8c908fdbb..9c07922bc4c 100644 --- a/pom.xml +++ b/pom.xml @@ -68,7 +68,7 @@ - ${project.version} + @{project.version} false From 9b1c253eaa0481cec532056c44ae6326ace3f592 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 24 Apr 2013 15:07:09 +0100 Subject: [PATCH 0441/2152] Set maven-release-plugin version Some options are only available in recent versions of the plugin use the last version currently available --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 9c07922bc4c..1dcb4c04e6c 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,12 @@ + + org.apache.maven.plugins + maven-release-plugin + 2.4.1 + + org.apache.maven.plugins maven-compiler-plugin From 51e0cf79d4329748f88bd1dc16b234403a5550b0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 24 Apr 2013 15:32:41 +0100 Subject: [PATCH 0442/2152] Set up the maven-release-plugin --- pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pom.xml b/pom.xml index 8465c4cc2dd..5eeda2336ff 100644 --- a/pom.xml +++ b/pom.xml @@ -202,6 +202,16 @@ + + org.apache.maven.plugins + maven-release-plugin + 2.4.1 + + @{project.version} + false + true + + com.github.github site-maven-plugin From 6a05ea08b2ad12e33558ea7362ba03987f2fbd6c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 24 Apr 2013 16:04:17 +0100 Subject: [PATCH 0443/2152] Fix pom.xml indentation --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5eeda2336ff..1142dedabef 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,5 @@ - 4.0.0 From 8cae4ac736084d2c131fed9328dafebeeda3317a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 24 Apr 2013 16:04:39 +0100 Subject: [PATCH 0444/2152] Stop integration tests to run by default See https://bugs.eclipse.org/bugs/show_bug.cgi?id=405631 --- pom.xml | 80 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/pom.xml b/pom.xml index 1142dedabef..7aa22a3f83a 100644 --- a/pom.xml +++ b/pom.xml @@ -105,6 +105,9 @@ 6 6 + + true + 1.6 2.1.4 @@ -310,42 +313,55 @@
    - - org.eclipse.jetty - jetty-maven-plugin - 9.0.1.v20130408 - - 10 - foo - 9999 - ${project.build.directory}/webapps/sentry-stub.war - - - - start-sentry-stub - pre-integration-test - - deploy-war - - - 0 - true - ${skipTests} - - - - stop-sentry-stub - post-integration-test - - stop - - - -
    + + + jetty + + false + + + + + org.eclipse.jetty + jetty-maven-plugin + 9.0.1.v20130408 + + 10 + foo + 9999 + ${project.build.directory}/webapps/sentry-stub.war + + + + start-sentry-stub + pre-integration-test + + deploy-war + + + 0 + true + ${skipTests} + + + + stop-sentry-stub + post-integration-test + + stop + + + + + + + + + From b5eed93b6176701bfe0917fb366f6fc089a701c3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 24 Apr 2013 16:05:15 +0100 Subject: [PATCH 0445/2152] Backport maven-release-plugin config from parent --- pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pom.xml b/pom.xml index 7aa22a3f83a..8d6361b61fa 100644 --- a/pom.xml +++ b/pom.xml @@ -212,6 +212,10 @@ @{project.version} false true + + forked-path + false + -Psonatype-oss-release From 59cba5674b0a7fb031481e1337b5d224a9ef5b81 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 24 Apr 2013 16:07:23 +0100 Subject: [PATCH 0446/2152] Enable jetty for integration tests --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9eddb872fd7..08f6cba5595 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: java script: - mvn test - - cd raven; mvn integration-test; cd ../ - - cd raven-legacy; mvn integration-test; cd ../ - - cd raven-log4j; mvn integration-test; cd ../ - - cd raven-log4j2; mvn integration-test; cd ../ - - cd raven-logback; mvn integration-test; cd ../ - - cd sentry-stub; mvn integration-test; cd ../ + - cd raven; mvn integration-test -Pjetty; cd ../ + - cd raven-legacy; mvn integration-test -Pjetty; cd ../ + - cd raven-log4j; mvn integration-test -Pjetty; cd ../ + - cd raven-log4j2; mvn integration-test -Pjetty; cd ../ + - cd raven-logback; mvn integration-test -Pjetty; cd ../ + - cd sentry-stub; mvn integration-test -Pjetty; cd ../ From 21266ca8cf270e8a81d7be004a7bd1aa4fecae0d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 24 Apr 2013 16:14:23 +0100 Subject: [PATCH 0447/2152] Enable autoVersionSubmodules --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 8d6361b61fa..f3e8c87a64a 100644 --- a/pom.xml +++ b/pom.xml @@ -212,6 +212,7 @@ @{project.version} false true + true forked-path false From 9680f681dcc9cd82ec76b793925cc55d21992fe7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 24 Apr 2013 16:30:11 +0100 Subject: [PATCH 0448/2152] Always provide jetty-plugin --- pom.xml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index f3e8c87a64a..9eb0cbb15e4 100644 --- a/pom.xml +++ b/pom.xml @@ -238,6 +238,17 @@ + + org.eclipse.jetty + jetty-maven-plugin + 9.0.1.v20130408 + + 10 + foo + 9999 + ${project.build.directory}/webapps/sentry-stub.war + + org.apache.maven.plugins maven-site-plugin @@ -333,13 +344,6 @@ org.eclipse.jetty jetty-maven-plugin - 9.0.1.v20130408 - - 10 - foo - 9999 - ${project.build.directory}/webapps/sentry-stub.war - start-sentry-stub From 56dc0c82e14e514b2c7b476521fe1a4021909d9a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 24 Apr 2013 16:18:05 +0100 Subject: [PATCH 0449/2152] Remove suppressCommitBeforeTag --- pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9eb0cbb15e4..ed276ea7420 100644 --- a/pom.xml +++ b/pom.xml @@ -211,7 +211,6 @@ @{project.version} false - true true forked-path From 13cd6696a15abb8b7c8524450686d4b5bc581081 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 24 Apr 2013 16:49:12 +0100 Subject: [PATCH 0450/2152] Prepare indentation for release-plugin --- pom.xml | 3 +-- raven-legacy/pom.xml | 4 +--- raven-log4j/pom.xml | 4 +--- raven-log4j2/pom.xml | 4 +--- raven-logback/pom.xml | 4 +--- raven/pom.xml | 4 +--- sentry-stub/pom.xml | 4 +--- 7 files changed, 7 insertions(+), 20 deletions(-) diff --git a/pom.xml b/pom.xml index ed276ea7420..5f68d4c4b03 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,5 @@ - + 4.0.0 diff --git a/raven-legacy/pom.xml b/raven-legacy/pom.xml index 87c2b9aa6cc..079e96d5824 100644 --- a/raven-legacy/pom.xml +++ b/raven-legacy/pom.xml @@ -1,7 +1,5 @@ - + 4.0.0 diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index e2e999de8cb..2e077a65864 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -1,7 +1,5 @@ - + 4.0.0 diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 9b3b55f675e..7b4ebd2994c 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -1,7 +1,5 @@ - + 4.0.0 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 1ad26bcc8d3..bd858d9e395 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -1,7 +1,5 @@ - + 4.0.0 diff --git a/raven/pom.xml b/raven/pom.xml index b1c6602a55c..dcdd4472da8 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -1,7 +1,5 @@ - + 4.0.0 diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 4b6b88feea4..17fec4def15 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -1,7 +1,5 @@ - + 4.0.0 From f0b0ed19500874bef82163440d84b6751e2db860 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 24 Apr 2013 16:49:34 +0100 Subject: [PATCH 0451/2152] Set tag to HEAD in SCM --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 5f68d4c4b03..e79c4f54a8c 100644 --- a/pom.xml +++ b/pom.xml @@ -75,6 +75,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git + HEAD https://github.com/${github.repo}/issues From 6467e6c7a9e7ca3d8530e3ec46e5650371027a14 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 24 Apr 2013 16:59:42 +0100 Subject: [PATCH 0452/2152] Add more settings for the release plugin --- pom.xml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 1dcb4c04e6c..1a00a797ec4 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ git@github.com:kencochrane/raven-java.git scm:git:git://github.com/kencochrane/raven-java.git scm:git:git@github.com:kencochrane/raven-java.git + HEAD @@ -67,17 +68,21 @@ raven-logback - - @{project.version} - false - - org.apache.maven.plugins maven-release-plugin 2.4.1 + + @{project.version} + false + true + + forked-path + false + -Psonatype-oss-release + From 7c19c050c5a7116e9defd03ad0f68667b354ed7e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 24 Apr 2013 20:24:09 +0100 Subject: [PATCH 0453/2152] Add Build status to README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ebf72bf5867..3f26b09649d 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ to send the logs directly to sentry: Raven-Java supports both HTTP(S) and UDP transport of events. +[![Build Status](https://secure.travis-ci.org/kencochrane/raven-java.png?branch=master)](http://travis-ci.org/kencochrane/raven-java) + ## Sentry Protocol and supported versions ### Sentry Protocol versions Since the version 3.0, Raven-Java the versionning system is based on the From 626b54159ccd68ad3b077e97cb1e7f759f8b16d6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 24 Apr 2013 20:35:31 +0100 Subject: [PATCH 0454/2152] Remove content from .gitignore IDE files should be ignored by the developers not the project (set that in the global git ignore, or in the local repo settings). Old generated files do not need to be ignore anymore. Only files generated during the build should be ignored (so only target/) --- .gitignore | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 42a0f43729f..2f7896d1d13 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1 @@ -*.DS_Store -out/* -target/* -**/target/* -.idea/* -*.iml -example.log -raven-integration-tests/src/main/resources/sentry.db \ No newline at end of file +target/ From 6ef3464b4bd88ce92d058a1813c67585db83da8c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 24 Apr 2013 20:38:00 +0100 Subject: [PATCH 0455/2152] Avoid using Raven-Java when possible Java is trademarked, it's fine to know that the project is maven for Java but Raven-Java could become a problem --- INSTALL.md | 20 ++++++++++---------- README.md | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 5c28e016e44..c47560f95b5 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,18 +1,18 @@ -# Installation and setup of Raven-Java +# Installation and setup of Raven -## Installing Raven-Java +## Installing Raven Currently there are 6 modules in the project: - `raven`, the core of the project, providing the client and support for JUL - - `raven-legacy`, support of the Raven-Java 2.0 API (it's recommended to move - to the new API as legacy will be removed in the future releases of Raven-Java) + - `raven-legacy`, support of the Raven-2.0 API (it's recommended to move + to the new API as legacy will be removed in the future releases of Raven) - `raven-log4j`, Appender for log4j - `raven-log4j2`, Appender for log4j2 - `raven-logback`, Appander for Logback - `sentry-stub`, Sentry server stub, allowing to test the protocol ### Build -It's possible to get the latest version of Raven-Java by building it from the +It's possible to get the latest version of Raven by building it from the sources. $ git clone https://github.com/kencochrane/raven-java.git @@ -24,7 +24,7 @@ jetty, it is currently not possible to run the integration tests from the main project. They have to be run manually on each module independently._ ### Maven dependency -To add raven-java as a dependency, simply add this to your pom.xml: +To add raven as a dependency, simply add this to your pom.xml: net.kencochrane.raven @@ -37,7 +37,7 @@ name of the module. ### Manual installation -## Using Raven-Java +## Using Raven ### Manual usage @@ -50,7 +50,7 @@ name of the module. String rawDsn = args[0]; Raven client = new Raven(rawDsn); EventBuilder eventBuilder = new EventBuilder() - .setMessage("Hello from Raven-Java!"); + .setMessage("Hello from Raven!"); client.sendEvent(eventBuilder.build()); } } @@ -72,7 +72,7 @@ The client will lookup for the first DSN configuration provided: public static void main(String[] args) { Raven client = new Raven(); EventBuilder eventBuilder = new EventBuilder() - .setMessage("Hello from Raven-Java!"); + .setMessage("Hello from Raven!"); client.sendEvent(eventBuilder.build()); } } @@ -103,7 +103,7 @@ event is sent to Sentry, it is recommended to use instead the option It is possible to send events to Sentry over different protocols, depending on the security and performance requirements. So far Sentry accepts HTTP(S) and UDP which are both fully supported by -Raven-Java. +Raven. ### HTTP The most common way to access Sentry is through HTTP, this can be done by diff --git a/README.md b/README.md index 3f26b09649d..bccec14f1e4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Raven-Java +# Raven -Raven-Java is a Java client for [Sentry](https://www.getsentry.com/). +Raven is a Java client for [Sentry](https://www.getsentry.com/). Besides the regular client you can use within your application code, this project also provides tools allowing the most popular logging frameworks to send the logs directly to sentry: @@ -12,25 +12,25 @@ to send the logs directly to sentry: [log4j2](https://logging.apache.org/log4j/2.x/). - `raven-logback` adds the support for [logback](http://logback.qos.ch/). -Raven-Java supports both HTTP(S) and UDP transport of events. +Raven supports both HTTP(S) and UDP transport of events. [![Build Status](https://secure.travis-ci.org/kencochrane/raven-java.png?branch=master)](http://travis-ci.org/kencochrane/raven-java) ## Sentry Protocol and supported versions ### Sentry Protocol versions -Since the version 3.0, Raven-Java the versionning system is based on the -protocol version of Sentry. This means that Raven-Java 3.0 only supports -the version 3 of Sentry's protocol while Raven-Java 4.0 only supports +Since the version 3.0, Raven the versionning system is based on the +protocol version of Sentry. This means that Raven-3.x only supports +the version 3 of Sentry's protocol while Raven-4.x only supports the version 4. Sentry only supports the last two major releases of the protocol, for this -reason, only the last two major versions of Raven-Java are maintained. +reason, only the last two major versions of Raven are maintained. ### Sentry versions - - Sentry protocol v4 is not yet available (use Raven-Java 4.0) - - Sentry protocol v3 is available since Sentry 5.1 (use Raven-Java 3.0) - - Sentry protocol v2 is available since Sentry 2.0 (use Raven-Java 2.0) + - Sentry protocol v4 is not yet available (use Raven-4.x) + - Sentry protocol v3 is available since Sentry 5.1 (use Raven-3.x) + - Sentry protocol v2 is available since Sentry 2.0 (use Raven-2.x) ## Build and Installation **See From 565a7500fd0118df497176689833f7acee33e074 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 28 Apr 2013 21:52:50 +0200 Subject: [PATCH 0456/2152] Update librairies (jackson, logback, log4j2) --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index e79c4f54a8c..c044623179d 100644 --- a/pom.xml +++ b/pom.xml @@ -110,11 +110,11 @@ 1.6 - 2.1.4 + 2.2.0 3.0.1 - 1.0.11 + 1.0.12 1.2.17 - 2.0-beta4 + 2.0-beta5 4.11 1.9.5 1.3 From c079f5eb537a517c6a8c8bd0d58747e977666412 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 29 Apr 2013 09:44:03 +0200 Subject: [PATCH 0457/2152] Make dependencies explicit when needed --- raven-legacy/pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/raven-legacy/pom.xml b/raven-legacy/pom.xml index 079e96d5824..194ad16c87f 100644 --- a/raven-legacy/pom.xml +++ b/raven-legacy/pom.xml @@ -24,6 +24,10 @@ ${project.groupId} raven + + commons-codec + commons-codec + com.googlecode.json-simple json-simple From 3f41b53f899488dd1542c4e6ca0b49e6a5e23721 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 29 Apr 2013 09:45:48 +0200 Subject: [PATCH 0458/2152] Add a simple unit test for log4j2 --- raven-log4j2/pom.xml | 17 +++++ .../raven/log4j2/SentryAppenderTest.java | 72 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 7b4ebd2994c..35665a346c0 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -27,6 +27,13 @@ org.apache.logging.log4j log4j-core + + + ${project.groupId} + raven + test-jar + test + org.mockito mockito-core @@ -37,6 +44,16 @@ junit test + + org.hamcrest + hamcrest-core + test + + + org.hamcrest + hamcrest-library + test + diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java new file mode 100644 index 00000000000..f441afc7eb0 --- /dev/null +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -0,0 +1,72 @@ +package net.kencochrane.raven.log4j2; + +import net.kencochrane.raven.AbstractLoggerTest; +import net.kencochrane.raven.event.Event; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.FormattedMessage; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; + +public class SentryAppenderTest extends AbstractLoggerTest { + private static final String LOGGER_NAME = SentryAppenderTest.class.getCanonicalName(); + private SentryAppender sentryAppender; + + @Before + public void setUp() throws Exception { + sentryAppender = new SentryAppender(getMockRaven()); + } + + @Override + public void logAnyLevel(String message) { + logEvent(Level.INFO, null, message, null); + } + + @Override + public void logAnyLevel(String message, Throwable exception) { + logEvent(Level.INFO, exception, message, null); + } + + @Override + public void logAnyLevel(String message, List parameters) { + logEvent(Level.INFO, null, message, parameters); + } + + @Override + public String getCurrentLoggerName() { + return LOGGER_NAME; + } + + @Test + @Override + public void testLogLevelConversions() throws Exception { + assertLevelConverted(Event.Level.DEBUG, Level.TRACE); + assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); + assertLevelConverted(Event.Level.INFO, Level.INFO); + assertLevelConverted(Event.Level.WARNING, Level.WARN); + assertLevelConverted(Event.Level.ERROR, Level.ERROR); + assertLevelConverted(Event.Level.FATAL, Level.FATAL); + } + + private void assertLevelConverted(Event.Level expectedLevel, Level level) { + logEvent(level, null, "", null); + assertLogLevel(expectedLevel); + } + + private void logEvent(Level level, Throwable exception, String messageString, List messageParameters) { + Message message; + if (messageParameters != null) + message = new FormattedMessage(messageString, messageParameters.toArray()); + else + message = new SimpleMessage(messageString); + + LogEvent event = new Log4jLogEvent(LOGGER_NAME, null, SentryAppenderTest.class.getName(), level, + message, exception); + sentryAppender.append(event); + } +} From 9e67203f3e63003e940bab293490ceaa847228e9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 29 Apr 2013 09:47:18 +0200 Subject: [PATCH 0459/2152] Do not always rely on the source as it may be null --- .../java/net/kencochrane/raven/log4j2/SentryAppender.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index ca9d64b5d00..bc894f01120 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -110,16 +110,17 @@ public void append(LogEvent event) { .setTimestamp(new Date(event.getMillis())) .setMessage(event.getMessage().getFormattedMessage()) .setLogger(event.getLoggerName()) - .setLevel(formatLevel(event.getLevel())) - .setCulprit(formatCulprit(event.getSource())); + .setLevel(formatLevel(event.getLevel())); if (event.getThrown() != null) { Throwable throwable = event.getThrown(); eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); eventBuilder.setCulprit(throwable); - } else { + } else if (event.getSource() != null) { // When it's a message try to rely on the position of the log (the same message can be logged from // different places, or a same place can log a message in different ways. + eventBuilder.setCulprit(formatCulprit(event.getSource())); + eventBuilder.generateChecksum(formatCulprit(event.getSource())); } From 27df7eda8b2424633ad2f542331800a7fca5ff45 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 29 Apr 2013 09:48:42 +0200 Subject: [PATCH 0460/2152] Capture formated message and add a MessageInterface --- .../raven/log4j2/SentryAppender.java | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index bc894f01120..f45593458c1 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -4,6 +4,7 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.MessageInterface; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; @@ -14,9 +15,12 @@ import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.message.Message; import java.io.IOException; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.Map; @Plugin(name = "Sentry", type = "Sentry", elementType = "appender") @@ -106,9 +110,10 @@ public void start() { @Override public void append(LogEvent event) { + Message eventMessage = event.getMessage(); EventBuilder eventBuilder = new EventBuilder() .setTimestamp(new Date(event.getMillis())) - .setMessage(event.getMessage().getFormattedMessage()) + .setMessage(eventMessage.getFormattedMessage()) .setLogger(event.getLoggerName()) .setLevel(formatLevel(event.getLevel())); @@ -118,10 +123,15 @@ public void append(LogEvent event) { eventBuilder.setCulprit(throwable); } else if (event.getSource() != null) { // When it's a message try to rely on the position of the log (the same message can be logged from - // different places, or a same place can log a message in different ways. - eventBuilder.setCulprit(formatCulprit(event.getSource())); + // different places, or a same place can log a message in different ways). + String source = formatCulprit(event.getSource()); + eventBuilder.setCulprit(source); + eventBuilder.generateChecksum(source); + } - eventBuilder.generateChecksum(formatCulprit(event.getSource())); + if (eventMessage.getFormattedMessage() != eventMessage.getFormat()) { + eventBuilder.addSentryInterface(new MessageInterface(eventMessage.getFormat(), + formatMessageParameters(eventMessage.getParameters()))); } if (event.getContextStack() != null) { @@ -139,6 +149,13 @@ public void append(LogEvent event) { raven.sendEvent(eventBuilder.build()); } + private List formatMessageParameters(Object[] parameters) { + List stringParameters = new ArrayList(parameters.length); + for (Object parameter : parameters) + stringParameters.add(parameter.toString()); + return stringParameters; + } + public void setDsn(String dsn) { this.dsn = dsn; } From 5d48b2a60f236d47a89f3f16fbcce4a7b5ffdb29 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 29 Apr 2013 09:50:37 +0200 Subject: [PATCH 0461/2152] Do not use equals during the test of exceptions The exception provided by the logging framework can be a proxy (it is with log4j2) which means that the equals method doesn't work. It shouldn't have been done in the first place as ImmutableThrowable shouldn't have overriden the equals and hashCode method. --- .../java/net/kencochrane/raven/AbstractLoggerTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java index 8d4c45d627f..fd762f7dae9 100644 --- a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java @@ -77,9 +77,10 @@ public void testLogException() throws Exception { SentryInterface exceptionInterface = event.getSentryInterfaces().get(ExceptionInterface.EXCEPTION_INTERFACE); assertThat(exceptionInterface, instanceOf(ExceptionInterface.class)); - // The object isn't exactly the same, but equals delegates to the actual exception in ImmutableThrowable. - // This is _BAD_ and shouldn't be done, but it's the best way to do it in this particular case. - assertThat(((ExceptionInterface) exceptionInterface).getThrowable(), Matchers.equalTo(exception)); + Throwable capturedException = ((ExceptionInterface) exceptionInterface).getThrowable(); + + assertThat(capturedException.getMessage(), is(exception.getMessage())); + assertThat(capturedException.getStackTrace(), is(capturedException.getStackTrace())); } @Test From 84f74ee563391b083f227ab3013df8fa63cb24d3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 29 Apr 2013 09:53:03 +0200 Subject: [PATCH 0462/2152] Remove incorrect equals and hashCode implementations --- .../raven/event/interfaces/ImmutableThrowable.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java index ecf1e820100..43e30b3c135 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java @@ -79,14 +79,4 @@ public StackTraceElement[] getStackTrace() { public void setStackTrace(StackTraceElement[] stackTrace) { throw new UnsupportedOperationException(); } - - @Override - public int hashCode() { - return actualThrowable.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return actualThrowable.equals(obj); - } } From 8eef717af8aa19f658a5adb545b884b9f9f9fa1a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 29 Apr 2013 10:04:02 +0200 Subject: [PATCH 0463/2152] Do not verify the captured exception against itself --- .../src/test/java/net/kencochrane/raven/AbstractLoggerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java index fd762f7dae9..2695de8e28d 100644 --- a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java @@ -80,7 +80,7 @@ public void testLogException() throws Exception { Throwable capturedException = ((ExceptionInterface) exceptionInterface).getThrowable(); assertThat(capturedException.getMessage(), is(exception.getMessage())); - assertThat(capturedException.getStackTrace(), is(capturedException.getStackTrace())); + assertThat(capturedException.getStackTrace(), is(exception.getStackTrace())); } @Test From 24c2cb805c8cfa8126ea497dd571413f3c0c7905 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 29 Apr 2013 10:41:37 +0200 Subject: [PATCH 0464/2152] Set an implementation of equals/hashCode for ImmutableThrowable --- .../raven/event/interfaces/ImmutableThrowable.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java index 43e30b3c135..328c7e68d83 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java @@ -79,4 +79,17 @@ public StackTraceElement[] getStackTrace() { public void setStackTrace(StackTraceElement[] stackTrace) { throw new UnsupportedOperationException(); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + return actualThrowable == ((ImmutableThrowable) o).actualThrowable; + } + + @Override + public int hashCode() { + return actualThrowable.hashCode(); + } } From cca03c0c91acca78b03864302f43be4932d51eeb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 29 Apr 2013 10:43:14 +0200 Subject: [PATCH 0465/2152] Ensure that circular exception are handled --- .../marshaller/json/ExceptionInterfaceBinding.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java index a5ee6e398c9..753996ba58d 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java @@ -6,8 +6,12 @@ import net.kencochrane.raven.event.interfaces.StackTraceInterface; import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Logger; public class ExceptionInterfaceBinding implements InterfaceBinding { + private static final Logger logger = Logger.getLogger(ExceptionInterfaceBinding.class.getCanonicalName()); private static final String TYPE_PARAMETER = "type"; private static final String VALUE_PARAMETER = "value"; private static final String MODULE_PARAMETER = "module"; @@ -20,10 +24,13 @@ public ExceptionInterfaceBinding(InterfaceBinding stackTrac @Override public void writeInterface(JsonGenerator generator, ExceptionInterface exceptionInterface) throws IOException { + Set dejaVu = new HashSet(); ImmutableThrowable throwable = exceptionInterface.getThrowable(); generator.writeStartArray(); while (throwable != null) { + dejaVu.add(throwable); + generator.writeStartObject(); generator.writeStringField(TYPE_PARAMETER, throwable.getActualClass().getSimpleName()); generator.writeStringField(VALUE_PARAMETER, throwable.getMessage()); @@ -32,6 +39,11 @@ public void writeInterface(JsonGenerator generator, ExceptionInterface exception stackTraceInterfaceBinding.writeInterface(generator, new StackTraceInterface(throwable.getStackTrace())); generator.writeEndObject(); throwable = throwable.getCause(); + + if (dejaVu.contains(throwable)) { + logger.warning("Exiting a circular referencing exception!"); + break; + } } generator.writeEndArray(); } From 3a8f67c29975ed996454f9a4e691514742dde2d5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 29 Apr 2013 10:52:29 +0200 Subject: [PATCH 0466/2152] Fix indentation, imports, code order --- .../java/net/kencochrane/raven/log4j/SentryAppenderIT.java | 6 +++--- .../main/java/net/kencochrane/raven/event/EventBuilder.java | 1 - .../test/java/net/kencochrane/raven/AbstractLoggerTest.java | 1 - .../raven/marshaller/json/TestMessageInterfaceBinding.java | 1 - .../net/kencochrane/raven/sentrystub/SentryHttpServlet.java | 2 -- .../java/net/kencochrane/raven/sentrystub/SentryStub.java | 4 ++-- .../raven/sentrystub/SentryUdpContextListener.java | 4 +--- 7 files changed, 6 insertions(+), 13 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java index a7eca83b53e..284a78b3210 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java @@ -14,17 +14,17 @@ public class SentryAppenderIT { private SentryStub sentryStub; @Before - public void setUp(){ + public void setUp() { sentryStub = new SentryStub(); } @After - public void tearDown(){ + public void tearDown() { sentryStub.removeEvents(); } @Test - public void testInfoLog(){ + public void testInfoLog() { assertThat(sentryStub.getEventCount(), is(0)); logger.info("This is a test"); assertThat(sentryStub.getEventCount(), is(1)); diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index a1ce52727fb..35105ff137d 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -239,7 +239,6 @@ public EventBuilder setServerName(String serverName) { return this; } - /** * Adds an extra property to the event. * diff --git a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java index 2695de8e28d..28a1e1c8e0b 100644 --- a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java @@ -4,7 +4,6 @@ import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.SentryInterface; -import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java index 68a0423bfde..025792c02ba 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java @@ -24,7 +24,6 @@ public class TestMessageInterfaceBinding extends AbstractTestInterfaceBinding { @Mock private MessageInterface mockMessageInterface; - @Before public void setUp() throws Exception { super.setUp(); diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java index 18e04caffee..12b4ca4f713 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryHttpServlet.java @@ -1,8 +1,6 @@ package net.kencochrane.raven.sentrystub; import net.kencochrane.raven.sentrystub.event.Event; -import net.kencochrane.raven.sentrystub.unmarshaller.JsonUnmarshaller; -import net.kencochrane.raven.sentrystub.unmarshaller.Unmarshaller; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStub.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStub.java index 90f15bbb3a9..bd14b7cb462 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStub.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStub.java @@ -30,7 +30,7 @@ public void addEvent(Event event) { events.add(event); } - public void validateEvent(Event event){ + public void validateEvent(Event event) { } public Event parseEvent(InputStream source) { @@ -49,7 +49,7 @@ public void validateAuth(Map authHeader) { authValidator.validateSentryAuth(authHeader); } - public void removeEvents(){ + public void removeEvents() { events.clear(); } } diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java index 6105c4adbb8..89040272b2f 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java @@ -2,8 +2,6 @@ import net.kencochrane.raven.sentrystub.auth.InvalidAuthException; import net.kencochrane.raven.sentrystub.event.Event; -import net.kencochrane.raven.sentrystub.unmarshaller.JsonUnmarshaller; -import net.kencochrane.raven.sentrystub.unmarshaller.Unmarshaller; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; @@ -32,9 +30,9 @@ public class SentryUdpContextListener implements ServletContextListener { private static final Logger logger = Logger.getLogger(SentryUdpContextListener.class.getCanonicalName()); private static final int DEFAULT_SENTRY_UDP_PORT = 9001; private static final String SENTRY_UDP_PORT_PARAMETER = "sentryUdpPort"; + private final SentryStub sentryStub = SentryStub.getInstance(); private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); private DatagramSocket udpSocket; - private final SentryStub sentryStub = SentryStub.getInstance(); @Override public void contextInitialized(ServletContextEvent sce) { From 354fbdda3484c6df83373d2f3c9d998b7a2bfa84 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 29 Apr 2013 10:54:09 +0200 Subject: [PATCH 0467/2152] Remove legacy support (supported only in raven-3.x) --- pom.xml | 1 - raven-legacy/pom.xml | 59 --- .../java/net/kencochrane/raven/Client.java | 444 ----------------- .../java/net/kencochrane/raven/SentryDsn.java | 445 ------------------ .../java/net/kencochrane/raven/Utils.java | 125 ----- 5 files changed, 1074 deletions(-) delete mode 100644 raven-legacy/pom.xml delete mode 100644 raven-legacy/src/main/java/net/kencochrane/raven/Client.java delete mode 100644 raven-legacy/src/main/java/net/kencochrane/raven/SentryDsn.java delete mode 100644 raven-legacy/src/main/java/net/kencochrane/raven/Utils.java diff --git a/pom.xml b/pom.xml index c044623179d..415b2fffc80 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,6 @@ raven - raven-legacy raven-log4j raven-logback raven-log4j2 diff --git a/raven-legacy/pom.xml b/raven-legacy/pom.xml deleted file mode 100644 index 194ad16c87f..00000000000 --- a/raven-legacy/pom.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - 4.0.0 - - - net.kencochrane.raven - raven-all - 4.0-SNAPSHOT - - - raven-legacy - jar - - Raven-Java legacy client - Legacy interface for the new version of Raven-Java. - - - 1.1.1 - 2.5 - - - - - ${project.groupId} - raven - - - commons-codec - commons-codec - - - com.googlecode.json-simple - json-simple - ${json-simple.version} - - - commons-lang - commons-lang - ${commons-lang.version} - - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - - org.apache.maven.plugins - maven-dependency-plugin - - - org.eclipse.jetty - jetty-maven-plugin - - - - diff --git a/raven-legacy/src/main/java/net/kencochrane/raven/Client.java b/raven-legacy/src/main/java/net/kencochrane/raven/Client.java deleted file mode 100644 index b0da221faf2..00000000000 --- a/raven-legacy/src/main/java/net/kencochrane/raven/Client.java +++ /dev/null @@ -1,444 +0,0 @@ -//CHECKSTYLE.OFF: .* - -package net.kencochrane.raven; - -import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.EventBuilder; -import net.kencochrane.raven.event.helper.EventBuilderHelper; -import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import org.apache.commons.codec.binary.Hex; -import org.apache.commons.lang.time.DateFormatUtils; -import org.json.simple.JSONObject; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.io.IOException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.*; -import java.util.zip.CRC32; -import java.util.zip.Checksum; - -import static org.apache.commons.codec.binary.Base64.encodeBase64String; - -/** - * Raven client for Java, allowing sending of messages to Sentry. - *

    - * Clients will typically automatically start the underlying transport layer once instantiated. The default client - * configuration relies on the following mapping of schemes to transport classes: - *

    - *
      - *
    • http:
    • - *
    • https:
    • - *
    • naive+https
    • - *
    • udp
    • - *
    - *

    - *

    - * - * @deprecated Use {@link Raven} instead. - */ -@Deprecated -public class Client { - - private final Raven raven; - - @Deprecated - public interface Default { - @Deprecated - String LOGGER = "root"; - @Deprecated - int LOG_LEVEL = 5000; - @Deprecated - String EMPTY_MESSAGE = "(empty)"; - } - - /** - * Async transport layers require some extra work when instantiating. - */ - @Deprecated - public static final String VARIANT_ASYNC = "async"; - - /** - * The registry mapping schemes to transport layer classes. - */ - @Deprecated - protected static final Map TRANSPORT_REGISTRY = new HashMap(); - - /** - * The dsn used by this client. - */ - @Deprecated - public final SentryDsn dsn = null; - - /** - * Whether messages should be compressed or not - defaults to true. - */ - @Deprecated - protected boolean messageCompressionEnabled = true; - - /** - * Default, easy constructor. - *

    - * A client instance instantiated through this constructor will use the Sentry DSN returned by - * {@link SentryDsn#buildOptional()} and perform an automatic start. - *

    - */ - @Deprecated - public Client() { - raven = new Raven(); - } - - /** - * Extension of the easy constructor {@link #Client()} that allows you to turn off the autostart behavior. - * - * @param autoStart whether to start the underlying transport automatically or not - */ - @Deprecated - public Client(boolean autoStart) { - raven = new Raven(); - } - - /** - * Constructor that performs an autostart using the transport determined by the supplied dsn. - *

    - * Watch out: this constructor will always use the supplied dsn and not look for a Sentry DSN in other locations - * such as an environment variable or system property. - *

    - * - * @param dsn dsn to use - */ - @Deprecated - public Client(SentryDsn dsn) { - raven = new Raven(dsn.toString(true)); - } - - /** - * Constructor using the transport determined by the supplied dsn. - *

    - * Watch out: this constructor will always use the supplied dsn and not look for a Sentry DSN in other locations - * such as an environment variable or system property. - *

    - * - * @param dsn dsn to use - * @param autoStart whether to start the underlying transport layer automatically - */ - @Deprecated - public Client(SentryDsn dsn, boolean autoStart) { - raven = new Raven(dsn.toString(true)); - } - - /** - * Set the processors to be used by this client. Instances from the list are - * copied over. - * - * @param processors a list of processors to be used by this client - */ - @Deprecated - public synchronized void setJSONProcessors(List processors) { - //NOOP - } - - @Deprecated - public boolean isMessageCompressionEnabled() { - //NOOP - return false; - } - - @Deprecated - public void setMessageCompressionEnabled(boolean messageCompressionEnabled) { - //NOOP - } - - private String buildAndSendEvent(EventBuilder eventBuilder) { - for (EventBuilderHelper builderHelper : raven.getBuilderHelpers()) { - builderHelper.helpBuildingEvent(eventBuilder); - } - Event event = eventBuilder.build(); - raven.sendEvent(event); - return event.getId().toString().replaceAll("-", ""); - - } - - private static Event.Level convertLevel(Integer logLevel) { - if (logLevel == null) - return null; - - switch (logLevel) { - case 1: - return Event.Level.DEBUG; - case 2: - return Event.Level.INFO; - case 3: - return Event.Level.WARNING; - case 4: - return Event.Level.ERROR; - case 5: - return Event.Level.FATAL; - default: - return null; - } - } - - - @Deprecated - public String captureMessage(String msg) { - EventBuilder eventBuilder = new EventBuilder().setMessage(msg); - return buildAndSendEvent(eventBuilder); - } - - @Deprecated - public String captureMessage(String msg, Map tags) { - EventBuilder eventBuilder = new EventBuilder().setMessage(msg); - for (Map.Entry tag : tags.entrySet()) { - eventBuilder.addTag(tag.getKey(), tag.getValue().toString()); - } - return buildAndSendEvent(eventBuilder); - } - - @Deprecated - public String captureMessage(String message, Long timestamp, String loggerClass, Integer logLevel, String culprit) { - EventBuilder eventBuilder = new EventBuilder() - .setMessage(message) - .setTimestamp(new Date(timestamp)) - .setLogger(loggerClass) - .setLevel(convertLevel(logLevel)) - .setCulprit(culprit); - - return buildAndSendEvent(eventBuilder); - } - - @Deprecated - public String captureMessage(String message, Long timestamp, String loggerClass, Integer logLevel, String culprit, Map tags) { - EventBuilder eventBuilder = new EventBuilder() - .setMessage(message) - .setTimestamp(new Date(timestamp)) - .setLogger(loggerClass) - .setLevel(convertLevel(logLevel)) - .setCulprit(culprit); - for (Map.Entry tag : tags.entrySet()) { - eventBuilder.addTag(tag.getKey(), tag.getValue().toString()); - } - - return buildAndSendEvent(eventBuilder); - } - - @Deprecated - public String captureException(Throwable exception) { - EventBuilder eventBuilder = new EventBuilder() - .setMessage(exception.getMessage()) - .addSentryInterface(new ExceptionInterface(exception)); - return buildAndSendEvent(eventBuilder); - } - - @Deprecated - public String captureException(Throwable exception, Map tags) { - EventBuilder eventBuilder = new EventBuilder() - .setMessage(exception.getMessage()) - .addSentryInterface(new ExceptionInterface(exception)); - for (Map.Entry tag : tags.entrySet()) { - eventBuilder.addTag(tag.getKey(), tag.getValue().toString()); - } - return buildAndSendEvent(eventBuilder); - } - - @Deprecated - public String captureException(String logMessage, long timestamp, String loggerName, Integer logLevel, String culprit, Throwable exception) { - EventBuilder eventBuilder = new EventBuilder() - .setMessage(logMessage) - .setTimestamp(new Date(timestamp)) - .setLogger(loggerName) - .setLevel(convertLevel(logLevel)) - .setCulprit(culprit) - .addSentryInterface(new ExceptionInterface(exception)); - return buildAndSendEvent(eventBuilder); - } - - @Deprecated - public String captureException(String logMessage, long timestamp, String loggerName, Integer logLevel, String culprit, Throwable exception, Map tags) { - EventBuilder eventBuilder = new EventBuilder() - .setMessage(logMessage) - .setTimestamp(new Date(timestamp)) - .setLogger(loggerName) - .setLevel(convertLevel(logLevel)) - .setCulprit(culprit) - .addSentryInterface(new ExceptionInterface(exception)); - for (Map.Entry tag : tags.entrySet()) { - eventBuilder.addTag(tag.getKey(), tag.getValue().toString()); - } - return buildAndSendEvent(eventBuilder); - } - - @Deprecated - public synchronized void start() { - //NOOP - } - - @Deprecated - public boolean isStarted() { - return true; - } - - @Deprecated - public synchronized void stop() { - try { - raven.getConnection().close(); - } catch (IOException e) { - //TODO: Handle this properly - e.printStackTrace(); - } - } - - @Deprecated - public boolean isDisabled() { - return false; - } - - @Deprecated - protected Message buildMessage(String message, String timestamp, String loggerClass, Integer logLevel, String culprit, Throwable exception, Map tags) { - throw new UnsupportedOperationException(); - } - - @Deprecated - protected void send(Message message, long timestamp) { - throw new UnsupportedOperationException(); - } - - /** - * Generates a unique event id. - * - * @return hexadecimal UUID4 String - */ - @Deprecated - protected String generateEventId() { - // If we keep the -'s in the uuid, it is too long, remove them - return UUID.randomUUID().toString().replaceAll("-", ""); - } - - /** - * Formats a timestamp in the format expected by Sentry. - * - * @param timestamp timestamp to format - * @return formatted timestamp - */ - @Deprecated - protected String formatTimestamp(long timestamp) { - return DateFormatUtils.formatUTC(timestamp, DateFormatUtils.ISO_DATETIME_FORMAT.getPattern()); - } - - /** - * Registers the transport class for the given scheme. - * - * @param scheme scheme to register for - * @param transportClass transport class to register for the scheme - * @return the previously registered transport class for the scheme, if any - */ - @Deprecated - public static Class register(String scheme, Class transportClass) { - return TRANSPORT_REGISTRY.put(scheme, transportClass); - } - - /** - * Registers the default transport classes. - */ - @Deprecated - public static void registerDefaults() { - //NOOP - } - - /** - * Builds the HMAC sentry signature. - *

    - * The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated, - * and an arbitrary client version string. - *

    - * The client version should be something distinct to your client, and is simply for reporting purposes. - * To generate the HMAC signature, take the following example (in Python): - *

    - * hmac.new(public_key, '%s %s' % (timestamp, message), hashlib.sha1).hexdigest() - * - * @param message the error message to send to sentry - * @param timestamp the timestamp for when the message was created - * @param key sentry public key - * @return SHA1-signed HMAC string - */ - @Deprecated - public static String sign(String message, long timestamp, String key) { - final String algo = "HmacSHA1"; - try { - SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), algo); - Mac mac = Mac.getInstance(algo); - mac.init(signingKey); - byte[] rawHmac = mac.doFinal((timestamp + " " + message).getBytes()); - return new String(Hex.encodeHex(rawHmac)); - } catch (NoSuchAlgorithmException e) { - throw new InvalidConfig("Could not sign message: " + e.getMessage(), e); - } catch (InvalidKeyException e) { - throw new InvalidConfig("Could not sign message: " + e.getMessage(), e); - } - } - - /** - * An almost-unique hash identifying the this event to improve aggregation. - * - * @param message The message we are sending to sentry - * @return CRC32 Checksum string - */ - @Deprecated - public static String calculateChecksum(String message) { - byte bytes[] = message.getBytes(); - Checksum checksum = new CRC32(); - checksum.update(bytes, 0, bytes.length); - return String.valueOf(checksum.getValue()); - } - - @Deprecated - public static class InvalidConfig extends RuntimeException { - - public InvalidConfig(String msg) { - super(msg); - } - - public InvalidConfig(String msg, Throwable t) { - super(msg, t); - } - - } - - @Deprecated - public static class Message { - - @Deprecated - public static final Message NONE = new Message(null, "-1", false); - - @Deprecated - public final JSONObject json; - @Deprecated - public final String eventId; - @Deprecated - public final boolean compress; - - @Deprecated - public Message(JSONObject json, String eventId, boolean compress) { - this.json = json; - this.eventId = eventId; - this.compress = compress; - } - - @Deprecated - public String encoded() { - byte[] raw = Utils.toUtf8(json.toJSONString()); - if (compress) { - raw = Utils.compress(raw); - } - return encodeBase64String(raw); - } - - @Override - @Deprecated - public String toString() { - return json.toJSONString(); - } - } - -} diff --git a/raven-legacy/src/main/java/net/kencochrane/raven/SentryDsn.java b/raven-legacy/src/main/java/net/kencochrane/raven/SentryDsn.java deleted file mode 100644 index f06fc50e7a3..00000000000 --- a/raven-legacy/src/main/java/net/kencochrane/raven/SentryDsn.java +++ /dev/null @@ -1,445 +0,0 @@ -//CHECKSTYLE.OFF: .* - -package net.kencochrane.raven; - -import org.apache.commons.lang.StringUtils; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static org.apache.commons.lang.StringUtils.defaultString; - -/** - * The Sentry DSN is the string you can copy-paste from the Sentry client configuration screen. - *

    - * Use this class to extract the interesting parts from the DSN. - *

    - *

    - * To provide flexible configuration, you can specify configuration options though a query string. For example, to - * enable the sending of the (deprecated) HMAC signature to Sentry, your DSN should look a bit like this: - *

    - *
    http://public:private@host:port/path/projectid?raven.includeSignature=true
    - *

    - * The options that are taken into account depend on the selected transport layer. - *

    - * - * @deprecated Use {@link Dsn} instead. - */ -@Deprecated -public class SentryDsn { - - /** - * Logger. - */ - @Deprecated - private static final Logger LOG = Logger.getLogger("raven.client"); - - /** - * The scheme, e.g. http, https or udp. - */ - @Deprecated - public final String scheme; - - /** - * The scheme variants, e.g. in case of a full scheme of "naive+https" this will hold "naive". - */ - @Deprecated - public final String[] variants; - - /** - * The Sentry host. - */ - @Deprecated - public final String host; - - /** - * The public key of the client. - */ - @Deprecated - public final String publicKey; - - /** - * The secret, private key of the client. - */ - @Deprecated - public final String secretKey; - - /** - * Optional extra path. - */ - @Deprecated - public final String path; - - /** - * The id of the project to log to in Sentry. - */ - @Deprecated - public final String projectId; - - /** - * The server port. - */ - @Deprecated - public final int port; - - /** - * Extra Raven client options. - */ - @Deprecated - public final Map options; - - /** - * Constructor for your convenience. - *

    - * It's recommended to use one of the {@link #build()} or {@link #buildOptional()} methods instead. - *

    - * - * @param scheme scheme - * @param variants scheme variants (e.g. naive, async) - * @param host host - * @param publicKey public key - * @param secretKey private key - * @param path path - * @param projectId project id - * @param port the port - * @param options miscellaneous options - */ - @Deprecated - public SentryDsn(String scheme, String[] variants, String host, String publicKey, String secretKey, String path, String projectId, int port, Map options) { - this.scheme = scheme; - this.variants = (variants == null ? new String[0] : variants); - this.host = host; - this.publicKey = publicKey; - this.secretKey = secretKey; - this.path = path; - this.projectId = projectId; - this.port = port; - if (options == null) { - this.options = Collections.emptyMap(); - } else { - this.options = Collections.unmodifiableMap(options); - } - } - - /** - * Gets the value of an option as a boolean. - *

    - * In case no option is specified, the default value is returned. - *

    - * - * @param key key of the option - * @param defaultValue default value to return when no matching option value was found - * @return the value of the option or the default value when absent - */ - @Deprecated - public boolean getOptionAsBoolean(String key, boolean defaultValue) { - String value = options.get(key); - if (value == null) { - return defaultValue; - } - return Boolean.parseBoolean(value); - } - - /** - * Gets the value of an option as an int. - * - * @param key key of the option - * @param defaultValue value to return when the option was not specified - * @return the value of the option or the default value when absent - */ - @Deprecated - public int getOptionAsInt(String key, int defaultValue) { - String value = options.get(key); - if (value == null) { - return defaultValue; - } - try { - return Integer.parseInt(value); - } catch (NumberFormatException e) { - throw new RuntimeException("Expected " + key + " to be a valid int"); - } - } - - /** - * Checks whether the scheme variant is specified in this dsn. - * - * @param variant variant - * @return true when the variant was specified - */ - @Deprecated - public boolean isVariantIncluded(String variant) { - return Arrays.binarySearch(variants, variant) >= 0; - } - - /** - * Gets the full scheme, optionally excluding some parts. - *

    - * This allows the async transport layer to get the actual underlying transport layer to use, ignoring the async - * variant. - *

    - * - * @param excludes variants to exclude from the full scheme - * @return the full scheme - */ - @Deprecated - public String getFullScheme(String... excludes) { - Set excludedSchemes = Collections.emptySet(); - if (excludes != null && excludes.length > 0) { - excludedSchemes = new HashSet(Arrays.asList(excludes)); - } - String full = ""; - List parts = new LinkedList(); - for (String variant : variants) { - if (!excludedSchemes.contains(variant)) { - parts.add(variant); - } - } - parts.add(scheme); - return StringUtils.join(parts, '+'); - } - - /** - * Returns the full dsn or the derived dsn typically used for transport. - * - * @param full whether to generate the full or the derived dsn - * @return the full or derived dsn - */ - @Deprecated - public String toString(boolean full) { - String protocol = (!full || variants.length == 0 ? scheme : StringUtils.join(variants, '+') + "+" + scheme); - String fullHost = (port < 0 ? host : host + ":" + port); - String fullPath = (path == null ? "" : path); - if (!full) { - return String.format("%s://%s%s", protocol, fullHost, fullPath); - } - fullPath += "/" + projectId; - String user = (StringUtils.isBlank(secretKey) ? publicKey : publicKey + ":" + secretKey); - return String.format("%s://%s@%s%s", protocol, user, fullHost, fullPath); - } - - @Override - @Deprecated - public String toString() { - return toString(true); - } - - /** - * Builds the Sentry dsn based on the default lookups as specified by {@link DefaultLookUps}. - *

    - * PaaS providers such as Heroku prefer environment variables. This method will first examine the environment and - * then the system properties to find a Sentry dsn value. - *

    - * - * @return the Sentry dsn when found - */ - @Deprecated - public static SentryDsn build() { - return build(null, DefaultLookUps.values(), null); - } - - /** - * Performs the same logic as {@link #build()} but will catch any {@link InvalidDsnException} thrown and return null - * instead. - * - * @return the Sentry dsn when found and valid or null instead - */ - @Deprecated - public static SentryDsn buildOptional() { - try { - return build(); - } catch (SentryDsn.InvalidDsnException e) { - LOG.log(Level.WARNING, "Could not automatically determine a valid DSN; client will be disabled", e); - return null; - } - } - - /** - * Builds the Sentry dsn. - *

    - * In case a Sentry dsn is specified in the environment or system properties, that value takes precedence over the - * fullDsn parameter. - *

    - * - * @param fullDsn dsn - * @return the dsn found in either the environment, system properties or derived from the parameter fullDsn - */ - @Deprecated - public static SentryDsn build(String fullDsn) { - return build(fullDsn, DefaultLookUps.values(), null); - } - - /** - * Performs the same logic as {@link #build(String)} but will catch any {@link InvalidDsnException} thrown and - * return null instead. - * - * @return the dsn found in either the environment, system properties or derived from the parameter - * fullDsn; if no valid dsn is available, this will return null - */ - @Deprecated - public static SentryDsn buildOptional(String fullDsn) { - try { - return build(fullDsn); - } catch (SentryDsn.InvalidDsnException e) { - LOG.log(Level.WARNING, "Could not automatically determine a valid DSN; client will be disabled", e); - return null; - } - } - - /** - * Builds the dsn. - *

    - * The overrides take precedence over the dsn parameter, while the fallbacks provide a way to look for the dsn when - * the dsn parameter was empty. - *

    - * - * @param fullDsn the supplied dsn - * @param overrides places to check for a dsn value before using the supplied dsn - * @param fallbacks places to check for a dsn value when the supplied dsn is an empty or null string - * @return the built Sentry dsn - */ - @Deprecated - public static SentryDsn build(final String fullDsn, LookUp[] overrides, LookUp[] fallbacks) { - String dsn = defaultString(firstResult(overrides), defaultString(fullDsn, firstResult(fallbacks))); - if (StringUtils.isBlank(dsn)) { - throw new InvalidDsnException("No valid Sentry DSN found"); - } - int schemeEnd = dsn.indexOf("://"); - if (schemeEnd <= 0) { - throw new InvalidDsnException("Expected to discover a scheme in the Sentry DSN"); - } - String fullScheme = dsn.substring(0, schemeEnd); - String[] schemeParts = StringUtils.split(fullScheme, '+'); - String scheme = fullScheme; - String[] variants = null; - if (schemeParts.length > 1) { - variants = Arrays.copyOfRange(schemeParts, 0, schemeParts.length - 1); - scheme = schemeParts[schemeParts.length - 1]; - } - try { - // To prevent us from having to register a handler for for example udp URLs, we'll replace the original - // scheme with "http" and let the URL code of Java handle the parsing. - URL url = new URL("http" + dsn.substring(schemeEnd)); - String[] userParts = url.getUserInfo().split(":"); - String publicKey = userParts[0]; - String secretKey = null; - if (userParts.length > 1) { - secretKey = userParts[1]; - } - String urlPath = url.getPath(); - int lastSlash = urlPath.lastIndexOf('/'); - String path = urlPath.substring(0, lastSlash); - String projectId = urlPath.substring(lastSlash + 1); - Map options = parseQueryString(url.getQuery()); - return new SentryDsn(scheme, variants, url.getHost(), publicKey, secretKey, path, projectId, url.getPort(), options); - } catch (MalformedURLException e) { - // This exception should only be thrown when an unhandled scheme slips in which the above code should - // prevent. Nevertheless: throw something. - throw new InvalidDsnException("Failed to parse " + dsn, e); - } - } - - /** - * See {@link #build(String, net.kencochrane.raven.SentryDsn.LookUp[], net.kencochrane.raven.SentryDsn.LookUp[])}. - * This method will return null when no valid DSN was found instead of throwing a - * {@link InvalidDsnException}. - * - * @param fullDsn the supplied dsn - * @param overrides places to check for a dsn value before using the supplied dsn - * @param fallbacks places to check for a dsn value when the supplied dsn is an empty or null string - * @return the built Sentry dsn or null when no such value was found - */ - @Deprecated - public static SentryDsn buildOptional(final String fullDsn, LookUp[] overrides, LookUp[] fallbacks) { - try { - return build(fullDsn, overrides, fallbacks); - } catch (SentryDsn.InvalidDsnException e) { - LOG.log(Level.WARNING, "Could not automatically determine a valid DSN; client will be disabled", e); - return null; - } - } - - /** - * Parses simple query strings. - *

    - * We don't expect complex query strings - they are only used to pass in Raven options. - *

    - * - * @param q the query string - * @return the key/value pairs in the query string - */ - @Deprecated - protected static Map parseQueryString(String q) { - Map map = new HashMap(); - String[] pairs = StringUtils.split(q, '&'); - if (pairs == null) { - return map; - } - for (String pair : pairs) { - String[] components = StringUtils.split(pair, '='); - String value = (components.length == 1 ? null : StringUtils.join(components, '=', 1, components.length)); - map.put(components[0], value); - } - return map; - } - - @Deprecated - public static String firstResult(LookUp[] lookups) { - if (lookups == null) { - return null; - } - for (LookUp lookup : lookups) { - String dsn = lookup.findDsn(); - if (!StringUtils.isBlank(dsn)) { - return dsn; - } - } - return null; - } - - @Deprecated - public static class InvalidDsnException extends RuntimeException { - - public InvalidDsnException(String message) { - super(message); - } - - public InvalidDsnException(String message, Throwable t) { - super(message, t); - } - - } - - @Deprecated - public interface LookUp { - - @Deprecated - String findDsn(); - - } - - @Deprecated - public enum DefaultLookUps implements LookUp { - - @Deprecated - ENV { - @Override - public String findDsn() { - return System.getenv(Utils.SENTRY_DSN); - } - }, - - @Deprecated - SYSTEM_PROPERTY { - @Override - public String findDsn() { - return System.getProperty(Utils.SENTRY_DSN); - } - } - - - } - -} diff --git a/raven-legacy/src/main/java/net/kencochrane/raven/Utils.java b/raven-legacy/src/main/java/net/kencochrane/raven/Utils.java deleted file mode 100644 index f0b90088f53..00000000000 --- a/raven-legacy/src/main/java/net/kencochrane/raven/Utils.java +++ /dev/null @@ -1,125 +0,0 @@ -//CHECKSTYLE.OFF: .* -package net.kencochrane.raven; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.HashMap; -import java.util.Map; -import java.util.zip.DeflaterOutputStream; -import java.util.zip.InflaterOutputStream; - -/** - * Utilities for the Raven client. - */ -@Deprecated -public abstract class Utils { - - @Deprecated - public static final String SENTRY_DSN = "SENTRY_DSN"; - - @Deprecated - private static final Map CACHE = new HashMap(); - - @Deprecated - public interface Client { - @Deprecated - String VERSION = "2.0"; - @Deprecated - String NAME = "Raven-Java " + VERSION; - } - - @Deprecated - @SuppressWarnings("unchecked") - public static String hostname() { - final String cacheKey = "hostname"; - String name = fromCache(cacheKey, 360000); - if (name == null) { - try { - name = InetAddress.getLocalHost().getCanonicalHostName(); - } catch (UnknownHostException e) { - // can't get hostname - name = "unavailable"; - } - CACHE.put(cacheKey, new CacheEntry(name)); - } - return name; - } - - @Deprecated - public static long now() { - return System.currentTimeMillis(); - } - - @Deprecated - public static byte[] toUtf8(String s) { - try { - return s == null ? new byte[0] : s.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - @Deprecated - public static String fromUtf8(byte[] b) { - try { - return new String(b, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - @Deprecated - public static byte[] compress(byte[] input) { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - DeflaterOutputStream output = new DeflaterOutputStream(bytes); - try { - output.write(input); - output.close(); - return bytes.toByteArray(); - } catch (IOException e) { - // Look, if this thing starts throwing IOExceptions, you're on your own. Sorry. - throw new RuntimeException(e); - } - } - - @Deprecated - public static byte[] decompress(byte[] input) { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - InflaterOutputStream output = new InflaterOutputStream(bytes); - try { - output.write(input); - output.close(); - return bytes.toByteArray(); - } catch (IOException e) { - // ... Let things crash if this happens - throw new RuntimeException(e); - } - } - - @Deprecated - @SuppressWarnings("unchecked") - protected static T fromCache(String key, long timeout) { - CacheEntry entry = (CacheEntry) CACHE.get(key); - if (entry == null) { - return null; - } - return (entry.timestamp + timeout > now() ? entry.value : null); - } - - @Deprecated - protected static class CacheEntry { - @Deprecated - public final T value; - @Deprecated - public final long timestamp; - - public CacheEntry(T value) { - this.value = value; - timestamp = now(); - } - } - -} From 96e5d1e5c7c49ecb4b024ce733ae6a3a2be7dbd1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 29 Apr 2013 12:26:27 +0200 Subject: [PATCH 0468/2152] Store the number of frames in common with the enclosing exception --- .../event/interfaces/StackTraceInterface.java | 16 +++++++++++++++ .../interfaces/StackTraceInterfaceTest.java | 20 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/event/interfaces/StackTraceInterfaceTest.java diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java index 61e324d6e8a..2bb38ee114a 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java @@ -5,9 +5,21 @@ public class StackTraceInterface implements SentryInterface { public static final String STACKTRACE_INTERFACE = "sentry.interfaces.Stacktrace"; private final StackTraceElement[] stackTrace; + private final int framesCommonWithEnclosing; public StackTraceInterface(StackTraceElement[] stackTrace) { + this(stackTrace, new StackTraceElement[0]); + } + + public StackTraceInterface(StackTraceElement[] stackTrace, StackTraceElement[] enclosingStackTrace) { this.stackTrace = Arrays.copyOf(stackTrace, stackTrace.length); + + int m = stackTrace.length - 1; + int n = enclosingStackTrace.length - 1; + while (m >= 0 && n >=0 && stackTrace[m].equals(enclosingStackTrace[n])) { + m--; n--; + } + framesCommonWithEnclosing = stackTrace.length - 1 - m; } @Override @@ -18,4 +30,8 @@ public String getInterfaceName() { public StackTraceElement[] getStackTrace() { return Arrays.copyOf(stackTrace, stackTrace.length); } + + public int getFramesCommonWithEnclosing() { + return framesCommonWithEnclosing; + } } diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/StackTraceInterfaceTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/StackTraceInterfaceTest.java new file mode 100644 index 00000000000..fc81e456a2b --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/StackTraceInterfaceTest.java @@ -0,0 +1,20 @@ +package net.kencochrane.raven.event.interfaces; + + +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class StackTraceInterfaceTest { + @Test + public void testCalculationCommonStackFrames() { + Exception exception = new RuntimeException("exception1"); + exception = new RuntimeException("exception2", exception); + + StackTraceInterface stackTraceInterface = new StackTraceInterface(exception.getCause().getStackTrace(), + exception.getStackTrace()); + + assertThat(stackTraceInterface.getFramesCommonWithEnclosing(), is(exception.getStackTrace().length - 1)); + } +} From b167d335c4c997b70299387639b3a39329f43258 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 29 Apr 2013 12:36:35 +0200 Subject: [PATCH 0469/2152] Mark frames common with enclosing as not in_app --- .../json/StackTraceInterfaceBinding.java | 14 ++++++-- .../json/TestStackTraceInterfaceBinding.java | 34 +++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java index 7968dc790bb..8874841f709 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java @@ -20,6 +20,7 @@ public class StackTraceInterfaceBinding implements InterfaceBinding notInAppFrames; + private boolean removeCommonFramesWithEnclosing = true; public StackTraceInterfaceBinding() { notInAppFrames = new HashSet(); @@ -42,12 +43,14 @@ public StackTraceInterfaceBinding(Set notInAppFrames) { * * @param stackTraceElement current frame in the stackTrace. */ - private void writeFrame(JsonGenerator generator, StackTraceElement stackTraceElement) throws IOException { + private void writeFrame(JsonGenerator generator, StackTraceElement stackTraceElement, boolean commonWithEnclosing) + throws IOException { generator.writeStartObject(); // Do not display the file name (irrelevant) as it replaces the module in the sentry interface. //generator.writeStringField(FILENAME_PARAMETER, stackTraceElement.getFileName()); generator.writeStringField(MODULE_PARAMETER, stackTraceElement.getClassName()); - generator.writeBooleanField(IN_APP_PARAMETER, isFrameInApp(stackTraceElement)); + generator.writeBooleanField(IN_APP_PARAMETER, !(removeCommonFramesWithEnclosing && commonWithEnclosing) + && isFrameInApp(stackTraceElement)); generator.writeStringField(FUNCTION_PARAMETER, stackTraceElement.getMethodName()); generator.writeNumberField(LINE_NO_PARAMETER, stackTraceElement.getLineNumber()); generator.writeEndObject(); @@ -69,13 +72,18 @@ public void writeInterface(JsonGenerator generator, StackTraceInterface stackTra generator.writeStartObject(); generator.writeArrayFieldStart(FRAMES_PARAMETER); + int commonWithEnclosing = stackTraceInterface.getFramesCommonWithEnclosing(); // Go through the stackTrace frames from the first call to the last for (int i = stackTrace.length - 1; i >= 0; i--) { - writeFrame(generator, stackTrace[i]); + writeFrame(generator, stackTrace[i], commonWithEnclosing-- > 0); } generator.writeEndArray(); generator.writeEndObject(); } + + public void setRemoveCommonFramesWithEnclosing(boolean removeCommonFramesWithEnclosing) { + this.removeCommonFramesWithEnclosing = removeCommonFramesWithEnclosing; + } } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java index 7ad4f3f079c..70674387e7a 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java @@ -45,4 +45,38 @@ public void testSingleStackFrame() throws Exception { assertThat(frames.get(0).get("function").asText(), is(methodName)); assertThat(frames.get(0).get("lineno").asInt(), is(lineNumber)); } + + @Test + public void testFramesCommonWithEnclosing() throws Exception { + StackTraceElement stackTraceElement = new StackTraceElement("", "", null, 0); + when(mockStackTraceInterface.getStackTrace()).thenReturn(new StackTraceElement[]{stackTraceElement, stackTraceElement}); + when(mockStackTraceInterface.getFramesCommonWithEnclosing()).thenReturn(1); + + JsonGenerator jSonGenerator = getJsonGenerator(); + interfaceBinding.setRemoveCommonFramesWithEnclosing(true); + interfaceBinding.writeInterface(jSonGenerator, mockStackTraceInterface); + jSonGenerator.close(); + + JsonNode frames = getMapper().readValue(getJsonParser(), JsonNode.class).get("frames"); + assertThat(frames.size(), is(2)); + assertThat(frames.get(0).get("in_app").asBoolean(), is(false)); + assertThat(frames.get(1).get("in_app").asBoolean(), is(true)); + } + + @Test + public void testFramesCommonWithEnclosingDisabled() throws Exception { + StackTraceElement stackTraceElement = new StackTraceElement("", "", null, 0); + when(mockStackTraceInterface.getStackTrace()).thenReturn(new StackTraceElement[]{stackTraceElement, stackTraceElement}); + when(mockStackTraceInterface.getFramesCommonWithEnclosing()).thenReturn(1); + + JsonGenerator jSonGenerator = getJsonGenerator(); + interfaceBinding.setRemoveCommonFramesWithEnclosing(false); + interfaceBinding.writeInterface(jSonGenerator, mockStackTraceInterface); + jSonGenerator.close(); + + JsonNode frames = getMapper().readValue(getJsonParser(), JsonNode.class).get("frames"); + assertThat(frames.size(), is(2)); + assertThat(frames.get(0).get("in_app").asBoolean(), is(true)); + assertThat(frames.get(1).get("in_app").asBoolean(), is(true)); + } } From aaf3edccc6b7a800d09cb6c25fab844a78f499c7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 29 Apr 2013 13:55:46 +0200 Subject: [PATCH 0470/2152] Add details on enclosing stacktrace in Exception handling --- .../raven/marshaller/json/ExceptionInterfaceBinding.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java index 753996ba58d..4e632a476af 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java @@ -26,6 +26,7 @@ public ExceptionInterfaceBinding(InterfaceBinding stackTrac public void writeInterface(JsonGenerator generator, ExceptionInterface exceptionInterface) throws IOException { Set dejaVu = new HashSet(); ImmutableThrowable throwable = exceptionInterface.getThrowable(); + StackTraceElement[] enclosingStackTrace = new StackTraceElement[0]; generator.writeStartArray(); while (throwable != null) { @@ -36,8 +37,9 @@ public void writeInterface(JsonGenerator generator, ExceptionInterface exception generator.writeStringField(VALUE_PARAMETER, throwable.getMessage()); generator.writeStringField(MODULE_PARAMETER, throwable.getActualClass().getPackage().getName()); generator.writeFieldName(STACKTRACE_PARAMETER); - stackTraceInterfaceBinding.writeInterface(generator, new StackTraceInterface(throwable.getStackTrace())); + stackTraceInterfaceBinding.writeInterface(generator, new StackTraceInterface(throwable.getStackTrace(), enclosingStackTrace)); generator.writeEndObject(); + enclosingStackTrace = throwable.getStackTrace(); throwable = throwable.getCause(); if (dejaVu.contains(throwable)) { From df2342fb2e5c53f86653724479e3ce93c09d0310 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 29 Apr 2013 22:22:32 +0200 Subject: [PATCH 0471/2152] Decode queryString with an URLDecoder --- .../main/java/net/kencochrane/raven/Dsn.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Dsn.java b/raven/src/main/java/net/kencochrane/raven/Dsn.java index 6b9258aca5c..fd595b6ba53 100644 --- a/raven/src/main/java/net/kencochrane/raven/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/Dsn.java @@ -6,8 +6,10 @@ import javax.naming.InitialContext; import javax.naming.NamingException; import javax.naming.NoInitialContextException; +import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; +import java.net.URLDecoder; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; @@ -181,12 +183,17 @@ private void extractUserKeys(URI dsnUri) { */ private void extractOptions(URI dsnUri) { String query = dsnUri.getQuery(); - if (query == null) + if (query == null || query.isEmpty()) return; - String[] optionPairs = query.split("&"); - for (String optionPair : optionPairs) { - String[] pairDetails = optionPair.split("="); - options.put(pairDetails[0], (pairDetails.length > 1) ? pairDetails[1] : ""); + for (String optionPair : query.split("&")) { + try { + String[] pairDetails = optionPair.split("="); + String key = URLDecoder.decode(pairDetails[0], "UTF-8"); + String value = pairDetails.length > 1 ? URLDecoder.decode(pairDetails[1], "UTF-8") : null; + options.put(key, value); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("Impossible to decode the query parameter '" + optionPair + "'", e); + } } } From ba95a3aea8b7cd9192d3eb622a89b000da700958 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 29 Apr 2013 23:47:50 +0200 Subject: [PATCH 0472/2152] Create a Simple factory to create Raven instances --- .../net/kencochrane/raven/RavenFactory.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/RavenFactory.java diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java new file mode 100644 index 00000000000..42d46f09928 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -0,0 +1,48 @@ +package net.kencochrane.raven; + +import java.util.ServiceLoader; +import java.util.logging.Level; +import java.util.logging.Logger; + +public abstract class RavenFactory { + private static final Logger logger = Logger.getLogger(RavenFactory.class.getCanonicalName()); + private static final ServiceLoader RAVEN_FACTORIES = ServiceLoader.load(RavenFactory.class); + + public static Raven ravenInstance(Dsn dsn) { + for (RavenFactory ravenFactory : RAVEN_FACTORIES) { + Raven raven = getRavenSafely(dsn, ravenFactory); + if (raven != null) { + return raven; + } + } + + throw new IllegalStateException("Couldn't create a raven instance for '" + dsn + "'"); + } + + public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { + for (RavenFactory ravenFactory : RAVEN_FACTORIES) { + if (!ravenFactoryName.equals(ravenFactory.getClass().getCanonicalName())) + continue; + + Raven raven = getRavenSafely(dsn, ravenFactory); + if (raven != null) { + return raven; + } + } + + throw new IllegalStateException("Couldn't create a raven instance for '" + dsn + "'"); + } + + private static Raven getRavenSafely(Dsn dsn, RavenFactory ravenFactory) { + Raven raven = null; + try { + raven = ravenFactory.createRavenInstance(dsn); + } catch (Exception e) { + logger.log(Level.WARNING, "An exception occurred during the creation of a Raven instance with " + + "'" + ravenFactory + "' using the DSN '" + dsn + "'", e); + } + return raven; + } + + public abstract Raven createRavenInstance(Dsn dsn); +} From d7eae90f875df79f44d1cde35ff7cb2cf8c304f8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 29 Apr 2013 23:48:07 +0200 Subject: [PATCH 0473/2152] Create a simple implementation of RavenFactory --- .../raven/DefaultRavenFactory.java | 109 ++++++++++++++++++ .../raven/connection/AsyncConnection.java | 4 +- .../raven/connection/HttpConnection.java | 2 +- .../raven/connection/UdpConnection.java | 2 +- 4 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java new file mode 100644 index 00000000000..138df9f7cbf --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -0,0 +1,109 @@ +package net.kencochrane.raven; + +import net.kencochrane.raven.connection.AsyncConnection; +import net.kencochrane.raven.connection.Connection; +import net.kencochrane.raven.connection.HttpConnection; +import net.kencochrane.raven.connection.UdpConnection; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.HttpInterface; +import net.kencochrane.raven.event.interfaces.MessageInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import net.kencochrane.raven.marshaller.Marshaller; +import net.kencochrane.raven.marshaller.json.*; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class DefaultRavenFactory extends RavenFactory { + private static final Logger logger = Logger.getLogger(DefaultRavenFactory.class.getCanonicalName()); + + @Override + public Raven createRavenInstance(Dsn dsn) { + Raven raven = new Raven(); + raven.setConnection(createConnection(dsn)); + return raven; + } + + private Connection createConnection(Dsn dsn) { + String protocol = dsn.getProtocol(); + Connection connection; + + if (protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https")) { + logger.log(Level.INFO, "Using an HTTP connection to Sentry."); + connection = createHttpConnection(dsn); + } else if (protocol.equalsIgnoreCase("udp")) { + logger.log(Level.INFO, "Using an UDP connection to Sentry."); + connection = createUdpConnection(dsn); + } else { + throw new IllegalStateException("Couldn't create a connection for the protocol '" + protocol + "'"); + } + + if (dsn.getOptions().containsKey(Dsn.ASYNC_OPTION)) { + connection = createAsyncConnection(dsn, connection); + } + + return connection; + } + + private Connection createAsyncConnection(Dsn dsn, Connection connection) { + int maxThreads; + if (dsn.getOptions().containsKey(AsyncConnection.DSN_MAX_THREADS_OPTION)) { + maxThreads = Integer.parseInt(dsn.getOptions().get(AsyncConnection.DSN_MAX_THREADS_OPTION)); + } else { + maxThreads = AsyncConnection.DEFAULT_MAX_THREADS; + } + + int priority; + if (dsn.getOptions().containsKey(AsyncConnection.DSN_PRIORITY_OPTION)) { + priority = Integer.parseInt(dsn.getOptions().get(AsyncConnection.DSN_PRIORITY_OPTION)); + } else { + priority = AsyncConnection.DEFAULT_PRIORITY; + } + + return new AsyncConnection(connection, true, maxThreads, priority); + } + + private Connection createHttpConnection(Dsn dsn) { + HttpConnection httpConnection = new HttpConnection(HttpConnection.getSentryUrl(dsn), + dsn.getPublicKey(), dsn.getSecretKey()); + httpConnection.setMarshaller(createMarshaller(dsn)); + + // Set the naive mode + httpConnection.setBypassSecurity(dsn.getProtocolSettings().contains(Dsn.NAIVE_PROTOCOL)); + // Set the HTTP timeout + if (dsn.getOptions().containsKey(Dsn.TIMEOUT_OPTION)) + httpConnection.setTimeout(Integer.parseInt(dsn.getOptions().get(Dsn.TIMEOUT_OPTION))); + return httpConnection; + } + + private Connection createUdpConnection(Dsn dsn) { + //String hostname, int port, String publicKey, String secretKey + int port = dsn.getPort() != -1 ? dsn.getPort() : UdpConnection.DEFAULT_UDP_PORT; + UdpConnection udpConnection = new UdpConnection(dsn.getHost(), port, dsn.getPublicKey(), dsn.getSecretKey()); + udpConnection.setMarshaller(createMarshaller(dsn)); + return udpConnection; + } + + private Marshaller createMarshaller(Dsn dsn) { + JsonMarshaller marshaller = new JsonMarshaller(); + + // Set JSON marshaller bindings + StackTraceInterfaceBinding stackTraceBinding = new StackTraceInterfaceBinding(); + //TODO: Set that properly + stackTraceBinding.setRemoveCommonFramesWithEnclosing(true); + //TODO: Add a way to remove in_app frames + //stackTraceBinding. + marshaller.addInterfaceBinding(StackTraceInterface.class, stackTraceBinding); + marshaller.addInterfaceBinding(ExceptionInterface.class, new ExceptionInterfaceBinding(stackTraceBinding)); + marshaller.addInterfaceBinding(MessageInterface.class, new MessageInterfaceBinding()); + HttpInterfaceBinding httpBinding = new HttpInterfaceBinding(); + //TODO: Add a way to clean the HttpRequest + //httpBinding. + marshaller.addInterfaceBinding(HttpInterface.class, httpBinding); + + // Set compression + marshaller.setCompression(!dsn.getOptions().containsKey(Dsn.NOCOMPRESSION_OPTION)); + + return marshaller; + } +} diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index c1ecea2e5f8..a71f120dd0b 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -37,11 +37,11 @@ public class AsyncConnection implements Connection { /** * Number of threads dedicated to the connection usage by default (Number of processors available). */ - private static final int DEFAULT_MAX_THREADS = Runtime.getRuntime().availableProcessors(); + public static final int DEFAULT_MAX_THREADS = Runtime.getRuntime().availableProcessors(); /** * Default threads priority. */ - private static final int DEFAULT_PRIORITY = Thread.MIN_PRIORITY; + public static final int DEFAULT_PRIORITY = Thread.MIN_PRIORITY; /** * Connection used to actually send the events. */ diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 39febdbb90d..f08abf56341 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -89,7 +89,7 @@ public HttpConnection(URL sentryUrl, String publicKey, String secretKey) { this.sentryUrl = sentryUrl; } - private URL getSentryUrl(Dsn dsn) { + public static URL getSentryUrl(Dsn dsn) { try { String url = dsn.getUri().toString() + "api/" + dsn.getProjectId() + "/store/"; return new URL(url); diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index 8ab9a3d91dd..8d4862030e8 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -18,7 +18,7 @@ * Connection to a Sentry server through an UDP connection. */ public class UdpConnection extends AbstractConnection { - private static final int DEFAULT_UDP_PORT = 9001; + public static final int DEFAULT_UDP_PORT = 9001; private DatagramSocket socket; private Marshaller marshaller = new JsonMarshaller(); From c449f7353b258f872388daa77665b6ab36243e9f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 00:03:57 +0200 Subject: [PATCH 0474/2152] Make DefaultRavenFactory methods protected to ease inheritance --- .../net/kencochrane/raven/DefaultRavenFactory.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 138df9f7cbf..854351fe01c 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -24,7 +24,7 @@ public Raven createRavenInstance(Dsn dsn) { return raven; } - private Connection createConnection(Dsn dsn) { + protected Connection createConnection(Dsn dsn) { String protocol = dsn.getProtocol(); Connection connection; @@ -45,7 +45,7 @@ private Connection createConnection(Dsn dsn) { return connection; } - private Connection createAsyncConnection(Dsn dsn, Connection connection) { + protected Connection createAsyncConnection(Dsn dsn, Connection connection) { int maxThreads; if (dsn.getOptions().containsKey(AsyncConnection.DSN_MAX_THREADS_OPTION)) { maxThreads = Integer.parseInt(dsn.getOptions().get(AsyncConnection.DSN_MAX_THREADS_OPTION)); @@ -63,7 +63,7 @@ private Connection createAsyncConnection(Dsn dsn, Connection connection) { return new AsyncConnection(connection, true, maxThreads, priority); } - private Connection createHttpConnection(Dsn dsn) { + protected Connection createHttpConnection(Dsn dsn) { HttpConnection httpConnection = new HttpConnection(HttpConnection.getSentryUrl(dsn), dsn.getPublicKey(), dsn.getSecretKey()); httpConnection.setMarshaller(createMarshaller(dsn)); @@ -76,7 +76,7 @@ private Connection createHttpConnection(Dsn dsn) { return httpConnection; } - private Connection createUdpConnection(Dsn dsn) { + protected Connection createUdpConnection(Dsn dsn) { //String hostname, int port, String publicKey, String secretKey int port = dsn.getPort() != -1 ? dsn.getPort() : UdpConnection.DEFAULT_UDP_PORT; UdpConnection udpConnection = new UdpConnection(dsn.getHost(), port, dsn.getPublicKey(), dsn.getSecretKey()); @@ -84,7 +84,7 @@ private Connection createUdpConnection(Dsn dsn) { return udpConnection; } - private Marshaller createMarshaller(Dsn dsn) { + protected Marshaller createMarshaller(Dsn dsn) { JsonMarshaller marshaller = new JsonMarshaller(); // Set JSON marshaller bindings From 98597ed18171f08deb6514887ca95a1337fbff0a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 00:06:01 +0200 Subject: [PATCH 0475/2152] Move DSN options to the DefaultRavenFactory Each factory will be able to define its own options --- .../raven/DefaultRavenFactory.java | 43 +++++++++++++++---- .../main/java/net/kencochrane/raven/Dsn.java | 16 ------- .../raven/connection/AsyncConnection.java | 16 ++----- 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 854351fe01c..6d54f930e32 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -15,6 +15,31 @@ import java.util.logging.Logger; public class DefaultRavenFactory extends RavenFactory { + /** + * Option specific to raven-java, allowing to disable the compression of requests to the Sentry Server. + */ + public static final String NOCOMPRESSION_OPTION = "raven.nocompression"; + /** + * Option specific to raven-java, allowing to set a timeout (in ms) for a request to the Sentry server. + */ + public static final String TIMEOUT_OPTION = "raven.timeout"; + /** + * Option to send events asynchronously. + */ + public static final String ASYNC_OPTION = "raven.async"; + /** + * Protocol setting to disable security checks over an SSL connection. + */ + public static final String NAIVE_PROTOCOL = "naive"; + /** + * DSN option for the number of threads assigned for the connection. + */ + public static final String DSN_MAX_THREADS_OPTION = "raven.async.threads"; + /** + * DSN option for the priority of threads assigned for the connection. + */ + public static final String DSN_PRIORITY_OPTION = "raven.async.priority"; + private static final Logger logger = Logger.getLogger(DefaultRavenFactory.class.getCanonicalName()); @Override @@ -38,7 +63,7 @@ protected Connection createConnection(Dsn dsn) { throw new IllegalStateException("Couldn't create a connection for the protocol '" + protocol + "'"); } - if (dsn.getOptions().containsKey(Dsn.ASYNC_OPTION)) { + if (dsn.getOptions().containsKey(ASYNC_OPTION)) { connection = createAsyncConnection(dsn, connection); } @@ -47,15 +72,15 @@ protected Connection createConnection(Dsn dsn) { protected Connection createAsyncConnection(Dsn dsn, Connection connection) { int maxThreads; - if (dsn.getOptions().containsKey(AsyncConnection.DSN_MAX_THREADS_OPTION)) { - maxThreads = Integer.parseInt(dsn.getOptions().get(AsyncConnection.DSN_MAX_THREADS_OPTION)); + if (dsn.getOptions().containsKey(DSN_MAX_THREADS_OPTION)) { + maxThreads = Integer.parseInt(dsn.getOptions().get(DSN_MAX_THREADS_OPTION)); } else { maxThreads = AsyncConnection.DEFAULT_MAX_THREADS; } int priority; - if (dsn.getOptions().containsKey(AsyncConnection.DSN_PRIORITY_OPTION)) { - priority = Integer.parseInt(dsn.getOptions().get(AsyncConnection.DSN_PRIORITY_OPTION)); + if (dsn.getOptions().containsKey(DSN_PRIORITY_OPTION)) { + priority = Integer.parseInt(dsn.getOptions().get(DSN_PRIORITY_OPTION)); } else { priority = AsyncConnection.DEFAULT_PRIORITY; } @@ -69,10 +94,10 @@ protected Connection createHttpConnection(Dsn dsn) { httpConnection.setMarshaller(createMarshaller(dsn)); // Set the naive mode - httpConnection.setBypassSecurity(dsn.getProtocolSettings().contains(Dsn.NAIVE_PROTOCOL)); + httpConnection.setBypassSecurity(dsn.getProtocolSettings().contains(NAIVE_PROTOCOL)); // Set the HTTP timeout - if (dsn.getOptions().containsKey(Dsn.TIMEOUT_OPTION)) - httpConnection.setTimeout(Integer.parseInt(dsn.getOptions().get(Dsn.TIMEOUT_OPTION))); + if (dsn.getOptions().containsKey(TIMEOUT_OPTION)) + httpConnection.setTimeout(Integer.parseInt(dsn.getOptions().get(TIMEOUT_OPTION))); return httpConnection; } @@ -102,7 +127,7 @@ protected Marshaller createMarshaller(Dsn dsn) { marshaller.addInterfaceBinding(HttpInterface.class, httpBinding); // Set compression - marshaller.setCompression(!dsn.getOptions().containsKey(Dsn.NOCOMPRESSION_OPTION)); + marshaller.setCompression(!dsn.getOptions().containsKey(NOCOMPRESSION_OPTION)); return marshaller; } diff --git a/raven/src/main/java/net/kencochrane/raven/Dsn.java b/raven/src/main/java/net/kencochrane/raven/Dsn.java index fd595b6ba53..d306362b701 100644 --- a/raven/src/main/java/net/kencochrane/raven/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/Dsn.java @@ -22,22 +22,6 @@ public class Dsn { * Name of the environment or system variable containing the DSN. */ public static final String DSN_VARIABLE = "SENTRY_DSN"; - /** - * Option specific to raven-java, allowing to disable the compression of requests to the Sentry Server. - */ - public static final String NOCOMPRESSION_OPTION = "raven.nocompression"; - /** - * Option specific to raven-java, allowing to set a timeout (in ms) for a request to the Sentry server. - */ - public static final String TIMEOUT_OPTION = "raven.timeout"; - /** - * Option to send events asynchronously. - */ - public static final String ASYNC_OPTION = "raven.async"; - /** - * Protocol setting to disable security checks over an SSL connection. - */ - public static final String NAIVE_PROTOCOL = "naive"; /** * Lookup name for the DSN in JNDI. */ diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index a71f120dd0b..abef7ce7b38 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -22,26 +22,18 @@ */ public class AsyncConnection implements Connection { /** - * DSN option for the number of threads assigned for the connection. + * Number of threads dedicated to the connection usage by default (Number of processors available). */ - public static final String DSN_MAX_THREADS_OPTION = "raven.async.threads"; + public static final int DEFAULT_MAX_THREADS = Runtime.getRuntime().availableProcessors(); /** - * DSN option for the priority of threads assigned for the connection. + * Default threads priority. */ - public static final String DSN_PRIORITY_OPTION = "raven.async.priority"; + public static final int DEFAULT_PRIORITY = Thread.MIN_PRIORITY; private static final Logger logger = Logger.getLogger(AsyncConnection.class.getCanonicalName()); /** * Timeout of the {@link #executorService}. */ private static final int TIMEOUT = 1000; - /** - * Number of threads dedicated to the connection usage by default (Number of processors available). - */ - public static final int DEFAULT_MAX_THREADS = Runtime.getRuntime().availableProcessors(); - /** - * Default threads priority. - */ - public static final int DEFAULT_PRIORITY = Thread.MIN_PRIORITY; /** * Connection used to actually send the events. */ From c10d2aae63c563a5e0e0a8dec8550177430e08c9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 00:07:21 +0200 Subject: [PATCH 0476/2152] Remove constructors with DSN, let the RavenFactory take care of that --- .../java/net/kencochrane/raven/Raven.java | 58 ------------------- .../raven/connection/AbstractConnection.java | 10 ---- .../raven/connection/AsyncConnection.java | 51 ---------------- .../raven/connection/HttpConnection.java | 22 +------ .../raven/connection/UdpConnection.java | 6 -- 5 files changed, 1 insertion(+), 146 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index c8bafa2a450..e50e219cb1d 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -34,29 +34,8 @@ public class Raven { /** * Builds a default Raven client, trying to figure out which {@link Dsn} can be used. - * - * @see net.kencochrane.raven.Dsn#dsnLookup() */ public Raven() { - this(new Dsn()); - } - - /** - * Builds a default Raven client using the given DSN. - * - * @param dsn Data Source Name as a String to use to connect to sentry. - */ - public Raven(String dsn) { - this(new Dsn(dsn)); - } - - /** - * Builds a default Raven client using the given DSN. - * - * @param dsn Data Source Name as a String to use to connect to sentry. - */ - public Raven(Dsn dsn) { - this(determineConnection(dsn)); } /** @@ -68,43 +47,6 @@ public Raven(Connection connection) { this.connection = connection; } - /** - * Builds a {@link Connection} based on a {@link Dsn}. - *

    - * Currently supports the protocols HTTP(s) with {@link HttpConnection} and UPD with {@link UdpConnection}. - *

    - * - * @param dsn Data Source Name from which the connection will be generated. - * @return a {@link Connection} allowing to send events to a Sentry server or {@code null} if nothing was found. - */ - //TODO: Replace with a factory? - private static Connection determineConnection(Dsn dsn) { - String protocol = dsn.getProtocol(); - Connection connection = null; - JsonMarshaller marshaller = new JsonMarshaller(); - marshaller.setCompression(!dsn.getOptions().containsKey(Dsn.NOCOMPRESSION_OPTION)); - - if (protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https")) { - logger.log(Level.INFO, "Using an HTTP connection to Sentry."); - HttpConnection httpConnection = new HttpConnection(dsn); - httpConnection.setMarshaller(marshaller); - connection = httpConnection; - } else if (protocol.equalsIgnoreCase("udp")) { - logger.log(Level.INFO, "Using an UDP connection to Sentry."); - UdpConnection udpConnection = new UdpConnection(dsn); - udpConnection.setMarshaller(marshaller); - connection = udpConnection; - } else { - logger.log(Level.WARNING, - "Couldn't figure out automatically a connection to Sentry, one should be set manually"); - } - - if (dsn.getOptions().containsKey(Dsn.ASYNC_OPTION)) - connection = new AsyncConnection(connection, dsn); - - return connection; - } - /** * Runs the {@link EventBuilderHelper} against the {@link EventBuilder} to obtain additional information with a * MDC-like system. diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index bf1c8629aef..924fd9020a8 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -1,6 +1,5 @@ package net.kencochrane.raven.connection; -import net.kencochrane.raven.Dsn; import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.exception.ConnectionException; @@ -36,15 +35,6 @@ public abstract class AbstractConnection implements Connection { private final ReentrantLock lock = new ReentrantLock(); private long waitingTime = BASE_WAITING_TIME; - /** - * Creates a connection based on a DSN. - * - * @param dsn Data Source Name of the sentry server. - */ - protected AbstractConnection(Dsn dsn) { - this(dsn.getPublicKey(), dsn.getSecretKey()); - } - /** * Creates a connection based on the public and secret keys. * diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index abef7ce7b38..6ea6c4583af 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -1,6 +1,5 @@ package net.kencochrane.raven.connection; -import net.kencochrane.raven.Dsn; import net.kencochrane.raven.event.Event; import java.io.IOException; @@ -59,20 +58,6 @@ public AsyncConnection(Connection actualConnection) { this(actualConnection, true, DEFAULT_MAX_THREADS, DEFAULT_PRIORITY); } - /** - * Creates a connection which will rely on an executor to send events. - *

    - * Will propagate the {@link #close()} operation and attempt to get the number of Threads and their priority from - * the DSN configuration. - *

    - * - * @param actualConnection connection used to send the events. - * @param dsn Data Source Name containing the additional settings for the async connection. - */ - public AsyncConnection(Connection actualConnection, Dsn dsn) { - this(actualConnection, true, getMaxThreads(dsn), getPriority(dsn)); - } - /** * Creates a connection which will rely on an executor to send events. * @@ -89,42 +74,6 @@ public AsyncConnection(Connection actualConnection, boolean propagateClose, int addShutdownHook(); } - /** - * Gets the number of {@code Thread}s that should be available in the pool. - *

    - * Attempts to get the {@link #DSN_MAX_THREADS_OPTION} option from the {@code Dsn}, - * defaults to {@link #DEFAULT_MAX_THREADS} if not available. - *

    - * - * @param dsn Data Source Name potentially containing settings for the {@link AsyncConnection}. - * @return the number of threads that should be available in the pool. - */ - private static int getMaxThreads(Dsn dsn) { - if (dsn.getOptions().containsKey(DSN_MAX_THREADS_OPTION)) { - return Integer.parseInt(dsn.getOptions().get(DSN_MAX_THREADS_OPTION)); - } else { - return DEFAULT_MAX_THREADS; - } - } - - /** - * Gets the priority of {@code Thread}s in the pool. - *

    - * Attempts to get the {@link #DSN_PRIORITY_OPTION} option from the {@code Dsn}, - * defaults to {@link #DEFAULT_PRIORITY} if not available. - *

    - * - * @param dsn Data Source Name potentially containing settings for the {@link AsyncConnection}. - * @return the priority of threads available in the pool. - */ - private static int getPriority(Dsn dsn) { - if (dsn.getOptions().containsKey(DSN_PRIORITY_OPTION)) { - return Integer.parseInt(dsn.getOptions().get(DSN_PRIORITY_OPTION)); - } else { - return DEFAULT_PRIORITY; - } - } - /** * Adds a hook to shutdown the {@link #executorService} gracefully when the JVM shuts down. */ diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index f08abf56341..99aff16eaea 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -20,8 +20,7 @@ /** * Basic connection to a Sentry server, using HTTP and HTTPS. *

    - * It is possible to enable the "naive mode" through the DSN with {@link Dsn#NAIVE_PROTOCOL} to allow a connection over - * SSL using a certificate with a wildcard. + * It is possible to enable the "naive mode" to allow a connection over SSL using a certificate with a wildcard. *

    */ public class HttpConnection extends AbstractConnection { @@ -65,25 +64,6 @@ public boolean verify(String hostname, SSLSession sslSession) { */ private boolean bypassSecurity; - /** - * Creates a connection through HTTP(s) based on the settings in the {@code dsn}. - * - * @param dsn Data Source Name containing details and options for the connection to Sentry. - */ - public HttpConnection(Dsn dsn) { - super(dsn); - - this.sentryUrl = getSentryUrl(dsn); - - // Check if a timeout is set - if (dsn.getOptions().containsKey(Dsn.TIMEOUT_OPTION)) - setTimeout(Integer.parseInt(dsn.getOptions().get(Dsn.TIMEOUT_OPTION))); - - // Check if the naive mode is on - if (dsn.getProtocolSettings().contains(Dsn.NAIVE_PROTOCOL)) - setBypassSecurity(true); - } - public HttpConnection(URL sentryUrl, String publicKey, String secretKey) { super(publicKey, secretKey); this.sentryUrl = sentryUrl; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index 8d4862030e8..b0ce51e2694 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -1,6 +1,5 @@ package net.kencochrane.raven.connection; -import net.kencochrane.raven.Dsn; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.exception.ConnectionException; import net.kencochrane.raven.marshaller.Marshaller; @@ -22,11 +21,6 @@ public class UdpConnection extends AbstractConnection { private DatagramSocket socket; private Marshaller marshaller = new JsonMarshaller(); - public UdpConnection(Dsn dsn) { - super(dsn); - openSocket(dsn.getHost(), dsn.getPort() != -1 ? dsn.getPort() : DEFAULT_UDP_PORT); - } - public UdpConnection(String hostname, String publicKey, String secretKey) { this(hostname, DEFAULT_UDP_PORT, publicKey, secretKey); } From 0788263f19ef4f1bdbfa9cb5cd5003e049a2fe63 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 00:07:48 +0200 Subject: [PATCH 0477/2152] Change name of getSentryUrl into getSentryApiUrl --- .../main/java/net/kencochrane/raven/DefaultRavenFactory.java | 2 +- .../java/net/kencochrane/raven/connection/HttpConnection.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 6d54f930e32..2367e83b3d3 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -89,7 +89,7 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { } protected Connection createHttpConnection(Dsn dsn) { - HttpConnection httpConnection = new HttpConnection(HttpConnection.getSentryUrl(dsn), + HttpConnection httpConnection = new HttpConnection(HttpConnection.getSentryApiUrl(dsn), dsn.getPublicKey(), dsn.getSecretKey()); httpConnection.setMarshaller(createMarshaller(dsn)); diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 99aff16eaea..193461dc82e 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -69,7 +69,7 @@ public HttpConnection(URL sentryUrl, String publicKey, String secretKey) { this.sentryUrl = sentryUrl; } - public static URL getSentryUrl(Dsn dsn) { + public static URL getSentryApiUrl(Dsn dsn) { try { String url = dsn.getUri().toString() + "api/" + dsn.getProjectId() + "/store/"; return new URL(url); From e9c11b0664c4a58aa4245268db4e1ff71b1d026d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 00:13:01 +0200 Subject: [PATCH 0478/2152] Add simple way to specify frames which aren't in_app --- .../raven/DefaultRavenFactory.java | 3 ++- .../json/StackTraceInterfaceBinding.java | 23 +++++-------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 2367e83b3d3..8f298b121f5 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -11,6 +11,7 @@ import net.kencochrane.raven.marshaller.Marshaller; import net.kencochrane.raven.marshaller.json.*; +import java.util.Collections; import java.util.logging.Level; import java.util.logging.Logger; @@ -117,7 +118,7 @@ protected Marshaller createMarshaller(Dsn dsn) { //TODO: Set that properly stackTraceBinding.setRemoveCommonFramesWithEnclosing(true); //TODO: Add a way to remove in_app frames - //stackTraceBinding. + stackTraceBinding.setNotInAppFrames(Collections.emptySet()); marshaller.addInterfaceBinding(StackTraceInterface.class, stackTraceBinding); marshaller.addInterfaceBinding(ExceptionInterface.class, new ExceptionInterfaceBinding(stackTraceBinding)); marshaller.addInterfaceBinding(MessageInterface.class, new MessageInterfaceBinding()); diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java index 8874841f709..21de22b0f1b 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java @@ -4,6 +4,7 @@ import net.kencochrane.raven.event.interfaces.StackTraceInterface; import java.io.IOException; +import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -19,25 +20,9 @@ public class StackTraceInterfaceBinding implements InterfaceBinding notInAppFrames; + private Set notInAppFrames = Collections.emptySet(); private boolean removeCommonFramesWithEnclosing = true; - public StackTraceInterfaceBinding() { - notInAppFrames = new HashSet(); - notInAppFrames.add("com.sun."); - notInAppFrames.add("java."); - notInAppFrames.add("javax."); - notInAppFrames.add("org.omg."); - notInAppFrames.add("sun."); - notInAppFrames.add("junit."); - notInAppFrames.add("com.intellij.rt."); - } - - public StackTraceInterfaceBinding(Set notInAppFrames) { - // Makes a copy to avoid an external modification. - this.notInAppFrames = new HashSet(notInAppFrames); - } - /** * Writes a single frame based on a {@code StackTraceElement}. * @@ -86,4 +71,8 @@ public void writeInterface(JsonGenerator generator, StackTraceInterface stackTra public void setRemoveCommonFramesWithEnclosing(boolean removeCommonFramesWithEnclosing) { this.removeCommonFramesWithEnclosing = removeCommonFramesWithEnclosing; } + + public void setNotInAppFrames(Set notInAppFrames) { + this.notInAppFrames = notInAppFrames; + } } From 0b4d05449ea5454f062fe2e07b9e7a00bbeb5bce Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 00:19:45 +0200 Subject: [PATCH 0479/2152] Add early support of RavenFactory to appenders --- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 8 ++++++-- .../java/net/kencochrane/raven/log4j2/SentryAppender.java | 8 ++++++-- .../net/kencochrane/raven/logback/SentryAppender.java | 8 ++++++-- .../java/net/kencochrane/raven/jul/SentryHandler.java | 8 ++++++-- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index e06c84d5871..f0f7be1c7e4 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -1,6 +1,8 @@ package net.kencochrane.raven.log4j; +import net.kencochrane.raven.Dsn; import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; @@ -48,8 +50,10 @@ private static Event.Level formatLevel(Level level) { @Override public void activateOptions() { - if (raven == null) - raven = (dsn != null) ? new Raven(dsn) : new Raven(); + if (raven == null){ + //TODO: Handle null dsn, Add a way to select the factory + raven = RavenFactory.ravenInstance(new Dsn(dsn)); + } } @Override diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index f45593458c1..0c9b8b9d294 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -1,6 +1,8 @@ package net.kencochrane.raven.log4j2; +import net.kencochrane.raven.Dsn; import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; @@ -104,8 +106,10 @@ private static String formatCulprit(StackTraceElement stackTraceElement) { @Override public void start() { - if (raven == null) - raven = (dsn != null) ? new Raven(dsn) : new Raven(); + if (raven == null){ + //TODO: Handle null dsn, Add a way to select the factory + raven = RavenFactory.ravenInstance(new Dsn(dsn)); + } } @Override diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 112553b410f..e3adc2bb7dd 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -4,7 +4,9 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.ThrowableProxy; import ch.qos.logback.core.AppenderBase; +import net.kencochrane.raven.Dsn; import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; @@ -57,8 +59,10 @@ private static Event.Level formatLevel(ILoggingEvent iLoggingEvent) { @Override public void start() { super.start(); - if (raven == null) - raven = (dsn != null) ? new Raven(dsn) : new Raven(); + if (raven == null){ + //TODO: Handle null dsn, Add a way to select the factory + raven = RavenFactory.ravenInstance(new Dsn(dsn)); + } } @Override diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index d00ba797300..423c652c887 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -1,6 +1,8 @@ package net.kencochrane.raven.jul; +import net.kencochrane.raven.Dsn; import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; @@ -78,8 +80,10 @@ public void publish(LogRecord record) { } private Raven getRaven() { - if (raven == null) - raven = (dsn != null) ? new Raven(dsn) : new Raven(); + if (raven == null){ + //TODO: Handle null dsn, Add a way to select the factory + raven = RavenFactory.ravenInstance(new Dsn(dsn)); + } return raven; } From 8026c86edc3ff25f450764ede9dcc2fa09c0e637 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 00:30:00 +0200 Subject: [PATCH 0480/2152] Enable SPI for RavenFactory --- .../META-INF/services/net.kencochrane.raven.RavenFactory | 1 + 1 file changed, 1 insertion(+) create mode 100644 raven/src/main/resources/META-INF/services/net.kencochrane.raven.RavenFactory diff --git a/raven/src/main/resources/META-INF/services/net.kencochrane.raven.RavenFactory b/raven/src/main/resources/META-INF/services/net.kencochrane.raven.RavenFactory new file mode 100644 index 00000000000..2d46dd356a3 --- /dev/null +++ b/raven/src/main/resources/META-INF/services/net.kencochrane.raven.RavenFactory @@ -0,0 +1 @@ +net.kencochrane.raven.DefaultRavenFactory From a62829917c630bf698ab74746e00403102629418 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 00:35:44 +0200 Subject: [PATCH 0481/2152] Handle null DSN by attempting to find it automatically --- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 7 +++++-- .../java/net/kencochrane/raven/log4j2/SentryAppender.java | 7 +++++-- .../net/kencochrane/raven/logback/SentryAppender.java | 8 ++++++-- .../java/net/kencochrane/raven/jul/SentryHandler.java | 5 ++++- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index f0f7be1c7e4..a4ba6bf65b1 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -50,8 +50,11 @@ private static Event.Level formatLevel(Level level) { @Override public void activateOptions() { - if (raven == null){ - //TODO: Handle null dsn, Add a way to select the factory + if (dsn == null) + dsn = Dsn.dsnLookup(); + + if (raven == null) { + //TODO: Add a way to select the factory raven = RavenFactory.ravenInstance(new Dsn(dsn)); } } diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 0c9b8b9d294..3ab2a2f5051 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -106,8 +106,11 @@ private static String formatCulprit(StackTraceElement stackTraceElement) { @Override public void start() { - if (raven == null){ - //TODO: Handle null dsn, Add a way to select the factory + if (dsn == null) + dsn = Dsn.dsnLookup(); + + if (raven == null) { + //TODO: Add a way to select the factory raven = RavenFactory.ravenInstance(new Dsn(dsn)); } } diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index e3adc2bb7dd..4c39710e7df 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -59,8 +59,12 @@ private static Event.Level formatLevel(ILoggingEvent iLoggingEvent) { @Override public void start() { super.start(); - if (raven == null){ - //TODO: Handle null dsn, Add a way to select the factory + + if (dsn == null) + dsn = Dsn.dsnLookup(); + + if (raven == null) { + //TODO: Add a way to select the factory raven = RavenFactory.ravenInstance(new Dsn(dsn)); } } diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 423c652c887..0153add35d8 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -80,8 +80,11 @@ public void publish(LogRecord record) { } private Raven getRaven() { + if (dsn == null) + dsn = Dsn.dsnLookup(); + if (raven == null){ - //TODO: Handle null dsn, Add a way to select the factory + //TODO: Add a way to select the factory raven = RavenFactory.ravenInstance(new Dsn(dsn)); } return raven; From 23be07cf0dfbd249c6ab891bfcdf2a89b7fbe27d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 00:36:11 +0200 Subject: [PATCH 0482/2152] Fix typo --- .../raven/marshaller/json/StackTraceInterfaceBinding.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java index 21de22b0f1b..0e269b141df 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java @@ -42,7 +42,7 @@ private void writeFrame(JsonGenerator generator, StackTraceElement stackTraceEle } private boolean isFrameInApp(StackTraceElement stackTraceElement) { - //TODO: A set is absolutely not performant here, a Trie could be a better solution. + //TODO: A set is absolutely not efficient here, a Trie could be a better solution. for (String notInAppFrame : notInAppFrames) { if (stackTraceElement.getClassName().startsWith(notInAppFrame)) { return false; From 92c0e0df8dcefeb92fda4edb6d4a4cbf6f134607 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 00:36:21 +0200 Subject: [PATCH 0483/2152] Add HttpEventBuilderHelper from the DefaultRavenFactory --- .../main/java/net/kencochrane/raven/DefaultRavenFactory.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 8f298b121f5..5be7710aa4a 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -4,6 +4,7 @@ import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.connection.HttpConnection; import net.kencochrane.raven.connection.UdpConnection; +import net.kencochrane.raven.event.helper.HttpEventBuilderHelper; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.HttpInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; @@ -47,6 +48,8 @@ public class DefaultRavenFactory extends RavenFactory { public Raven createRavenInstance(Dsn dsn) { Raven raven = new Raven(); raven.setConnection(createConnection(dsn)); + //TODO: do not add that all the time. Check if HttpServlet is accessible?? + raven.addBuilderHelper(new HttpEventBuilderHelper()); return raven; } From 65ebb5ab2931bd995a42570100b9d61ac0432ca3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 01:02:00 +0200 Subject: [PATCH 0484/2152] Move the protocol option out of query parameters --- .../java/net/kencochrane/raven/DefaultRavenFactory.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 5be7710aa4a..23f9eedaaf1 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -17,6 +17,10 @@ import java.util.logging.Logger; public class DefaultRavenFactory extends RavenFactory { + /** + * Protocol setting to disable security checks over an SSL connection. + */ + public static final String NAIVE_PROTOCOL = "naive"; /** * Option specific to raven-java, allowing to disable the compression of requests to the Sentry Server. */ @@ -29,10 +33,6 @@ public class DefaultRavenFactory extends RavenFactory { * Option to send events asynchronously. */ public static final String ASYNC_OPTION = "raven.async"; - /** - * Protocol setting to disable security checks over an SSL connection. - */ - public static final String NAIVE_PROTOCOL = "naive"; /** * DSN option for the number of threads assigned for the connection. */ From c11b970b849bd122c197a5082c9763c3b4b622af Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 01:02:31 +0200 Subject: [PATCH 0485/2152] Use same naming convention for DSN options --- .../kencochrane/raven/DefaultRavenFactory.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 23f9eedaaf1..a194738ff55 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -34,13 +34,13 @@ public class DefaultRavenFactory extends RavenFactory { */ public static final String ASYNC_OPTION = "raven.async"; /** - * DSN option for the number of threads assigned for the connection. + * Option for the number of threads assigned for the connection. */ - public static final String DSN_MAX_THREADS_OPTION = "raven.async.threads"; + public static final String MAX_THREADS_OPTION = "raven.async.threads"; /** - * DSN option for the priority of threads assigned for the connection. + * Option for the priority of threads assigned for the connection. */ - public static final String DSN_PRIORITY_OPTION = "raven.async.priority"; + public static final String PRIORITY_OPTION = "raven.async.priority"; private static final Logger logger = Logger.getLogger(DefaultRavenFactory.class.getCanonicalName()); @@ -76,15 +76,15 @@ protected Connection createConnection(Dsn dsn) { protected Connection createAsyncConnection(Dsn dsn, Connection connection) { int maxThreads; - if (dsn.getOptions().containsKey(DSN_MAX_THREADS_OPTION)) { - maxThreads = Integer.parseInt(dsn.getOptions().get(DSN_MAX_THREADS_OPTION)); + if (dsn.getOptions().containsKey(MAX_THREADS_OPTION)) { + maxThreads = Integer.parseInt(dsn.getOptions().get(MAX_THREADS_OPTION)); } else { maxThreads = AsyncConnection.DEFAULT_MAX_THREADS; } int priority; - if (dsn.getOptions().containsKey(DSN_PRIORITY_OPTION)) { - priority = Integer.parseInt(dsn.getOptions().get(DSN_PRIORITY_OPTION)); + if (dsn.getOptions().containsKey(PRIORITY_OPTION)) { + priority = Integer.parseInt(dsn.getOptions().get(PRIORITY_OPTION)); } else { priority = AsyncConnection.DEFAULT_PRIORITY; } From dcfe97c0c9656ca83f36a152daf25d6686ab5afb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 01:02:47 +0200 Subject: [PATCH 0486/2152] Allow to disable Enclosed stackframes --- .../java/net/kencochrane/raven/DefaultRavenFactory.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index a194738ff55..9604eefa460 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -41,6 +41,10 @@ public class DefaultRavenFactory extends RavenFactory { * Option for the priority of threads assigned for the connection. */ public static final String PRIORITY_OPTION = "raven.async.priority"; + /** + * Option to hide common stackframes with enclosing exceptions. + */ + public static final String HIDE_COMMON_FRAMES_OPTION = "raven.stacktrace.hidecommon"; private static final Logger logger = Logger.getLogger(DefaultRavenFactory.class.getCanonicalName()); @@ -118,8 +122,7 @@ protected Marshaller createMarshaller(Dsn dsn) { // Set JSON marshaller bindings StackTraceInterfaceBinding stackTraceBinding = new StackTraceInterfaceBinding(); - //TODO: Set that properly - stackTraceBinding.setRemoveCommonFramesWithEnclosing(true); + stackTraceBinding.setRemoveCommonFramesWithEnclosing(dsn.getOptions().containsKey(HIDE_COMMON_FRAMES_OPTION)); //TODO: Add a way to remove in_app frames stackTraceBinding.setNotInAppFrames(Collections.emptySet()); marshaller.addInterfaceBinding(StackTraceInterface.class, stackTraceBinding); From f7423dcf17d39f7b20aabfd25b3ac3eb56641d73 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 12:57:54 +0200 Subject: [PATCH 0487/2152] Fix indentation/imports/code order --- raven/src/main/java/net/kencochrane/raven/Raven.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index e50e219cb1d..ac8fc24903c 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -1,13 +1,9 @@ package net.kencochrane.raven; -import net.kencochrane.raven.connection.AsyncConnection; import net.kencochrane.raven.connection.Connection; -import net.kencochrane.raven.connection.HttpConnection; -import net.kencochrane.raven.connection.UdpConnection; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.helper.EventBuilderHelper; -import net.kencochrane.raven.marshaller.json.JsonMarshaller; import java.util.Collections; import java.util.HashSet; @@ -92,6 +88,10 @@ public void addBuilderHelper(EventBuilderHelper builderHelper) { builderHelpers.add(builderHelper); } + public Set getBuilderHelpers() { + return Collections.unmodifiableSet(builderHelpers); + } + public Connection getConnection() { return connection; } @@ -99,8 +99,4 @@ public Connection getConnection() { public void setConnection(Connection connection) { this.connection = connection; } - - public Set getBuilderHelpers() { - return Collections.unmodifiableSet(builderHelpers); - } } From a624dd2ad7157190aefc47112819aca07d6b41d9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 12:58:06 +0200 Subject: [PATCH 0488/2152] Force a shutdown of the executorService if it has been interrupted --- .../net/kencochrane/raven/connection/AsyncConnection.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 6ea6c4583af..c6c99c5cb88 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -119,9 +119,11 @@ public void close() throws IOException { List tasks = executorService.shutdownNow(); logger.log(Level.INFO, tasks.size() + " tasks failed to execute before the shutdown."); } - logger.log(Level.SEVERE, "Shutdown interrupted."); + logger.log(Level.SEVERE, "Shutdown finished."); } catch (InterruptedException e) { - logger.log(Level.SEVERE, "Shutdown interrupted."); + logger.log(Level.SEVERE, "Graceful shutdown interrupted, forcing the shutdown."); + List tasks = executorService.shutdownNow(); + logger.log(Level.INFO, tasks.size() + " tasks failed to execute before the shutdown."); } finally { if (propagateClose) actualConnection.close(); From 235dd2c8e39cdd209e40cb4cdf0458e7031f9dc9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 13:09:38 +0200 Subject: [PATCH 0489/2152] Check that servlets are accessible Ensure that servlets are accessible before adding the HttpEventBuilderHelper --- .../java/net/kencochrane/raven/DefaultRavenFactory.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 9604eefa460..1e366f638b4 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -52,8 +52,13 @@ public class DefaultRavenFactory extends RavenFactory { public Raven createRavenInstance(Dsn dsn) { Raven raven = new Raven(); raven.setConnection(createConnection(dsn)); - //TODO: do not add that all the time. Check if HttpServlet is accessible?? - raven.addBuilderHelper(new HttpEventBuilderHelper()); + try { + Class.forName("javax.servlet.Servlet", false, this.getClass().getClassLoader()); + //TODO: Is it enough? Shouldn't it look for Servlet >= 3.0 ? + raven.addBuilderHelper(new HttpEventBuilderHelper()); + } catch (Exception e) { + logger.fine("It seems that the current environment doesn't provide access to servlets."); + } return raven; } From 60b58a57faea04417f5e824ee112b0897ad8ad63 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 13:22:23 +0200 Subject: [PATCH 0490/2152] Update Raven documentation --- raven/src/main/java/net/kencochrane/raven/Raven.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index ac8fc24903c..f21c1ef1a66 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -14,8 +14,8 @@ /** * Raven is a client for Sentry allowing to send an {@link Event} that will be processed and sent to a Sentry server. *

    - * A default client will use the protocol defined in the DSN and will send the content in the JSON format - * (optionally compressed and encoded in base64). + * It is recommended to create an instance of Raven through {@link RavenFactory#createRavenInstance(Dsn)}, this + * will use the best factory available to create a sensible instance of Raven. *

    */ public class Raven { From d861913dfd27e3fc2bf61ef30db281da13753cec Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 13:22:36 +0200 Subject: [PATCH 0491/2152] Remove unnecessary constructors for Raven --- .../main/java/net/kencochrane/raven/Raven.java | 15 --------------- .../java/net/kencochrane/raven/RavenTest.java | 3 ++- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index f21c1ef1a66..b2e439045ee 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -28,21 +28,6 @@ public class Raven { private final Set builderHelpers = new HashSet(); private Connection connection; - /** - * Builds a default Raven client, trying to figure out which {@link Dsn} can be used. - */ - public Raven() { - } - - /** - * Builds a Raven client using the given connection. - * - * @param connection connection to sentry. - */ - public Raven(Connection connection) { - this.connection = connection; - } - /** * Runs the {@link EventBuilderHelper} against the {@link EventBuilder} to obtain additional information with a * MDC-like system. diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index c07160edb20..68456e36519 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -27,7 +27,8 @@ public class RavenTest { @Before public void setUp() throws Exception { - raven = new Raven(mockConnection); + raven = new Raven(); + raven.setConnection(mockConnection); } @Test From 8e96ddb5010509bc9e4baea2bfe6f5f1eaa1193e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 13:26:40 +0200 Subject: [PATCH 0492/2152] Update INSTALL.md and README.md --- INSTALL.md | 46 ++++------------------------------------------ README.md | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index c47560f95b5..06d794133ea 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -10,7 +10,7 @@ Currently there are 6 modules in the project: - `raven-log4j2`, Appender for log4j2 - `raven-logback`, Appander for Logback - `sentry-stub`, Sentry server stub, allowing to test the protocol - + ### Build It's possible to get the latest version of Raven by building it from the sources. @@ -43,7 +43,7 @@ name of the module. import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.EventBuilder; - + public class Example { public static void main(String[] args) { // The DSN from Sentry: "http://public:private@host:port/1" @@ -67,7 +67,7 @@ The client will lookup for the first DSN configuration provided: import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.EventBuilder; - + public class Example { public static void main(String[] args) { Raven client = new Raven(); @@ -83,7 +83,7 @@ TODO ### Using log4j TODO -#### Asynchronous logging with AsyncAppander +#### Asynchronous logging with AsyncAppender log4j supports asynchronous logging with [AsyncAppender](http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/AsyncAppender.html). While this is a common solution to avoid blocking the current thread until the @@ -98,41 +98,3 @@ event is sent to Sentry, it is recommended to use instead the option ### Capturing the HTTP environment **TODO** - -## Connection and protocol -It is possible to send events to Sentry over different protocols, depending -on the security and performance requirements. -So far Sentry accepts HTTP(S) and UDP which are both fully supported by -Raven. - -### HTTP -The most common way to access Sentry is through HTTP, this can be done by -using a DSN using this form: - - http://public:private@host:port/1 - -If not provided, the port will default to `80`. - -### HTTPS -It is possible to use an encrypted connection to Sentry using HTTPS: - - https://public:private@host:port/1 - -If not provided, the port will default to `443`. -### HTTPS (naive) -If the certificate used over HTTPS is a wildcard certificate (which is not -handled by every version of Java), and the certificate isn't added to the -truststore, it is possible to add a protocol setting to tell the client to be -naive and ignore the hostname verification: - - naive+https://public:private@host:port/1 - -### UDP -It is possible to use a DSN with the UDP protocol: - - udp://public:private@host:port/1 - -If not provided the port will default to `9001`. - -While being faster because there is no TCP and HTTP overhead, UDP doesn't wait -for a reply, and if a connection problem occurs, there will be no notification. diff --git a/README.md b/README.md index bccec14f1e4..b3169e337a4 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,44 @@ reason, only the last two major versions of Raven are maintained. **See [INSTALL.md](https://github.com/kencochrane/raven-java/blob/master/INSTALL.md)** +## Connection and protocol +It is possible to send events to Sentry over different protocols, depending +on the security and performance requirements. +So far Sentry accepts HTTP(S) and UDP which are both fully supported by +Raven. + +### HTTP +The most common way to access Sentry is through HTTP, this can be done by +using a DSN using this form: + + http://public:private@host:port/1 + +If not provided, the port will default to `80`. + +### HTTPS +It is possible to use an encrypted connection to Sentry using HTTPS: + + https://public:private@host:port/1 + +If not provided, the port will default to `443`. +### HTTPS (naive) +If the certificate used over HTTPS is a wildcard certificate (which is not +handled by every version of Java), and the certificate isn't added to the +truststore, it is possible to add a protocol setting to tell the client to be +naive and ignore the hostname verification: + + naive+https://public:private@host:port/1 + +### UDP +It is possible to use a DSN with the UDP protocol: + + udp://public:private@host:port/1 + +If not provided the port will default to `9001`. + +While being faster because there is no TCP and HTTP overhead, UDP doesn't wait +for a reply, and if a connection problem occurs, there will be no notification. + ## Options It is possible to enable some options by adding data to the query string of the DSN: From 5c118663e8c877693c56c37ad648ec00471c2c8d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 13:27:08 +0200 Subject: [PATCH 0493/2152] Fix indentation --- .../src/main/java/net/kencochrane/raven/DefaultRavenFactory.java | 1 - 1 file changed, 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 1e366f638b4..43865127705 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -45,7 +45,6 @@ public class DefaultRavenFactory extends RavenFactory { * Option to hide common stackframes with enclosing exceptions. */ public static final String HIDE_COMMON_FRAMES_OPTION = "raven.stacktrace.hidecommon"; - private static final Logger logger = Logger.getLogger(DefaultRavenFactory.class.getCanonicalName()); @Override From de397980e7a710078a1d4e96da5d0cda5d4712b0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 14:10:44 +0200 Subject: [PATCH 0494/2152] Use a SNAPSHOT version of Jetty To make the pom more readable use the SNAPSHOT version of Jetty fixing https://bugs.eclipse.org/bugs/show_bug.cgi?id=405631 This is temporary and should be fixed ASAP! --- pom.xml | 100 +++++++++++++++++++++++++++----------------------------- 1 file changed, 48 insertions(+), 52 deletions(-) diff --git a/pom.xml b/pom.xml index 415b2fffc80..a8c76082768 100644 --- a/pom.xml +++ b/pom.xml @@ -104,9 +104,6 @@ 6 6 - - true - 1.6 2.2.0 @@ -236,17 +233,6 @@ - - org.eclipse.jetty - jetty-maven-plugin - 9.0.1.v20130408 - - 10 - foo - 9999 - ${project.build.directory}/webapps/sentry-stub.war - - org.apache.maven.plugins maven-site-plugin @@ -279,6 +265,39 @@ + + org.eclipse.jetty + jetty-maven-plugin + + 9.0.3-SNAPSHOT + + 10 + foo + 9999 + ${project.build.directory}/webapps/sentry-stub.war + + + + start-sentry-stub + pre-integration-test + + deploy-war + + + 0 + true + ${skipTests} + + + + stop-sentry-stub + post-integration-test + + stop + + + + org.apache.maven.plugins maven-failsafe-plugin @@ -331,44 +350,6 @@ - - - jetty - - false - - - - - org.eclipse.jetty - jetty-maven-plugin - - - start-sentry-stub - pre-integration-test - - deploy-war - - - 0 - true - ${skipTests} - - - - stop-sentry-stub - post-integration-test - - stop - - - - - - - - - @@ -401,4 +382,19 @@ + + + + + sonatype-jetty-snapshots + Sonatype Jetty Snapshots + https://oss.sonatype.org/content/repositories/jetty-snapshots + + false + + + true + + + From cff642ec9f7bdd516c44b40b7d8916a58d1c09bb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 14:13:13 +0200 Subject: [PATCH 0495/2152] Reduce travis configuration to a verify --- .travis.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 08f6cba5595..969c98a519a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,2 @@ language: java -script: - - mvn test - - cd raven; mvn integration-test -Pjetty; cd ../ - - cd raven-legacy; mvn integration-test -Pjetty; cd ../ - - cd raven-log4j; mvn integration-test -Pjetty; cd ../ - - cd raven-log4j2; mvn integration-test -Pjetty; cd ../ - - cd raven-logback; mvn integration-test -Pjetty; cd ../ - - cd sentry-stub; mvn integration-test -Pjetty; cd ../ +script: mvn verify From 3684bc676a044d69385a07348a1ab2252c871466 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 14:25:15 +0200 Subject: [PATCH 0496/2152] Remove useless comment --- .../src/main/java/net/kencochrane/raven/DefaultRavenFactory.java | 1 - 1 file changed, 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 43865127705..233030b6117 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -114,7 +114,6 @@ protected Connection createHttpConnection(Dsn dsn) { } protected Connection createUdpConnection(Dsn dsn) { - //String hostname, int port, String publicKey, String secretKey int port = dsn.getPort() != -1 ? dsn.getPort() : UdpConnection.DEFAULT_UDP_PORT; UdpConnection udpConnection = new UdpConnection(dsn.getHost(), port, dsn.getPublicKey(), dsn.getSecretKey()); udpConnection.setMarshaller(createMarshaller(dsn)); From 63c37075b704abee7f2bf1f317f609e0b7dee322 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 16:54:32 +0200 Subject: [PATCH 0497/2152] Add a way to set NotInAppFrames --- .../kencochrane/raven/DefaultRavenFactory.java | 16 ++++++++++++++-- .../json/StackTraceInterfaceBinding.java | 5 +++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 233030b6117..371ee6e3958 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -12,6 +12,8 @@ import net.kencochrane.raven.marshaller.Marshaller; import net.kencochrane.raven.marshaller.json.*; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.logging.Level; import java.util.logging.Logger; @@ -126,8 +128,8 @@ protected Marshaller createMarshaller(Dsn dsn) { // Set JSON marshaller bindings StackTraceInterfaceBinding stackTraceBinding = new StackTraceInterfaceBinding(); stackTraceBinding.setRemoveCommonFramesWithEnclosing(dsn.getOptions().containsKey(HIDE_COMMON_FRAMES_OPTION)); - //TODO: Add a way to remove in_app frames - stackTraceBinding.setNotInAppFrames(Collections.emptySet()); + stackTraceBinding.setNotInAppFrames(getNotInAppFrames()); + marshaller.addInterfaceBinding(StackTraceInterface.class, stackTraceBinding); marshaller.addInterfaceBinding(ExceptionInterface.class, new ExceptionInterfaceBinding(stackTraceBinding)); marshaller.addInterfaceBinding(MessageInterface.class, new MessageInterfaceBinding()); @@ -141,4 +143,14 @@ protected Marshaller createMarshaller(Dsn dsn) { return marshaller; } + + protected Collection getNotInAppFrames(){ + return Arrays.asList("com.sun.", + "java.", + "javax.", + "org.omg.", + "sun.", + "junit.", + "com.intellij.rt."); + } } diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java index 0e269b141df..1c7ef437b50 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java @@ -4,6 +4,7 @@ import net.kencochrane.raven.event.interfaces.StackTraceInterface; import java.io.IOException; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -20,7 +21,7 @@ public class StackTraceInterfaceBinding implements InterfaceBinding notInAppFrames = Collections.emptySet(); + private Collection notInAppFrames = Collections.emptyList(); private boolean removeCommonFramesWithEnclosing = true; /** @@ -72,7 +73,7 @@ public void setRemoveCommonFramesWithEnclosing(boolean removeCommonFramesWithEnc this.removeCommonFramesWithEnclosing = removeCommonFramesWithEnclosing; } - public void setNotInAppFrames(Set notInAppFrames) { + public void setNotInAppFrames(Collection notInAppFrames) { this.notInAppFrames = notInAppFrames; } } From 64bed266a91681511ec7c556be2188bd219cbc51 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 18:35:37 +0200 Subject: [PATCH 0498/2152] Rename Test to use the convention ClassTest not TestClass --- ...tInterfaceBinding.java => AbstractInterfaceBindingTest.java} | 2 +- ...InterfaceBinding.java => ExceptionInterfaceBindingTest.java} | 2 +- ...geInterfaceBinding.java => MessageInterfaceBindingTest.java} | 2 +- ...nterfaceBinding.java => StackTraceInterfaceBindingTest.java} | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename raven/src/test/java/net/kencochrane/raven/marshaller/json/{AbstractTestInterfaceBinding.java => AbstractInterfaceBindingTest.java} (95%) rename raven/src/test/java/net/kencochrane/raven/marshaller/json/{TestExceptionInterfaceBinding.java => ExceptionInterfaceBindingTest.java} (97%) rename raven/src/test/java/net/kencochrane/raven/marshaller/json/{TestMessageInterfaceBinding.java => MessageInterfaceBindingTest.java} (95%) rename raven/src/test/java/net/kencochrane/raven/marshaller/json/{TestStackTraceInterfaceBinding.java => StackTraceInterfaceBindingTest.java} (97%) diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractTestInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractInterfaceBindingTest.java similarity index 95% rename from raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractTestInterfaceBinding.java rename to raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractInterfaceBindingTest.java index cc8707d9adb..7ad68032b50 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractTestInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractInterfaceBindingTest.java @@ -9,7 +9,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; -public abstract class AbstractTestInterfaceBinding { +public abstract class AbstractInterfaceBindingTest { private JsonFactory jsonFactory; private ObjectMapper mapper; private ByteArrayOutputStream jsonContentStream; diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java similarity index 97% rename from raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java rename to raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index 5590c8c8451..d0cccba269d 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestExceptionInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -22,7 +22,7 @@ import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) -public class TestExceptionInterfaceBinding extends AbstractTestInterfaceBinding { +public class ExceptionInterfaceBindingTest extends AbstractInterfaceBindingTest { private ExceptionInterfaceBinding interfaceBinding; @Mock private ExceptionInterface mockExceptionInterface; diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java similarity index 95% rename from raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java rename to raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java index 025792c02ba..2b35fa4366c 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestMessageInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java @@ -19,7 +19,7 @@ import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) -public class TestMessageInterfaceBinding extends AbstractTestInterfaceBinding { +public class MessageInterfaceBindingTest extends AbstractInterfaceBindingTest { private MessageInterfaceBinding interfaceBinding; @Mock private MessageInterface mockMessageInterface; diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java similarity index 97% rename from raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java rename to raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java index 70674387e7a..e4a1b6b8258 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/TestStackTraceInterfaceBinding.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java @@ -16,7 +16,7 @@ import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) -public class TestStackTraceInterfaceBinding extends AbstractTestInterfaceBinding { +public class StackTraceInterfaceBindingTest extends AbstractInterfaceBindingTest { private StackTraceInterfaceBinding interfaceBinding; @Mock private StackTraceInterface mockStackTraceInterface; From 36a78630b006e8b08078507927798673f03082ea Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 22:30:23 +0200 Subject: [PATCH 0499/2152] Make the doSend method protected --- .../java/net/kencochrane/raven/connection/HttpConnection.java | 2 +- .../java/net/kencochrane/raven/connection/UdpConnection.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 193461dc82e..86dee5c4a43 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -96,7 +96,7 @@ private HttpURLConnection getConnection() { } @Override - public void doSend(Event event) { + protected void doSend(Event event) { HttpURLConnection connection = getConnection(); try { connection.connect(); diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index b0ce51e2694..c1443a28b4c 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -31,7 +31,7 @@ public UdpConnection(String hostname, int port, String publicKey, String secretK } @Override - public void doSend(Event event) { + protected void doSend(Event event) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); writeHeader(baos); From b0b86d75b85732a10bb7fd20c0a5fa301ca4a131 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 22:30:43 +0200 Subject: [PATCH 0500/2152] Allow maxWaitingTime and baseWaitingTime to be set manually --- .../raven/connection/AbstractConnection.java | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 924fd9020a8..81e57207a45 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -21,19 +21,19 @@ public abstract class AbstractConnection implements Connection { * Current sentry protocol version. */ public static final String SENTRY_PROTOCOL_VERSION = "4"; + private static final Logger logger = Logger.getLogger(Raven.class.getCanonicalName()); + private final String publicKey; + private final String secretKey; + private final ReentrantLock lock = new ReentrantLock(); /** * At most wait 5 minutes if the connection failed too many times. */ - private static final long MAX_WAITING_TIME = 300000; + private long maxWaitingTime = 300000; /** * When the first exception occurs, wait 10 millis before trying again. */ - private static final long BASE_WAITING_TIME = 10; - private static final Logger logger = Logger.getLogger(Raven.class.getCanonicalName()); - private final String publicKey; - private final String secretKey; - private final ReentrantLock lock = new ReentrantLock(); - private long waitingTime = BASE_WAITING_TIME; + private long baseWaitingTime = 10; + private long waitingTime = baseWaitingTime; /** * Creates a connection based on the public and secret keys. @@ -65,7 +65,7 @@ public final void send(Event event) { try { if (!lock.isLocked()) { doSend(event); - waitingTime = BASE_WAITING_TIME; + waitingTime = baseWaitingTime; } else { logger.info("The event '" + event + "' hasn't been sent to the server due to a lockdown."); } @@ -87,7 +87,7 @@ private void lockDown() { Thread.sleep(waitingTime); // Double the wait until the maximum is reached - if (waitingTime > MAX_WAITING_TIME) + if (waitingTime > maxWaitingTime) waitingTime <<= 1; } catch (Exception e) { logger.log(Level.SEVERE, "An exception occurred during the lockdown.", e); @@ -104,4 +104,12 @@ private void lockDown() { * @throws ConnectionException whenever a temporary exception due to the connection happened. */ protected abstract void doSend(Event event) throws ConnectionException; + + public void setMaxWaitingTime(long maxWaitingTime) { + this.maxWaitingTime = maxWaitingTime; + } + + public void setBaseWaitingTime(long baseWaitingTime) { + this.baseWaitingTime = baseWaitingTime; + } } From f47b4563fd4fe898f830aeb996fadb99427d884a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 30 Apr 2013 23:52:23 +0200 Subject: [PATCH 0501/2152] Remove the default Marshaller --- .../java/net/kencochrane/raven/connection/HttpConnection.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 86dee5c4a43..105035531b1 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -5,7 +5,6 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.exception.ConnectionException; import net.kencochrane.raven.marshaller.Marshaller; -import net.kencochrane.raven.marshaller.json.JsonMarshaller; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; @@ -53,7 +52,7 @@ public boolean verify(String hostname, SSLSession sslSession) { /** * Marshaller used to transform and send the {@link Event} over a stream. */ - private Marshaller marshaller = new JsonMarshaller(); + private Marshaller marshaller; /** * Timeout of an HTTP connection to Sentry. */ From 66f67ad035441cade1c36930ed0a0cb6eecc0a2a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 1 May 2013 00:21:15 +0200 Subject: [PATCH 0502/2152] Remove default interface bindings (should be set manually) --- .../raven/marshaller/json/JsonMarshaller.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index be25c9112dc..fc8c1b04920 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -86,15 +86,6 @@ public class JsonMarshaller implements Marshaller { ISO_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); } - public JsonMarshaller() { - StackTraceInterfaceBinding stackTraceBinding = new StackTraceInterfaceBinding(); - - addInterfaceBinding(StackTraceInterface.class, stackTraceBinding); - addInterfaceBinding(ExceptionInterface.class, new ExceptionInterfaceBinding(stackTraceBinding)); - addInterfaceBinding(MessageInterface.class, new MessageInterfaceBinding()); - addInterfaceBinding(HttpInterface.class, new HttpInterfaceBinding()); - } - @Override public void marshall(Event event, OutputStream destination) { // Prevent the stream from being closed automatically From f65e0ee7b37a3e08ebcddc431602d0c7419f21fb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 1 May 2013 00:31:43 +0200 Subject: [PATCH 0503/2152] Remove JsonMarshaller from UdpConnection --- .../java/net/kencochrane/raven/connection/UdpConnection.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index c1443a28b4c..e4870ef8764 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -3,7 +3,6 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.exception.ConnectionException; import net.kencochrane.raven.marshaller.Marshaller; -import net.kencochrane.raven.marshaller.json.JsonMarshaller; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -19,7 +18,7 @@ public class UdpConnection extends AbstractConnection { public static final int DEFAULT_UDP_PORT = 9001; private DatagramSocket socket; - private Marshaller marshaller = new JsonMarshaller(); + private Marshaller marshaller; public UdpConnection(String hostname, String publicKey, String secretKey) { this(hostname, DEFAULT_UDP_PORT, publicKey, secretKey); From 52b3fbbf5d0ddc6e9b72377f212dbd4514a4e960 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 1 May 2013 00:32:01 +0200 Subject: [PATCH 0504/2152] Remove unsued method --- .../kencochrane/raven/event/interfaces/HttpInterface.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java index bfb4aa41693..808d2ae6289 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java @@ -17,14 +17,6 @@ public HttpInterface(HttpServletRequest request) { this.request = request; } - public static Map> extractHeaders(HttpServletRequest servletRequest) { - Collection headerNames = Collections.list(servletRequest.getHeaderNames()); - Map> headers = new HashMap>(headerNames.size()); - for (String headerName : headerNames) - headers.put(headerName, Collections.list(servletRequest.getHeaders(headerName))); - return headers; - } - @Override public String getInterfaceName() { return HTTP_INTERFACE; From 6141b700983876b1374d71d7e66430d163ac1538 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 1 May 2013 00:33:06 +0200 Subject: [PATCH 0505/2152] Rename TIMEOUT in SHUTDOWN_TIMEOUT which is more apt --- .../net/kencochrane/raven/connection/AsyncConnection.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index c6c99c5cb88..54dcb4343b8 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -32,7 +32,7 @@ public class AsyncConnection implements Connection { /** * Timeout of the {@link #executorService}. */ - private static final int TIMEOUT = 1000; + private static final int SHUTDOWN_TIMEOUT = 1000; /** * Connection used to actually send the events. */ @@ -105,7 +105,8 @@ public void send(Event event) { * {@inheritDoc}. *

    * Closing the {@link AsyncConnection} will attempt a graceful shutdown of the {@link #executorService} with a - * timeout of {@link #TIMEOUT}, allowing the current events to be submitted while new events will be rejected.
    + * timeout of {@link #SHUTDOWN_TIMEOUT}, allowing the current events to be submitted while new events will + * be rejected.
    * If the shutdown times out, the {@code executorService} will be forced to shutdown. *

    */ @@ -114,7 +115,7 @@ public void close() throws IOException { logger.log(Level.INFO, "Gracefully shutdown sentry threads."); executorService.shutdown(); try { - if (!executorService.awaitTermination(TIMEOUT, TimeUnit.MILLISECONDS)) { + if (!executorService.awaitTermination(SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS)) { logger.log(Level.WARNING, "Graceful shutdown took too much time, forcing the shutdown."); List tasks = executorService.shutdownNow(); logger.log(Level.INFO, tasks.size() + " tasks failed to execute before the shutdown."); From 7e6ea3a67afb6428b42aa7cb194572854147409b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 1 May 2013 10:17:34 +0200 Subject: [PATCH 0506/2152] Make it clear that bypassSecurity is disabled by default --- .../java/net/kencochrane/raven/connection/HttpConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 105035531b1..62313729e31 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -61,7 +61,7 @@ public boolean verify(String hostname, SSLSession sslSession) { * Setting allowing to bypass the security system which requires wildcard certificates * to be added to the truststore. */ - private boolean bypassSecurity; + private boolean bypassSecurity = false; public HttpConnection(URL sentryUrl, String publicKey, String secretKey) { super(publicKey, secretKey); From e173a7a73620d35220cfdfbde531db8e38200a9b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 1 May 2013 10:18:31 +0200 Subject: [PATCH 0507/2152] Add documentation on Collections in Event --- .../src/main/java/net/kencochrane/raven/event/Event.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/event/Event.java b/raven/src/main/java/net/kencochrane/raven/event/Event.java index 13e59f2e847..dea26686db6 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/Event.java +++ b/raven/src/main/java/net/kencochrane/raven/event/Event.java @@ -55,6 +55,9 @@ public class Event { private String culprit; /** * A map or list of tags for this event. + *

    + * Automatically created with a Map that is made unmodifiable by the {@link EventBuilder}. + *

    */ private Map> tags = new HashMap>(); /** @@ -63,6 +66,9 @@ public class Event { private String serverName; /** * A map or list of additional properties for this event. + *

    + * Automatically created with a Map that is made unmodifiable by the {@link EventBuilder}. + *

    */ private Map extra = new HashMap(); /** @@ -71,6 +77,9 @@ public class Event { private String checksum; /** * Additional interfaces for other information and metadata. + *

    + * Automatically created with a Map that is made unmodifiable by the {@link EventBuilder}. + *

    */ private Map sentryInterfaces = new HashMap(); From 58167d4bda2d54933c0026d83714540c2ea86e90 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 1 May 2013 10:19:56 +0200 Subject: [PATCH 0508/2152] Add unit tests for HttpConnection --- .../raven/connection/HttpConnectionTest.java | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java new file mode 100644 index 00000000000..50abf169e62 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -0,0 +1,99 @@ +package net.kencochrane.raven.connection; + +import net.kencochrane.raven.Dsn; +import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.marshaller.Marshaller; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class HttpConnectionTest { + private HttpConnection httpConnection; + private String publicKey = UUID.randomUUID().toString(); + private String secretKey = UUID.randomUUID().toString(); + @Mock(answer = Answers.RETURNS_MOCKS) + private HttpsURLConnection mockUrlConnection; + @Mock + private Marshaller mockMarshaller; + + @Before + public void setUp() throws Exception { + URLStreamHandler stubUrlHandler = new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL u) throws IOException { + return mockUrlConnection; + } + }; + + URL mockUrl = new URL(null, "http://", stubUrlHandler); + httpConnection = new HttpConnection(mockUrl, publicKey, secretKey); + httpConnection.setMarshaller(mockMarshaller); + } + + @Test + public void testTimeout() throws Exception { + int timeout = 12; + httpConnection.setTimeout(timeout); + httpConnection.send(new EventBuilder().build()); + + verify(mockUrlConnection).setConnectTimeout(timeout); + } + + @Test + public void testByPassSecurity() throws Exception { + httpConnection.send(new EventBuilder().build()); + verify(mockUrlConnection, never()).setHostnameVerifier(any(HostnameVerifier.class)); + + reset(mockUrlConnection); + httpConnection.setBypassSecurity(true); + httpConnection.send(new EventBuilder().build()); + verify(mockUrlConnection).setHostnameVerifier(any(HostnameVerifier.class)); + + reset(mockUrlConnection); + httpConnection.setBypassSecurity(false); + httpConnection.send(new EventBuilder().build()); + verify(mockUrlConnection, never()).setHostnameVerifier(any(HostnameVerifier.class)); + } + + @Test + public void testContentMarshalled() throws Exception { + Event event = new EventBuilder().build(); + + httpConnection.send(event); + + verify(mockMarshaller).marshall(eq(event), any(OutputStream.class)); + } + + @Test + public void testApiUrlCreation() throws Exception { + Dsn dsn = mock(Dsn.class); + String projectId = UUID.randomUUID().toString(); + String uri = "http://host/sentry/"; + when(dsn.getUri()).thenReturn(new URI(uri)); + when(dsn.getProjectId()).thenReturn(projectId); + + URL sentryApiUrl = HttpConnection.getSentryApiUrl(dsn); + + assertThat(sentryApiUrl.toString(), is(uri + "api/" + projectId + "/store/")); + } +} From 02ce8a3021dae0b9cc6fee48318532909cd4c796 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 1 May 2013 10:55:17 +0200 Subject: [PATCH 0509/2152] Remove the last line feed in the message from an HTTPError --- .../java/net/kencochrane/raven/connection/HttpConnection.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 62313729e31..801270dec81 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -122,6 +122,8 @@ private String getErrorMessageFromStream(InputStream errorStream) { String line; while ((line = reader.readLine()) != null) sb.append(line).append("\n"); + //Remove last \n + sb.deleteCharAt(sb.length()-1); } catch (Exception e2) { logger.log(Level.SEVERE, "Exception while reading the error message from the connection.", e2); From 74155c4ad79c713a29a0ecb4efe01933b912cb67 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 1 May 2013 10:56:30 +0200 Subject: [PATCH 0510/2152] Ensure that the HostnameVerifier lets everything go through --- .../net/kencochrane/raven/connection/HttpConnectionTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index 50abf169e62..1893e74b21a 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -8,6 +8,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; @@ -61,13 +62,15 @@ public void testTimeout() throws Exception { @Test public void testByPassSecurity() throws Exception { + ArgumentCaptor hostnameVerifierCaptor = ArgumentCaptor.forClass(HostnameVerifier.class); httpConnection.send(new EventBuilder().build()); verify(mockUrlConnection, never()).setHostnameVerifier(any(HostnameVerifier.class)); reset(mockUrlConnection); httpConnection.setBypassSecurity(true); httpConnection.send(new EventBuilder().build()); - verify(mockUrlConnection).setHostnameVerifier(any(HostnameVerifier.class)); + verify(mockUrlConnection).setHostnameVerifier(hostnameVerifierCaptor.capture()); + assertThat(hostnameVerifierCaptor.getValue().verify(null, null), is(true)); reset(mockUrlConnection); httpConnection.setBypassSecurity(false); From be0379bd284c9930460d738ea5990c900a5db63b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 1 May 2013 10:57:24 +0200 Subject: [PATCH 0511/2152] Test that the httpErrorMessage is properly logged --- .../raven/connection/HttpConnectionTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index 1893e74b21a..74577eb872c 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -4,6 +4,7 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.marshaller.Marshaller; +import org.hamcrest.CustomTypeSafeMatcher; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -14,6 +15,7 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URI; @@ -21,8 +23,12 @@ import java.net.URLConnection; import java.net.URLStreamHandler; import java.util.UUID; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.util.logging.Logger; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.core.Is.is; import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; @@ -87,6 +93,29 @@ public void testContentMarshalled() throws Exception { verify(mockMarshaller).marshall(eq(event), any(OutputStream.class)); } + @Test + public void testHttpErrorLogged() throws Exception { + final String httpErrorMessage = UUID.randomUUID().toString(); + ArgumentCaptor logRecordCaptor = ArgumentCaptor.forClass(LogRecord.class); + Handler handler = mock(Handler.class); + Logger.getLogger(HttpConnection.class.getCanonicalName()).addHandler(handler); + when(mockUrlConnection.getOutputStream()).thenThrow(new IOException()); + when(mockUrlConnection.getErrorStream()).thenReturn(new ByteArrayInputStream(httpErrorMessage.getBytes())); + + httpConnection.send(new EventBuilder().build()); + + verify(handler).publish(logRecordCaptor.capture()); + assertThat(logRecordCaptor.getAllValues(), + hasItem(new CustomTypeSafeMatcher("Looks for message '" + httpErrorMessage + "'") { + @Override + protected boolean matchesSafely(LogRecord logRecord) { + return httpErrorMessage.equals(logRecord.getMessage()); + } + })); + + Logger.getLogger(HttpConnection.class.getCanonicalName()).removeHandler(handler); + } + @Test public void testApiUrlCreation() throws Exception { Dsn dsn = mock(Dsn.class); From 98745bd1ea64952329c165420cb331cb3b3944d3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 1 May 2013 11:01:49 +0200 Subject: [PATCH 0512/2152] Fix Logger name --- .../net/kencochrane/raven/connection/AbstractConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 81e57207a45..19c24e398ae 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -21,7 +21,7 @@ public abstract class AbstractConnection implements Connection { * Current sentry protocol version. */ public static final String SENTRY_PROTOCOL_VERSION = "4"; - private static final Logger logger = Logger.getLogger(Raven.class.getCanonicalName()); + private static final Logger logger = Logger.getLogger(AbstractConnection.class.getCanonicalName()); private final String publicKey; private final String secretKey; private final ReentrantLock lock = new ReentrantLock(); From a800453235128b356ab5bf6f0a3a823df3d648d0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 1 May 2013 16:52:25 +0200 Subject: [PATCH 0513/2152] Check AuthHeader in HttpConnection --- .../raven/connection/HttpConnectionTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index 74577eb872c..48dbec6f11a 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -1,6 +1,7 @@ package net.kencochrane.raven.connection; import net.kencochrane.raven.Dsn; +import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.marshaller.Marshaller; @@ -93,6 +94,18 @@ public void testContentMarshalled() throws Exception { verify(mockMarshaller).marshall(eq(event), any(OutputStream.class)); } + @Test + public void testAuthHeaderSent() throws Exception { + httpConnection.send(new EventBuilder().build()); + + verify(mockUrlConnection).setRequestProperty("User-Agent", Raven.NAME); + String expectedAuthRequest = "Sentry sentry_version=4," + + "sentry_client=" + Raven.NAME + "," + + "sentry_key=" + publicKey + "," + + "sentry_secret=" + secretKey; + verify(mockUrlConnection).setRequestProperty("X-Sentry-Auth", expectedAuthRequest); + } + @Test public void testHttpErrorLogged() throws Exception { final String httpErrorMessage = UUID.randomUUID().toString(); From 6e12e33a691e8c3471fe1467bdf69e9cbf2f8a97 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 1 May 2013 16:52:39 +0200 Subject: [PATCH 0514/2152] Add tests for UdpConnection --- .../raven/connection/UdpConnectionTest.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java new file mode 100644 index 00000000000..f5b9496964b --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java @@ -0,0 +1,38 @@ +package net.kencochrane.raven.connection; + +import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.marshaller.Marshaller; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.io.OutputStream; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; + +@RunWith(MockitoJUnitRunner.class) +public class UdpConnectionTest { + private UdpConnection udpConnection; + @Mock + private Marshaller mockMarshaller; + + @Before + public void setUp() throws Exception { + udpConnection = new UdpConnection("", "", ""); + udpConnection.setMarshaller(mockMarshaller); + } + + @Test + public void testContentMarshalled() throws Exception { + Event event = new EventBuilder().build(); + + udpConnection.send(event); + + verify(mockMarshaller).marshall(eq(event), any(OutputStream.class)); + } +} From 973a59cdff0201af1d5dde5219f37aae771bbbd5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 1 May 2013 17:14:47 +0200 Subject: [PATCH 0515/2152] Allow the user to set up its own ExecutorService --- .../raven/connection/AsyncConnection.java | 59 +++---------------- 1 file changed, 9 insertions(+), 50 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 54dcb4343b8..c5668452f57 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -6,9 +6,7 @@ import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; @@ -20,14 +18,6 @@ *

    */ public class AsyncConnection implements Connection { - /** - * Number of threads dedicated to the connection usage by default (Number of processors available). - */ - public static final int DEFAULT_MAX_THREADS = Runtime.getRuntime().availableProcessors(); - /** - * Default threads priority. - */ - public static final int DEFAULT_PRIORITY = Thread.MIN_PRIORITY; private static final Logger logger = Logger.getLogger(AsyncConnection.class.getCanonicalName()); /** * Timeout of the {@link #executorService}. @@ -37,25 +27,25 @@ public class AsyncConnection implements Connection { * Connection used to actually send the events. */ private final Connection actualConnection; - /** - * Executor service in charge of running the connection in separate threads. - */ - private final ExecutorService executorService; /** * Option to disable the propagation of the {@link #close()} operation to the actual connection. */ private final boolean propagateClose; + /** + * Executor service in charge of running the connection in separate threads. + */ + private ExecutorService executorService = Executors.newSingleThreadExecutor(); /** * Creates a connection which will rely on an executor to send events. *

    - * Will propagate the {@link #close()} operation and use {@link #DEFAULT_MAX_THREADS} and {@link #DEFAULT_PRIORITY}. + * Will propagate the {@link #close()} operation. *

    * * @param actualConnection connection used to send the events. */ public AsyncConnection(Connection actualConnection) { - this(actualConnection, true, DEFAULT_MAX_THREADS, DEFAULT_PRIORITY); + this(actualConnection, true); } /** @@ -64,13 +54,10 @@ public AsyncConnection(Connection actualConnection) { * @param actualConnection connection used to send the events. * @param propagateClose whether or not the {@link #actualConnection} should be closed * when this connection closes. - * @param maxThreads number of {@code Thread}s available in the thread pool. - * @param priority priority of the {@code Thread}s. */ - public AsyncConnection(Connection actualConnection, boolean propagateClose, int maxThreads, int priority) { + public AsyncConnection(Connection actualConnection, boolean propagateClose) { this.actualConnection = actualConnection; this.propagateClose = propagateClose; - executorService = Executors.newFixedThreadPool(maxThreads, new DaemonThreadFactory(priority)); addShutdownHook(); } @@ -131,36 +118,8 @@ public void close() throws IOException { } } - /** - * Thread factory generating daemon threads with a custom priority. - *

    - * Those (usually) low priority threads will allow to send event details to sentry concurrently without slowing - * down the main application. - *

    - */ - private static final class DaemonThreadFactory implements ThreadFactory { - private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1); - private final ThreadGroup group; - private final AtomicInteger threadNumber = new AtomicInteger(1); - private final String namePrefix; - private final int priority; - - private DaemonThreadFactory(int priority) { - SecurityManager s = System.getSecurityManager(); - group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); - namePrefix = "pool-" + POOL_NUMBER.getAndIncrement() + "-thread-"; - this.priority = priority; - } - - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); - if (!t.isDaemon()) - t.setDaemon(true); - if (t.getPriority() != priority) - t.setPriority(priority); - return t; - } + public void setExecutorService(ExecutorService executorService) { + this.executorService = executorService; } /** From 4576678dea187563fee83bbdee50c366df263af0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 1 May 2013 17:15:45 +0200 Subject: [PATCH 0516/2152] Set up a proper Executor for AsyncConnections in DefaultRavenFactory --- .../raven/DefaultRavenFactory.java | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 371ee6e3958..935ca423b2b 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -15,6 +15,9 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; @@ -85,21 +88,25 @@ protected Connection createConnection(Dsn dsn) { } protected Connection createAsyncConnection(Dsn dsn, Connection connection) { + AsyncConnection asyncConnection = new AsyncConnection(connection, true); + int maxThreads; if (dsn.getOptions().containsKey(MAX_THREADS_OPTION)) { maxThreads = Integer.parseInt(dsn.getOptions().get(MAX_THREADS_OPTION)); } else { - maxThreads = AsyncConnection.DEFAULT_MAX_THREADS; + maxThreads = Runtime.getRuntime().availableProcessors(); } int priority; if (dsn.getOptions().containsKey(PRIORITY_OPTION)) { priority = Integer.parseInt(dsn.getOptions().get(PRIORITY_OPTION)); } else { - priority = AsyncConnection.DEFAULT_PRIORITY; + priority = Thread.MIN_PRIORITY; } - return new AsyncConnection(connection, true, maxThreads, priority); + asyncConnection.setExecutorService(Executors.newFixedThreadPool(maxThreads, new DaemonThreadFactory(priority))); + + return asyncConnection; } protected Connection createHttpConnection(Dsn dsn) { @@ -153,4 +160,36 @@ protected Collection getNotInAppFrames(){ "junit.", "com.intellij.rt."); } + + /** + * Thread factory generating daemon threads with a custom priority. + *

    + * Those (usually) low priority threads will allow to send event details to sentry concurrently without slowing + * down the main application. + *

    + */ + private static final class DaemonThreadFactory implements ThreadFactory { + private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1); + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + private final int priority; + + private DaemonThreadFactory(int priority) { + SecurityManager s = System.getSecurityManager(); + group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); + namePrefix = "pool-" + POOL_NUMBER.getAndIncrement() + "-thread-"; + this.priority = priority; + } + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); + if (!t.isDaemon()) + t.setDaemon(true); + if (t.getPriority() != priority) + t.setPriority(priority); + return t; + } + } } From b8634e7fe4de709a39094985bb494ab1f95dcf55 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 1 May 2013 17:17:29 +0200 Subject: [PATCH 0517/2152] Fix indentation and imports --- .../main/java/net/kencochrane/raven/DefaultRavenFactory.java | 3 +-- .../net/kencochrane/raven/connection/HttpConnection.java | 2 +- .../kencochrane/raven/event/interfaces/HttpInterface.java | 4 ---- .../raven/event/interfaces/StackTraceInterface.java | 5 +++-- .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 2 +- .../kencochrane/raven/marshaller/json/JsonMarshaller.java | 2 +- .../raven/marshaller/json/StackTraceInterfaceBinding.java | 2 -- 7 files changed, 7 insertions(+), 13 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 935ca423b2b..90cffbdd53e 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -14,7 +14,6 @@ import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; @@ -151,7 +150,7 @@ protected Marshaller createMarshaller(Dsn dsn) { return marshaller; } - protected Collection getNotInAppFrames(){ + protected Collection getNotInAppFrames() { return Arrays.asList("com.sun.", "java.", "javax.", diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 801270dec81..a6c3766c8e6 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -123,7 +123,7 @@ private String getErrorMessageFromStream(InputStream errorStream) { while ((line = reader.readLine()) != null) sb.append(line).append("\n"); //Remove last \n - sb.deleteCharAt(sb.length()-1); + sb.deleteCharAt(sb.length() - 1); } catch (Exception e2) { logger.log(Level.SEVERE, "Exception while reading the error message from the connection.", e2); diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java index 808d2ae6289..73af47334fe 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java @@ -1,10 +1,6 @@ package net.kencochrane.raven.event.interfaces; import javax.servlet.http.HttpServletRequest; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; /** * The HTTP interface for Sentry allows to add an HTTP request to an event. diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java index 2bb38ee114a..7311e49d354 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java @@ -16,8 +16,9 @@ public StackTraceInterface(StackTraceElement[] stackTrace, StackTraceElement[] e int m = stackTrace.length - 1; int n = enclosingStackTrace.length - 1; - while (m >= 0 && n >=0 && stackTrace[m].equals(enclosingStackTrace[n])) { - m--; n--; + while (m >= 0 && n >= 0 && stackTrace[m].equals(enclosingStackTrace[n])) { + m--; + n--; } framesCommonWithEnclosing = stackTrace.length - 1 - m; } diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 0153add35d8..a4c8935ddf9 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -83,7 +83,7 @@ private Raven getRaven() { if (dsn == null) dsn = Dsn.dsnLookup(); - if (raven == null){ + if (raven == null) { //TODO: Add a way to select the factory raven = RavenFactory.ravenInstance(new Dsn(dsn)); } diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index fc8c1b04920..3156b821a2b 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.interfaces.*; +import net.kencochrane.raven.event.interfaces.SentryInterface; import net.kencochrane.raven.marshaller.Marshaller; import org.apache.commons.codec.binary.Base64OutputStream; diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java index 1c7ef437b50..26bb94a6fe7 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java @@ -6,8 +6,6 @@ import java.io.IOException; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; -import java.util.Set; public class StackTraceInterfaceBinding implements InterfaceBinding { private static final String FRAMES_PARAMETER = "frames"; From 35ddd2e436ec88ca803d2162e89d132ae58b7aaf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 1 May 2013 17:55:55 +0200 Subject: [PATCH 0518/2152] Add tests for the AsyncConnection --- .../raven/connection/AsyncConnectionTest.java | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java new file mode 100644 index 00000000000..155606ea8de --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -0,0 +1,66 @@ +package net.kencochrane.raven.connection; + +import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.EventBuilder; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; + +@RunWith(MockitoJUnitRunner.class) +public class AsyncConnectionTest { + private AsyncConnection asyncConnection; + @Mock + private Connection mockConnection; + @Mock + private ExecutorService mockExecutorService; + + @Before + public void setUp() throws Exception { + asyncConnection = new AsyncConnection(mockConnection); + asyncConnection.setExecutorService(mockExecutorService); + } + + @Test + public void testCloseOperation() throws Exception { + asyncConnection.close(); + + verify(mockConnection).close(); + verify(mockExecutorService).awaitTermination(any(Long.class), any(TimeUnit.class)); + } + + @Test + public void testSendEventQueued() throws Exception { + Event event = new EventBuilder().build(); + + asyncConnection.send(event); + + verify(mockExecutorService).execute(any(Runnable.class)); + } + + @Test + public void testQueuedEventExecuted() throws Exception { + Event event = new EventBuilder().build(); + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + ((Runnable) invocation.getArguments()[0]).run(); + return null; + } + }).when(mockExecutorService).execute(any(Runnable.class)); + + asyncConnection.send(event); + + verify(mockConnection).send(event); + } +} From 330d5a4aa49a83af7aa3e00d4f689f187860d2bb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 May 2013 10:36:44 +0200 Subject: [PATCH 0519/2152] Remove setCulprit based on an exception The goal of setCulprit is to determine where the event was captured, not where it originates from (if it's an exception). --- .../kencochrane/raven/event/EventBuilder.java | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 35105ff137d..0c0b7548a08 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -108,27 +108,6 @@ private static void makeImmutable(Event event) { event.setSentryInterfaces(Collections.unmodifiableMap(event.getSentryInterfaces())); } - /** - * Determines the culprit value for an event based on a {@code Throwable}. - * - * @param throwable throwable caught, responsible of the event. - * @return the name of the method/class responsible for the event, based on the {@code Throwable}. - */ - private static String determineCulprit(Throwable throwable) { - Throwable currentThrowable = throwable; - String culprit = null; - // Attempts to go through each cause, in case the last ones do not provide a stacktrace. - while (currentThrowable != null) { - StackTraceElement[] elements = currentThrowable.getStackTrace(); - if (elements.length > 0) { - StackTraceElement trace = elements[0]; - culprit = trace.getClassName() + "." + trace.getMethodName(); - } - currentThrowable = currentThrowable.getCause(); - } - return culprit; - } - /** * Sets the message in the event. * @@ -185,13 +164,33 @@ public EventBuilder setPlatform(String platform) { } /** - * Sets the culprit in the event based on a {@code Throwable}. + * Sets the culprit in the event based on a {@link StackTraceElement}. * - * @param throwable throwable responsible of the event. + * @param frame stack frame during which the event was captured. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder setCulprit(Throwable throwable) { - return setCulprit(determineCulprit(throwable)); + public EventBuilder setCulprit(StackTraceElement frame) { + StringBuilder sb = new StringBuilder(); + + if (frame.getClassName() != null) { + sb.append(frame.getClassName()); + if (frame.getMethodName() != null) + sb.append("."); + } + + if (frame.getMethodName() != null) { + sb.append(frame.getMethodName()); + } + + if(frame.getFileName() != null){ + sb.append("(").append(frame.getFileName()); + if(frame.getLineNumber() >= 0){ + sb.append(":").append(frame.getLineNumber()); + } + sb.append(")"); + } + + return setCulprit(sb.toString()); } /** From e4e9f809008054b315a36d63cb26f0d3da75fb4b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 May 2013 10:37:44 +0200 Subject: [PATCH 0520/2152] Set the culprit in jul with a fake stackframe --- .../java/net/kencochrane/raven/jul/SentryHandler.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index a4c8935ddf9..7cf4e8ee23d 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -62,8 +62,12 @@ public void publish(LogRecord record) { EventBuilder eventBuilder = new EventBuilder() .setLevel(getLevel(record.getLevel())) .setTimestamp(new Date(record.getMillis())) - .setLogger(record.getLoggerName()) - .setCulprit(record.getSourceClassName() + "." + record.getSourceMethodName() + "()"); + .setLogger(record.getLoggerName()); + + StackTraceElement fakeFrame = new StackTraceElement(record.getSourceClassName(),record.getSourceMethodName(), + null, -1); + eventBuilder.setCulprit(fakeFrame); + if (record.getThrown() != null) { eventBuilder.addSentryInterface(new ExceptionInterface(record.getThrown())); } From 800b0cd9c1e82af27aa620f531dc2cd18b576577 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 May 2013 10:38:22 +0200 Subject: [PATCH 0521/2152] Properly set the culprit in log4j --- .../raven/log4j/SentryAppender.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index a4ba6bf65b1..f0caf73add1 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -8,6 +8,7 @@ import net.kencochrane.raven.event.interfaces.ExceptionInterface; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; +import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; import java.io.IOException; @@ -48,6 +49,14 @@ private static Event.Level formatLevel(Level level) { } else return null; } + private static StackTraceElement asStackTraceElement(LocationInfo location) { + String className = (LocationInfo.NA.equals(location.getClassName())) ? null : location.getClassName(); + String methodName = (LocationInfo.NA.equals(location.getMethodName())) ? null : location.getMethodName(); + String fileName = (LocationInfo.NA.equals(location.getFileName())) ? null : location.getFileName(); + int line = (LocationInfo.NA.equals(location.getLineNumber())) ? -1 : Integer.parseInt(location.getLineNumber()); + return new StackTraceElement(className, methodName, fileName, line); + } + @Override public void activateOptions() { if (dsn == null) @@ -65,18 +74,22 @@ protected void append(LoggingEvent loggingEvent) { .setTimestamp(new Date(loggingEvent.getTimeStamp())) .setMessage(loggingEvent.getRenderedMessage()) .setLogger(loggingEvent.getLoggerName()) - .setLevel(formatLevel(loggingEvent.getLevel())) - .setCulprit(loggingEvent.getLoggerName()); + .setLevel(formatLevel(loggingEvent.getLevel())); if (loggingEvent.getThrowableInformation() != null) { Throwable throwable = loggingEvent.getThrowableInformation().getThrowable(); eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); - eventBuilder.setCulprit(throwable); } else if (loggingEvent.getLocationInformation().fullInfo != null) { // When it's a message try to rely on the position of the log (the same message can be logged from // different places, or a same place can log a message in different ways). eventBuilder.generateChecksum(loggingEvent.getLocationInformation().fullInfo); - eventBuilder.setCulprit(loggingEvent.getLocationInformation().fullInfo); + } + + // Set culprit + if (loggingEvent.getLocationInformation().fullInfo != null) { + eventBuilder.setCulprit(asStackTraceElement(loggingEvent.getLocationInformation())); + } else { + eventBuilder.setCulprit(loggingEvent.getLoggerName()); } if (loggingEvent.getNDC() != null) From 4fb1c0ca9365ef149924c3a21aa25e663c20a268 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 May 2013 10:39:20 +0200 Subject: [PATCH 0522/2152] Fix string comparison using == --- .../main/java/net/kencochrane/raven/log4j2/SentryAppender.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 3ab2a2f5051..c4f96205e44 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -136,7 +136,7 @@ public void append(LogEvent event) { eventBuilder.generateChecksum(source); } - if (eventMessage.getFormattedMessage() != eventMessage.getFormat()) { + if (!eventMessage.getFormattedMessage().equals(eventMessage.getFormat())) { eventBuilder.addSentryInterface(new MessageInterface(eventMessage.getFormat(), formatMessageParameters(eventMessage.getParameters()))); } From 7eb06bc3ffa9345d3f7d1c5208cb94a64d4373f8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 May 2013 10:40:04 +0200 Subject: [PATCH 0523/2152] Properly set the culprit in log4j2 and logback --- .../net/kencochrane/raven/log4j2/SentryAppender.java | 8 ++++++-- .../kencochrane/raven/logback/SentryAppender.java | 12 +++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index c4f96205e44..293c4304713 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -127,15 +127,19 @@ public void append(LogEvent event) { if (event.getThrown() != null) { Throwable throwable = event.getThrown(); eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); - eventBuilder.setCulprit(throwable); } else if (event.getSource() != null) { // When it's a message try to rely on the position of the log (the same message can be logged from // different places, or a same place can log a message in different ways). String source = formatCulprit(event.getSource()); - eventBuilder.setCulprit(source); eventBuilder.generateChecksum(source); } + if (event.getSource() != null) { + eventBuilder.setCulprit(event.getSource()); + } else { + eventBuilder.setCulprit(event.getLoggerName()); + } + if (!eventMessage.getFormattedMessage().equals(eventMessage.getFormat())) { eventBuilder.addSentryInterface(new MessageInterface(eventMessage.getFormat(), formatMessageParameters(eventMessage.getParameters()))); diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 4c39710e7df..09d410c979e 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -75,8 +75,7 @@ protected void append(ILoggingEvent iLoggingEvent) { .setTimestamp(new Date(iLoggingEvent.getTimeStamp())) .setMessage(iLoggingEvent.getFormattedMessage()) .setLogger(iLoggingEvent.getLoggerName()) - .setLevel(formatLevel(iLoggingEvent)) - .setCulprit(iLoggingEvent.getLoggerName()); + .setLevel(formatLevel(iLoggingEvent)); if (iLoggingEvent.getThrowableProxy() != null) { Throwable throwable = ((ThrowableProxy) iLoggingEvent.getThrowableProxy()).getThrowable(); @@ -87,8 +86,15 @@ protected void append(ILoggingEvent iLoggingEvent) { formatArguments(iLoggingEvent.getArgumentArray()))); // When it's a message try to rely on the position of the log (the same message can be logged from // different places, or a same place can log a message in different ways. - if (iLoggingEvent.getCallerData().length > 0) + if (iLoggingEvent.getCallerData().length > 0) { eventBuilder.generateChecksum(getEventPosition(iLoggingEvent)); + } + } + + if (iLoggingEvent.getCallerData().length > 0) { + eventBuilder.setCulprit(iLoggingEvent.getCallerData()[0]); + } else { + eventBuilder.setCulprit(iLoggingEvent.getLoggerName()); } for (Map.Entry mdcEntry : iLoggingEvent.getMDCPropertyMap().entrySet()) { From 444f9b0494f70d2e49877225d799a78d85409d78 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 May 2013 10:55:02 +0200 Subject: [PATCH 0524/2152] className and methodName should never be null in StackTraceElement --- .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index f0caf73add1..3197cf432ad 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -50,11 +50,9 @@ private static Event.Level formatLevel(Level level) { } private static StackTraceElement asStackTraceElement(LocationInfo location) { - String className = (LocationInfo.NA.equals(location.getClassName())) ? null : location.getClassName(); - String methodName = (LocationInfo.NA.equals(location.getMethodName())) ? null : location.getMethodName(); String fileName = (LocationInfo.NA.equals(location.getFileName())) ? null : location.getFileName(); int line = (LocationInfo.NA.equals(location.getLineNumber())) ? -1 : Integer.parseInt(location.getLineNumber()); - return new StackTraceElement(className, methodName, fileName, line); + return new StackTraceElement(location.getClassName(), location.getMethodName(), fileName, line); } @Override From 3ff42d4224cf144cfe3727d750d46c3dd91a839d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 May 2013 10:55:31 +0200 Subject: [PATCH 0525/2152] In jul, sourceClassName and sourceMethodName could be null --- .../java/net/kencochrane/raven/jul/SentryHandler.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 7cf4e8ee23d..a96d4095864 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -64,9 +64,14 @@ public void publish(LogRecord record) { .setTimestamp(new Date(record.getMillis())) .setLogger(record.getLoggerName()); - StackTraceElement fakeFrame = new StackTraceElement(record.getSourceClassName(),record.getSourceMethodName(), - null, -1); - eventBuilder.setCulprit(fakeFrame); + if (record.getSourceClassName() != null && record.getSourceMethodName() != null) { + + StackTraceElement fakeFrame = new StackTraceElement(record.getSourceClassName(), + record.getSourceMethodName(), null, -1); + eventBuilder.setCulprit(fakeFrame); + } else { + eventBuilder.setCulprit(record.getLoggerName()); + } if (record.getThrown() != null) { eventBuilder.addSentryInterface(new ExceptionInterface(record.getThrown())); From 5b2e355e23ab00325ab5889249ad6c1b83c1b822 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 May 2013 10:56:02 +0200 Subject: [PATCH 0526/2152] In a StackTraceElement the class and method are always provided --- .../kencochrane/raven/event/EventBuilder.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 0c0b7548a08..f9028f92943 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -172,19 +172,13 @@ public EventBuilder setPlatform(String platform) { public EventBuilder setCulprit(StackTraceElement frame) { StringBuilder sb = new StringBuilder(); - if (frame.getClassName() != null) { - sb.append(frame.getClassName()); - if (frame.getMethodName() != null) - sb.append("."); - } - - if (frame.getMethodName() != null) { - sb.append(frame.getMethodName()); - } + sb.append(frame.getClassName()) + .append(".") + .append(frame.getMethodName()); - if(frame.getFileName() != null){ + if (frame.getFileName() != null && !frame.getFileName().isEmpty()) { sb.append("(").append(frame.getFileName()); - if(frame.getLineNumber() >= 0){ + if (frame.getLineNumber() >= 0) { sb.append(":").append(frame.getLineNumber()); } sb.append(")"); From 5df91257142984e608b8e1404848b328f553d7bc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 May 2013 10:57:18 +0200 Subject: [PATCH 0527/2152] Add tests for EventBuilder and Event --- .../raven/event/EventBuilderTest.java | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java new file mode 100644 index 00000000000..deb905300a4 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -0,0 +1,157 @@ +package net.kencochrane.raven.event; + +import net.kencochrane.raven.event.interfaces.SentryInterface; +import org.junit.Before; +import org.junit.Test; + +import java.net.InetAddress; +import java.util.*; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class EventBuilderTest { + private EventBuilder eventBuilder; + + @Before + public void setUp() throws Exception { + eventBuilder = new EventBuilder(); + } + + @Test + public void testMandatoryValuesAutomaticallySet() throws Exception { + Event event = eventBuilder.build(); + + assertThat(event.getId(), is(notNullValue())); + assertThat(event.getTimestamp(), is(notNullValue())); + assertThat(event.getPlatform(), is(EventBuilder.DEFAULT_PLATFORM)); + assertThat(event.getServerName(), is(InetAddress.getLocalHost().getCanonicalHostName())); + } + + @Test + public void testMandatoryValuesNotOverwritten() throws Exception { + UUID uuid = UUID.randomUUID(); + Date timestamp = new Date(); + String platform = UUID.randomUUID().toString(); + String serverName = UUID.randomUUID().toString(); + + Event event = new EventBuilder(uuid) + .setTimestamp(timestamp) + .setPlatform(platform) + .setServerName(serverName) + .build(); + + assertThat(event.getId(), is(uuid)); + assertThat(event.getTimestamp(), is(timestamp)); + assertThat(event.getPlatform(), is(platform)); + assertThat(event.getServerName(), is(serverName)); + } + + @Test + public void testEventParameters() throws Exception { + String culprit = UUID.randomUUID().toString(); + String checksum = UUID.randomUUID().toString(); + Event.Level level = Event.Level.INFO; + String logger = UUID.randomUUID().toString(); + String message = UUID.randomUUID().toString(); + + Event event = eventBuilder.setCulprit(culprit) + .setChecksum(checksum) + .setLevel(level) + .setLogger(logger) + .setMessage(message) + .build(); + + assertThat(event.getCulprit(), is(culprit)); + assertThat(event.getChecksum(), is(checksum)); + assertThat(event.getLevel(), is(level)); + assertThat(event.getLogger(), is(logger)); + assertThat(event.getMessage(), is(message)); + } + + @Test + public void testUseStackFrameAsCulprit(){ + StackTraceElement frame1 = new StackTraceElement("class", "method", "file", 1); + StackTraceElement frame2 = new StackTraceElement("class", "method", "file", -1); + StackTraceElement frame3 = new StackTraceElement("class", "method", null, 1); + + String culprit1 = new EventBuilder().setCulprit(frame1).build().getCulprit(); + String culprit2 = new EventBuilder().setCulprit(frame2).build().getCulprit(); + String culprit3 = new EventBuilder().setCulprit(frame3).build().getCulprit(); + + assertThat(culprit1, is("class.method(file:1)")); + assertThat(culprit2, is("class.method(file)")); + assertThat(culprit3, is("class.method")); + } + + @Test + public void testChecksumGeneration() throws Exception { + String cont = UUID.randomUUID().toString(); + Event noChecksumEvent = new EventBuilder().build(); + Event firstChecksumEvent = new EventBuilder().generateChecksum(cont).build(); + Event secondChecksumEvent = new EventBuilder().generateChecksum(cont).build(); + Event differentChecksumEvent = new EventBuilder().generateChecksum(UUID.randomUUID().toString()).build(); + + assertThat(noChecksumEvent.getChecksum(), is(nullValue())); + assertThat(firstChecksumEvent.getChecksum(), is(notNullValue())); + assertThat(differentChecksumEvent.getChecksum(), is(notNullValue())); + assertThat(firstChecksumEvent.getChecksum(), is(not(differentChecksumEvent.getChecksum()))); + assertThat(firstChecksumEvent.getChecksum(), is(secondChecksumEvent.getChecksum())); + } + + @Test(expected = UnsupportedOperationException.class) + public void testTagsAreImmutable() throws Exception { + String tagKey = UUID.randomUUID().toString(); + String tagValue = UUID.randomUUID().toString(); + + Map> tags = eventBuilder.addTag(tagKey, tagValue).build().getTags(); + + assertThat(tags.size(), is(1)); + assertThat(tags.get(tagKey).size(), is(1)); + assertThat(tags.get(tagKey), contains(tagValue)); + + tags.put(UUID.randomUUID().toString(), Collections.emptySet()); + } + + @Test(expected = UnsupportedOperationException.class) + public void testExtrasAreImmutable() throws Exception { + String extraKey = UUID.randomUUID().toString(); + Object extraValue = mock(Object.class); + + Map extra = eventBuilder.addExtra(extraKey, extraValue).build().getExtra(); + + assertThat(extra.size(), is(1)); + assertThat(extra.get(extraKey), is(extraValue)); + + extra.put(UUID.randomUUID().toString(), mock(Object.class)); + } + + @Test(expected = UnsupportedOperationException.class) + public void testSentryInterfacesAreImmutable() throws Exception { + SentryInterface sentryInterface = mock(SentryInterface.class); + when(sentryInterface.getInterfaceName()).thenReturn(UUID.randomUUID().toString()); + + Map sentryInterfaces = eventBuilder + .addSentryInterface(sentryInterface) + .build() + .getSentryInterfaces(); + + assertThat(sentryInterfaces.size(), is(1)); + assertThat(sentryInterfaces.get(sentryInterface.getInterfaceName()), is(sentryInterface)); + + sentryInterfaces.put(UUID.randomUUID().toString(), null); + } + + @Test(expected = IllegalStateException.class) + public void testBuildCanBeCalledOnlyOnce() throws Exception { + eventBuilder.build(); + eventBuilder.build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testNoUuidFails() throws Exception { + new EventBuilder(null); + } +} From 3ac75114d42554f43a6c660e093c7f6a79063f3e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 May 2013 11:19:10 +0200 Subject: [PATCH 0528/2152] Ensure that parametrisedMessage is indeed parametrised --- .../src/test/java/net/kencochrane/raven/AbstractLoggerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java index 28a1e1c8e0b..a556a110c35 100644 --- a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java @@ -85,7 +85,7 @@ public void testLogException() throws Exception { @Test public void testLogParametrisedMessage() throws Exception { ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - String message = UUID.randomUUID().toString(); + String message = "Some content %s"; List parameters = Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()); Event event; From 9a79dd6feb0983ed1e8e8c37620414d1ba7afd7a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 May 2013 12:11:04 +0200 Subject: [PATCH 0529/2152] Do not execute DsnLookup if a Raven instance is already set --- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 6 +++--- .../java/net/kencochrane/raven/log4j2/SentryAppender.java | 6 +++--- .../java/net/kencochrane/raven/logback/SentryAppender.java | 6 +++--- .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 3197cf432ad..e6c2114ed6f 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -57,10 +57,10 @@ private static StackTraceElement asStackTraceElement(LocationInfo location) { @Override public void activateOptions() { - if (dsn == null) - dsn = Dsn.dsnLookup(); - if (raven == null) { + if (dsn == null) + dsn = Dsn.dsnLookup(); + //TODO: Add a way to select the factory raven = RavenFactory.ravenInstance(new Dsn(dsn)); } diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 293c4304713..3fba4cb8702 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -106,10 +106,10 @@ private static String formatCulprit(StackTraceElement stackTraceElement) { @Override public void start() { - if (dsn == null) - dsn = Dsn.dsnLookup(); - if (raven == null) { + if (dsn == null) + dsn = Dsn.dsnLookup(); + //TODO: Add a way to select the factory raven = RavenFactory.ravenInstance(new Dsn(dsn)); } diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 09d410c979e..87dc2acea6f 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -60,10 +60,10 @@ private static Event.Level formatLevel(ILoggingEvent iLoggingEvent) { public void start() { super.start(); - if (dsn == null) - dsn = Dsn.dsnLookup(); - if (raven == null) { + if (dsn == null) + dsn = Dsn.dsnLookup(); + //TODO: Add a way to select the factory raven = RavenFactory.ravenInstance(new Dsn(dsn)); } diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index a96d4095864..f011dfa696c 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -89,10 +89,10 @@ public void publish(LogRecord record) { } private Raven getRaven() { - if (dsn == null) - dsn = Dsn.dsnLookup(); - if (raven == null) { + if (dsn == null) + dsn = Dsn.dsnLookup(); + //TODO: Add a way to select the factory raven = RavenFactory.ravenInstance(new Dsn(dsn)); } From 30ee268fe6a340198c21199d65483c8179e258b6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 May 2013 12:12:45 +0200 Subject: [PATCH 0530/2152] Add tests to check close propagation in JUL --- .../raven/jul/SentryHandlerTest.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java index 8c689f7375f..212eaadf7e9 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java @@ -1,14 +1,18 @@ package net.kencochrane.raven.jul; import net.kencochrane.raven.AbstractLoggerTest; +import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.event.Event; import org.junit.Before; import org.junit.Test; import java.util.List; +import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; +import static org.mockito.Mockito.*; + public class SentryHandlerTest extends AbstractLoggerTest { private Logger logger; @@ -52,6 +56,26 @@ public void testLogLevelConversions() throws Exception { assertLevelConverted(Event.Level.ERROR, Level.SEVERE); } + @Test + public void testClosePropagates() throws Exception { + when(getMockRaven().getConnection()).thenReturn(mock(Connection.class)); + Handler handler = new SentryHandler(getMockRaven(), true); + + handler.close(); + + verify(getMockRaven().getConnection()).close(); + } + + @Test + public void testCloseDoesntPropagate() throws Exception { + when(getMockRaven().getConnection()).thenReturn(mock(Connection.class)); + Handler handler = new SentryHandler(getMockRaven(), false); + + handler.close(); + + verify(getMockRaven().getConnection(), never()).close(); + } + private void assertLevelConverted(Event.Level expectedLevel, Level level) { logger.log(level, null); assertLogLevel(expectedLevel); From 39b4c5b4acacc6a991041b25ec65aaf5c9f81a1a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 May 2013 12:18:38 +0200 Subject: [PATCH 0531/2152] Handle exception on close in JUL --- .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index f011dfa696c..0833db66fe6 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.logging.ErrorManager; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; @@ -113,8 +114,7 @@ public void close() throws SecurityException { if (propagateClose) raven.getConnection().close(); } catch (IOException e) { - //TODO: What to do with that exception? - e.printStackTrace(); + reportError("Am exception occurred while closing the connection", e, ErrorManager.CLOSE_FAILURE); } } } From 0fbb1e2963f170b6422ab27b162b0ba30558fdf2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 May 2013 12:25:16 +0200 Subject: [PATCH 0532/2152] Properly handle exceptions while closing the connection --- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 5 +++-- .../java/net/kencochrane/raven/log4j2/SentryAppender.java | 3 +-- .../java/net/kencochrane/raven/logback/SentryAppender.java | 3 +-- .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index e6c2114ed6f..06cbee2d53d 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -8,6 +8,7 @@ import net.kencochrane.raven.event.interfaces.ExceptionInterface; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; +import org.apache.log4j.spi.ErrorCode; import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; @@ -111,8 +112,8 @@ public void close() { if (propagateClose) raven.getConnection().close(); } catch (IOException e) { - //TODO: What to do with that exception? - e.printStackTrace(); + getErrorHandler().error("An exception occurred while closing the raven connection", e, + ErrorCode.CLOSE_FAILURE); } } diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 3fba4cb8702..da6a54048e3 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -179,8 +179,7 @@ public void stop() { if (propagateClose) raven.getConnection().close(); } catch (IOException e) { - //TODO: What to do with that exception? - e.printStackTrace(); + error("An exception occurred while closing the raven connection", e); } } } diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 87dc2acea6f..dd036bce1a0 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -128,8 +128,7 @@ public void stop() { if (propagateClose) raven.getConnection().close(); } catch (IOException e) { - //TODO: What to do with that exception? - e.printStackTrace(); + addError("An exception occurred while closing the raven connection", e); } } } diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 0833db66fe6..42ddde73a0f 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -114,7 +114,7 @@ public void close() throws SecurityException { if (propagateClose) raven.getConnection().close(); } catch (IOException e) { - reportError("Am exception occurred while closing the connection", e, ErrorManager.CLOSE_FAILURE); + reportError("An exception occurred while closing the raven connection", e, ErrorManager.CLOSE_FAILURE); } } } From 4f963d8a02712e47e022e393c00b9cb12e8d182a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 May 2013 11:00:45 +0200 Subject: [PATCH 0533/2152] If the factory name isn't specified, default to the first available --- raven/src/main/java/net/kencochrane/raven/RavenFactory.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index 42d46f09928..83a1fdd2cca 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -20,6 +20,9 @@ public static Raven ravenInstance(Dsn dsn) { } public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { + if (ravenFactoryName == null) + return ravenInstance(dsn); + for (RavenFactory ravenFactory : RAVEN_FACTORIES) { if (!ravenFactoryName.equals(ravenFactory.getClass().getCanonicalName())) continue; From c086994001586f73019c52e76418b00c3f9eb588 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 May 2013 11:01:06 +0200 Subject: [PATCH 0534/2152] Allow the appenders/handlers to specify a custom ravenFactory --- .../net/kencochrane/raven/log4j/SentryAppender.java | 8 ++++++-- .../net/kencochrane/raven/log4j2/SentryAppender.java | 10 ++++++++-- .../net/kencochrane/raven/logback/SentryAppender.java | 8 ++++++-- .../java/net/kencochrane/raven/jul/SentryHandler.java | 8 ++++++-- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 06cbee2d53d..5ac1b40b653 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -22,6 +22,7 @@ public class SentryAppender extends AppenderSkeleton { private final boolean propagateClose; private Raven raven; private String dsn; + private String ravenFactory; public SentryAppender() { this.propagateClose = true; @@ -62,8 +63,7 @@ public void activateOptions() { if (dsn == null) dsn = Dsn.dsnLookup(); - //TODO: Add a way to select the factory - raven = RavenFactory.ravenInstance(new Dsn(dsn)); + raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); } } @@ -106,6 +106,10 @@ public void setDsn(String dsn) { this.dsn = dsn; } + public void setRavenFactory(String ravenFactory) { + this.ravenFactory = ravenFactory; + } + @Override public void close() { try { diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index da6a54048e3..4a6da0c5e9d 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -32,6 +32,7 @@ public class SentryAppender extends AbstractAppender { private final boolean propagateClose; private Raven raven; private String dsn; + private String ravenFactory; public SentryAppender() { this(APPENDER_NAME, PatternLayout.createLayout(null, null, null, null), null, true); @@ -65,6 +66,7 @@ private SentryAppender(String name, Layout layout, Filter filter, boolea @PluginFactory public static SentryAppender createAppender(@PluginAttr("name") final String name, @PluginAttr("dsn") final String dsn, + @PluginAttr("ravenFactory") final String ravenFactory, @PluginElement("layout") Layout layout, @PluginElement("filters") final Filter filter) { @@ -78,6 +80,7 @@ public static SentryAppender createAppender(@PluginAttr("name") final String nam } SentryAppender sentryAppender = new SentryAppender(name, layout, filter, true); sentryAppender.setDsn(dsn); + sentryAppender.setRavenFactory(ravenFactory); return sentryAppender; } @@ -110,8 +113,7 @@ public void start() { if (dsn == null) dsn = Dsn.dsnLookup(); - //TODO: Add a way to select the factory - raven = RavenFactory.ravenInstance(new Dsn(dsn)); + raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); } } @@ -171,6 +173,10 @@ public void setDsn(String dsn) { this.dsn = dsn; } + public void setRavenFactory(String ravenFactory) { + this.ravenFactory = ravenFactory; + } + @Override public void stop() { super.stop(); diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index dd036bce1a0..1cc8e782456 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -22,6 +22,7 @@ public class SentryAppender extends AppenderBase { private final boolean propagateClose; private Raven raven; private String dsn; + private String ravenFactory; public SentryAppender() { propagateClose = true; @@ -64,8 +65,7 @@ public void start() { if (dsn == null) dsn = Dsn.dsnLookup(); - //TODO: Add a way to select the factory - raven = RavenFactory.ravenInstance(new Dsn(dsn)); + raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); } } @@ -120,6 +120,10 @@ public void setDsn(String dsn) { this.dsn = dsn; } + public void setRavenFactory(String ravenFactory) { + this.ravenFactory = ravenFactory; + } + @Override public void stop() { super.stop(); diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 42ddde73a0f..e5b9ae8802c 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -21,6 +21,7 @@ public class SentryHandler extends Handler { private final boolean propagateClose; private Raven raven; private String dsn; + private String ravenFactory; public SentryHandler() { propagateClose = true; @@ -94,8 +95,7 @@ private Raven getRaven() { if (dsn == null) dsn = Dsn.dsnLookup(); - //TODO: Add a way to select the factory - raven = RavenFactory.ravenInstance(new Dsn(dsn)); + raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); } return raven; } @@ -104,6 +104,10 @@ public void setDsn(String dsn) { this.dsn = dsn; } + public void setRavenFactory(String ravenFactory) { + this.ravenFactory = ravenFactory; + } + @Override public void flush() { } From 5763d774b1d499c348cf0b4d3bc131f5181cbbc2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 May 2013 11:38:00 +0200 Subject: [PATCH 0535/2152] Add a cache system to keep track of the current hostname --- .../kencochrane/raven/event/EventBuilder.java | 80 ++++++++++++++++--- 1 file changed, 69 insertions(+), 11 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index f9028f92943..2a7cf289144 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -16,10 +16,8 @@ public class EventBuilder { * Default platform if it isn't set manually. */ public static final String DEFAULT_PLATFORM = "java"; - /** - * Default hostname if it isn't set manually (or can't be determined). - */ - public static final String DEFAULT_HOSTNAME = "unavailable"; + public static final int HOSTNAME_CACHE_DURATION = 18000000; + private static final HostnameCache hostnameCache = new HostnameCache(HOSTNAME_CACHE_DURATION); private final Event event; private boolean alreadyBuilt = false; @@ -58,15 +56,10 @@ private static String calculateChecksum(String string) { /** * Obtains the current hostname. * - * @return the current hostname, or {@link #DEFAULT_HOSTNAME} if it couldn't be determined. + * @return the current hostname. */ private static String getHostname() { - try { - // TODO: Cache this info - return InetAddress.getLocalHost().getCanonicalHostName(); - } catch (UnknownHostException e) { - return DEFAULT_HOSTNAME; - } + return hostnameCache.getHostname(); } /** @@ -302,4 +295,69 @@ public Event build() { alreadyBuilt = true; return event; } + + /** + * Time sensitive cache in charge of keeping track of the hostname. + *

    + * The {@code InetAddress.getLocalHost().getCanonicalHostName()} call can be quite expensive and could be called + * for the creation of each {@link Event}. This system will prevent unnecessary costs by keeping track of the + * hostname for a period defined during the construction. + *

    + */ + private static class HostnameCache { + /** + * Default hostname if it isn't set manually (or can't be determined). + */ + public static final String DEFAULT_HOSTNAME = "unavailable"; + /** + * Time for which the cache is kept. + */ + private final long cacheDuration; + /** + * Current value for hostname (might change over time) + */ + private String hostname = DEFAULT_HOSTNAME; + /** + * Time at which the cache should expire. + */ + private long expirationTimestamp; + + /** + * Sets up a cache for the hostname. + * + * @param cacheDuration cache duration in milliseconds. + */ + private HostnameCache(long cacheDuration) { + this.cacheDuration = cacheDuration; + } + + /** + * Gets the hostname of the current machine. + *

    + * Gets the value from the cache if possible otherwise calls {@link #updateCache()}. + *

    + * + * @return the hostname of the current machine. + */ + public String getHostname() { + if (expirationTimestamp < System.currentTimeMillis()) { + updateCache(); + } + + return hostname; + } + + /** + * Force an update of the cache to get the current value of the hostname. + */ + public void updateCache() { + expirationTimestamp = System.currentTimeMillis() + cacheDuration; + + try { + hostname = InetAddress.getLocalHost().getCanonicalHostName(); + } catch (UnknownHostException e) { + hostname = DEFAULT_HOSTNAME; + } + } + } } From 59a3e9285ce6eeea7a8a45fe7e0e6adf51833511 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 May 2013 11:38:59 +0200 Subject: [PATCH 0536/2152] Remove unnecessary intermediary method --- .../net/kencochrane/raven/event/EventBuilder.java | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 2a7cf289144..007ea5bde89 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -53,15 +53,6 @@ private static String calculateChecksum(String string) { return String.valueOf(checksum.getValue()); } - /** - * Obtains the current hostname. - * - * @return the current hostname. - */ - private static String getHostname() { - return hostnameCache.getHostname(); - } - /** * Sets default values for each field that hasn't been provided manually. * @@ -78,7 +69,7 @@ private static void autoSetMissingValues(Event event) { // Ensure that a hostname is set if (event.getServerName() == null) - event.setServerName(getHostname()); + event.setServerName(hostnameCache.getHostname()); } /** From 45997a0cde00e988444ba149fab3c13b66622b89 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 May 2013 11:51:38 +0200 Subject: [PATCH 0537/2152] Update commons-codec to 1.8 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a8c76082768..5143ed9966e 100644 --- a/pom.xml +++ b/pom.xml @@ -105,7 +105,7 @@ 6 - 1.6 + 1.8 2.2.0 3.0.1 1.0.12 From 7edfd854aecb40849a1bf35b15615d5dc3a7d9d0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 May 2013 12:01:36 +0200 Subject: [PATCH 0538/2152] Update github site plugin --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5143ed9966e..735f74a7644 100644 --- a/pom.xml +++ b/pom.xml @@ -217,7 +217,7 @@ com.github.github site-maven-plugin - 0.7 + 0.8 Creating site for ${project.artifactId} ${project.version} ${project.distributionManagement.site.url} From b96a7e9c97f5204e06de6600a200351fc1010bdf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 May 2013 20:36:22 +0200 Subject: [PATCH 0539/2152] Add some documentation to set raven with log4j --- INSTALL.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 06d794133ea..00a6fed0033 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -81,10 +81,14 @@ The client will lookup for the first DSN configuration provided: TODO ### Using log4j -TODO +To use the `SentryAppender` with log4j use this configuration: + + log4j.rootLogger=DEBUG, SentryAppender + log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender + log4j.appender.SentryAppender.dsn=http://publicKey:secretKey@host:port/projectId?options #### Asynchronous logging with AsyncAppender -log4j supports asynchronous logging with +It is not recommended to attempt to set up a `SentryAppender` with an [AsyncAppender](http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/AsyncAppender.html). While this is a common solution to avoid blocking the current thread until the event is sent to Sentry, it is recommended to use instead the option From e91e10f66571ab824b1c980ba6fde0bd9c7a996e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 May 2013 20:48:34 +0200 Subject: [PATCH 0540/2152] Add an option to set the size of the async queue --- .../raven/DefaultRavenFactory.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 90cffbdd53e..2945d83d907 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -14,8 +14,7 @@ import java.util.Arrays; import java.util.Collection; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; @@ -45,6 +44,10 @@ public class DefaultRavenFactory extends RavenFactory { * Option for the priority of threads assigned for the connection. */ public static final String PRIORITY_OPTION = "raven.async.priority"; + /** + * Option for the maximum size of the queue. + */ + public static final String QUEUE_SIZE_OPTION = "raven.async.queuesize"; /** * Option to hide common stackframes with enclosing exceptions. */ @@ -103,7 +106,18 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { priority = Thread.MIN_PRIORITY; } - asyncConnection.setExecutorService(Executors.newFixedThreadPool(maxThreads, new DaemonThreadFactory(priority))); + BlockingDeque queue; + if (dsn.getOptions().containsKey(QUEUE_SIZE_OPTION)) { + queue = new LinkedBlockingDeque(Integer.parseInt(dsn.getOptions().get(QUEUE_SIZE_OPTION))); + } else { + queue = new LinkedBlockingDeque(); + } + + asyncConnection.setExecutorService(new ThreadPoolExecutor( + maxThreads, maxThreads, + 0L, TimeUnit.MILLISECONDS, + queue, + new DaemonThreadFactory(priority))); return asyncConnection; } From afae045c3ac04b5dfdb5edf65656571d78b4ec4a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 May 2013 20:53:47 +0200 Subject: [PATCH 0541/2152] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b3169e337a4..58e7dfa1a1b 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ DSN: Not every option requires a value. ### Async connection -In order to avoid performance issues due to a large amout of logs being +In order to avoid performance issues due to a large amount of logs being generated or a slow connection to the Sentry server, it is recommended to use the asynchronous connection which will use a low priority thread pool to submit events to Sentry. From 41efba5050a753eeb408948d7d3e26e0f2200588 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 May 2013 20:53:56 +0200 Subject: [PATCH 0542/2152] Document the new raven.async.queuesize option --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 58e7dfa1a1b..d15d69c4ea5 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,19 @@ To enable the async mode, add the `raven.async` option to your DSN: http://public:private@host:port/1?raven.async +#### Queue size (advanced) +The default queue used to store the not yet processed events doesn't have a +limit. +Depending on the environment (where memory is sparse) it is important to be +able to control the size of that queue to avoid memory issues. + +It is possible to set a maximum with the option `raven.async.queuesize`: + + http://public:private@host:port/1?raven.async&raven.async.queuesize=100 + +This means that if the connection to the Sentry server, only the first 100 +events will be stored and be processed as soon as the server is back up. + #### Threads count (advanced) By default the thread pool used by the async connection contains one thread per processor available to the JVM (more threads wouldn't be useful). From 8d79bc9cbbe9747e2ef4cb16a4e07347bfd6cd8a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 May 2013 20:55:08 +0200 Subject: [PATCH 0543/2152] Make events Serializable --- raven/src/main/java/net/kencochrane/raven/event/Event.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/Event.java b/raven/src/main/java/net/kencochrane/raven/event/Event.java index dea26686db6..33fac0bdd91 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/Event.java +++ b/raven/src/main/java/net/kencochrane/raven/event/Event.java @@ -2,6 +2,7 @@ import net.kencochrane.raven.event.interfaces.SentryInterface; +import java.io.Serializable; import java.util.*; /** @@ -24,7 +25,7 @@ * *

    */ -public class Event { +public class Event implements Serializable { /** * Unique identifier of the event. */ From 5ced9531029c49f8fab8652f04d75e1a5ee15f0b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 May 2013 20:55:18 +0200 Subject: [PATCH 0544/2152] Fix indentation --- raven/src/main/java/net/kencochrane/raven/event/Event.java | 1 - 1 file changed, 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/Event.java b/raven/src/main/java/net/kencochrane/raven/event/Event.java index 33fac0bdd91..0a73637e292 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/Event.java +++ b/raven/src/main/java/net/kencochrane/raven/event/Event.java @@ -193,7 +193,6 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; return id.equals(((Event) o).id); - } @Override From 5194f25388e46bc124c4fc59a969411cfcec5c76 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 May 2013 20:55:36 +0200 Subject: [PATCH 0545/2152] Make SentryInterface Serializable --- .../kencochrane/raven/event/interfaces/SentryInterface.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryInterface.java index 2e182501777..fc30e43ddcb 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryInterface.java @@ -1,9 +1,11 @@ package net.kencochrane.raven.event.interfaces; +import java.io.Serializable; + /** * A SentryInterface is an additional structured data that can be provided with a message. */ -public interface SentryInterface { +public interface SentryInterface extends Serializable { /** * Gets the unique name of the interface. * From a6d314a6faeef52fe82696dccf646fcc5e58f1c2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 May 2013 20:55:53 +0200 Subject: [PATCH 0546/2152] Remove call to deprecated methods --- .../raven/marshaller/json/AbstractInterfaceBindingTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractInterfaceBindingTest.java index 7ad68032b50..0e1024ac373 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractInterfaceBindingTest.java @@ -22,11 +22,11 @@ protected void setUp() throws Exception { protected JsonGenerator getJsonGenerator() throws IOException { jsonContentStream = new ByteArrayOutputStream(); - return jsonFactory.createJsonGenerator(jsonContentStream); + return jsonFactory.createGenerator(jsonContentStream); } protected JsonParser getJsonParser() throws IOException { - return jsonFactory.createJsonParser(jsonContentStream.toByteArray()); + return jsonFactory.createParser(jsonContentStream.toByteArray()); } protected ObjectMapper getMapper() { From cacf234d3e7ad81031d072142060f4a205adf526 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 May 2013 21:07:41 +0200 Subject: [PATCH 0547/2152] Rename constant following the convention --- .../main/java/net/kencochrane/raven/event/EventBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 007ea5bde89..f45db758202 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -17,7 +17,7 @@ public class EventBuilder { */ public static final String DEFAULT_PLATFORM = "java"; public static final int HOSTNAME_CACHE_DURATION = 18000000; - private static final HostnameCache hostnameCache = new HostnameCache(HOSTNAME_CACHE_DURATION); + private static final HostnameCache HOSTNAME_CACHE = new HostnameCache(HOSTNAME_CACHE_DURATION); private final Event event; private boolean alreadyBuilt = false; @@ -69,7 +69,7 @@ private static void autoSetMissingValues(Event event) { // Ensure that a hostname is set if (event.getServerName() == null) - event.setServerName(hostnameCache.getHostname()); + event.setServerName(HOSTNAME_CACHE.getHostname()); } /** From a7c53bf6a0b0719b2baaaa39f1fbfe4616eafc9b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 May 2013 22:50:20 +0200 Subject: [PATCH 0548/2152] Fix some checkstyle issues --- raven/src/main/java/net/kencochrane/raven/RavenFactory.java | 4 ++-- .../kencochrane/raven/connection/AbstractConnection.java | 6 ++++-- .../main/java/net/kencochrane/raven/event/EventBuilder.java | 4 ++-- .../raven/marshaller/json/ExceptionInterfaceBinding.java | 3 ++- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index 83a1fdd2cca..eab4039a63b 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -41,8 +41,8 @@ private static Raven getRavenSafely(Dsn dsn, RavenFactory ravenFactory) { try { raven = ravenFactory.createRavenInstance(dsn); } catch (Exception e) { - logger.log(Level.WARNING, "An exception occurred during the creation of a Raven instance with " + - "'" + ravenFactory + "' using the DSN '" + dsn + "'", e); + logger.log(Level.WARNING, "An exception occurred during the creation of a Raven instance with " + + "'" + ravenFactory + "' using the DSN '" + dsn + "'", e); } return raven; } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 19c24e398ae..1f45cc1923d 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -21,6 +21,8 @@ public abstract class AbstractConnection implements Connection { * Current sentry protocol version. */ public static final String SENTRY_PROTOCOL_VERSION = "4"; + public static final int DEFAULT_MAX_WAITING_TIME = 300000; + public static final int DEFAULT_BASE_WAITING_TIME = 10; private static final Logger logger = Logger.getLogger(AbstractConnection.class.getCanonicalName()); private final String publicKey; private final String secretKey; @@ -28,11 +30,11 @@ public abstract class AbstractConnection implements Connection { /** * At most wait 5 minutes if the connection failed too many times. */ - private long maxWaitingTime = 300000; + private long maxWaitingTime = DEFAULT_MAX_WAITING_TIME; /** * When the first exception occurs, wait 10 millis before trying again. */ - private long baseWaitingTime = 10; + private long baseWaitingTime = DEFAULT_BASE_WAITING_TIME; private long waitingTime = baseWaitingTime; /** diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index f45db758202..71161870cfe 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -295,7 +295,7 @@ public Event build() { * hostname for a period defined during the construction. *

    */ - private static class HostnameCache { + private static final class HostnameCache { /** * Default hostname if it isn't set manually (or can't be determined). */ @@ -305,7 +305,7 @@ private static class HostnameCache { */ private final long cacheDuration; /** - * Current value for hostname (might change over time) + * Current value for hostname (might change over time). */ private String hostname = DEFAULT_HOSTNAME; /** diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java index 4e632a476af..13d5b3734c6 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java @@ -37,7 +37,8 @@ public void writeInterface(JsonGenerator generator, ExceptionInterface exception generator.writeStringField(VALUE_PARAMETER, throwable.getMessage()); generator.writeStringField(MODULE_PARAMETER, throwable.getActualClass().getPackage().getName()); generator.writeFieldName(STACKTRACE_PARAMETER); - stackTraceInterfaceBinding.writeInterface(generator, new StackTraceInterface(throwable.getStackTrace(), enclosingStackTrace)); + stackTraceInterfaceBinding.writeInterface(generator, + new StackTraceInterface(throwable.getStackTrace(), enclosingStackTrace)); generator.writeEndObject(); enclosingStackTrace = throwable.getStackTrace(); throwable = throwable.getCause(); From fbdb3edc73ea4c759aa04f3b6c85f3c7667c9e8a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 4 May 2013 01:34:20 +0200 Subject: [PATCH 0549/2152] Add more javadoc to the project --- .../raven/log4j/SentryAppender.java | 3 +++ .../raven/log4j2/SentryAppender.java | 17 ++++++++++++----- .../raven/logback/SentryAppender.java | 3 +++ .../kencochrane/raven/DefaultRavenFactory.java | 6 ++++++ .../net/kencochrane/raven/RavenFactory.java | 6 ++++++ .../raven/connection/AbstractConnection.java | 13 +++++++++++-- .../raven/connection/UdpConnection.java | 3 +++ .../net/kencochrane/raven/event/Event.java | 18 ++++++++++++++++++ .../kencochrane/raven/event/EventBuilder.java | 5 +++++ .../raven/event/helper/EventBuilderHelper.java | 4 ++++ .../event/helper/HttpEventBuilderHelper.java | 3 +++ .../event/interfaces/ExceptionInterface.java | 6 ++++++ .../raven/event/interfaces/HttpInterface.java | 3 +++ .../event/interfaces/MessageInterface.java | 3 +++ .../event/interfaces/StackTraceInterface.java | 6 ++++++ .../raven/exception/ConnectionException.java | 6 ++++++ .../kencochrane/raven/jul/SentryHandler.java | 3 +++ .../json/ExceptionInterfaceBinding.java | 3 +++ .../json/MessageInterfaceBinding.java | 3 +++ .../json/StackTraceInterfaceBinding.java | 3 +++ .../servlet/RavenServletRequestListener.java | 5 +++++ 21 files changed, 115 insertions(+), 7 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 5ac1b40b653..97df99030f4 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -17,6 +17,9 @@ import java.util.Map; import java.util.Set; +/** + * Appender for log4j in charge of sending the logged events to a Sentry server. + */ public class SentryAppender extends AppenderSkeleton { private static final String LOG4J_NDC = "Log4J-NDC"; private final boolean propagateClose; diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 4a6da0c5e9d..3af4cd57f5f 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -25,8 +25,14 @@ import java.util.List; import java.util.Map; +/** + * Appender for log4j2 in charge of sending the logged events to a Sentry server. + */ @Plugin(name = "Sentry", type = "Sentry", elementType = "appender") public class SentryAppender extends AbstractAppender { + /** + * Default name for the appender. + */ public static final String APPENDER_NAME = "raven"; private static final String LOG4J_NDC = "Log4J-NDC"; private final boolean propagateClose; @@ -56,11 +62,12 @@ private SentryAppender(String name, Layout layout, Filter filter, boolea /** * Create a Sentry Appender. * - * @param name The name of the Appender. - * @param dsn Data Source Name to access the Sentry server. - * @param layout The layout to use to format the event. If no layout is provided the default PatternLayout - * will be used. - * @param filter The filter, if any, to use. + * @param name The name of the Appender. + * @param dsn Data Source Name to access the Sentry server. + * @param ravenFactory name of the factory to use to build the {@link Raven} instance. + * @param layout The layout to use to format the event. If no layout is provided the default PatternLayout + * will be used. + * @param filter The filter, if any, to use. * @return The SentryAppender. */ @PluginFactory diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 1cc8e782456..a929cbcbcc8 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -18,6 +18,9 @@ import java.util.List; import java.util.Map; +/** + * Appender for logback in charge of sending the logged events to a Sentry server. + */ public class SentryAppender extends AppenderBase { private final boolean propagateClose; private Raven raven; diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 2945d83d907..2b4c4a4fedf 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -19,6 +19,12 @@ import java.util.logging.Level; import java.util.logging.Logger; +/** + * Default implementation of {@link RavenFactory}. + *

    + * In most cases this is the implementation to use or extend for additional features. + *

    + */ public class DefaultRavenFactory extends RavenFactory { /** * Protocol setting to disable security checks over an SSL connection. diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index eab4039a63b..1e257c5879b 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -4,6 +4,12 @@ import java.util.logging.Level; import java.util.logging.Logger; +/** + * Factory in charge of creating {@link Raven} instances. + *

    + * The factories register themselves through the {@link ServiceLoader} system. + *

    + */ public abstract class RavenFactory { private static final Logger logger = Logger.getLogger(RavenFactory.class.getCanonicalName()); private static final ServiceLoader RAVEN_FACTORIES = ServiceLoader.load(RavenFactory.class); diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 1f45cc1923d..d65e28c6e8f 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -21,18 +21,27 @@ public abstract class AbstractConnection implements Connection { * Current sentry protocol version. */ public static final String SENTRY_PROTOCOL_VERSION = "4"; + /** + * Default maximum duration for a lockdown (5 minutes). + */ public static final int DEFAULT_MAX_WAITING_TIME = 300000; + /** + * Default base duration for a lockdown (10 milliseconds). + */ public static final int DEFAULT_BASE_WAITING_TIME = 10; private static final Logger logger = Logger.getLogger(AbstractConnection.class.getCanonicalName()); private final String publicKey; private final String secretKey; private final ReentrantLock lock = new ReentrantLock(); /** - * At most wait 5 minutes if the connection failed too many times. + * Maximum duration for a lockdown. */ private long maxWaitingTime = DEFAULT_MAX_WAITING_TIME; /** - * When the first exception occurs, wait 10 millis before trying again. + * Base duration for a lockdown. + *

    + * On each attempt the time is doubled until it reaches {@link #maxWaitingTime}. + *

    */ private long baseWaitingTime = DEFAULT_BASE_WAITING_TIME; private long waitingTime = baseWaitingTime; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index e4870ef8764..e16b8095677 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -16,6 +16,9 @@ * Connection to a Sentry server through an UDP connection. */ public class UdpConnection extends AbstractConnection { + /** + * Default UDP port for a Sentry instance. + */ public static final int DEFAULT_UDP_PORT = 9001; private DatagramSocket socket; private Marshaller marshaller; diff --git a/raven/src/main/java/net/kencochrane/raven/event/Event.java b/raven/src/main/java/net/kencochrane/raven/event/Event.java index 0a73637e292..94867c75d8d 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/Event.java +++ b/raven/src/main/java/net/kencochrane/raven/event/Event.java @@ -209,11 +209,29 @@ public String toString() { + '}'; } + /** + * Levels of log available in Sentry. + */ public static enum Level { + /** + * Fatal is the highest form of log available, use it for unrecoverable issues. + */ FATAL, + /** + * Error denotes an unexpected behaviour that prevented the code to work properly. + */ ERROR, + /** + * Warning should be used to define logs generated by expected and handled bad behaviour. + */ WARNING, + /** + * Info is used to give general details on the running application, usually only messages. + */ INFO, + /** + * Debug information to track every detail of the application execution process. + */ DEBUG } } diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 71161870cfe..c2440b64954 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -16,6 +16,11 @@ public class EventBuilder { * Default platform if it isn't set manually. */ public static final String DEFAULT_PLATFORM = "java"; + /** + * Duration of the hostname caching. + * + * @see HostnameCache + */ public static final int HOSTNAME_CACHE_DURATION = 18000000; private static final HostnameCache HOSTNAME_CACHE = new HostnameCache(HOSTNAME_CACHE_DURATION); private final Event event; diff --git a/raven/src/main/java/net/kencochrane/raven/event/helper/EventBuilderHelper.java b/raven/src/main/java/net/kencochrane/raven/event/helper/EventBuilderHelper.java index 397a18047a7..b5dd370922e 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/helper/EventBuilderHelper.java +++ b/raven/src/main/java/net/kencochrane/raven/event/helper/EventBuilderHelper.java @@ -2,6 +2,10 @@ import net.kencochrane.raven.event.EventBuilder; +/** + * Helper allowing to add extra information to the {@link EventBuilder} before creating the + * {@link net.kencochrane.raven.event.Event} itself. + */ public interface EventBuilderHelper { void helpBuildingEvent(EventBuilder eventBuilder); } diff --git a/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java b/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java index 9bd877fe7df..e6043c109b1 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java +++ b/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java @@ -6,6 +6,9 @@ import javax.servlet.http.HttpServletRequest; +/** + * EventBuilderHelper allowing to retrieve the current {@link HttpServletRequest}. + */ public class HttpEventBuilderHelper implements EventBuilderHelper { @Override public void helpBuildingEvent(EventBuilder eventBuilder) { diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java index da281a7645a..c0013aef484 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java @@ -1,6 +1,12 @@ package net.kencochrane.raven.event.interfaces; +/** + * The Exception interface for Sentry allowing to add an Exception details to an event. + */ public class ExceptionInterface implements SentryInterface { + /** + * Name of the exception interface in Sentry. + */ public static final String EXCEPTION_INTERFACE = "sentry.interfaces.Exception"; private final ImmutableThrowable throwable; diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java index 73af47334fe..51fd6cd1fac 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java @@ -6,6 +6,9 @@ * The HTTP interface for Sentry allows to add an HTTP request to an event. */ public class HttpInterface implements SentryInterface { + /** + * Name of the HTTP interface in Sentry. + */ public static final String HTTP_INTERFACE = "sentry.interfaces.Http"; private final HttpServletRequest request; diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java index aa07a8ef31a..10c3b415244 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java @@ -8,6 +8,9 @@ * The Message interface for Sentry allows to add a message that will be formatted by sentry. */ public class MessageInterface implements SentryInterface { + /** + * Name of the message interface in Sentry. + */ public static final String MESSAGE_INTERFACE = "sentry.interfaces.Message"; private final String message; private final List params; diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java index 7311e49d354..8856f793805 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java @@ -2,7 +2,13 @@ import java.util.Arrays; +/** + * The StackTrace interface for Sentry, allowing to add a stackTrace to an event. + */ public class StackTraceInterface implements SentryInterface { + /** + * Name of the Sentry interface allowing to send a StackTrace. + */ public static final String STACKTRACE_INTERFACE = "sentry.interfaces.Stacktrace"; private final StackTraceElement[] stackTrace; private final int framesCommonWithEnclosing; diff --git a/raven/src/main/java/net/kencochrane/raven/exception/ConnectionException.java b/raven/src/main/java/net/kencochrane/raven/exception/ConnectionException.java index 1c29a076db2..68276c3d1d3 100644 --- a/raven/src/main/java/net/kencochrane/raven/exception/ConnectionException.java +++ b/raven/src/main/java/net/kencochrane/raven/exception/ConnectionException.java @@ -1,5 +1,11 @@ package net.kencochrane.raven.exception; +/** + * Exception thrown by a {@link net.kencochrane.raven.connection.Connection} if something went wrong temporarily. + *

    + * This allows connections to know when to back off for a while. + *

    + */ public class ConnectionException extends RuntimeException { public ConnectionException() { diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index e5b9ae8802c..2c5bc68dce9 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -17,6 +17,9 @@ import java.util.logging.Level; import java.util.logging.LogRecord; +/** + * Logging handler in charge of sending the java.util.logging records to a Sentry server. + */ public class SentryHandler extends Handler { private final boolean propagateClose; private Raven raven; diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java index 13d5b3734c6..476d0890cf3 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java @@ -10,6 +10,9 @@ import java.util.Set; import java.util.logging.Logger; +/** + * Binding system allowing to convert an {@link ExceptionInterface} to a JSON stream. + */ public class ExceptionInterfaceBinding implements InterfaceBinding { private static final Logger logger = Logger.getLogger(ExceptionInterfaceBinding.class.getCanonicalName()); private static final String TYPE_PARAMETER = "type"; diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBinding.java index 143a60f418e..6f93bd149f3 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBinding.java @@ -5,6 +5,9 @@ import java.io.IOException; +/** + * Binding allowing to transform a {@link MessageInterface} into a JSON stream. + */ public class MessageInterfaceBinding implements InterfaceBinding { /** * Maximum length for a message. diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java index 26bb94a6fe7..250976e23bc 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java @@ -7,6 +7,9 @@ import java.util.Collection; import java.util.Collections; +/** + * Binding allowing to convert a {@link StackTraceInterface} into a JSON stream. + */ public class StackTraceInterfaceBinding implements InterfaceBinding { private static final String FRAMES_PARAMETER = "frames"; private static final String FILENAME_PARAMETER = "filename"; diff --git a/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java b/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java index 8103e7fb546..19c7b2a5bc5 100644 --- a/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java +++ b/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java @@ -6,6 +6,11 @@ import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpServletRequest; +/** + * Request listener in charge of capturing {@link HttpServletRequest} to allow + * {@link net.kencochrane.raven.event.helper.HttpEventBuilderHelper} to provide details on the current HTTP session + * in the event sent to Sentry. + */ //TODO: Consider Servlet < 3? @WebListener public class RavenServletRequestListener implements ServletRequestListener { From 13a6a5a0364be75cd6b7adc1791e08eea4e7f580 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 09:09:47 +0200 Subject: [PATCH 0550/2152] Make the DaemonThreadFactory available for subclasses --- .../main/java/net/kencochrane/raven/DefaultRavenFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 2b4c4a4fedf..7cf6121fdc4 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -187,7 +187,7 @@ protected Collection getNotInAppFrames() { * down the main application. *

    */ - private static final class DaemonThreadFactory implements ThreadFactory { + protected static final class DaemonThreadFactory implements ThreadFactory { private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1); private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); From ba590169873cc91aa2b0146256e56a0236eaca65 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 09:45:01 +0200 Subject: [PATCH 0551/2152] Make the travis status the first line --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d15d69c4ea5..ee72f28ae6c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Raven +[![Build Status](https://secure.travis-ci.org/kencochrane/raven-java.png?branch=master)](http://travis-ci.org/kencochrane/raven-java) + Raven is a Java client for [Sentry](https://www.getsentry.com/). Besides the regular client you can use within your application code, this project also provides tools allowing the most popular logging frameworks @@ -14,7 +16,6 @@ to send the logs directly to sentry: Raven supports both HTTP(S) and UDP transport of events. -[![Build Status](https://secure.travis-ci.org/kencochrane/raven-java.png?branch=master)](http://travis-ci.org/kencochrane/raven-java) ## Sentry Protocol and supported versions ### Sentry Protocol versions From 4aee9696e150c382889b80c39ef9481491549839 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 09:45:22 +0200 Subject: [PATCH 0552/2152] Add more content to the README file --- README.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ee72f28ae6c..213bbdcda03 100644 --- a/README.md +++ b/README.md @@ -33,10 +33,12 @@ reason, only the last two major versions of Raven are maintained. - Sentry protocol v3 is available since Sentry 5.1 (use Raven-3.x) - Sentry protocol v2 is available since Sentry 2.0 (use Raven-2.x) + ## Build and Installation **See [INSTALL.md](https://github.com/kencochrane/raven-java/blob/master/INSTALL.md)** + ## Connection and protocol It is possible to send events to Sentry over different protocols, depending on the security and performance requirements. @@ -44,8 +46,8 @@ So far Sentry accepts HTTP(S) and UDP which are both fully supported by Raven. ### HTTP -The most common way to access Sentry is through HTTP, this can be done by -using a DSN using this form: +The most common way send events to Sentry is through HTTP, this can be done by +using a DSN of this form: http://public:private@host:port/1 @@ -57,6 +59,7 @@ It is possible to use an encrypted connection to Sentry using HTTPS: https://public:private@host:port/1 If not provided, the port will default to `443`. + ### HTTPS (naive) If the certificate used over HTTPS is a wildcard certificate (which is not handled by every version of Java), and the certificate isn't added to the @@ -75,13 +78,15 @@ If not provided the port will default to `9001`. While being faster because there is no TCP and HTTP overhead, UDP doesn't wait for a reply, and if a connection problem occurs, there will be no notification. + ## Options It is possible to enable some options by adding data to the query string of the DSN: http://public:private@host:port/1?option1=value1&option2&option3=value3 -Not every option requires a value. +Some options do not require a value, just being declared signifies that the +option is enabled. ### Async connection In order to avoid performance issues due to a large amount of logs being @@ -96,22 +101,22 @@ To enable the async mode, add the `raven.async` option to your DSN: #### Queue size (advanced) The default queue used to store the not yet processed events doesn't have a limit. -Depending on the environment (where memory is sparse) it is important to be +Depending on the environment (if the memory is sparse) it is important to be able to control the size of that queue to avoid memory issues. It is possible to set a maximum with the option `raven.async.queuesize`: http://public:private@host:port/1?raven.async&raven.async.queuesize=100 -This means that if the connection to the Sentry server, only the first 100 -events will be stored and be processed as soon as the server is back up. +This means that if the connection to the Sentry server is down, only the first +100 events will be stored and be processed as soon as the server is back up. #### Threads count (advanced) By default the thread pool used by the async connection contains one thread per processor available to the JVM (more threads wouldn't be useful). It's possible to manually set the number of threads (for example if you want -only one Thread) with the option `raven.async.threads`: +only one thread) with the option `raven.async.threads`: http://public:private@host:port/1?raven.async&raven.async.threads=1 @@ -126,7 +131,53 @@ with the option `raven.async.priority`: http://public:private@host:port/1?raven.async.priority=10&raven.async ### Inapp classes -**TODO** +Sentry differentiate `in_app` stack frames (which are directly related to your application) +and the "not `in_app`" ones. +This difference is visible in the Sentry web interface where only the `in_app` +frames are displayed by default. + +#### Same frame as enclosing exception +Raven can use the `in_app` system to hide frames in the context of chained exceptions. + +Usually when a StackTrace is printed, the result looks like this: + + HighLevelException: MidLevelException: LowLevelException + at Main.a(Main.java:13) + at Junk.main(Main.java:4) + Caused by: MidLevelException: LowLevelException + at Main.c(Main.java:23) + at Main.b(Main.java:17) + at Main.a(Main.java:11) + ... 1 more + Caused by: LowLevelException + at Main.e(Main.java:30) + at Main.d(Main.java:27) + at Main.c(Main.java:21) + ... 3 more + +Some frames are replaced by the `... N more` line as they are the same frames +as in the enclosing exception. + +To enable a similar behaviour from raven use the `raven.stacktrace.hidecommon` option. + + http://public:private@host:port/1?raven.stacktrace.hidecommon + +#### Hide frames based on the class name +Raven can also mark some frames as `in_app` based on the name of the class. + +This can be used to hide parts of the stacktrace that are irrelevant to the problem +for example the stack frames in the `java.util` package will not help determining +what the problem was and will just create a longer stacktrace. + +Currently this is not configurable (see #49) and some packages are ignored by default: + +- com.sun.* +- java.* +- javax.* +- org.omg.* +- sun.* +- junit.* +- com.intellij.rt.* ### Compression By default the content sent to Sentry is compressed and encoded in base64 before @@ -152,6 +203,7 @@ manually set one with `raven.timeout` (in milliseconds): http://public:private@host:port/1?raven.timeout=10000 + ## History - 4.0 From c23598604fddfc13822c6b1396f1f9cb7c375cbf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 11:22:50 +0200 Subject: [PATCH 0553/2152] Replace the negative compression option by a positive one nocompression doesn't make sense, it should be the compression option. --- README.md | 5 +++-- .../java/net/kencochrane/raven/DefaultRavenFactory.java | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 213bbdcda03..72a584ef0ff 100644 --- a/README.md +++ b/README.md @@ -190,9 +190,10 @@ Depending on the limitations of the project (ie: a mobile application with a limited connection, Sentry hosted on an external network), it can be interesting to compress the data beforehand or not. -It's possible to disable the compression with the option `raven.nocompression` +It's possible to manually enable/disable the compression with the option +`raven.compression` - http://public:private@host:port/1?raven.nocompression + http://public:private@host:port/1?raven.compression=false ### Timeout (advanced) To avoid blocking the thread because of a connection taking too much time, a diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 7cf6121fdc4..d67c3b1d793 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -31,9 +31,9 @@ public class DefaultRavenFactory extends RavenFactory { */ public static final String NAIVE_PROTOCOL = "naive"; /** - * Option specific to raven-java, allowing to disable the compression of requests to the Sentry Server. + * Option specific to raven-java, allowing to enable/disable the compression of requests to the Sentry Server. */ - public static final String NOCOMPRESSION_OPTION = "raven.nocompression"; + public static final String COMPRESSION_OPTION = "raven.compression"; /** * Option specific to raven-java, allowing to set a timeout (in ms) for a request to the Sentry server. */ @@ -165,7 +165,7 @@ protected Marshaller createMarshaller(Dsn dsn) { marshaller.addInterfaceBinding(HttpInterface.class, httpBinding); // Set compression - marshaller.setCompression(!dsn.getOptions().containsKey(NOCOMPRESSION_OPTION)); + marshaller.setCompression(!dsn.getOptions().containsKey(COMPRESSION_OPTION)); return marshaller; } From c51d2afbc55f630a4687795170202664d7cf886d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 11:23:23 +0200 Subject: [PATCH 0554/2152] Enable some options by default Instead of checking if the option is set, check that it isn't set to "false". This way the option is enabled by default. --- .../net/kencochrane/raven/DefaultRavenFactory.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index d67c3b1d793..9589d1520ef 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -59,6 +59,7 @@ public class DefaultRavenFactory extends RavenFactory { */ public static final String HIDE_COMMON_FRAMES_OPTION = "raven.stacktrace.hidecommon"; private static final Logger logger = Logger.getLogger(DefaultRavenFactory.class.getCanonicalName()); + private static final String FALSE = Boolean.FALSE.toString(); @Override public Raven createRavenInstance(Dsn dsn) { @@ -88,7 +89,8 @@ protected Connection createConnection(Dsn dsn) { throw new IllegalStateException("Couldn't create a connection for the protocol '" + protocol + "'"); } - if (dsn.getOptions().containsKey(ASYNC_OPTION)) { + // Enable async unless its value is 'false'. + if (!FALSE.equalsIgnoreCase(dsn.getOptions().get(ASYNC_OPTION))) { connection = createAsyncConnection(dsn, connection); } @@ -153,7 +155,9 @@ protected Marshaller createMarshaller(Dsn dsn) { // Set JSON marshaller bindings StackTraceInterfaceBinding stackTraceBinding = new StackTraceInterfaceBinding(); - stackTraceBinding.setRemoveCommonFramesWithEnclosing(dsn.getOptions().containsKey(HIDE_COMMON_FRAMES_OPTION)); + // Enable common frames hiding unless its value is 'false'. + stackTraceBinding.setRemoveCommonFramesWithEnclosing( + !FALSE.equalsIgnoreCase(dsn.getOptions().get(HIDE_COMMON_FRAMES_OPTION))); stackTraceBinding.setNotInAppFrames(getNotInAppFrames()); marshaller.addInterfaceBinding(StackTraceInterface.class, stackTraceBinding); @@ -164,8 +168,8 @@ protected Marshaller createMarshaller(Dsn dsn) { //httpBinding. marshaller.addInterfaceBinding(HttpInterface.class, httpBinding); - // Set compression - marshaller.setCompression(!dsn.getOptions().containsKey(COMPRESSION_OPTION)); + // Enable compression unless the option is set to false + marshaller.setCompression(!FALSE.equalsIgnoreCase(dsn.getOptions().get(COMPRESSION_OPTION))); return marshaller; } From af4d2dbc73b4ee819957f5a38d20fc73cc315f32 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 11:59:16 +0200 Subject: [PATCH 0555/2152] Disable async connection during integration tests --- raven-log4j/src/test/resources/log4j.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j/src/test/resources/log4j.properties b/raven-log4j/src/test/resources/log4j.properties index 0e03b7b0214..58b657a5211 100644 --- a/raven-log4j/src/test/resources/log4j.properties +++ b/raven-log4j/src/test/resources/log4j.properties @@ -3,5 +3,5 @@ log4j.rootLogger=DEBUG, ConsoleAppender, SentryAppender log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender -log4j.appender.SentryAppender.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1 +log4j.appender.SentryAppender.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false log4j.appender.ConsoleAppender.layout=org.apache.log4j.SimpleLayout From 74ed6f9a6baf5a26b44e2b027d714adf1e6149fc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 16:56:13 +0200 Subject: [PATCH 0556/2152] Call parent method to start Appenders (log4j/log4j2) --- .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 2 ++ .../main/java/net/kencochrane/raven/log4j2/SentryAppender.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 97df99030f4..f5522cb62ac 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -62,6 +62,8 @@ private static StackTraceElement asStackTraceElement(LocationInfo location) { @Override public void activateOptions() { + super.activateOptions(); + if (raven == null) { if (dsn == null) dsn = Dsn.dsnLookup(); diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 3af4cd57f5f..e494461f863 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -116,6 +116,8 @@ private static String formatCulprit(StackTraceElement stackTraceElement) { @Override public void start() { + super.start(); + if (raven == null) { if (dsn == null) dsn = Dsn.dsnLookup(); From 0f5785fd88c3249c4baf8aa16562407aac64e643 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 16:57:51 +0200 Subject: [PATCH 0557/2152] Improve logs while trying to get the DSN from JNDI --- raven/src/main/java/net/kencochrane/raven/Dsn.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Dsn.java b/raven/src/main/java/net/kencochrane/raven/Dsn.java index d306362b701..051160b58ea 100644 --- a/raven/src/main/java/net/kencochrane/raven/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/Dsn.java @@ -89,11 +89,11 @@ public static String dsnLookup() { Context c = new InitialContext(); dsn = (String) c.lookup(JNDI_DSN_NAME); } catch (NoInitialContextException e) { - logger.log(Level.INFO, "JNDI not configured for sentry (NoInitialContextEx)"); + logger.log(Level.FINE, "JNDI not configured for sentry (NoInitialContextEx)"); } catch (NamingException e) { - logger.log(Level.INFO, "No /sentry/dsn in JNDI"); + logger.log(Level.FINE, "No /sentry/dsn in JNDI"); } catch (RuntimeException ex) { - logger.log(Level.INFO, "Odd RuntimeException while testing for JNDI: " + ex.getMessage()); + logger.log(Level.WARNING, "Odd RuntimeException while testing for JNDI", ex); } // Try to obtain the DSN from a System Environment Variable From 71d561a811dbb018a0385048c779a715ba634f5f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 16:58:22 +0200 Subject: [PATCH 0558/2152] Do not obtain a Raven instance safely Obtaining a raven instance should fail if the DSN isn't valid or something goes wrong, it's the user's responsibility to handle that. --- .../java/net/kencochrane/raven/RavenFactory.java | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index 1e257c5879b..15a6e3660fb 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -11,12 +11,11 @@ *

    */ public abstract class RavenFactory { - private static final Logger logger = Logger.getLogger(RavenFactory.class.getCanonicalName()); private static final ServiceLoader RAVEN_FACTORIES = ServiceLoader.load(RavenFactory.class); public static Raven ravenInstance(Dsn dsn) { for (RavenFactory ravenFactory : RAVEN_FACTORIES) { - Raven raven = getRavenSafely(dsn, ravenFactory); + Raven raven = ravenFactory.createRavenInstance(dsn); if (raven != null) { return raven; } @@ -33,7 +32,7 @@ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { if (!ravenFactoryName.equals(ravenFactory.getClass().getCanonicalName())) continue; - Raven raven = getRavenSafely(dsn, ravenFactory); + Raven raven = ravenFactory.createRavenInstance(dsn); if (raven != null) { return raven; } @@ -42,16 +41,5 @@ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { throw new IllegalStateException("Couldn't create a raven instance for '" + dsn + "'"); } - private static Raven getRavenSafely(Dsn dsn, RavenFactory ravenFactory) { - Raven raven = null; - try { - raven = ravenFactory.createRavenInstance(dsn); - } catch (Exception e) { - logger.log(Level.WARNING, "An exception occurred during the creation of a Raven instance with " - + "'" + ravenFactory + "' using the DSN '" + dsn + "'", e); - } - return raven; - } - public abstract Raven createRavenInstance(Dsn dsn); } From 065d1a5078bfc94b9699e350901940b34b74ff18 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 17:04:18 +0200 Subject: [PATCH 0559/2152] Handle potential exception from the RavenFactory --- .../raven/log4j/SentryAppender.java | 18 +++++++++++------- .../raven/log4j2/SentryAppender.java | 15 +++++++++------ .../raven/logback/SentryAppender.java | 15 +++++++++------ 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index f5522cb62ac..01582a000ef 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -62,13 +62,17 @@ private static StackTraceElement asStackTraceElement(LocationInfo location) { @Override public void activateOptions() { - super.activateOptions(); - - if (raven == null) { - if (dsn == null) - dsn = Dsn.dsnLookup(); - - raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + try { + if (raven == null) { + if (dsn == null) + dsn = Dsn.dsnLookup(); + + raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + } + super.activateOptions(); + } catch (Exception e) { + getErrorHandler().error("An exception occurred during the creation of a raven instance", e, + ErrorCode.FILE_OPEN_FAILURE); } } diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index e494461f863..fb13742c92a 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -116,13 +116,16 @@ private static String formatCulprit(StackTraceElement stackTraceElement) { @Override public void start() { - super.start(); - - if (raven == null) { - if (dsn == null) - dsn = Dsn.dsnLookup(); + try { + if (raven == null) { + if (dsn == null) + dsn = Dsn.dsnLookup(); - raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + } + super.start(); + } catch (Exception e) { + error("An exception occurred during the creation of a raven instance", e); } } diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index a929cbcbcc8..4470603a132 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -62,13 +62,16 @@ private static Event.Level formatLevel(ILoggingEvent iLoggingEvent) { @Override public void start() { - super.start(); - - if (raven == null) { - if (dsn == null) - dsn = Dsn.dsnLookup(); + try { + if (raven == null) { + if (dsn == null) + dsn = Dsn.dsnLookup(); - raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + } + super.start(); + } catch (Exception e) { + addError("An exception occurred during the creation of a raven instance", e); } } From ed260ffbd84a144c6a15df9cc1e080fad413176a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 17:04:55 +0200 Subject: [PATCH 0560/2152] Start the SentryHandler properly Ensure that the SentryHandler creates one instance of Raven only and doesn't try to send logs to itself. --- .../kencochrane/raven/jul/SentryHandler.java | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 2c5bc68dce9..5160ef26736 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.ErrorManager; import java.util.logging.Handler; import java.util.logging.Level; @@ -25,6 +26,7 @@ public class SentryHandler extends Handler { private Raven raven; private String dsn; private String ravenFactory; + private ReentrantLock startLock = new ReentrantLock(); public SentryHandler() { propagateClose = true; @@ -64,6 +66,20 @@ public void publish(LogRecord record) { return; } + if (raven == null) { + // Prevent recursive start + if (startLock.isHeldByCurrentThread()) { + return; + } + + try { + start(); + } catch (Exception e) { + reportError("An exception occurred while creating an instance of raven", e, ErrorManager.OPEN_FAILURE); + return; + } + } + EventBuilder eventBuilder = new EventBuilder() .setLevel(getLevel(record.getLevel())) .setTimestamp(new Date(record.getMillis())) @@ -88,19 +104,25 @@ public void publish(LogRecord record) { else eventBuilder.setMessage(record.getMessage()); - getRaven().runBuilderHelpers(eventBuilder); + raven.runBuilderHelpers(eventBuilder); - getRaven().sendEvent(eventBuilder.build()); + raven.sendEvent(eventBuilder.build()); } - private Raven getRaven() { - if (raven == null) { + private void start() { + // Attempt to start raven + startLock.lock(); + try { + if (raven != null) + return; + if (dsn == null) dsn = Dsn.dsnLookup(); raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + } finally { + startLock.unlock(); } - return raven; } public void setDsn(String dsn) { From e06ea0f3fc52cf6d1fc9f4dcbde5d0790004b207 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 17:06:39 +0200 Subject: [PATCH 0561/2152] Load the JUL config file during integration tests --- raven/pom.xml | 7 +++++++ raven/src/test/resources/logging.properties | 10 ++++++++++ 2 files changed, 17 insertions(+) create mode 100644 raven/src/test/resources/logging.properties diff --git a/raven/pom.xml b/raven/pom.xml index dcdd4472da8..582622708d4 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -73,6 +73,13 @@ org.apache.maven.plugins maven-failsafe-plugin + + + + ${project.basedir}/src/test/resources/logging.properties + + + org.apache.maven.plugins diff --git a/raven/src/test/resources/logging.properties b/raven/src/test/resources/logging.properties new file mode 100644 index 00000000000..c6ddf59420e --- /dev/null +++ b/raven/src/test/resources/logging.properties @@ -0,0 +1,10 @@ +handlers=java.util.logging.ConsoleHandler +level = INFO + +net.kencochrane.raven.handlers=java.util.logging.ConsoleHandler, net.kencochrane.raven.jul.SentryHandler +net.kencochrane.raven.level = ALL + +net.kencochrane.raven.SentryStub.handlers=java.util.logging.ConsoleHandler +net.kencochrane.raven.SentryStub.level = INFO + +net.kencochrane.raven.jul.SentryHandler.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false From 653dfb228563a922b05ac2c9e47867e1b4e2f035 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 18:20:36 +0200 Subject: [PATCH 0562/2152] Rewrite the guard system for JUL to avoid recursive logging --- .../kencochrane/raven/jul/SentryHandler.java | 67 ++++++++----------- 1 file changed, 27 insertions(+), 40 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 5160ef26736..a2c35bb8dea 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -12,11 +12,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.concurrent.locks.ReentrantLock; -import java.util.logging.ErrorManager; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.LogRecord; +import java.util.logging.*; /** * Logging handler in charge of sending the java.util.logging records to a Sentry server. @@ -24,9 +20,7 @@ public class SentryHandler extends Handler { private final boolean propagateClose; private Raven raven; - private String dsn; - private String ravenFactory; - private ReentrantLock startLock = new ReentrantLock(); + private boolean guard = false; public SentryHandler() { propagateClose = true; @@ -61,25 +55,29 @@ private static List formatParameters(Object[] parameters) { } @Override - public void publish(LogRecord record) { - if (!isLoggable(record)) { + public synchronized void publish(LogRecord record) { + if (!isLoggable(record) || guard) { return; } - if (raven == null) { - // Prevent recursive start - if (startLock.isHeldByCurrentThread()) { - return; + guard = true; + try { + if (raven == null) { + try { + start(); + } catch (Exception e) { + reportError("An exception occurred while creating an instance of raven", e, ErrorManager.OPEN_FAILURE); + return; + } } - try { - start(); - } catch (Exception e) { - reportError("An exception occurred while creating an instance of raven", e, ErrorManager.OPEN_FAILURE); - return; - } + raven.sendEvent(buildEvent(record)); + } finally { + guard = false; } + } + private Event buildEvent(LogRecord record) { EventBuilder eventBuilder = new EventBuilder() .setLevel(getLevel(record.getLevel())) .setTimestamp(new Date(record.getMillis())) @@ -105,32 +103,21 @@ public void publish(LogRecord record) { eventBuilder.setMessage(record.getMessage()); raven.runBuilderHelpers(eventBuilder); - - raven.sendEvent(eventBuilder.build()); + return eventBuilder.build(); } private void start() { - // Attempt to start raven - startLock.lock(); - try { - if (raven != null) - return; - - if (dsn == null) - dsn = Dsn.dsnLookup(); + if (raven != null) + return; - raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); - } finally { - startLock.unlock(); - } - } + LogManager manager = LogManager.getLogManager(); + String dsn = manager.getProperty(this.getClass().getCanonicalName() + ".dsn"); + String ravenFactory = manager.getProperty(this.getClass().getCanonicalName() + ".ravenFactory"); - public void setDsn(String dsn) { - this.dsn = dsn; - } + if (dsn == null) + dsn = Dsn.dsnLookup(); - public void setRavenFactory(String ravenFactory) { - this.ravenFactory = ravenFactory; + raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); } @Override From bcc1a87c7c294eacfc8e7f35f169d2507e0f517d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 18:21:17 +0200 Subject: [PATCH 0563/2152] Add integration tests for JUL --- .../raven/jul/SentryHandlerIT.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java new file mode 100644 index 00000000000..5be37c23ec1 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java @@ -0,0 +1,33 @@ +package net.kencochrane.raven.jul; + +import net.kencochrane.raven.SentryStub; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.logging.Logger; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SentryHandlerIT { + private static final Logger logger = Logger.getLogger(SentryHandlerIT.class.getCanonicalName()); + private SentryStub sentryStub; + + @Before + public void setUp() { + sentryStub = new SentryStub(); + } + + @After + public void tearDown() { + sentryStub.removeEvents(); + } + + @Test + public void testInfoLog() { + assertThat(sentryStub.getEventCount(), is(0)); + logger.info("This is a test"); + assertThat(sentryStub.getEventCount(), is(1)); + } +} From fb68283018f965aed4a61a00dc63e2b8ad911233 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 18:21:35 +0200 Subject: [PATCH 0564/2152] Change loglevel to ALL in log4j integraiton tests --- raven-log4j/src/test/resources/log4j.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j/src/test/resources/log4j.properties b/raven-log4j/src/test/resources/log4j.properties index 58b657a5211..c925dfeff3a 100644 --- a/raven-log4j/src/test/resources/log4j.properties +++ b/raven-log4j/src/test/resources/log4j.properties @@ -1,4 +1,4 @@ -log4j.rootLogger=DEBUG, ConsoleAppender, SentryAppender +log4j.rootLogger=ALL, ConsoleAppender, SentryAppender log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender From 995791210672b27d339fadba943829433380269c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 18:42:57 +0200 Subject: [PATCH 0565/2152] Move the event building in a dedicated method in log4j --- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 01582a000ef..a9bb8e25b56 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -78,6 +78,11 @@ public void activateOptions() { @Override protected void append(LoggingEvent loggingEvent) { + Event event = buildEvent(loggingEvent); + raven.sendEvent(event); + } + + private Event buildEvent(LoggingEvent loggingEvent) { EventBuilder eventBuilder = new EventBuilder() .setTimestamp(new Date(loggingEvent.getTimeStamp())) .setMessage(loggingEvent.getRenderedMessage()) @@ -107,8 +112,7 @@ protected void append(LoggingEvent loggingEvent) { eventBuilder.addExtra(mdcEntry.getKey().toString(), mdcEntry.getValue()); raven.runBuilderHelpers(eventBuilder); - - raven.sendEvent(eventBuilder.build()); + return eventBuilder.build(); } public void setDsn(String dsn) { From d58a40eafcbfd928ccae7444429ecc17ec9c8801 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 18:48:05 +0200 Subject: [PATCH 0566/2152] Add non-transitive dependencies from the test-jar --- raven-logback/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index bd858d9e395..6b2a55cb301 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -34,6 +34,12 @@ logback-classic + + + com.fasterxml.jackson.core + jackson-databind + test + org.mockito mockito-core From 22fadc7d2f2d1b52088119be557e4d58c36d6eb6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 18:48:26 +0200 Subject: [PATCH 0567/2152] Move the event creation in a dedicated method --- .../java/net/kencochrane/raven/logback/SentryAppender.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 4470603a132..99570d6074a 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -77,6 +77,11 @@ public void start() { @Override protected void append(ILoggingEvent iLoggingEvent) { + Event event = buildEvent(iLoggingEvent); + raven.sendEvent(event); + } + + private Event buildEvent(ILoggingEvent iLoggingEvent) { EventBuilder eventBuilder = new EventBuilder() .setTimestamp(new Date(iLoggingEvent.getTimeStamp())) .setMessage(iLoggingEvent.getFormattedMessage()) @@ -109,7 +114,7 @@ protected void append(ILoggingEvent iLoggingEvent) { raven.runBuilderHelpers(eventBuilder); - raven.sendEvent(eventBuilder.build()); + return eventBuilder.build(); } private String getEventPosition(ILoggingEvent iLoggingEvent) { From 0575eaca666af3fd2a2b408cf591279e39bebf4d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 18:48:46 +0200 Subject: [PATCH 0568/2152] Add integration tests for logback --- .../raven/logback/SentryAppenderIT.java | 33 +++++++++++++++++++ raven-logback/src/test/resources/logback.xml | 11 +++++++ 2 files changed, 44 insertions(+) create mode 100644 raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java create mode 100644 raven-logback/src/test/resources/logback.xml diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java new file mode 100644 index 00000000000..5028e8a248c --- /dev/null +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java @@ -0,0 +1,33 @@ +package net.kencochrane.raven.logback; + +import net.kencochrane.raven.SentryStub; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SentryAppenderIT { + private static final Logger logger = LoggerFactory.getLogger(SentryAppenderIT.class); + private SentryStub sentryStub; + + @Before + public void setUp() { + sentryStub = new SentryStub(); + } + + @After + public void tearDown() { + sentryStub.removeEvents(); + } + + @Test + public void testInfoLog() { + assertThat(sentryStub.getEventCount(), is(0)); + logger.info("This is a test"); + assertThat(sentryStub.getEventCount(), is(1)); + } +} diff --git a/raven-logback/src/test/resources/logback.xml b/raven-logback/src/test/resources/logback.xml new file mode 100644 index 00000000000..194d49e1499 --- /dev/null +++ b/raven-logback/src/test/resources/logback.xml @@ -0,0 +1,11 @@ + + + + http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false + + + + + + + From 606f28bb5ebf3f10ef7ec9c0b71b1bbdbbb9d661 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 22:27:17 +0200 Subject: [PATCH 0569/2152] Add transitive dependencies from test-jar --- raven-log4j2/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 35665a346c0..dc6e5635af4 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -34,6 +34,12 @@ test-jar test + + + com.fasterxml.jackson.core + jackson-databind + test + org.mockito mockito-core From 632bdc12d42a58e6ab9793d4fa4b9e78cd2c94c6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 22:29:38 +0200 Subject: [PATCH 0570/2152] Remove notions of layout in SentryAPpender --- .../raven/log4j2/SentryAppender.java | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index fb13742c92a..9ee904fc94e 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -9,14 +9,12 @@ import net.kencochrane.raven.event.interfaces.MessageInterface; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginAttr; import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.layout.PatternLayout; import org.apache.logging.log4j.message.Message; import java.io.IOException; @@ -41,7 +39,7 @@ public class SentryAppender extends AbstractAppender { private String ravenFactory; public SentryAppender() { - this(APPENDER_NAME, PatternLayout.createLayout(null, null, null, null), null, true); + this(APPENDER_NAME, null, true); } @@ -50,12 +48,12 @@ public SentryAppender(Raven raven) { } public SentryAppender(Raven raven, boolean propagateClose) { - this(APPENDER_NAME, PatternLayout.createLayout(null, null, null, null), null, propagateClose); + this(APPENDER_NAME, null, propagateClose); this.raven = raven; } - private SentryAppender(String name, Layout layout, Filter filter, boolean propagateClose) { - super(name, filter, layout, true); + private SentryAppender(String name, Filter filter, boolean propagateClose) { + super(name, filter, null, true); this.propagateClose = propagateClose; } @@ -65,8 +63,6 @@ private SentryAppender(String name, Layout layout, Filter filter, boolea * @param name The name of the Appender. * @param dsn Data Source Name to access the Sentry server. * @param ravenFactory name of the factory to use to build the {@link Raven} instance. - * @param layout The layout to use to format the event. If no layout is provided the default PatternLayout - * will be used. * @param filter The filter, if any, to use. * @return The SentryAppender. */ @@ -74,7 +70,6 @@ private SentryAppender(String name, Layout layout, Filter filter, boolea public static SentryAppender createAppender(@PluginAttr("name") final String name, @PluginAttr("dsn") final String dsn, @PluginAttr("ravenFactory") final String ravenFactory, - @PluginElement("layout") Layout layout, @PluginElement("filters") final Filter filter) { if (name == null) { @@ -82,10 +77,7 @@ public static SentryAppender createAppender(@PluginAttr("name") final String nam return null; } - if (layout == null) { - layout = PatternLayout.createLayout(null, null, null, null); - } - SentryAppender sentryAppender = new SentryAppender(name, layout, filter, true); + SentryAppender sentryAppender = new SentryAppender(name, filter, true); sentryAppender.setDsn(dsn); sentryAppender.setRavenFactory(ravenFactory); return sentryAppender; From 15878e8302530c38530d95d9210c6138bf0a746e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 22:30:36 +0200 Subject: [PATCH 0571/2152] Rename the plugin into Raven and make it a part of Core --- .../main/java/net/kencochrane/raven/log4j2/SentryAppender.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 9ee904fc94e..5107360dab8 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -26,7 +26,7 @@ /** * Appender for log4j2 in charge of sending the logged events to a Sentry server. */ -@Plugin(name = "Sentry", type = "Sentry", elementType = "appender") +@Plugin(name = "Raven", type = "Core", elementType = "appender") public class SentryAppender extends AbstractAppender { /** * Default name for the appender. From 685663d7e365da4fe73d3973d2653c81572913ec Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 22:31:29 +0200 Subject: [PATCH 0572/2152] Fix typo --- .../main/java/net/kencochrane/raven/log4j2/SentryAppender.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 5107360dab8..27a54128c15 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -62,7 +62,7 @@ private SentryAppender(String name, Filter filter, boolean propagateClose) { * * @param name The name of the Appender. * @param dsn Data Source Name to access the Sentry server. - * @param ravenFactory name of the factory to use to build the {@link Raven} instance. + * @param ravenFactory Name of the factory to use to build the {@link Raven} instance. * @param filter The filter, if any, to use. * @return The SentryAppender. */ From 61b9545db2c1d27a96b55fc72b7a5f32e0c42478 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 22:31:41 +0200 Subject: [PATCH 0573/2152] Change the name of the class in error messages --- .../main/java/net/kencochrane/raven/log4j2/SentryAppender.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 27a54128c15..463a37fbe53 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -73,7 +73,7 @@ public static SentryAppender createAppender(@PluginAttr("name") final String nam @PluginElement("filters") final Filter filter) { if (name == null) { - LOGGER.error("No name provided for FileAppender"); + LOGGER.error("No name provided for SentryAppender"); return null; } From 1b5c4c6fc156ae3e856b3c4897ea933417d12ceb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 22:31:58 +0200 Subject: [PATCH 0574/2152] Move event building in a dedicated method --- .../net/kencochrane/raven/log4j2/SentryAppender.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 463a37fbe53..15355e6f34c 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -122,7 +122,12 @@ public void start() { } @Override - public void append(LogEvent event) { + public void append(LogEvent logEvent) { + Event event = buildEvent(logEvent); + raven.sendEvent(event); + } + + private Event buildEvent(LogEvent event) { Message eventMessage = event.getMessage(); EventBuilder eventBuilder = new EventBuilder() .setTimestamp(new Date(event.getMillis())) @@ -163,7 +168,7 @@ public void append(LogEvent event) { raven.runBuilderHelpers(eventBuilder); - raven.sendEvent(eventBuilder.build()); + return eventBuilder.build(); } private List formatMessageParameters(Object[] parameters) { From ae6c08a5894afd8514c9bebed107c7e2e5357f0c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 22:32:15 +0200 Subject: [PATCH 0575/2152] Allow JsonMarshaller to handle arrays and iterables By default the JsonGenerator.writeObject method only handle primitives not arrays or iterables. This add a light support for those objects --- .../raven/marshaller/json/JsonMarshaller.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 3156b821a2b..50b7ae27a83 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -148,7 +148,19 @@ private void writeInterfaces(JsonGenerator generator, Map extras) throws IOException { generator.writeObjectFieldStart(EXTRA); for (Map.Entry extra : extras.entrySet()) { - generator.writeObjectField(extra.getKey(), extra.getValue()); + Object value = extra.getValue(); + if (value.getClass().isArray()) { + value = Arrays.asList((Object[]) value); + } + if (value instanceof Iterable) { + generator.writeArrayFieldStart(extra.getKey()); + for (Object subValue : (Iterable) value) { + generator.writeObject(subValue); + } + generator.writeEndArray(); + } else { + generator.writeObjectField(extra.getKey(), extra.getValue()); + } } generator.writeEndObject(); } From 62e4db9a87381c720a4afe94ab4a8603b567e4b4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 5 May 2013 22:33:14 +0200 Subject: [PATCH 0576/2152] Add integration tests for log4j2 --- .../raven/log4j2/SentryAppenderIT.java | 33 +++++++++++++++++++ raven-log4j2/src/test/resources/log4j2.xml | 17 ++++++++++ 2 files changed, 50 insertions(+) create mode 100644 raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java create mode 100644 raven-log4j2/src/test/resources/log4j2.xml diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java new file mode 100644 index 00000000000..7b367470885 --- /dev/null +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java @@ -0,0 +1,33 @@ +package net.kencochrane.raven.log4j2; + +import net.kencochrane.raven.SentryStub; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SentryAppenderIT { + private static final Logger logger = LogManager.getLogger(SentryAppenderIT.class); + private SentryStub sentryStub; + + @Before + public void setUp() { + sentryStub = new SentryStub(); + } + + @After + public void tearDown() { + sentryStub.removeEvents(); + } + + @Test + public void testInfoLog() { + assertThat(sentryStub.getEventCount(), is(0)); + logger.info("This is a test"); + assertThat(sentryStub.getEventCount(), is(1)); + } +} diff --git a/raven-log4j2/src/test/resources/log4j2.xml b/raven-log4j2/src/test/resources/log4j2.xml new file mode 100644 index 00000000000..eaf1a2abb4c --- /dev/null +++ b/raven-log4j2/src/test/resources/log4j2.xml @@ -0,0 +1,17 @@ + + + + + + + http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false + + + + + + + + + + From bfc5e470201a828aa04b101927a52bd35c320a3e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 6 May 2013 01:00:11 +0200 Subject: [PATCH 0577/2152] Fix indentation --- raven/src/test/resources/logging.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/raven/src/test/resources/logging.properties b/raven/src/test/resources/logging.properties index c6ddf59420e..58f91b76dd6 100644 --- a/raven/src/test/resources/logging.properties +++ b/raven/src/test/resources/logging.properties @@ -1,10 +1,10 @@ handlers=java.util.logging.ConsoleHandler -level = INFO +level=INFO net.kencochrane.raven.handlers=java.util.logging.ConsoleHandler, net.kencochrane.raven.jul.SentryHandler -net.kencochrane.raven.level = ALL +net.kencochrane.raven.level=ALL net.kencochrane.raven.SentryStub.handlers=java.util.logging.ConsoleHandler -net.kencochrane.raven.SentryStub.level = INFO +net.kencochrane.raven.SentryStub.level=INFO net.kencochrane.raven.jul.SentryHandler.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false From 8fb5d3157138981f8a19c5873bf68690c851749a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 6 May 2013 01:00:18 +0200 Subject: [PATCH 0578/2152] Add documentation for log4j2 logback and JUL --- INSTALL.md | 46 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 00a6fed0033..a7623e545db 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -78,27 +78,61 @@ The client will lookup for the first DSN configuration provided: } ### Using `java.util.logging` -TODO +To use the `SentryHandler` with `java.util.loggin` use this `logging.properties` + + level=INFO + handlers=net.kencochrane.raven.jul.SentryHandler + net.kencochrane.raven.jul.SentryHandler.dsn=http://publicKey:secretKey@host:port/1?options + ### Using log4j To use the `SentryAppender` with log4j use this configuration: log4j.rootLogger=DEBUG, SentryAppender log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender - log4j.appender.SentryAppender.dsn=http://publicKey:secretKey@host:port/projectId?options + log4j.appender.SentryAppender.dsn=http://publicKey:secretKey@host:port/1?options #### Asynchronous logging with AsyncAppender It is not recommended to attempt to set up a `SentryAppender` with an [AsyncAppender](http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/AsyncAppender.html). While this is a common solution to avoid blocking the current thread until the -event is sent to Sentry, it is recommended to use instead the option -`raven.async` to enable asynchronous logging for raven. +event is sent to Sentry, it is recommended to rely instead on the asynchronous +connection within Raven. ### Using log4j2 -**TODO** +To use the `SentryAppender` with log4j2 use this configuration: + + + + + + + http://publicKey:secretKey@host:port/1?options + + + + + + + + + + ### Using logback -**TODO** +To use the `SentryAppender` with logback use this configuration: + + + + + http://publicKey:secretKey@host:port/1?options + + + + + + + ### Capturing the HTTP environment **TODO** From 478e47dc177d81ed011ce80695cd99f3b911850d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 6 May 2013 01:06:21 +0200 Subject: [PATCH 0579/2152] Fix configuration indentation --- INSTALL.md | 9 +++++---- raven-log4j2/src/test/resources/log4j2.xml | 7 ++++--- raven-logback/src/test/resources/logback.xml | 10 ++++++---- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index a7623e545db..6706bf07d47 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -106,11 +106,12 @@ To use the `SentryAppender` with log4j2 use this configuration: - + http://publicKey:secretKey@host:port/1?options - + + @@ -126,11 +127,11 @@ To use the `SentryAppender` with logback use this configuration: http://publicKey:secretKey@host:port/1?options - + - + diff --git a/raven-log4j2/src/test/resources/log4j2.xml b/raven-log4j2/src/test/resources/log4j2.xml index eaf1a2abb4c..99e0e8d8adb 100644 --- a/raven-log4j2/src/test/resources/log4j2.xml +++ b/raven-log4j2/src/test/resources/log4j2.xml @@ -1,13 +1,14 @@ - + - + http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false - + + diff --git a/raven-logback/src/test/resources/logback.xml b/raven-logback/src/test/resources/logback.xml index 194d49e1499..82c33e22a6b 100644 --- a/raven-logback/src/test/resources/logback.xml +++ b/raven-logback/src/test/resources/logback.xml @@ -1,11 +1,13 @@ - + - http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false + + http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false + - - + + From 3742f4ade8b3b6d36e10c88e790c25ecc473598c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 6 May 2013 01:07:13 +0200 Subject: [PATCH 0580/2152] Fix indentation and imports --- raven/src/main/java/net/kencochrane/raven/RavenFactory.java | 2 -- .../test/java/net/kencochrane/raven/event/EventBuilderTest.java | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index 15a6e3660fb..eadee9df07b 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -1,8 +1,6 @@ package net.kencochrane.raven; import java.util.ServiceLoader; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Factory in charge of creating {@link Raven} instances. diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index deb905300a4..a80be5fb981 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -72,7 +72,7 @@ public void testEventParameters() throws Exception { } @Test - public void testUseStackFrameAsCulprit(){ + public void testUseStackFrameAsCulprit() { StackTraceElement frame1 = new StackTraceElement("class", "method", "file", 1); StackTraceElement frame2 = new StackTraceElement("class", "method", "file", -1); StackTraceElement frame3 = new StackTraceElement("class", "method", null, 1); From e05d24d40cb0719253e528ccd0d997deca841074 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 6 May 2013 01:12:17 +0200 Subject: [PATCH 0581/2152] Only support sentry protocol V4 --- .../net/kencochrane/raven/sentrystub/auth/AuthValidator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java index eb258a56fa0..bb6afd54f30 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java @@ -13,7 +13,7 @@ */ public class AuthValidator { private static final Logger logger = Logger.getLogger(AuthValidator.class.getCanonicalName()); - private static final Collection SENTRY_PROTOCOL_VERSIONS = Arrays.asList("3", "4"); + private static final Collection SENTRY_PROTOCOL_VERSIONS = Arrays.asList("4"); private static final String SENTRY_VERSION_PARAMETER = "Sentry sentry_version"; private static final String PUBLIC_KEY_PARAMETER = "sentry_key"; private static final String SECRET_KEY_PARAMETER = "sentry_secret"; From 888ff0369c9322f0a7eca44d83518b1139c302f2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 7 May 2013 11:56:07 +0200 Subject: [PATCH 0582/2152] Fix typo --- INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index 6706bf07d47..b9f2d281dfe 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -8,7 +8,7 @@ Currently there are 6 modules in the project: to the new API as legacy will be removed in the future releases of Raven) - `raven-log4j`, Appender for log4j - `raven-log4j2`, Appender for log4j2 - - `raven-logback`, Appander for Logback + - `raven-logback`, Appender for Logback - `sentry-stub`, Sentry server stub, allowing to test the protocol ### Build From ddcd8f2785a5e2594ff274eece894785e7f66e72 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 7 May 2013 11:56:17 +0200 Subject: [PATCH 0583/2152] Add TODO --- INSTALL.md | 1 + 1 file changed, 1 insertion(+) diff --git a/INSTALL.md b/INSTALL.md index b9f2d281dfe..b6d6eab12a8 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -36,6 +36,7 @@ You can add the other modules the same way (replacing the `artifactId`) with the name of the module. ### Manual installation +TODO ## Using Raven From 3dde0a6dc863639ffcadf6a5f33412671d2b0fd1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 7 May 2013 12:21:15 +0200 Subject: [PATCH 0584/2152] Add explanations for the snapshot repository --- INSTALL.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index b6d6eab12a8..423f4fa57dd 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -24,7 +24,7 @@ jetty, it is currently not possible to run the integration tests from the main project. They have to be run manually on each module independently._ ### Maven dependency -To add raven as a dependency, simply add this to your pom.xml: +To add raven as a dependency, simply add this to the pom.xml file: net.kencochrane.raven @@ -32,9 +32,24 @@ To add raven as a dependency, simply add this to your pom.xml: 4.0-SNAPSHOT -You can add the other modules the same way (replacing the `artifactId`) with the +Other modules can be added the same way (replacing the `artifactId`) with the name of the module. +If the version is a snapshot it will be necessary to specify the +Sonatype Nexus snapshot repository: + + + sonatype-nexus-snapshots + Sonatype Nexus Snapshots + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + ### Manual installation TODO From 7d4b072b2dac2c69b8d7844b07aeea4f5fda0fc9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 7 May 2013 12:24:46 +0200 Subject: [PATCH 0585/2152] Remove details on the raven-legacy project --- INSTALL.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 423f4fa57dd..0b2b1819cc0 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -4,8 +4,6 @@ Currently there are 6 modules in the project: - `raven`, the core of the project, providing the client and support for JUL - - `raven-legacy`, support of the Raven-2.0 API (it's recommended to move - to the new API as legacy will be removed in the future releases of Raven) - `raven-log4j`, Appender for log4j - `raven-log4j2`, Appender for log4j2 - `raven-logback`, Appender for Logback From d012d3fda39ff48d3069f035853cbadf6055c6be Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 7 May 2013 12:24:59 +0200 Subject: [PATCH 0586/2152] Remove details on the jetty issue --- INSTALL.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 0b2b1819cc0..58bd9b0ddff 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -17,10 +17,6 @@ sources. $ cd raven-java $ mvn clean install -DskipTests=true -_Due to a [known issue](https://bugs.eclipse.org/bugs/show_bug.cgi?id=405631) in -jetty, it is currently not possible to run the integration tests from the main -project. They have to be run manually on each module independently._ - ### Maven dependency To add raven as a dependency, simply add this to the pom.xml file: From 410cd9dca20527a109d3abcf8ad25f69c777a0da Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 7 May 2013 12:29:13 +0200 Subject: [PATCH 0587/2152] Document the default enabled async mode --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 72a584ef0ff..dc5ffb567a6 100644 --- a/README.md +++ b/README.md @@ -90,13 +90,12 @@ option is enabled. ### Async connection In order to avoid performance issues due to a large amount of logs being -generated or a slow connection to the Sentry server, it is recommended to use -the asynchronous connection which will use a low priority thread pool to submit -events to Sentry. +generated or a slow connection to the Sentry server, an asynchronous connection +is set up, using a low priority thread pool to submit events to Sentry. -To enable the async mode, add the `raven.async` option to your DSN: +To disable the async mode, add `raven.async=false` to your DSN: - http://public:private@host:port/1?raven.async + http://public:private@host:port/1?raven.async=false #### Queue size (advanced) The default queue used to store the not yet processed events doesn't have a @@ -106,7 +105,7 @@ able to control the size of that queue to avoid memory issues. It is possible to set a maximum with the option `raven.async.queuesize`: - http://public:private@host:port/1?raven.async&raven.async.queuesize=100 + http://public:private@host:port/1?raven.async.queuesize=100 This means that if the connection to the Sentry server is down, only the first 100 events will be stored and be processed as soon as the server is back up. @@ -118,7 +117,7 @@ processor available to the JVM (more threads wouldn't be useful). It's possible to manually set the number of threads (for example if you want only one thread) with the option `raven.async.threads`: - http://public:private@host:port/1?raven.async&raven.async.threads=1 + http://public:private@host:port/1?raven.async.threads=1 #### Threads priority (advanced) As in most cases sending logs to Sentry isn't as important as an application @@ -128,7 +127,7 @@ running smoothly, the threads have a It is possible to customise this value to increase the priority of those threads with the option `raven.async.priority`: - http://public:private@host:port/1?raven.async.priority=10&raven.async + http://public:private@host:port/1?raven.async.priority=10 ### Inapp classes Sentry differentiate `in_app` stack frames (which are directly related to your application) From b22fe10c85fe565f35b45086131a7a0e6896643d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 9 May 2013 14:43:11 +0200 Subject: [PATCH 0588/2152] Tags are not supposed to be a set of strings Each tag is supposed to have only one value as a String. If tags are sent as a map of arrays, the displayed value will be the "stringified" version of the array in Sentry. ie: [u'value'] --- .../net/kencochrane/raven/event/Event.java | 11 ++++++---- .../kencochrane/raven/event/EventBuilder.java | 20 +++++-------------- .../raven/marshaller/json/JsonMarshaller.java | 10 +++------- .../raven/event/EventBuilderTest.java | 11 +++++----- 4 files changed, 21 insertions(+), 31 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/Event.java b/raven/src/main/java/net/kencochrane/raven/event/Event.java index 94867c75d8d..f3731e44cba 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/Event.java +++ b/raven/src/main/java/net/kencochrane/raven/event/Event.java @@ -3,7 +3,10 @@ import net.kencochrane.raven.event.interfaces.SentryInterface; import java.io.Serializable; -import java.util.*; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; /** * Plain Old Java Object describing an event that will be sent to a Sentry instance. @@ -60,7 +63,7 @@ public class Event implements Serializable { * Automatically created with a Map that is made unmodifiable by the {@link EventBuilder}. *

    */ - private Map> tags = new HashMap>(); + private Map tags = new HashMap(); /** * Identifies the host client from which the event was recorded. */ @@ -147,11 +150,11 @@ void setCulprit(String culprit) { this.culprit = culprit; } - public Map> getTags() { + public Map getTags() { return tags; } - void setTags(Map> tags) { + void setTags(Map tags) { this.tags = tags; } diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index c2440b64954..ce410550285 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -4,7 +4,9 @@ import java.net.InetAddress; import java.net.UnknownHostException; -import java.util.*; +import java.util.Collections; +import java.util.Date; +import java.util.UUID; import java.util.zip.CRC32; import java.util.zip.Checksum; @@ -84,11 +86,7 @@ private static void autoSetMissingValues(Event event) { */ private static void makeImmutable(Event event) { // Make the tags unmodifiable - Map> unmodifiableTags = new HashMap>(event.getTags().size()); - for (Map.Entry> tag : event.getTags().entrySet()) { - unmodifiableTags.put(tag.getKey(), Collections.unmodifiableSet(tag.getValue())); - } - event.setTags(Collections.unmodifiableMap(unmodifiableTags)); + event.setTags(Collections.unmodifiableMap(event.getTags())); // Make the extra properties unmodifiable (everything in it is still mutable though) event.setExtra(Collections.unmodifiableMap(event.getExtra())); @@ -190,7 +188,6 @@ public EventBuilder setCulprit(String culprit) { /** * Adds a tag to an event. *

    - * Multiple calls to {@code addTag} allow to have more that one value for a single tag.
    * This allows to set a tag value in different contexts. *

    * @@ -198,15 +195,8 @@ public EventBuilder setCulprit(String culprit) { * @param tagValue value of the tag. * @return the current {@code EventBuilder} for chained calls. */ - //TODO: Check that the tag system works indeed this way. public EventBuilder addTag(String tagKey, String tagValue) { - Set tagValues = event.getTags().get(tagKey); - if (tagValues == null) { - tagValues = new HashSet(); - event.getTags().put(tagKey, tagValues); - } - tagValues.add(tagValue); - + event.getTags().put(tagKey, tagValue); return this; } diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 50b7ae27a83..fe51e4bfe6c 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -165,14 +165,10 @@ private void writeExtras(JsonGenerator generator, Map extras) th generator.writeEndObject(); } - private void writeTags(JsonGenerator generator, Map> tags) throws IOException { + private void writeTags(JsonGenerator generator, Map tags) throws IOException { generator.writeObjectFieldStart(TAGS); - for (Map.Entry> tag : tags.entrySet()) { - generator.writeArrayFieldStart(tag.getKey()); - for (String tagValue : tag.getValue()) { - generator.writeString(tagValue); - } - generator.writeEndArray(); + for (Map.Entry tag : tags.entrySet()) { + generator.writeStringField(tag.getKey(), tag.getValue()); } generator.writeEndObject(); } diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index a80be5fb981..ce95bf8eac0 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -5,7 +5,9 @@ import org.junit.Test; import java.net.InetAddress; -import java.util.*; +import java.util.Date; +import java.util.Map; +import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -106,13 +108,12 @@ public void testTagsAreImmutable() throws Exception { String tagKey = UUID.randomUUID().toString(); String tagValue = UUID.randomUUID().toString(); - Map> tags = eventBuilder.addTag(tagKey, tagValue).build().getTags(); + Map tags = eventBuilder.addTag(tagKey, tagValue).build().getTags(); assertThat(tags.size(), is(1)); - assertThat(tags.get(tagKey).size(), is(1)); - assertThat(tags.get(tagKey), contains(tagValue)); + assertThat(tags.get(tagKey), is(tagValue)); - tags.put(UUID.randomUUID().toString(), Collections.emptySet()); + tags.put(UUID.randomUUID().toString(), UUID.randomUUID().toString()); } @Test(expected = UnsupportedOperationException.class) From fc21596019afc73afbba848043483184d80cffc7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 16 May 2013 19:17:50 +0100 Subject: [PATCH 0589/2152] Move away from JUL for logging and rely on slf4j --- pom.xml | 6 +++++ raven/pom.xml | 4 ++++ .../raven/DefaultRavenFactory.java | 12 +++++----- .../main/java/net/kencochrane/raven/Dsn.java | 12 +++++----- .../java/net/kencochrane/raven/Raven.java | 12 +++++----- .../raven/connection/AbstractConnection.java | 14 ++++++------ .../raven/connection/AsyncConnection.java | 22 +++++++++---------- .../raven/connection/HttpConnection.java | 10 ++++----- .../json/ExceptionInterfaceBinding.java | 7 +++--- .../raven/marshaller/json/JsonMarshaller.java | 14 ++++++------ 10 files changed, 62 insertions(+), 51 deletions(-) diff --git a/pom.xml b/pom.xml index 735f74a7644..a415a6144f3 100644 --- a/pom.xml +++ b/pom.xml @@ -105,6 +105,7 @@ 6 + 1.7.5 1.8 2.2.0 3.0.1 @@ -130,6 +131,11 @@ test-jar
    + + org.slf4j + slf4j-api + ${slf4j.version} + commons-codec commons-codec diff --git a/raven/pom.xml b/raven/pom.xml index 582622708d4..e62f03815a4 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -15,6 +15,10 @@ Sentry client written in Java. + + org.slf4j + slf4j-api + commons-codec commons-codec diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 9589d1520ef..a459b23544d 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -11,13 +11,13 @@ import net.kencochrane.raven.event.interfaces.StackTraceInterface; import net.kencochrane.raven.marshaller.Marshaller; import net.kencochrane.raven.marshaller.json.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Arrays; import java.util.Collection; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Default implementation of {@link RavenFactory}. @@ -58,7 +58,7 @@ public class DefaultRavenFactory extends RavenFactory { * Option to hide common stackframes with enclosing exceptions. */ public static final String HIDE_COMMON_FRAMES_OPTION = "raven.stacktrace.hidecommon"; - private static final Logger logger = Logger.getLogger(DefaultRavenFactory.class.getCanonicalName()); + private static final Logger logger = LoggerFactory.getLogger(DefaultRavenFactory.class); private static final String FALSE = Boolean.FALSE.toString(); @Override @@ -70,7 +70,7 @@ public Raven createRavenInstance(Dsn dsn) { //TODO: Is it enough? Shouldn't it look for Servlet >= 3.0 ? raven.addBuilderHelper(new HttpEventBuilderHelper()); } catch (Exception e) { - logger.fine("It seems that the current environment doesn't provide access to servlets."); + logger.trace("It seems that the current environment doesn't provide access to servlets."); } return raven; } @@ -80,10 +80,10 @@ protected Connection createConnection(Dsn dsn) { Connection connection; if (protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https")) { - logger.log(Level.INFO, "Using an HTTP connection to Sentry."); + logger.info("Using an HTTP connection to Sentry."); connection = createHttpConnection(dsn); } else if (protocol.equalsIgnoreCase("udp")) { - logger.log(Level.INFO, "Using an UDP connection to Sentry."); + logger.info("Using an UDP connection to Sentry."); connection = createUdpConnection(dsn); } else { throw new IllegalStateException("Couldn't create a connection for the protocol '" + protocol + "'"); diff --git a/raven/src/main/java/net/kencochrane/raven/Dsn.java b/raven/src/main/java/net/kencochrane/raven/Dsn.java index 051160b58ea..cff0848bf30 100644 --- a/raven/src/main/java/net/kencochrane/raven/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/Dsn.java @@ -1,6 +1,8 @@ package net.kencochrane.raven; import net.kencochrane.raven.exception.InvalidDsnException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.naming.Context; import javax.naming.InitialContext; @@ -11,8 +13,6 @@ import java.net.URISyntaxException; import java.net.URLDecoder; import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Data Source name allowing a direct connection to a Sentry server. @@ -26,7 +26,7 @@ public class Dsn { * Lookup name for the DSN in JNDI. */ private static final String JNDI_DSN_NAME = "java:comp/env/sentry/dsn"; - private static final Logger logger = Logger.getLogger(Raven.class.getCanonicalName()); + private static final Logger logger = LoggerFactory.getLogger(Raven.class); private String secretKey; private String publicKey; private String projectId; @@ -89,11 +89,11 @@ public static String dsnLookup() { Context c = new InitialContext(); dsn = (String) c.lookup(JNDI_DSN_NAME); } catch (NoInitialContextException e) { - logger.log(Level.FINE, "JNDI not configured for sentry (NoInitialContextEx)"); + logger.trace("JNDI not configured for sentry (NoInitialContextEx)"); } catch (NamingException e) { - logger.log(Level.FINE, "No /sentry/dsn in JNDI"); + logger.trace("No /sentry/dsn in JNDI"); } catch (RuntimeException ex) { - logger.log(Level.WARNING, "Odd RuntimeException while testing for JNDI", ex); + logger.warn("Odd RuntimeException while testing for JNDI", ex); } // Try to obtain the DSN from a System Environment Variable diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index b2e439045ee..f2bf1a3214b 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -4,12 +4,12 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.helper.EventBuilderHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Collections; import java.util.HashSet; import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Raven is a client for Sentry allowing to send an {@link Event} that will be processed and sent to a Sentry server. @@ -24,7 +24,7 @@ public class Raven { * for each release of this project. */ public static final String NAME = "Raven-Java/4.0"; - private static final Logger logger = Logger.getLogger(Raven.class.getCanonicalName()); + private static final Logger logger = LoggerFactory.getLogger(Raven.class); private final Set builderHelpers = new HashSet(); private Connection connection; @@ -49,7 +49,7 @@ public void sendEvent(Event event) { try { connection.send(event); } catch (Exception e) { - logger.log(Level.SEVERE, "An exception occurred while sending the event to Sentry.", e); + logger.error("An exception occurred while sending the event to Sentry.", e); } } @@ -59,7 +59,7 @@ public void sendEvent(Event event) { * @param builderHelper builder helper to remove. */ public void removeBuilderHelper(EventBuilderHelper builderHelper) { - logger.log(Level.INFO, "Removes '" + builderHelper + "' to the list of builder helpers."); + logger.error("Removes '" + builderHelper + "' to the list of builder helpers."); builderHelpers.remove(builderHelper); } @@ -69,7 +69,7 @@ public void removeBuilderHelper(EventBuilderHelper builderHelper) { * @param builderHelper builder helper to add. */ public void addBuilderHelper(EventBuilderHelper builderHelper) { - logger.log(Level.INFO, "Adding '" + builderHelper + "' to the list of builder helpers."); + logger.error("Adding '" + builderHelper + "' to the list of builder helpers."); builderHelpers.add(builderHelper); } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index d65e28c6e8f..df6f0dc45c0 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -3,10 +3,10 @@ import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.exception.ConnectionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.concurrent.locks.ReentrantLock; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Abstract connection to a Sentry server. @@ -29,7 +29,7 @@ public abstract class AbstractConnection implements Connection { * Default base duration for a lockdown (10 milliseconds). */ public static final int DEFAULT_BASE_WAITING_TIME = 10; - private static final Logger logger = Logger.getLogger(AbstractConnection.class.getCanonicalName()); + private static final Logger logger = LoggerFactory.getLogger(AbstractConnection.class); private final String publicKey; private final String secretKey; private final ReentrantLock lock = new ReentrantLock(); @@ -82,7 +82,7 @@ public final void send(Event event) { } } catch (ConnectionException e) { lock.tryLock(); - logger.log(Level.WARNING, "An exception due to the connection occurred, a lockdown will be initiated.", e); + logger.warn("An exception due to the connection occurred, a lockdown will be initiated.", e); } finally { if (lock.isHeldByCurrentThread()) lockDown(); @@ -94,17 +94,17 @@ public final void send(Event event) { */ private void lockDown() { try { - logger.log(Level.WARNING, "Lockdown started for " + waitingTime + "ms."); + logger.warn("Lockdown started for " + waitingTime + "ms."); Thread.sleep(waitingTime); // Double the wait until the maximum is reached if (waitingTime > maxWaitingTime) waitingTime <<= 1; } catch (Exception e) { - logger.log(Level.SEVERE, "An exception occurred during the lockdown.", e); + logger.warn("An exception occurred during the lockdown.", e); } finally { lock.unlock(); - logger.log(Level.WARNING, "Lockdown ended."); + logger.warn("Lockdown ended."); } } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index c5668452f57..d47fe1da7f9 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -1,14 +1,14 @@ package net.kencochrane.raven.connection; import net.kencochrane.raven.event.Event; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Asynchronous usage of a connection. @@ -18,7 +18,7 @@ *

    */ public class AsyncConnection implements Connection { - private static final Logger logger = Logger.getLogger(AsyncConnection.class.getCanonicalName()); + private static final Logger logger = LoggerFactory.getLogger(AsyncConnection.class); /** * Timeout of the {@link #executorService}. */ @@ -71,7 +71,7 @@ public void run() { try { AsyncConnection.this.close(); } catch (IOException e) { - logger.log(Level.SEVERE, "An exception occurred while closing the connection.", e); + logger.error("An exception occurred while closing the connection.", e); } } }); @@ -99,19 +99,19 @@ public void send(Event event) { */ @Override public void close() throws IOException { - logger.log(Level.INFO, "Gracefully shutdown sentry threads."); + logger.info("Gracefully shutdown sentry threads."); executorService.shutdown(); try { if (!executorService.awaitTermination(SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS)) { - logger.log(Level.WARNING, "Graceful shutdown took too much time, forcing the shutdown."); + logger.warn("Graceful shutdown took too much time, forcing the shutdown."); List tasks = executorService.shutdownNow(); - logger.log(Level.INFO, tasks.size() + " tasks failed to execute before the shutdown."); + logger.info(tasks.size() + " tasks failed to execute before the shutdown."); } - logger.log(Level.SEVERE, "Shutdown finished."); + logger.info("Shutdown finished."); } catch (InterruptedException e) { - logger.log(Level.SEVERE, "Graceful shutdown interrupted, forcing the shutdown."); + logger.error("Graceful shutdown interrupted, forcing the shutdown."); List tasks = executorService.shutdownNow(); - logger.log(Level.INFO, tasks.size() + " tasks failed to execute before the shutdown."); + logger.info(tasks.size() + " tasks failed to execute before the shutdown."); } finally { if (propagateClose) actualConnection.close(); @@ -138,7 +138,7 @@ public void run() { try { actualConnection.send(event); } catch (Exception e) { - logger.log(Level.SEVERE, "An exception occurred while sending the event to Sentry.", e); + logger.error("An exception occurred while sending the event to Sentry.", e); } } } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index a6c3766c8e6..8a9ac646c2c 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -5,6 +5,8 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.exception.ConnectionException; import net.kencochrane.raven.marshaller.Marshaller; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; @@ -13,8 +15,6 @@ import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Basic connection to a Sentry server, using HTTP and HTTPS. @@ -23,7 +23,7 @@ *

    */ public class HttpConnection extends AbstractConnection { - private static final Logger logger = Logger.getLogger(HttpConnection.class.getCanonicalName()); + private static final Logger logger = LoggerFactory.getLogger(HttpConnection.class); /** * HTTP Header for the user agent. */ @@ -105,7 +105,7 @@ protected void doSend(Event event) { connection.getInputStream().close(); } catch (IOException e) { if (connection.getErrorStream() != null) { - logger.log(Level.SEVERE, getErrorMessageFromStream(connection.getErrorStream()), e); + logger.error(getErrorMessageFromStream(connection.getErrorStream()), e); } else { throw new ConnectionException("An exception occurred while submitting the event to the sentry server." , e); @@ -126,7 +126,7 @@ private String getErrorMessageFromStream(InputStream errorStream) { sb.deleteCharAt(sb.length() - 1); } catch (Exception e2) { - logger.log(Level.SEVERE, "Exception while reading the error message from the connection.", e2); + logger.error("Exception while reading the error message from the connection.", e2); } return sb.toString(); } diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java index 476d0890cf3..0d1ab6eaf58 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java @@ -4,17 +4,18 @@ import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.ImmutableThrowable; import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.HashSet; import java.util.Set; -import java.util.logging.Logger; /** * Binding system allowing to convert an {@link ExceptionInterface} to a JSON stream. */ public class ExceptionInterfaceBinding implements InterfaceBinding { - private static final Logger logger = Logger.getLogger(ExceptionInterfaceBinding.class.getCanonicalName()); + private static final Logger logger = LoggerFactory.getLogger(ExceptionInterfaceBinding.class); private static final String TYPE_PARAMETER = "type"; private static final String VALUE_PARAMETER = "value"; private static final String MODULE_PARAMETER = "module"; @@ -47,7 +48,7 @@ public void writeInterface(JsonGenerator generator, ExceptionInterface exception throwable = throwable.getCause(); if (dejaVu.contains(throwable)) { - logger.warning("Exiting a circular referencing exception!"); + logger.warn("Exiting a circular referencing exception!"); break; } } diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index fe51e4bfe6c..29c762bfaaf 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -6,14 +6,14 @@ import net.kencochrane.raven.event.interfaces.SentryInterface; import net.kencochrane.raven.marshaller.Marshaller; import org.apache.commons.codec.binary.Base64OutputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.OutputStream; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.zip.DeflaterOutputStream; public class JsonMarshaller implements Marshaller { @@ -73,7 +73,7 @@ public class JsonMarshaller implements Marshaller { * Date format for ISO 8601. */ private static final DateFormat ISO_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - private static final Logger logger = Logger.getLogger(JsonMarshaller.class.getCanonicalName()); + private static final Logger logger = LoggerFactory.getLogger(JsonMarshaller.class); private final JsonFactory jsonFactory = new JsonFactory(); private final Map, InterfaceBinding> interfaceBindings = new HashMap, InterfaceBinding>(); @@ -99,13 +99,13 @@ public void marshall(Event event, OutputStream destination) { generator = jsonFactory.createGenerator(destination); writeContent(generator, event); } catch (IOException e) { - logger.log(Level.SEVERE, "An exception occurred while serialising the event.", e); + logger.error("An exception occurred while serialising the event.", e); } finally { try { if (generator != null) generator.close(); } catch (IOException e) { - logger.log(Level.SEVERE, "An exception occurred while closing the json stream.", e); + logger.error("An exception occurred while closing the json stream.", e); } } } @@ -139,7 +139,7 @@ private void writeInterfaces(JsonGenerator generator, Map Date: Thu, 16 May 2013 19:18:19 +0100 Subject: [PATCH 0590/2152] Ignore test relying on logs generated by JUL --- .../net/kencochrane/raven/connection/HttpConnectionTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index 48dbec6f11a..bfc7143ec69 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -7,6 +7,7 @@ import net.kencochrane.raven.marshaller.Marshaller; import org.hamcrest.CustomTypeSafeMatcher; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; @@ -106,6 +107,8 @@ public void testAuthHeaderSent() throws Exception { verify(mockUrlConnection).setRequestProperty("X-Sentry-Auth", expectedAuthRequest); } + //TODO: This test is ignored since the logs are sent through SLF4J rather than JUL. + @Ignore @Test public void testHttpErrorLogged() throws Exception { final String httpErrorMessage = UUID.randomUUID().toString(); From b548188b200726e3627c1ed85564846426fb7100 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 16 May 2013 19:18:44 +0100 Subject: [PATCH 0591/2152] Stop accepting new logs once the async connection is closed --- .../net/kencochrane/raven/connection/AsyncConnection.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index d47fe1da7f9..24f3c72a35e 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -35,6 +35,10 @@ public class AsyncConnection implements Connection { * Executor service in charge of running the connection in separate threads. */ private ExecutorService executorService = Executors.newSingleThreadExecutor(); + /** + * Boolean used to check whether the connection is still open or not. + */ + private boolean closed; /** * Creates a connection which will rely on an executor to send events. @@ -85,7 +89,8 @@ public void run() { */ @Override public void send(Event event) { - executorService.execute(new EventSubmitter(event)); + if (!closed) + executorService.execute(new EventSubmitter(event)); } /** @@ -100,6 +105,7 @@ public void send(Event event) { @Override public void close() throws IOException { logger.info("Gracefully shutdown sentry threads."); + closed = true; executorService.shutdown(); try { if (!executorService.awaitTermination(SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS)) { From 5df48da477668289a884aa4b3440734eff980beb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 16 May 2013 19:20:16 +0100 Subject: [PATCH 0592/2152] Add guard to prevent recursive logging with log4j --- .../kencochrane/raven/log4j/SentryAppender.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index a9bb8e25b56..8b7b845509b 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -26,6 +26,7 @@ public class SentryAppender extends AppenderSkeleton { private Raven raven; private String dsn; private String ravenFactory; + private boolean guard; public SentryAppender() { this.propagateClose = true; @@ -77,9 +78,17 @@ public void activateOptions() { } @Override - protected void append(LoggingEvent loggingEvent) { - Event event = buildEvent(loggingEvent); - raven.sendEvent(event); + protected synchronized void append(LoggingEvent loggingEvent) { + if (guard) + return; + + guard = true; + try { + Event event = buildEvent(loggingEvent); + raven.sendEvent(event); + } finally { + guard = false; + } } private Event buildEvent(LoggingEvent loggingEvent) { From cd0f6c72f5ea24e51d5ac3cc48f07b2596921d55 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 16 May 2013 19:42:55 +0100 Subject: [PATCH 0593/2152] Fix the condition to increase the waiting time --- .../net/kencochrane/raven/connection/AbstractConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index df6f0dc45c0..7d5c112ad30 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -98,7 +98,7 @@ private void lockDown() { Thread.sleep(waitingTime); // Double the wait until the maximum is reached - if (waitingTime > maxWaitingTime) + if (waitingTime < maxWaitingTime) waitingTime <<= 1; } catch (Exception e) { logger.warn("An exception occurred during the lockdown.", e); From 44e77f7b2e120c4067f4e638e7bd21304b602064 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 16 May 2013 19:43:10 +0100 Subject: [PATCH 0594/2152] Do no add more logs when a lockdown is on. --- .../net/kencochrane/raven/connection/AbstractConnection.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 7d5c112ad30..805584d09f6 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -77,8 +77,6 @@ public final void send(Event event) { if (!lock.isLocked()) { doSend(event); waitingTime = baseWaitingTime; - } else { - logger.info("The event '" + event + "' hasn't been sent to the server due to a lockdown."); } } catch (ConnectionException e) { lock.tryLock(); From 6e225ff34bc3916c43406ce7e72a62b32c987afb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 16 May 2013 19:54:16 +0100 Subject: [PATCH 0595/2152] Move to a stable version of jetty --- pom.xml | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index a415a6144f3..e9a4ee7c8b8 100644 --- a/pom.xml +++ b/pom.xml @@ -274,8 +274,7 @@ org.eclipse.jetty jetty-maven-plugin - - 9.0.3-SNAPSHOT + 9.0.3.v20130506 10 foo @@ -388,19 +387,4 @@ - - - - - sonatype-jetty-snapshots - Sonatype Jetty Snapshots - https://oss.sonatype.org/content/repositories/jetty-snapshots - - false - - - true - - - From 188e8871e44c8d89475f9186e457c5eb7ec69792 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 16 May 2013 20:05:50 +0100 Subject: [PATCH 0596/2152] Enable jetty tests only with java 7 --- pom.xml | 179 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 95 insertions(+), 84 deletions(-) diff --git a/pom.xml b/pom.xml index e9a4ee7c8b8..2c1f9e1e833 100644 --- a/pom.xml +++ b/pom.xml @@ -269,90 +269,6 @@
    - - - - org.eclipse.jetty - jetty-maven-plugin - 9.0.3.v20130506 - - 10 - foo - 9999 - ${project.build.directory}/webapps/sentry-stub.war - - - - start-sentry-stub - pre-integration-test - - deploy-war - - - 0 - true - ${skipTests} - - - - stop-sentry-stub - post-integration-test - - stop - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - 2.14.1 - - - - integration-test - verify - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 2.7 - - - copy-war-for-integration-tests - package - - copy - - - - - ${project.groupId} - sentry-stub - ${project.version} - war - - - true - true - ${project.build.directory}/webapps - - - - - - ${project.groupId} - sentry-stub - ${project.version} - war - - - - - @@ -387,4 +303,99 @@
    + + + + jetty + + 1.7 + + + + + + org.eclipse.jetty + jetty-maven-plugin + 9.0.3.v20130506 + + 10 + foo + 9999 + ${project.build.directory}/webapps/sentry-stub.war + + + + start-sentry-stub + pre-integration-test + + deploy-war + + + 0 + true + ${skipTests} + + + + stop-sentry-stub + post-integration-test + + stop + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.14.1 + + + + integration-test + verify + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.7 + + + copy-war-for-integration-tests + package + + copy + + + + + ${project.groupId} + sentry-stub + ${project.version} + war + + + true + true + ${project.build.directory}/webapps + + + + + + ${project.groupId} + sentry-stub + ${project.version} + war + + + + + + + + From b3950294cf3b353f871bc7dc1af19aa0447121d6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 16 May 2013 20:11:52 +0100 Subject: [PATCH 0597/2152] Add plugins to the default build to avoid warnings Even though the jetty plugin can't be used with java < 7 not providing a version in those cases will generate warnings in the logs --- pom.xml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 2c1f9e1e833..5c6501a4152 100644 --- a/pom.xml +++ b/pom.xml @@ -269,6 +269,20 @@
    + + + + org.eclipse.jetty + jetty-maven-plugin + 9.0.3.v20130506 + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.14.1 + + + @@ -316,7 +330,6 @@ org.eclipse.jetty jetty-maven-plugin - 9.0.3.v20130506 10 foo @@ -348,7 +361,6 @@ org.apache.maven.plugins maven-failsafe-plugin - 2.14.1 From f4cd1204cc7dd5ba2ca2f9c639c079a75b5cc5d2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 9 May 2013 18:49:34 +0200 Subject: [PATCH 0598/2152] Decouple HttpConnection and Dsn --- .../main/java/net/kencochrane/raven/DefaultRavenFactory.java | 5 +++-- .../net/kencochrane/raven/connection/HttpConnection.java | 5 +++-- .../net/kencochrane/raven/connection/HttpConnectionTest.java | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index a459b23544d..4168cc5b009 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -14,6 +14,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.net.URL; import java.util.Arrays; import java.util.Collection; import java.util.concurrent.*; @@ -131,8 +132,8 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { } protected Connection createHttpConnection(Dsn dsn) { - HttpConnection httpConnection = new HttpConnection(HttpConnection.getSentryApiUrl(dsn), - dsn.getPublicKey(), dsn.getSecretKey()); + URL sentryApiUrl = HttpConnection.getSentryApiUrl(dsn.getUri(), dsn.getProjectId()); + HttpConnection httpConnection = new HttpConnection(sentryApiUrl, dsn.getPublicKey(), dsn.getSecretKey()); httpConnection.setMarshaller(createMarshaller(dsn)); // Set the naive mode diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 8a9ac646c2c..febbeeb9289 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -14,6 +14,7 @@ import java.io.*; import java.net.HttpURLConnection; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; /** @@ -68,9 +69,9 @@ public HttpConnection(URL sentryUrl, String publicKey, String secretKey) { this.sentryUrl = sentryUrl; } - public static URL getSentryApiUrl(Dsn dsn) { + public static URL getSentryApiUrl(URI sentryUri, String projectId) { try { - String url = dsn.getUri().toString() + "api/" + dsn.getProjectId() + "/store/"; + String url = sentryUri.toString() + "api/" + projectId + "/store/"; return new URL(url); } catch (MalformedURLException e) { throw new IllegalArgumentException("Couldn't get a valid URL from the DSN.", e); diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index bfc7143ec69..4b015894335 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -140,7 +140,7 @@ public void testApiUrlCreation() throws Exception { when(dsn.getUri()).thenReturn(new URI(uri)); when(dsn.getProjectId()).thenReturn(projectId); - URL sentryApiUrl = HttpConnection.getSentryApiUrl(dsn); + URL sentryApiUrl = HttpConnection.getSentryApiUrl(dsn.getUri(), dsn.getProjectId()); assertThat(sentryApiUrl.toString(), is(uri + "api/" + projectId + "/store/")); } From 4ff03a7fbbc683f8ebbd571fd8e4ec04692b7eb4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 18 May 2013 18:19:39 +0100 Subject: [PATCH 0599/2152] Reduce the log level for helper addition --- raven/src/main/java/net/kencochrane/raven/Raven.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index f2bf1a3214b..0cf5ef13760 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -59,7 +59,7 @@ public void sendEvent(Event event) { * @param builderHelper builder helper to remove. */ public void removeBuilderHelper(EventBuilderHelper builderHelper) { - logger.error("Removes '" + builderHelper + "' to the list of builder helpers."); + logger.info("Removes '" + builderHelper + "' to the list of builder helpers."); builderHelpers.remove(builderHelper); } @@ -69,7 +69,7 @@ public void removeBuilderHelper(EventBuilderHelper builderHelper) { * @param builderHelper builder helper to add. */ public void addBuilderHelper(EventBuilderHelper builderHelper) { - logger.error("Adding '" + builderHelper + "' to the list of builder helpers."); + logger.info("Adding '" + builderHelper + "' to the list of builder helpers."); builderHelpers.add(builderHelper); } From d94a8077fc02c1d1d74b85c5ce50747e476c06c1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 21 May 2013 22:33:44 +0100 Subject: [PATCH 0600/2152] Avoid logging on threads spawned by raven --- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 6 ++++-- .../net/kencochrane/raven/log4j2/SentryAppender.java | 4 ++++ .../net/kencochrane/raven/logback/SentryAppender.java | 4 ++++ raven/src/main/java/net/kencochrane/raven/Raven.java | 9 +++++++++ .../kencochrane/raven/connection/AsyncConnection.java | 5 +++++ .../java/net/kencochrane/raven/jul/SentryHandler.java | 7 ++++--- 6 files changed, 30 insertions(+), 5 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 8b7b845509b..1884a5b9024 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -79,11 +79,13 @@ public void activateOptions() { @Override protected synchronized void append(LoggingEvent loggingEvent) { - if (guard) + // Do not log the event if the current thread has been spawned by raven or if the event has been created during + // the logging of an other event. + if (Raven.RAVEN_THREAD.get() || guard) return; - guard = true; try { + guard = true; Event event = buildEvent(loggingEvent); raven.sendEvent(event); } finally { diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 15355e6f34c..cd9cf631807 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -123,6 +123,10 @@ public void start() { @Override public void append(LogEvent logEvent) { + // Do not log the event if the current thread has been spawned by raven + if (Raven.RAVEN_THREAD.get()) + return; + Event event = buildEvent(logEvent); raven.sendEvent(event); } diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 99570d6074a..5e01560a4ee 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -77,6 +77,10 @@ public void start() { @Override protected void append(ILoggingEvent iLoggingEvent) { + // Do not log the event if the current thread has been spawned by raven + if (Raven.RAVEN_THREAD.get()) + return; + Event event = buildEvent(iLoggingEvent); raven.sendEvent(event); } diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 0cf5ef13760..3f1ea84ed06 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -19,6 +19,15 @@ *

    */ public class Raven { + /** + * Indicates whether the current thread has been spawned within raven or not. + */ + public static final ThreadLocal RAVEN_THREAD = new ThreadLocal(){ + @Override + protected Boolean initialValue() { + return false; + } + }; /** * Version of this client, the major version is the current supported Sentry protocol, the minor version changes * for each release of this project. diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 24f3c72a35e..22fb21e9a3e 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -1,5 +1,6 @@ package net.kencochrane.raven.connection; +import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -142,9 +143,13 @@ private EventSubmitter(Event event) { @Override public void run() { try { + // The current thread is spawned by raven + Raven.RAVEN_THREAD.set(true); actualConnection.send(event); } catch (Exception e) { logger.error("An exception occurred while sending the event to Sentry.", e); + } finally { + Raven.RAVEN_THREAD.remove(); } } } diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index a2c35bb8dea..4b1fa675e69 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -56,12 +56,13 @@ private static List formatParameters(Object[] parameters) { @Override public synchronized void publish(LogRecord record) { - if (!isLoggable(record) || guard) { + // Do not log the event if the current thread has been spawned by raven or if the event has been created during + // the logging of an other event. + if (!isLoggable(record) || Raven.RAVEN_THREAD.get() || guard) return; - } - guard = true; try { + guard = true; if (raven == null) { try { start(); From aef2664b02ecb6b38e9ad2b7f4ba343633849f6f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 22 May 2013 10:55:52 +0100 Subject: [PATCH 0601/2152] Update libraries and plugin versions --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 5c6501a4152..efdb1caf2de 100644 --- a/pom.xml +++ b/pom.xml @@ -109,9 +109,9 @@ 1.8 2.2.0 3.0.1 - 1.0.12 + 1.0.13 1.2.17 - 2.0-beta5 + 2.0-beta6 4.11 1.9.5 1.3 @@ -242,7 +242,7 @@ org.apache.maven.plugins maven-site-plugin - 3.2 + 3.3 true @@ -290,7 +290,7 @@ org.apache.maven.plugins maven-project-info-reports-plugin - 2.6 + 2.7 org.apache.maven.plugins @@ -373,7 +373,7 @@ org.apache.maven.plugins maven-dependency-plugin - 2.7 + 2.8 copy-war-for-integration-tests From 4345095942945ae659dc92766090e870115dc2be Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 22 May 2013 10:56:14 +0100 Subject: [PATCH 0602/2152] Update SentryAppender for the new version of log4j2 --- .../main/java/net/kencochrane/raven/log4j2/SentryAppender.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index cd9cf631807..ff6f5866530 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -26,7 +26,7 @@ /** * Appender for log4j2 in charge of sending the logged events to a Sentry server. */ -@Plugin(name = "Raven", type = "Core", elementType = "appender") +@Plugin(name = "Raven", category = "Core", elementType = "appender") public class SentryAppender extends AbstractAppender { /** * Default name for the appender. From ebfd2d4b17864c54d2ff234e1a1d3ff62e236324 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 22 May 2013 11:16:51 +0100 Subject: [PATCH 0603/2152] Fix the default tag name format --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index efdb1caf2de..a1e64954e51 100644 --- a/pom.xml +++ b/pom.xml @@ -211,7 +211,7 @@ maven-release-plugin 2.4.1 - @{project.version} + v@{project.version} false true From d4578edcdfb41073676474548c6efc63f524f011 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 22 May 2013 11:35:51 +0100 Subject: [PATCH 0604/2152] Force new version of plugins for a release --- pom.xml | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/pom.xml b/pom.xml index a1e64954e51..18a941d73d1 100644 --- a/pom.xml +++ b/pom.xml @@ -409,5 +409,54 @@ + + + + sonatype-oss-release + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.4 + + + sign-artifacts + verify + + sign + + + + + + + From 7c764b371c5f8f921e150e117abb6926926f6611 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 22 May 2013 11:48:35 +0100 Subject: [PATCH 0605/2152] Do not deploy the site automatically --- pom.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pom.xml b/pom.xml index 18a941d73d1..59ea5ee0aba 100644 --- a/pom.xml +++ b/pom.xml @@ -214,6 +214,8 @@ v@{project.version} false true + + deploy forked-path false From 585cf856c901a69c8e515a1eef8d6851a228b557 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 22 May 2013 12:52:29 +0100 Subject: [PATCH 0606/2152] [maven-release-plugin] prepare release v4.0 --- pom.xml | 4 ++-- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 59ea5ee0aba..b1529a579b3 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 4.0-SNAPSHOT + 4.0 pom Raven-Java @@ -74,7 +74,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v4.0 https://github.com/${github.repo}/issues diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 2e077a65864..cbfbf3afc48 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.0-SNAPSHOT + 4.0 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index dc6e5635af4..a814ddaa5a2 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.0-SNAPSHOT + 4.0 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 6b2a55cb301..76f361cff48 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.0-SNAPSHOT + 4.0 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index e62f03815a4..ba893587bd8 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.0-SNAPSHOT + 4.0 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 17fec4def15..2b475fc2137 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.0-SNAPSHOT + 4.0 sentry-stub From 3bb3405b6a1afd2d4bbcaaa14b6327fe6191f6bc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 22 May 2013 12:52:30 +0100 Subject: [PATCH 0607/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index b1529a579b3..7ce1cf4ed5b 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 4.0 + 4.1-SNAPSHOT pom Raven-Java @@ -74,7 +74,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v4.0 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index cbfbf3afc48..9c5f1d2c5e2 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.0 + 4.1-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index a814ddaa5a2..1c65994bba8 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.0 + 4.1-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 76f361cff48..1de5c6ded64 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.0 + 4.1-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index ba893587bd8..87c02ef7849 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.0 + 4.1-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 2b475fc2137..1cf30bc7cf8 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.0 + 4.1-SNAPSHOT sentry-stub From 6bcabd1f798da87c943050986820d9e173b8f4da Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 22 May 2013 13:23:43 +0100 Subject: [PATCH 0608/2152] Do not force tags to be pushed during a release --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 7ce1cf4ed5b..3d4d99beb2d 100644 --- a/pom.xml +++ b/pom.xml @@ -212,6 +212,7 @@ 2.4.1 v@{project.version} + true false true From 017717e8b0abe1aac6b4734f368ac8d30940a613 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 22 May 2013 14:40:59 +0100 Subject: [PATCH 0609/2152] Do not recommend to use the snapshot version of raven --- INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index 58bd9b0ddff..c97253cf6e1 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -23,7 +23,7 @@ To add raven as a dependency, simply add this to the pom.xml file: net.kencochrane.raven raven - 4.0-SNAPSHOT + 4.0 Other modules can be added the same way (replacing the `artifactId`) with the From 4b13eeaa7e331c51c678a777f6e6e07295aeb56b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 29 May 2013 09:34:12 +0100 Subject: [PATCH 0610/2152] Fix indentation and imports --- raven/src/main/java/net/kencochrane/raven/Raven.java | 2 +- .../java/net/kencochrane/raven/connection/HttpConnection.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 3f1ea84ed06..10ecc0ccd65 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -22,7 +22,7 @@ public class Raven { /** * Indicates whether the current thread has been spawned within raven or not. */ - public static final ThreadLocal RAVEN_THREAD = new ThreadLocal(){ + public static final ThreadLocal RAVEN_THREAD = new ThreadLocal() { @Override protected Boolean initialValue() { return false; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index febbeeb9289..a2ca33948d1 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -1,6 +1,5 @@ package net.kencochrane.raven.connection; -import net.kencochrane.raven.Dsn; import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.exception.ConnectionException; From 1ca0df409aec93a94e2d3fc2865f2032b2313c34 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 29 May 2013 11:31:27 +0100 Subject: [PATCH 0611/2152] Avoid unnecessary concatenation in the logs --- raven/src/main/java/net/kencochrane/raven/Raven.java | 4 ++-- .../kencochrane/raven/connection/AbstractConnection.java | 2 +- .../net/kencochrane/raven/connection/AsyncConnection.java | 4 ++-- .../kencochrane/raven/marshaller/json/JsonMarshaller.java | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 10ecc0ccd65..8000c8f4293 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -68,7 +68,7 @@ public void sendEvent(Event event) { * @param builderHelper builder helper to remove. */ public void removeBuilderHelper(EventBuilderHelper builderHelper) { - logger.info("Removes '" + builderHelper + "' to the list of builder helpers."); + logger.info("Removes '{}' to the list of builder helpers.", builderHelper); builderHelpers.remove(builderHelper); } @@ -78,7 +78,7 @@ public void removeBuilderHelper(EventBuilderHelper builderHelper) { * @param builderHelper builder helper to add. */ public void addBuilderHelper(EventBuilderHelper builderHelper) { - logger.info("Adding '" + builderHelper + "' to the list of builder helpers."); + logger.info("Adding '{}' to the list of builder helpers.", builderHelper); builderHelpers.add(builderHelper); } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 805584d09f6..6e93ccfb744 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -92,7 +92,7 @@ public final void send(Event event) { */ private void lockDown() { try { - logger.warn("Lockdown started for " + waitingTime + "ms."); + logger.warn("Lockdown started for {}ms.", waitingTime); Thread.sleep(waitingTime); // Double the wait until the maximum is reached diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 22fb21e9a3e..675fbd8ca44 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -112,13 +112,13 @@ public void close() throws IOException { if (!executorService.awaitTermination(SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS)) { logger.warn("Graceful shutdown took too much time, forcing the shutdown."); List tasks = executorService.shutdownNow(); - logger.info(tasks.size() + " tasks failed to execute before the shutdown."); + logger.info("{} tasks failed to execute before the shutdown.", tasks.size()); } logger.info("Shutdown finished."); } catch (InterruptedException e) { logger.error("Graceful shutdown interrupted, forcing the shutdown."); List tasks = executorService.shutdownNow(); - logger.info(tasks.size() + " tasks failed to execute before the shutdown."); + logger.info("{} tasks failed to execute before the shutdown.", tasks.size()); } finally { if (propagateClose) actualConnection.close(); diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 29c762bfaaf..8983b7d4f6f 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -139,8 +139,8 @@ private void writeInterfaces(JsonGenerator generator, Map Date: Wed, 29 May 2013 13:53:41 +0100 Subject: [PATCH 0612/2152] Do not rely on the canonical name but on the classname --- .../java/net/kencochrane/raven/log4j2/SentryAppenderTest.java | 2 +- raven/src/main/java/net/kencochrane/raven/RavenFactory.java | 2 +- .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 4 ++-- raven/src/test/java/net/kencochrane/raven/DsnTest.java | 2 +- .../test/java/net/kencochrane/raven/jul/SentryHandlerIT.java | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index f441afc7eb0..d853c858472 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -14,7 +14,7 @@ import java.util.List; public class SentryAppenderTest extends AbstractLoggerTest { - private static final String LOGGER_NAME = SentryAppenderTest.class.getCanonicalName(); + private static final String LOGGER_NAME = SentryAppenderTest.class.getName(); private SentryAppender sentryAppender; @Before diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index eadee9df07b..b27aacc7ca8 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -27,7 +27,7 @@ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { return ravenInstance(dsn); for (RavenFactory ravenFactory : RAVEN_FACTORIES) { - if (!ravenFactoryName.equals(ravenFactory.getClass().getCanonicalName())) + if (!ravenFactoryName.equals(ravenFactory.getClass().getName())) continue; Raven raven = ravenFactory.createRavenInstance(dsn); diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 4b1fa675e69..21ad86fd69e 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -112,8 +112,8 @@ private void start() { return; LogManager manager = LogManager.getLogManager(); - String dsn = manager.getProperty(this.getClass().getCanonicalName() + ".dsn"); - String ravenFactory = manager.getProperty(this.getClass().getCanonicalName() + ".ravenFactory"); + String dsn = manager.getProperty(SentryHandler.class.getName() + ".dsn"); + String ravenFactory = manager.getProperty(SentryHandler.class.getName() + ".ravenFactory"); if (dsn == null) dsn = Dsn.dsnLookup(); diff --git a/raven/src/test/java/net/kencochrane/raven/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/DsnTest.java index d1b508bc31b..3010c0b22c1 100644 --- a/raven/src/test/java/net/kencochrane/raven/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/DsnTest.java @@ -24,7 +24,7 @@ public class DsnTest { @Before public void setUp() throws Exception { - System.setProperty("java.naming.factory.initial", InitialContextMockFactory.class.getCanonicalName()); + System.setProperty("java.naming.factory.initial", InitialContextMockFactory.class.getName()); InitialContextMockFactory.context = mockContext; } diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java index 5be37c23ec1..ad0c1bfa6b2 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java @@ -11,7 +11,7 @@ import static org.hamcrest.Matchers.is; public class SentryHandlerIT { - private static final Logger logger = Logger.getLogger(SentryHandlerIT.class.getCanonicalName()); + private static final Logger logger = Logger.getLogger(SentryHandlerIT.class.getName()); private SentryStub sentryStub; @Before From f091d2d7ecad94e12d4006fd08c4d5b17a43719a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 29 May 2013 13:54:16 +0100 Subject: [PATCH 0613/2152] Use slf4j for logs during tests --- .../java/net/kencochrane/raven/SentryStub.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/SentryStub.java b/raven/src/test/java/net/kencochrane/raven/SentryStub.java index 92f0fd58906..3c4cdc93a4b 100644 --- a/raven/src/test/java/net/kencochrane/raven/SentryStub.java +++ b/raven/src/test/java/net/kencochrane/raven/SentryStub.java @@ -1,17 +1,17 @@ package net.kencochrane.raven; import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; public class SentryStub { - private static final Logger logger = Logger.getLogger(SentryStub.class.getCanonicalName()); + private static final Logger logger = LoggerFactory.getLogger(SentryStub.class); private static final URL DEFAULT_URL; private final URL url; private final ObjectMapper mapper = new ObjectMapper(); @@ -21,7 +21,7 @@ public class SentryStub { try { url = new URL("http://localhost:8080/stub/"); } catch (MalformedURLException e) { - logger.log(Level.FINE, "Couldn't create the URL http://localhost:8080/stub", e); + logger.debug("Couldn't create the URL http://localhost:8080/stub", e); } DEFAULT_URL = url; @@ -41,7 +41,7 @@ public int getEventCount() { connection.setRequestMethod("GET"); return (Integer) getContent(connection).get("count"); } catch (Exception e) { - logger.log(Level.SEVERE, "Couldn't get the number of events created.", e); + logger.error("Couldn't get the number of events created.", e); return -1; } } @@ -54,7 +54,7 @@ public void removeEvents() { connection.connect(); connection.getInputStream().close(); } catch (Exception e) { - logger.log(Level.SEVERE, "Couldn't remove stub events.", e); + logger.error("Couldn't remove stub events.", e); } } @@ -65,7 +65,7 @@ private Map getContent(HttpURLConnection connection) { try { connection.getOutputStream().close(); } catch (IOException e) { - logger.log(Level.FINE, "Couldn't open and close the outputstream", e); + logger.error("Couldn't open and close the outputstream", e); } return (Map) mapper.readValue(connection.getInputStream(), Map.class); } catch (Exception e) { From dbf45fc3fe61436ecb76e24fad8c9e70cd6f45e8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 29 May 2013 13:55:25 +0100 Subject: [PATCH 0614/2152] Add slf4j marker to the logback logs --- .../java/net/kencochrane/raven/logback/SentryAppender.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 5e01560a4ee..e27b7e415e2 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -11,6 +11,7 @@ import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; +import org.slf4j.Marker; import java.io.IOException; import java.util.ArrayList; @@ -116,6 +117,10 @@ private Event buildEvent(ILoggingEvent iLoggingEvent) { eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); } + if (iLoggingEvent.getMarker() != null) { + eventBuilder.addExtra(Marker.class.getName(), iLoggingEvent.getMarker()); + } + raven.runBuilderHelpers(eventBuilder); return eventBuilder.build(); From a3ad3b4b5b0177f9b5dd26c3d77d858eaf4f36e7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 29 May 2013 14:28:12 +0100 Subject: [PATCH 0615/2152] FormatLevel takes a level as a parameter --- .../kencochrane/raven/logback/SentryAppender.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index e27b7e415e2..78f44f84069 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -49,14 +49,14 @@ private static List formatArguments(Object[] argumentArray) { return arguments; } - private static Event.Level formatLevel(ILoggingEvent iLoggingEvent) { - if (iLoggingEvent.getLevel().isGreaterOrEqual(Level.ERROR)) { + private static Event.Level formatLevel(Level level) { + if (level.isGreaterOrEqual(Level.ERROR)) { return Event.Level.ERROR; - } else if (iLoggingEvent.getLevel().isGreaterOrEqual(Level.WARN)) { + } else if (level.isGreaterOrEqual(Level.WARN)) { return Event.Level.WARNING; - } else if (iLoggingEvent.getLevel().isGreaterOrEqual(Level.INFO)) { + } else if (level.isGreaterOrEqual(Level.INFO)) { return Event.Level.INFO; - } else if (iLoggingEvent.getLevel().isGreaterOrEqual(Level.ALL)) { + } else if (level.isGreaterOrEqual(Level.ALL)) { return Event.Level.DEBUG; } else return null; } @@ -91,7 +91,7 @@ private Event buildEvent(ILoggingEvent iLoggingEvent) { .setTimestamp(new Date(iLoggingEvent.getTimeStamp())) .setMessage(iLoggingEvent.getFormattedMessage()) .setLogger(iLoggingEvent.getLoggerName()) - .setLevel(formatLevel(iLoggingEvent)); + .setLevel(formatLevel(iLoggingEvent.getLevel())); if (iLoggingEvent.getThrowableProxy() != null) { Throwable throwable = ((ThrowableProxy) iLoggingEvent.getThrowableProxy()).getThrowable(); From c07912cb6fcf46257f04aaeb76f66bd33328cea4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 29 May 2013 14:28:55 +0100 Subject: [PATCH 0616/2152] Allow formatted message combined with exceptions --- .../raven/logback/SentryAppender.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 78f44f84069..1fcf7358203 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -93,18 +93,17 @@ private Event buildEvent(ILoggingEvent iLoggingEvent) { .setLogger(iLoggingEvent.getLoggerName()) .setLevel(formatLevel(iLoggingEvent.getLevel())); + if (iLoggingEvent.getArgumentArray() != null) + eventBuilder.addSentryInterface(new MessageInterface(iLoggingEvent.getMessage(), + formatArguments(iLoggingEvent.getArgumentArray()))); + if (iLoggingEvent.getThrowableProxy() != null) { Throwable throwable = ((ThrowableProxy) iLoggingEvent.getThrowableProxy()).getThrowable(); eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); - } else { - if (iLoggingEvent.getArgumentArray() != null) - eventBuilder.addSentryInterface(new MessageInterface(iLoggingEvent.getMessage(), - formatArguments(iLoggingEvent.getArgumentArray()))); - // When it's a message try to rely on the position of the log (the same message can be logged from - // different places, or a same place can log a message in different ways. - if (iLoggingEvent.getCallerData().length > 0) { - eventBuilder.generateChecksum(getEventPosition(iLoggingEvent)); - } + } else if (iLoggingEvent.getCallerData().length > 0) { + // When there is no exceptions try to rely on the position of the log (the same message can be logged from + // different places, or a same place can log a message in different ways). + eventBuilder.generateChecksum(getEventPosition(iLoggingEvent)); } if (iLoggingEvent.getCallerData().length > 0) { From 0823fc00fe2bbe0df3c33a4fb67a0e9157eab5d8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 29 May 2013 14:49:50 +0100 Subject: [PATCH 0617/2152] Force events to be cleared before IT --- .../test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java | 1 + .../test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java | 1 + .../java/net/kencochrane/raven/logback/SentryAppenderIT.java | 1 + .../src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java | 1 + 4 files changed, 4 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java index 284a78b3210..1cee3d3fde7 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java @@ -16,6 +16,7 @@ public class SentryAppenderIT { @Before public void setUp() { sentryStub = new SentryStub(); + sentryStub.removeEvents(); } @After diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java index 7b367470885..0cf952155de 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java @@ -17,6 +17,7 @@ public class SentryAppenderIT { @Before public void setUp() { sentryStub = new SentryStub(); + sentryStub.removeEvents(); } @After diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java index 5028e8a248c..2ea345275da 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java @@ -17,6 +17,7 @@ public class SentryAppenderIT { @Before public void setUp() { sentryStub = new SentryStub(); + sentryStub.removeEvents(); } @After diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java index ad0c1bfa6b2..8b2d7c4ddc9 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java @@ -17,6 +17,7 @@ public class SentryHandlerIT { @Before public void setUp() { sentryStub = new SentryStub(); + sentryStub.removeEvents(); } @After From 90f9991f2e26b4734ab6380b225dedf2035cabb4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 29 May 2013 14:50:10 +0100 Subject: [PATCH 0618/2152] Set log level to all during integration tests --- raven-log4j2/src/test/resources/log4j2.xml | 2 +- raven-logback/src/test/resources/logback.xml | 2 +- raven/src/test/resources/logging.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/raven-log4j2/src/test/resources/log4j2.xml b/raven-log4j2/src/test/resources/log4j2.xml index 99e0e8d8adb..6f0d999b13d 100644 --- a/raven-log4j2/src/test/resources/log4j2.xml +++ b/raven-log4j2/src/test/resources/log4j2.xml @@ -1,5 +1,5 @@ - + diff --git a/raven-logback/src/test/resources/logback.xml b/raven-logback/src/test/resources/logback.xml index 82c33e22a6b..488c8fe5732 100644 --- a/raven-logback/src/test/resources/logback.xml +++ b/raven-logback/src/test/resources/logback.xml @@ -6,7 +6,7 @@ - + diff --git a/raven/src/test/resources/logging.properties b/raven/src/test/resources/logging.properties index 58f91b76dd6..e500a42eb17 100644 --- a/raven/src/test/resources/logging.properties +++ b/raven/src/test/resources/logging.properties @@ -1,5 +1,5 @@ handlers=java.util.logging.ConsoleHandler -level=INFO +level=ALL net.kencochrane.raven.handlers=java.util.logging.ConsoleHandler, net.kencochrane.raven.jul.SentryHandler net.kencochrane.raven.level=ALL From 579085fab94149fa9bc2b0d190a8b3aa941f340d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 30 May 2013 16:12:26 +0100 Subject: [PATCH 0619/2152] Add a pattern for logback configuration --- raven-logback/src/test/resources/logback.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/raven-logback/src/test/resources/logback.xml b/raven-logback/src/test/resources/logback.xml index 488c8fe5732..c1f494c01c2 100644 --- a/raven-logback/src/test/resources/logback.xml +++ b/raven-logback/src/test/resources/logback.xml @@ -1,5 +1,9 @@ - + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false From 63a56c8232c0f21160a02f58be92bed4d7d11c72 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 30 May 2013 16:12:58 +0100 Subject: [PATCH 0620/2152] Do not close the outputstream and close connection --- raven/src/test/java/net/kencochrane/raven/SentryStub.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/SentryStub.java b/raven/src/test/java/net/kencochrane/raven/SentryStub.java index 3c4cdc93a4b..dcc7d7d5f9b 100644 --- a/raven/src/test/java/net/kencochrane/raven/SentryStub.java +++ b/raven/src/test/java/net/kencochrane/raven/SentryStub.java @@ -62,15 +62,12 @@ private Map getContent(HttpURLConnection connection) { try { connection.setDoInput(true); connection.connect(); - try { - connection.getOutputStream().close(); - } catch (IOException e) { - logger.error("Couldn't open and close the outputstream", e); - } return (Map) mapper.readValue(connection.getInputStream(), Map.class); } catch (Exception e) { throw new IllegalStateException( "Couldn't get the JSON content for the connection '" + connection + "'", e); + } finally { + connection.disconnect(); } } From 0bcad0137e0494a8a6d4863c633e0e755048de2f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Jun 2013 19:40:50 +0100 Subject: [PATCH 0621/2152] Mention that the scope of raven should be runtime --- INSTALL.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/INSTALL.md b/INSTALL.md index c97253cf6e1..4c3188bbaff 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -28,6 +28,8 @@ To add raven as a dependency, simply add this to the pom.xml file: Other modules can be added the same way (replacing the `artifactId`) with the name of the module. +It is preferable to use the scope `runtime` if the API is used through a +logging system. If the version is a snapshot it will be necessary to specify the Sonatype Nexus snapshot repository: From dec338a46c1a8976370daaa9a3823eac2d0c7c06 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Jun 2013 19:41:21 +0100 Subject: [PATCH 0622/2152] Remove unchecked warning with log4j --- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 1884a5b9024..a4239419ffa 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -15,7 +15,6 @@ import java.io.IOException; import java.util.Date; import java.util.Map; -import java.util.Set; /** * Appender for log4j in charge of sending the logged events to a Sentry server. @@ -119,8 +118,10 @@ private Event buildEvent(LoggingEvent loggingEvent) { if (loggingEvent.getNDC() != null) eventBuilder.addExtra(LOG4J_NDC, loggingEvent.getNDC()); - for (Map.Entry mdcEntry : (Set) loggingEvent.getProperties().entrySet()) - eventBuilder.addExtra(mdcEntry.getKey().toString(), mdcEntry.getValue()); + @SuppressWarnings("unchecked") + Map properties = (Map) loggingEvent.getProperties(); + for (Map.Entry mdcEntry : properties.entrySet()) + eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); raven.runBuilderHelpers(eventBuilder); return eventBuilder.build(); From 08872e215208dee3f2e2d35fc39855edd3374280 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 14:00:35 +0100 Subject: [PATCH 0623/2152] Reduce the visibility of createRavenInstance --- .../main/java/net/kencochrane/raven/DefaultRavenFactory.java | 2 +- raven/src/main/java/net/kencochrane/raven/RavenFactory.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 4168cc5b009..064af811097 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -63,7 +63,7 @@ public class DefaultRavenFactory extends RavenFactory { private static final String FALSE = Boolean.FALSE.toString(); @Override - public Raven createRavenInstance(Dsn dsn) { + protected Raven createRavenInstance(Dsn dsn) { Raven raven = new Raven(); raven.setConnection(createConnection(dsn)); try { diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index b27aacc7ca8..6b24e816df7 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -39,5 +39,5 @@ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { throw new IllegalStateException("Couldn't create a raven instance for '" + dsn + "'"); } - public abstract Raven createRavenInstance(Dsn dsn); + protected abstract Raven createRavenInstance(Dsn dsn); } From bf96c302a2d40f521075abd6fc67dc40939350ee Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 14:01:08 +0100 Subject: [PATCH 0624/2152] Create helpers to create a raven instance --- .../src/main/java/net/kencochrane/raven/RavenFactory.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index 6b24e816df7..4648ab5b325 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -11,6 +11,14 @@ public abstract class RavenFactory { private static final ServiceLoader RAVEN_FACTORIES = ServiceLoader.load(RavenFactory.class); + public static Raven ravenInstance() { + return ravenInstance(Dsn.dsnLookup()); + } + + public static Raven ravenInstance(String dsn) { + return ravenInstance(new Dsn(dsn)); + } + public static Raven ravenInstance(Dsn dsn) { for (RavenFactory ravenFactory : RAVEN_FACTORIES) { Raven raven = ravenFactory.createRavenInstance(dsn); From 58d9fda103ce57a242b8dd2f22fd33907e768ec3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 17:38:38 +0100 Subject: [PATCH 0625/2152] Update dependencies --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3d4d99beb2d..87b1cd8c656 100644 --- a/pom.xml +++ b/pom.xml @@ -107,11 +107,11 @@ 1.7.5 1.8 - 2.2.0 + 2.2.2 3.0.1 1.0.13 1.2.17 - 2.0-beta6 + 2.0-beta7 4.11 1.9.5 1.3 From a3ddba25cc894c77b86e7e7656287e02bb9a172b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 17:38:54 +0100 Subject: [PATCH 0626/2152] Fix indentation and style --- .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 21ad86fd69e..3141c1f140f 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -85,7 +85,6 @@ private Event buildEvent(LogRecord record) { .setLogger(record.getLoggerName()); if (record.getSourceClassName() != null && record.getSourceMethodName() != null) { - StackTraceElement fakeFrame = new StackTraceElement(record.getSourceClassName(), record.getSourceMethodName(), null, -1); eventBuilder.setCulprit(fakeFrame); @@ -97,11 +96,12 @@ private Event buildEvent(LogRecord record) { eventBuilder.addSentryInterface(new ExceptionInterface(record.getThrown())); } - if (record.getParameters() != null) + if (record.getParameters() != null) { eventBuilder.addSentryInterface(new MessageInterface(record.getMessage(), formatParameters(record.getParameters()))); - else + } else { eventBuilder.setMessage(record.getMessage()); + } raven.runBuilderHelpers(eventBuilder); return eventBuilder.build(); From a783038f589da9291958b495d77b15cf3f44941f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 18:38:09 +0100 Subject: [PATCH 0627/2152] Add log4j2 marker as an extra --- .../java/net/kencochrane/raven/log4j2/SentryAppender.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index ff6f5866530..21f3f25c367 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -8,6 +8,7 @@ import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AbstractAppender; @@ -33,6 +34,7 @@ public class SentryAppender extends AbstractAppender { */ public static final String APPENDER_NAME = "raven"; private static final String LOG4J_NDC = "Log4J-NDC"; + private static final String LOG4J_MARKER = "log4j2-Marker"; private final boolean propagateClose; private Raven raven; private String dsn; @@ -169,6 +171,9 @@ private Event buildEvent(LogEvent event) { eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); } } + if (event.getMarker() != null) { + eventBuilder.addExtra(LOG4J_MARKER, event.getMarker()); + } raven.runBuilderHelpers(eventBuilder); From 4c325d6d16fe5983283a4104a67eb4f516f97fe6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 18:38:20 +0100 Subject: [PATCH 0628/2152] Use constant naming convention for extra fields --- .../main/java/net/kencochrane/raven/log4j2/SentryAppender.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 21f3f25c367..8dd1c224bfa 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -33,7 +33,7 @@ public class SentryAppender extends AbstractAppender { * Default name for the appender. */ public static final String APPENDER_NAME = "raven"; - private static final String LOG4J_NDC = "Log4J-NDC"; + private static final String LOG4J_NDC = "log4j2-NDC"; private static final String LOG4J_MARKER = "log4j2-Marker"; private final boolean propagateClose; private Raven raven; From 856f059f4b270b3aeef4e563aebfcb05b8324db9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 18:38:38 +0100 Subject: [PATCH 0629/2152] Use a constant name for logback marker --- .../java/net/kencochrane/raven/logback/SentryAppender.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 1fcf7358203..d37a0faab0e 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -23,6 +23,7 @@ * Appender for logback in charge of sending the logged events to a Sentry server. */ public class SentryAppender extends AppenderBase { + private static final String LOGBACK_MARKER = "logback-Marker"; private final boolean propagateClose; private Raven raven; private String dsn; @@ -117,7 +118,7 @@ private Event buildEvent(ILoggingEvent iLoggingEvent) { } if (iLoggingEvent.getMarker() != null) { - eventBuilder.addExtra(Marker.class.getName(), iLoggingEvent.getMarker()); + eventBuilder.addExtra(LOGBACK_MARKER, iLoggingEvent.getMarker()); } raven.runBuilderHelpers(eventBuilder); From 652f63f308fc07d4fc1998b9596007d604ba77b8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 19:06:55 +0100 Subject: [PATCH 0630/2152] Use same naming convention as in Log4j 2 --- .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index a4239419ffa..d0b19dcc646 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -20,7 +20,7 @@ * Appender for log4j in charge of sending the logged events to a Sentry server. */ public class SentryAppender extends AppenderSkeleton { - private static final String LOG4J_NDC = "Log4J-NDC"; + private static final String LOG4J_NDC = "log4J-NDC"; private final boolean propagateClose; private Raven raven; private String dsn; From b85dba6e685afbd44d80dcbcd43f5032189da7b5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 23 Jun 2013 20:25:35 +0100 Subject: [PATCH 0631/2152] Externalise the JNDI DSN lookup --- .../main/java/net/kencochrane/raven/Dsn.java | 16 ++----- .../net/kencochrane/raven/JndiLookup.java | 47 +++++++++++++++++++ 2 files changed, 51 insertions(+), 12 deletions(-) create mode 100644 raven/src/main/java/net/kencochrane/raven/JndiLookup.java diff --git a/raven/src/main/java/net/kencochrane/raven/Dsn.java b/raven/src/main/java/net/kencochrane/raven/Dsn.java index cff0848bf30..3826bf79b3e 100644 --- a/raven/src/main/java/net/kencochrane/raven/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/Dsn.java @@ -22,10 +22,6 @@ public class Dsn { * Name of the environment or system variable containing the DSN. */ public static final String DSN_VARIABLE = "SENTRY_DSN"; - /** - * Lookup name for the DSN in JNDI. - */ - private static final String JNDI_DSN_NAME = "java:comp/env/sentry/dsn"; private static final Logger logger = LoggerFactory.getLogger(Raven.class); private String secretKey; private String publicKey; @@ -86,14 +82,10 @@ public static String dsnLookup() { // Try to obtain the DSN from JNDI try { - Context c = new InitialContext(); - dsn = (String) c.lookup(JNDI_DSN_NAME); - } catch (NoInitialContextException e) { - logger.trace("JNDI not configured for sentry (NoInitialContextEx)"); - } catch (NamingException e) { - logger.trace("No /sentry/dsn in JNDI"); - } catch (RuntimeException ex) { - logger.warn("Odd RuntimeException while testing for JNDI", ex); + Class.forName("javax.naming.InitialContext", false, Dsn.class.getClassLoader()); + dsn = JndiLookup.jndiLookup(); + } catch (ClassNotFoundException e) { + logger.trace("JNDI not available"); } // Try to obtain the DSN from a System Environment Variable diff --git a/raven/src/main/java/net/kencochrane/raven/JndiLookup.java b/raven/src/main/java/net/kencochrane/raven/JndiLookup.java new file mode 100644 index 00000000000..f4b438e743d --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/JndiLookup.java @@ -0,0 +1,47 @@ +package net.kencochrane.raven; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.naming.NoInitialContextException; + +/** + * JNDI Lookup allows to do a JNDI lookup without tying {@link Dsn} to the JNDI libraries. + *

    + * Android does not support JNDI making the automatic lookup through JNDI illegal and fatal to the application. + * Having the lookup in a separate class allows the classloader to load {@link Dsn} without exceptions. + *

    + */ +final class JndiLookup { + /** + * Lookup name for the DSN in JNDI. + */ + private static final String JNDI_DSN_NAME = "java:comp/env/sentry/dsn"; + private static final Logger logger = LoggerFactory.getLogger(JndiLookup.class); + + private JndiLookup() { + } + + /** + * Looks up for a JNDI definition of the DSN. + * + * @return the DSN defined in JNDI or null if it isn't defined. + */ + public static String jndiLookup() { + String dsn = null; + try { + Context c = new InitialContext(); + dsn = (String) c.lookup(JNDI_DSN_NAME); + } catch (NoInitialContextException e) { + logger.trace("JNDI not configured for sentry (NoInitialContextEx)"); + } catch (NamingException e) { + logger.trace("No /sentry/dsn in JNDI"); + } catch (RuntimeException e) { + logger.warn("Odd RuntimeException while testing for JNDI", e); + } + return dsn; + } +} From 2575b0c5b1f69100f0bc500817caab94cfb39db7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 23 Jun 2013 20:25:57 +0100 Subject: [PATCH 0632/2152] Do not catch every exception when registering HttpEventBuilderHelper --- .../main/java/net/kencochrane/raven/DefaultRavenFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 064af811097..f5ccc703362 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -70,7 +70,7 @@ protected Raven createRavenInstance(Dsn dsn) { Class.forName("javax.servlet.Servlet", false, this.getClass().getClassLoader()); //TODO: Is it enough? Shouldn't it look for Servlet >= 3.0 ? raven.addBuilderHelper(new HttpEventBuilderHelper()); - } catch (Exception e) { + } catch (ClassNotFoundException e) { logger.trace("It seems that the current environment doesn't provide access to servlets."); } return raven; From 618a2d8490386b71d39cf73b80dedb760fcefa1a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 23 Jun 2013 20:26:58 +0100 Subject: [PATCH 0633/2152] Move ConnectionException in the connection package --- .../net/kencochrane/raven/connection/AbstractConnection.java | 1 - .../raven/{exception => connection}/ConnectionException.java | 2 +- .../java/net/kencochrane/raven/connection/HttpConnection.java | 1 - .../java/net/kencochrane/raven/connection/UdpConnection.java | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) rename raven/src/main/java/net/kencochrane/raven/{exception => connection}/ConnectionException.java (93%) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 6e93ccfb744..faec2bcb48f 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -2,7 +2,6 @@ import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.exception.ConnectionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/raven/src/main/java/net/kencochrane/raven/exception/ConnectionException.java b/raven/src/main/java/net/kencochrane/raven/connection/ConnectionException.java similarity index 93% rename from raven/src/main/java/net/kencochrane/raven/exception/ConnectionException.java rename to raven/src/main/java/net/kencochrane/raven/connection/ConnectionException.java index 68276c3d1d3..e297830f7da 100644 --- a/raven/src/main/java/net/kencochrane/raven/exception/ConnectionException.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/ConnectionException.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven.exception; +package net.kencochrane.raven.connection; /** * Exception thrown by a {@link net.kencochrane.raven.connection.Connection} if something went wrong temporarily. diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index a2ca33948d1..0096ae564ce 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -2,7 +2,6 @@ import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.exception.ConnectionException; import net.kencochrane.raven.marshaller.Marshaller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index e16b8095677..e49c051ff94 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -1,7 +1,6 @@ package net.kencochrane.raven.connection; import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.exception.ConnectionException; import net.kencochrane.raven.marshaller.Marshaller; import java.io.ByteArrayOutputStream; From 1b11978eca3665f8a48f702f0a6c51164aa5fea8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 23 Jun 2013 20:32:45 +0100 Subject: [PATCH 0634/2152] Move DSN related classes in the dsn package --- .../net/kencochrane/raven/log4j/SentryAppender.java | 2 +- .../net/kencochrane/raven/log4j2/SentryAppender.java | 2 +- .../net/kencochrane/raven/logback/SentryAppender.java | 2 +- .../net/kencochrane/raven/DefaultRavenFactory.java | 1 + raven/src/main/java/net/kencochrane/raven/Raven.java | 5 +++-- .../main/java/net/kencochrane/raven/RavenFactory.java | 2 ++ .../main/java/net/kencochrane/raven/{ => dsn}/Dsn.java | 10 +++------- .../raven/{exception => dsn}/InvalidDsnException.java | 2 +- .../net/kencochrane/raven/{ => dsn}/JndiLookup.java | 2 +- .../java/net/kencochrane/raven/jul/SentryHandler.java | 2 +- .../raven/connection/HttpConnectionTest.java | 2 +- .../java/net/kencochrane/raven/{ => dsn}/DsnTest.java | 3 +-- .../raven/{ => dsn}/InitialContextMockFactory.java | 2 +- 13 files changed, 18 insertions(+), 19 deletions(-) rename raven/src/main/java/net/kencochrane/raven/{ => dsn}/Dsn.java (96%) rename raven/src/main/java/net/kencochrane/raven/{exception => dsn}/InvalidDsnException.java (89%) rename raven/src/main/java/net/kencochrane/raven/{ => dsn}/JndiLookup.java (97%) rename raven/src/test/java/net/kencochrane/raven/{ => dsn}/DsnTest.java (98%) rename raven/src/test/java/net/kencochrane/raven/{ => dsn}/InitialContextMockFactory.java (93%) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index d0b19dcc646..b51b84588e6 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -1,6 +1,6 @@ package net.kencochrane.raven.log4j; -import net.kencochrane.raven.Dsn; +import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.event.Event; diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 8dd1c224bfa..586e038c969 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -1,6 +1,6 @@ package net.kencochrane.raven.log4j2; -import net.kencochrane.raven.Dsn; +import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.event.Event; diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index d37a0faab0e..7e19db3d56d 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -4,7 +4,7 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.ThrowableProxy; import ch.qos.logback.core.AppenderBase; -import net.kencochrane.raven.Dsn; +import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.event.Event; diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index f5ccc703362..b2dd6e45be5 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -4,6 +4,7 @@ import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.connection.HttpConnection; import net.kencochrane.raven.connection.UdpConnection; +import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.helper.HttpEventBuilderHelper; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.HttpInterface; diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 8000c8f4293..863da2f5632 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -14,8 +14,9 @@ /** * Raven is a client for Sentry allowing to send an {@link Event} that will be processed and sent to a Sentry server. *

    - * It is recommended to create an instance of Raven through {@link RavenFactory#createRavenInstance(Dsn)}, this - * will use the best factory available to create a sensible instance of Raven. + * It is recommended to create an instance of Raven through + * {@link RavenFactory#createRavenInstance(net.kencochrane.raven.dsn.Dsn)}, this will use the best factory available to + * create a sensible instance of Raven. *

    */ public class Raven { diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index 4648ab5b325..97f99abd9de 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -1,5 +1,7 @@ package net.kencochrane.raven; +import net.kencochrane.raven.dsn.Dsn; + import java.util.ServiceLoader; /** diff --git a/raven/src/main/java/net/kencochrane/raven/Dsn.java b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java similarity index 96% rename from raven/src/main/java/net/kencochrane/raven/Dsn.java rename to raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java index 3826bf79b3e..af792f25c10 100644 --- a/raven/src/main/java/net/kencochrane/raven/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java @@ -1,13 +1,9 @@ -package net.kencochrane.raven; +package net.kencochrane.raven.dsn; -import net.kencochrane.raven.exception.InvalidDsnException; +import net.kencochrane.raven.Raven; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.naming.Context; -import javax.naming.InitialContext; -import javax.naming.NamingException; -import javax.naming.NoInitialContextException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; @@ -47,7 +43,7 @@ public Dsn() { * @param dsn dsn in a string form. * @throws InvalidDsnException the given DSN isn't usable. */ - public Dsn(String dsn) { + public Dsn(String dsn) throws InvalidDsnException { if (dsn == null) throw new InvalidDsnException("The sentry DSN must be provided and not be null"); diff --git a/raven/src/main/java/net/kencochrane/raven/exception/InvalidDsnException.java b/raven/src/main/java/net/kencochrane/raven/dsn/InvalidDsnException.java similarity index 89% rename from raven/src/main/java/net/kencochrane/raven/exception/InvalidDsnException.java rename to raven/src/main/java/net/kencochrane/raven/dsn/InvalidDsnException.java index 1a76b60d26a..2584fbb2550 100644 --- a/raven/src/main/java/net/kencochrane/raven/exception/InvalidDsnException.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/InvalidDsnException.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven.exception; +package net.kencochrane.raven.dsn; public class InvalidDsnException extends RuntimeException { public InvalidDsnException() { diff --git a/raven/src/main/java/net/kencochrane/raven/JndiLookup.java b/raven/src/main/java/net/kencochrane/raven/dsn/JndiLookup.java similarity index 97% rename from raven/src/main/java/net/kencochrane/raven/JndiLookup.java rename to raven/src/main/java/net/kencochrane/raven/dsn/JndiLookup.java index f4b438e743d..2d978486766 100644 --- a/raven/src/main/java/net/kencochrane/raven/JndiLookup.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/JndiLookup.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven; +package net.kencochrane.raven.dsn; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 3141c1f140f..c96dec25646 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -1,6 +1,6 @@ package net.kencochrane.raven.jul; -import net.kencochrane.raven.Dsn; +import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.event.Event; diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index 4b015894335..a5da1c118ad 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -1,6 +1,6 @@ package net.kencochrane.raven.connection; -import net.kencochrane.raven.Dsn; +import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; diff --git a/raven/src/test/java/net/kencochrane/raven/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java similarity index 98% rename from raven/src/test/java/net/kencochrane/raven/DsnTest.java rename to raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java index 3010c0b22c1..f8108b4a656 100644 --- a/raven/src/test/java/net/kencochrane/raven/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java @@ -1,6 +1,5 @@ -package net.kencochrane.raven; +package net.kencochrane.raven.dsn; -import net.kencochrane.raven.exception.InvalidDsnException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/raven/src/test/java/net/kencochrane/raven/InitialContextMockFactory.java b/raven/src/test/java/net/kencochrane/raven/dsn/InitialContextMockFactory.java similarity index 93% rename from raven/src/test/java/net/kencochrane/raven/InitialContextMockFactory.java rename to raven/src/test/java/net/kencochrane/raven/dsn/InitialContextMockFactory.java index 5d0872e43a0..abd76db5be2 100644 --- a/raven/src/test/java/net/kencochrane/raven/InitialContextMockFactory.java +++ b/raven/src/test/java/net/kencochrane/raven/dsn/InitialContextMockFactory.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven; +package net.kencochrane.raven.dsn; import javax.naming.Context; import javax.naming.NamingException; From 5030b59ecf41b502f253be5a01badc4463f10043 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 23 Jun 2013 22:28:59 +0100 Subject: [PATCH 0635/2152] Add the possibility to register factories manually --- .../net/kencochrane/raven/RavenFactory.java | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index 97f99abd9de..494ff6c0de4 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -2,6 +2,8 @@ import net.kencochrane.raven.dsn.Dsn; +import java.util.Collection; +import java.util.LinkedList; import java.util.ServiceLoader; /** @@ -11,7 +13,12 @@ *

    */ public abstract class RavenFactory { - private static final ServiceLoader RAVEN_FACTORIES = ServiceLoader.load(RavenFactory.class); + private static final ServiceLoader AUTO_REGISTERED_FACTORIES = ServiceLoader.load(RavenFactory.class); + private static final Collection MANUALLY_REGISTERED_FACTORIES = new LinkedList(); + + public static void registerFactory(RavenFactory ravenFactory) { + MANUALLY_REGISTERED_FACTORIES.add(ravenFactory); + } public static Raven ravenInstance() { return ravenInstance(Dsn.dsnLookup()); @@ -22,7 +29,14 @@ public static Raven ravenInstance(String dsn) { } public static Raven ravenInstance(Dsn dsn) { - for (RavenFactory ravenFactory : RAVEN_FACTORIES) { + for (RavenFactory ravenFactory : MANUALLY_REGISTERED_FACTORIES) { + Raven raven = ravenFactory.createRavenInstance(dsn); + if (raven != null) { + return raven; + } + } + + for (RavenFactory ravenFactory : AUTO_REGISTERED_FACTORIES) { Raven raven = ravenFactory.createRavenInstance(dsn); if (raven != null) { return raven; @@ -36,7 +50,17 @@ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { if (ravenFactoryName == null) return ravenInstance(dsn); - for (RavenFactory ravenFactory : RAVEN_FACTORIES) { + for (RavenFactory ravenFactory : MANUALLY_REGISTERED_FACTORIES) { + if (!ravenFactoryName.equals(ravenFactory.getClass().getName())) + continue; + + Raven raven = ravenFactory.createRavenInstance(dsn); + if (raven != null) { + return raven; + } + } + + for (RavenFactory ravenFactory : AUTO_REGISTERED_FACTORIES) { if (!ravenFactoryName.equals(ravenFactory.getClass().getName())) continue; From 7f6a2b12cb32f71fe61982d23636383cd7cfd3b0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 23 Jun 2013 23:34:55 +0100 Subject: [PATCH 0636/2152] Allow MessageInterface params to be sent as an array/vararg --- .../kencochrane/raven/event/interfaces/MessageInterface.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java index 10c3b415244..8a767dfb678 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java @@ -1,6 +1,7 @@ package net.kencochrane.raven.event.interfaces; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -30,6 +31,10 @@ public MessageInterface(String message) { this(message, Collections.emptyList()); } + public MessageInterface(String message, String... params) { + this(message, Arrays.asList(params)); + } + public MessageInterface(String message, List params) { this.message = message; this.params = Collections.unmodifiableList(new ArrayList(params)); From 876ac925ce45b2eeef9d931f1143fc6b40b472cd Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 27 Jun 2013 00:53:13 +0100 Subject: [PATCH 0637/2152] Add simplified API in Raven --- .../java/net/kencochrane/raven/Raven.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 863da2f5632..29e0dcff65e 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -4,6 +4,7 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.helper.EventBuilderHelper; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,6 +64,37 @@ public void sendEvent(Event event) { } } + /** + * Sends a message to the Sentry server. + *

    + * The message will be logged at the {@link Event.Level#INFO} level. + *

    + * + * @param message message to send to Sentry. + */ + public void sendMessage(String message) { + EventBuilder eventBuilder = new EventBuilder().setMessage(message) + .setLevel(Event.Level.INFO); + runBuilderHelpers(eventBuilder); + sendEvent(eventBuilder.build()); + } + + /** + * Sends an exception to the Sentry server. + *

    + * The Exception will be logged at the {@link Event.Level#ERROR} level. + *

    + * + * @param exception exception to send to Sentry. + */ + public void sendException(Exception exception) { + EventBuilder eventBuilder = new EventBuilder().setMessage(exception.getMessage()) + .setLevel(Event.Level.ERROR) + .addSentryInterface(new ExceptionInterface(exception)); + runBuilderHelpers(eventBuilder); + sendEvent(eventBuilder.build()); + } + /** * Removes a builder helper. * From 185415c2bd2e82f495914c9ebebe9a66595b9056 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 27 Jun 2013 12:46:42 +0100 Subject: [PATCH 0638/2152] Add tests for sendMessage and sendException --- .../java/net/kencochrane/raven/RavenTest.java | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index 68456e36519..0acd6f90fc1 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -4,15 +4,18 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.helper.EventBuilderHelper; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import java.util.UUID; + import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.*; import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) @@ -38,6 +41,30 @@ public void testSendEvent() throws Exception { verify(mockConnection).send(mockEvent); } + @Test + public void testSendMessage() throws Exception { + String message = UUID.randomUUID().toString(); + ArgumentCaptor eventArgumentCaptor = ArgumentCaptor.forClass(Event.class); + raven.sendMessage(message); + + verify(mockConnection).send(eventArgumentCaptor.capture()); + assertThat(eventArgumentCaptor.getValue().getLevel(), equalTo(Event.Level.INFO)); + assertThat(eventArgumentCaptor.getValue().getMessage(), equalTo(message)); + } + + @Test + public void testSendException() throws Exception { + String message = UUID.randomUUID().toString(); + Exception exception = new Exception(message); + ArgumentCaptor eventArgumentCaptor = ArgumentCaptor.forClass(Event.class); + raven.sendException(exception); + + verify(mockConnection).send(eventArgumentCaptor.capture()); + assertThat(eventArgumentCaptor.getValue().getLevel(), equalTo(Event.Level.ERROR)); + assertThat(eventArgumentCaptor.getValue().getMessage(), equalTo(message)); + assertThat(eventArgumentCaptor.getValue().getSentryInterfaces(), hasKey(ExceptionInterface.EXCEPTION_INTERFACE)); + } + @Test public void testChangeConnection() throws Exception { Connection mockNewConnection = mock(Connection.class); From 9281f4795761f0d14eea0fb62e71e12bb1dec180 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 3 Jul 2013 08:17:22 +0100 Subject: [PATCH 0639/2152] Instanciate the raven object after the appender initialisation --- .../raven/log4j2/SentryAppender.java | 41 +++++++++++-------- .../raven/logback/SentryAppender.java | 41 +++++++++++-------- 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 586e038c969..b044b92817c 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -1,14 +1,13 @@ package net.kencochrane.raven.log4j2; -import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AbstractAppender; @@ -108,31 +107,37 @@ private static String formatCulprit(StackTraceElement stackTraceElement) { + " at line " + stackTraceElement.getLineNumber(); } - @Override - public void start() { - try { - if (raven == null) { - if (dsn == null) - dsn = Dsn.dsnLookup(); - - raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); - } - super.start(); - } catch (Exception e) { - error("An exception occurred during the creation of a raven instance", e); - } - } - @Override public void append(LogEvent logEvent) { + // Do not log the event if the current thread has been spawned by raven if (Raven.RAVEN_THREAD.get()) return; + if (raven == null) + startRaven(); Event event = buildEvent(logEvent); raven.sendEvent(event); } + /** + * Initialises the Raven instance. + *

    + * The raven instance is set in this method instead of {@link #start()} in order to avoid substitute loggers + * being generated during the instantiation of {@link Raven}.
    + *

    + */ + private void startRaven() { + try { + if (dsn == null) + dsn = Dsn.dsnLookup(); + + raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + } catch (Exception e) { + error("An exception occurred during the creation of a raven instance", e); + } + } + private Event buildEvent(LogEvent event) { Message eventMessage = event.getMessage(); EventBuilder eventBuilder = new EventBuilder() @@ -200,7 +205,7 @@ public void stop() { super.stop(); try { - if (propagateClose) + if (propagateClose && raven != null) raven.getConnection().close(); } catch (IOException e) { error("An exception occurred while closing the raven connection", e); diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 7e19db3d56d..e20f6c160d7 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -4,14 +4,13 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.ThrowableProxy; import ch.qos.logback.core.AppenderBase; -import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; -import org.slf4j.Marker; import java.io.IOException; import java.util.ArrayList; @@ -62,31 +61,37 @@ private static Event.Level formatLevel(Level level) { } else return null; } - @Override - public void start() { - try { - if (raven == null) { - if (dsn == null) - dsn = Dsn.dsnLookup(); - - raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); - } - super.start(); - } catch (Exception e) { - addError("An exception occurred during the creation of a raven instance", e); - } - } - @Override protected void append(ILoggingEvent iLoggingEvent) { // Do not log the event if the current thread has been spawned by raven if (Raven.RAVEN_THREAD.get()) return; + if (raven == null) + startRaven(); Event event = buildEvent(iLoggingEvent); raven.sendEvent(event); } + /** + * Initialises the Raven instance. + *

    + * The raven instance is set in this method instead of {@link #start()} in order to avoid substitute loggers + * being generated during the instantiation of {@link Raven}.
    + * More on www.slf4j.org/codes.html#substituteLogger + *

    + */ + private void startRaven() { + try { + if (dsn == null) + dsn = Dsn.dsnLookup(); + + raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + } catch (Exception e) { + addError("An exception occurred during the creation of a raven instance", e); + } + } + private Event buildEvent(ILoggingEvent iLoggingEvent) { EventBuilder eventBuilder = new EventBuilder() .setTimestamp(new Date(iLoggingEvent.getTimeStamp())) @@ -149,7 +154,7 @@ public void stop() { super.stop(); try { - if (propagateClose) + if (propagateClose && raven != null) raven.getConnection().close(); } catch (IOException e) { addError("An exception occurred while closing the raven connection", e); From 94d8b4708e0d2404ff08c287115a0a2da455c40b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 3 Jul 2013 08:18:32 +0100 Subject: [PATCH 0640/2152] Redirect raven logging during tests --- pom.xml | 15 +++++++++++++++ raven-log4j/pom.xml | 5 +++++ raven-log4j2/pom.xml | 5 +++++ raven/pom.xml | 5 +++++ 4 files changed, 30 insertions(+) diff --git a/pom.xml b/pom.xml index 87b1cd8c656..f7c6afe0f1a 100644 --- a/pom.xml +++ b/pom.xml @@ -136,6 +136,16 @@ slf4j-api ${slf4j.version} + + org.slf4j + slf4j-log4j12 + ${slf4j.version} + + + org.slf4j + slf4j-jdk14 + ${slf4j.version} + commons-codec commons-codec @@ -181,6 +191,11 @@ log4j-core ${log4j2.version} + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4j2.version} + org.mockito mockito-core diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 9c5f1d2c5e2..e6ad2838b2f 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -30,6 +30,11 @@ test + + org.slf4j + slf4j-log4j12 + test + com.fasterxml.jackson.core jackson-databind diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 1c65994bba8..3885d4b3959 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -34,6 +34,11 @@ test-jar test + + org.apache.logging.log4j + log4j-slf4j-impl + test + com.fasterxml.jackson.core diff --git a/raven/pom.xml b/raven/pom.xml index 87c02ef7849..e5fcf049cc9 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -33,6 +33,11 @@ provided true + + org.slf4j + slf4j-jdk14 + test + com.fasterxml.jackson.core jackson-databind From 0f351c0dda8dd787f857b0b0a7d7cb7e20dad0ac Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 3 Jul 2013 08:20:15 +0100 Subject: [PATCH 0641/2152] Fix imports --- .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 2 +- .../src/main/java/net/kencochrane/raven/jul/SentryHandler.java | 2 +- raven/src/test/java/net/kencochrane/raven/SentryStub.java | 1 - .../net/kencochrane/raven/connection/HttpConnectionTest.java | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index b51b84588e6..6d8df654f74 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -1,8 +1,8 @@ package net.kencochrane.raven.log4j; -import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index c96dec25646..41ec2d41701 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -1,8 +1,8 @@ package net.kencochrane.raven.jul; -import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; diff --git a/raven/src/test/java/net/kencochrane/raven/SentryStub.java b/raven/src/test/java/net/kencochrane/raven/SentryStub.java index dcc7d7d5f9b..d3964ba437c 100644 --- a/raven/src/test/java/net/kencochrane/raven/SentryStub.java +++ b/raven/src/test/java/net/kencochrane/raven/SentryStub.java @@ -4,7 +4,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index a5da1c118ad..37815dc5ba2 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -1,7 +1,7 @@ package net.kencochrane.raven.connection; -import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.Raven; +import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.marshaller.Marshaller; From b47b9dfe255653333e0a82ad2068c9ed96498a61 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Jul 2013 08:12:48 +0100 Subject: [PATCH 0642/2152] Limit the amount of time spent looking for the localhost name. --- .../kencochrane/raven/event/EventBuilder.java | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index ce410550285..26e8b9db11d 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -1,12 +1,14 @@ package net.kencochrane.raven.event; import net.kencochrane.raven.event.interfaces.SentryInterface; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.net.InetAddress; -import java.net.UnknownHostException; import java.util.Collections; import java.util.Date; import java.util.UUID; +import java.util.concurrent.*; import java.util.zip.CRC32; import java.util.zip.Checksum; @@ -287,7 +289,9 @@ public Event build() { *

    * The {@code InetAddress.getLocalHost().getCanonicalHostName()} call can be quite expensive and could be called * for the creation of each {@link Event}. This system will prevent unnecessary costs by keeping track of the - * hostname for a period defined during the construction. + * hostname for a period defined during the construction.
    + * For performance purposes, the operation of retrieving the hostname will automatically fail after a period of time + * defined by {@link #GET_HOSTNAME_TIMEOUT} without result. *

    */ private static final class HostnameCache { @@ -295,6 +299,11 @@ private static final class HostnameCache { * Default hostname if it isn't set manually (or can't be determined). */ public static final String DEFAULT_HOSTNAME = "unavailable"; + /** + * Time before the get hostname operation times out (in ms). + */ + public static final int GET_HOSTNAME_TIMEOUT = 1000; + private static final Logger logger = LoggerFactory.getLogger(HostnameCache.class); /** * Time for which the cache is kept. */ @@ -338,12 +347,24 @@ public String getHostname() { */ public void updateCache() { expirationTimestamp = System.currentTimeMillis() + cacheDuration; + Future future = Executors.newSingleThreadExecutor().submit(new HostRetriever()); try { - hostname = InetAddress.getLocalHost().getCanonicalHostName(); - } catch (UnknownHostException e) { + hostname = future.get(GET_HOSTNAME_TIMEOUT, TimeUnit.MILLISECONDS); + } catch (Exception e) { + logger.warn("Localhost hostname lookup failed, defaulting back to '{}'", DEFAULT_HOSTNAME, e); hostname = DEFAULT_HOSTNAME; } } + + /** + * Task retrieving the current hostname. + */ + private static final class HostRetriever implements Callable { + @Override + public String call() throws Exception { + return InetAddress.getLocalHost().getCanonicalHostName(); + } + } } } From 272ba8560c14a96cfbb45e03abe6577aaca22efd Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Jul 2013 11:14:54 +0100 Subject: [PATCH 0643/2152] Move from commons-codec to guava --- pom.xml | 8 ++++---- raven/pom.xml | 4 ++-- .../raven/marshaller/json/JsonMarshaller.java | 7 +++++-- sentry-stub/pom.xml | 4 ++-- .../raven/sentrystub/unmarshaller/JsonDecoder.java | 11 ++++++----- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/pom.xml b/pom.xml index f7c6afe0f1a..13fb716a6eb 100644 --- a/pom.xml +++ b/pom.xml @@ -106,7 +106,7 @@ 1.7.5 - 1.8 + 14.0.1 2.2.2 3.0.1 1.0.13 @@ -147,9 +147,9 @@ ${slf4j.version}
    - commons-codec - commons-codec - ${commons-codec.version} + com.google.guava + guava + ${guava.version} com.fasterxml.jackson.core diff --git a/raven/pom.xml b/raven/pom.xml index e5fcf049cc9..c89b4b7774a 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -20,8 +20,8 @@ slf4j-api - commons-codec - commons-codec + com.google.guava + guava com.fasterxml.jackson.core diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 8983b7d4f6f..fb9ec088acd 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -2,20 +2,23 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; +import com.google.common.io.BaseEncoding; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.interfaces.SentryInterface; import net.kencochrane.raven.marshaller.Marshaller; -import org.apache.commons.codec.binary.Base64OutputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; import java.util.zip.DeflaterOutputStream; +import static com.google.common.io.BaseEncoding.base64; + public class JsonMarshaller implements Marshaller { /** * Hexadecimal string representing a uuid4 value. @@ -92,7 +95,7 @@ public void marshall(Event event, OutputStream destination) { destination = new UncloseableOutputStream(destination); if (compression) - destination = new DeflaterOutputStream(new Base64OutputStream(destination)); + destination = new DeflaterOutputStream(base64().encodingStream(new OutputStreamWriter(destination))); JsonGenerator generator = null; try { diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 1cf30bc7cf8..ab8c4da5a3b 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -16,8 +16,8 @@ - commons-codec - commons-codec + com.google.guava + guava com.fasterxml.jackson.core diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java index 890f96511ce..ca75045f5a3 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java @@ -2,15 +2,17 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import org.apache.commons.codec.binary.Base64InputStream; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.InflaterInputStream; +import static com.google.common.io.BaseEncoding.base64; + /** * Decodes a Stream as a JSON stream. *

    @@ -42,14 +44,13 @@ public InputStream decapsulateContent(InputStream originalStream) throws IOExcep originalStream = new Uncloseable(new BufferedInputStream(originalStream)); originalStream.mark(messageSize); InputStream inputStream = originalStream; - if (!isJson(originalStream)) { - inputStream = new Base64InputStream(inputStream); + inputStream = base64().decodingStream(new InputStreamReader(inputStream)); originalStream.reset(); - if (!isJson(new Base64InputStream(originalStream))) { + if (!isJson(base64().decodingStream(new InputStreamReader(originalStream)))) { inputStream = new InflaterInputStream(inputStream); originalStream.reset(); - if (!isJson(new InflaterInputStream(new Base64InputStream(originalStream)))) { + if (!isJson(new InflaterInputStream(base64().decodingStream(new InputStreamReader(originalStream))))) { throw new IllegalArgumentException("The given Stream is neither JSON, Base64'd JSON " + "nor Base64'd deflated JSON."); } From c9b2de1a262b23bbb93c06a90adae7d94ebb3e7a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Jul 2013 11:36:58 +0100 Subject: [PATCH 0644/2152] Avoid NPE when the argument is null --- .../main/java/net/kencochrane/raven/logback/SentryAppender.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index e20f6c160d7..1a20bd1f0f4 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -44,7 +44,7 @@ public SentryAppender(Raven raven, boolean propagateClose) { private static List formatArguments(Object[] argumentArray) { List arguments = new ArrayList(argumentArray.length); for (Object argument : argumentArray) { - arguments.add(argument.toString()); + arguments.add((argument != null) ? argument.toString() : null); } return arguments; } From c2257be50059ec462d9c0ccac25b5d66f25ae6ad Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Jul 2013 12:06:21 +0100 Subject: [PATCH 0645/2152] Deduplicate the ravenFactory code to generate instances --- .../net/kencochrane/raven/RavenFactory.java | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index 494ff6c0de4..a3fce9441a8 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -29,29 +29,12 @@ public static Raven ravenInstance(String dsn) { } public static Raven ravenInstance(Dsn dsn) { - for (RavenFactory ravenFactory : MANUALLY_REGISTERED_FACTORIES) { - Raven raven = ravenFactory.createRavenInstance(dsn); - if (raven != null) { - return raven; - } - } - - for (RavenFactory ravenFactory : AUTO_REGISTERED_FACTORIES) { - Raven raven = ravenFactory.createRavenInstance(dsn); - if (raven != null) { - return raven; - } - } - - throw new IllegalStateException("Couldn't create a raven instance for '" + dsn + "'"); + return ravenInstance(dsn, null); } public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { - if (ravenFactoryName == null) - return ravenInstance(dsn); - for (RavenFactory ravenFactory : MANUALLY_REGISTERED_FACTORIES) { - if (!ravenFactoryName.equals(ravenFactory.getClass().getName())) + if (ravenFactoryName != null && !ravenFactoryName.equals(ravenFactory.getClass().getName())) continue; Raven raven = ravenFactory.createRavenInstance(dsn); @@ -61,7 +44,7 @@ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { } for (RavenFactory ravenFactory : AUTO_REGISTERED_FACTORIES) { - if (!ravenFactoryName.equals(ravenFactory.getClass().getName())) + if (ravenFactoryName != null && !ravenFactoryName.equals(ravenFactory.getClass().getName())) continue; Raven raven = ravenFactory.createRavenInstance(dsn); From 3f4dda049107266b2327e8148e3bc55575afd2ac Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Jul 2013 12:07:16 +0100 Subject: [PATCH 0646/2152] Ensure that a parameter can be null --- .../main/java/net/kencochrane/raven/log4j2/SentryAppender.java | 2 +- .../src/main/java/net/kencochrane/raven/jul/SentryHandler.java | 2 +- .../src/test/java/net/kencochrane/raven/AbstractLoggerTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index b044b92817c..e4864f2f454 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -188,7 +188,7 @@ private Event buildEvent(LogEvent event) { private List formatMessageParameters(Object[] parameters) { List stringParameters = new ArrayList(parameters.length); for (Object parameter : parameters) - stringParameters.add(parameter.toString()); + stringParameters.add((parameter != null) ? parameter.toString() : null); return stringParameters; } diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 41ec2d41701..0a563443cc5 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -50,7 +50,7 @@ else if (level.intValue() >= Level.ALL.intValue()) private static List formatParameters(Object[] parameters) { List formattedParameters = new ArrayList(parameters.length); for (Object parameter : parameters) - formattedParameters.add(parameter.toString()); + formattedParameters.add((parameter != null) ? parameter.toString() : null); return formattedParameters; } diff --git a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java index a556a110c35..72832c2661f 100644 --- a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java @@ -86,7 +86,7 @@ public void testLogException() throws Exception { public void testLogParametrisedMessage() throws Exception { ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); String message = "Some content %s"; - List parameters = Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()); + List parameters = Arrays.asList(null, UUID.randomUUID().toString(), UUID.randomUUID().toString()); Event event; logAnyLevel(message, parameters); From ffb7a7a766ec890c64bae1be1a9d1d61dcb81ac7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Jul 2013 12:07:57 +0100 Subject: [PATCH 0647/2152] Add todos --- .../src/main/java/net/kencochrane/raven/DefaultRavenFactory.java | 1 + .../kencochrane/raven/marshaller/json/HttpInterfaceBinding.java | 1 + 2 files changed, 2 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index b2dd6e45be5..92e4a33a80f 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -28,6 +28,7 @@ *

    */ public class DefaultRavenFactory extends RavenFactory { + //TODO: Add support for tags set by default /** * Protocol setting to disable security checks over an SSL connection. */ diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java index 02b2195f591..c5fde78b6e0 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java @@ -78,6 +78,7 @@ private void writeHeaders(JsonGenerator generator, HttpServletRequest request) t } private void writeCookies(JsonGenerator generator, Cookie[] cookies) throws IOException { + //TODO: Cookies shouldn't be sent by default if (cookies == null) { generator.writeNull(); return; From 0ca66d40c20dc02de0b9fba88ebab0256aba27d8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Jul 2013 16:45:22 +0100 Subject: [PATCH 0648/2152] Fix raven initialisation in JUL --- .../kencochrane/raven/jul/SentryHandler.java | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 0a563443cc5..3bf754592b3 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -63,21 +63,29 @@ public synchronized void publish(LogRecord record) { try { guard = true; - if (raven == null) { - try { - start(); - } catch (Exception e) { - reportError("An exception occurred while creating an instance of raven", e, ErrorManager.OPEN_FAILURE); - return; - } - } - - raven.sendEvent(buildEvent(record)); + if (raven == null) + initRaven(); + Event event = buildEvent(record); + raven.sendEvent(event); } finally { guard = false; } } + /** + * Initialises the Raven instance. + */ + protected void initRaven() { + try { + if (dsn == null) + dsn = Dsn.dsnLookup(); + + raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + } catch (Exception e) { + reportError("An exception occurred during the creation of a raven instance", e, ErrorManager.OPEN_FAILURE); + } + } + private Event buildEvent(LogRecord record) { EventBuilder eventBuilder = new EventBuilder() .setLevel(getLevel(record.getLevel())) @@ -107,20 +115,6 @@ private Event buildEvent(LogRecord record) { return eventBuilder.build(); } - private void start() { - if (raven != null) - return; - - LogManager manager = LogManager.getLogManager(); - String dsn = manager.getProperty(SentryHandler.class.getName() + ".dsn"); - String ravenFactory = manager.getProperty(SentryHandler.class.getName() + ".ravenFactory"); - - if (dsn == null) - dsn = Dsn.dsnLookup(); - - raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); - } - @Override public void flush() { } From 2a3dca88112317dafa52a6f40b9f3ab1eb1185a5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Jul 2013 16:46:37 +0100 Subject: [PATCH 0649/2152] Fix raven initialisation in Log4j --- .../raven/log4j/SentryAppender.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 6d8df654f74..dec83dc5a5a 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -62,14 +62,20 @@ private static StackTraceElement asStackTraceElement(LocationInfo location) { @Override public void activateOptions() { + super.activateOptions(); + if (raven == null) + initRaven(); + } + + /** + * Initialises the Raven instance. + */ + protected void initRaven() { try { - if (raven == null) { - if (dsn == null) - dsn = Dsn.dsnLookup(); + if (dsn == null) + dsn = Dsn.dsnLookup(); - raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); - } - super.activateOptions(); + raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); } catch (Exception e) { getErrorHandler().error("An exception occurred during the creation of a raven instance", e, ErrorCode.FILE_OPEN_FAILURE); From a74a322e3b46790b4337f0fe828052f9f4cc5af2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Jul 2013 16:47:35 +0100 Subject: [PATCH 0650/2152] Fix raven initialisation in log4j2 --- .../raven/log4j2/SentryAppender.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index e4864f2f454..23f3c287349 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -107,6 +107,15 @@ private static String formatCulprit(StackTraceElement stackTraceElement) { + " at line " + stackTraceElement.getLineNumber(); } + /** + * {@inheritDoc} + *

    + * The raven instance is set in this method instead of {@link #start()} in order to avoid substitute loggers + * being generated during the instantiation of {@link Raven}.
    + *

    + * + * @param logEvent The LogEvent. + */ @Override public void append(LogEvent logEvent) { @@ -115,19 +124,15 @@ public void append(LogEvent logEvent) { return; if (raven == null) - startRaven(); + initRaven(); Event event = buildEvent(logEvent); raven.sendEvent(event); } /** * Initialises the Raven instance. - *

    - * The raven instance is set in this method instead of {@link #start()} in order to avoid substitute loggers - * being generated during the instantiation of {@link Raven}.
    - *

    */ - private void startRaven() { + protected void initRaven() { try { if (dsn == null) dsn = Dsn.dsnLookup(); From 612c7b1c8bbfcb949020e44e90f7e85264ce9be3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Jul 2013 16:49:55 +0100 Subject: [PATCH 0651/2152] Fix raven initialisation in logback --- .../raven/logback/SentryAppender.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 1a20bd1f0f4..bc8c119922b 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -61,6 +61,14 @@ private static Event.Level formatLevel(Level level) { } else return null; } + /** + * {@inheritDoc} + *

    + * The raven instance is started in this method instead of {@link #start()} in order to avoid substitute loggers + * being generated during the instantiation of {@link Raven}.
    + * More on www.slf4j.org/codes.html#substituteLogger + *

    + */ @Override protected void append(ILoggingEvent iLoggingEvent) { // Do not log the event if the current thread has been spawned by raven @@ -68,20 +76,15 @@ protected void append(ILoggingEvent iLoggingEvent) { return; if (raven == null) - startRaven(); + initRaven(); Event event = buildEvent(iLoggingEvent); raven.sendEvent(event); } /** * Initialises the Raven instance. - *

    - * The raven instance is set in this method instead of {@link #start()} in order to avoid substitute loggers - * being generated during the instantiation of {@link Raven}.
    - * More on www.slf4j.org/codes.html#substituteLogger - *

    */ - private void startRaven() { + protected void initRaven() { try { if (dsn == null) dsn = Dsn.dsnLookup(); From c20ce124d94698d1f04dfec5fc124f575fb91f43 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Jul 2013 16:52:15 +0100 Subject: [PATCH 0652/2152] Improve the possibility of inheritance with log4j --- .../kencochrane/raven/log4j/SentryAppender.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index dec83dc5a5a..38a872528d1 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -20,11 +20,11 @@ * Appender for log4j in charge of sending the logged events to a Sentry server. */ public class SentryAppender extends AppenderSkeleton { - private static final String LOG4J_NDC = "log4J-NDC"; + protected static final String LOG4J_NDC = "log4J-NDC"; + protected Raven raven; + protected String dsn; + protected String ravenFactory; private final boolean propagateClose; - private Raven raven; - private String dsn; - private String ravenFactory; private boolean guard; public SentryAppender() { @@ -40,7 +40,7 @@ public SentryAppender(Raven raven, boolean propagateClose) { this.propagateClose = propagateClose; } - private static Event.Level formatLevel(Level level) { + protected static Event.Level formatLevel(Level level) { if (level.isGreaterOrEqual(Level.FATAL)) { return Event.Level.FATAL; } else if (level.isGreaterOrEqual(Level.ERROR)) { @@ -54,7 +54,7 @@ private static Event.Level formatLevel(Level level) { } else return null; } - private static StackTraceElement asStackTraceElement(LocationInfo location) { + protected static StackTraceElement asStackTraceElement(LocationInfo location) { String fileName = (LocationInfo.NA.equals(location.getFileName())) ? null : location.getFileName(); int line = (LocationInfo.NA.equals(location.getLineNumber())) ? -1 : Integer.parseInt(location.getLineNumber()); return new StackTraceElement(location.getClassName(), location.getMethodName(), fileName, line); @@ -98,7 +98,7 @@ protected synchronized void append(LoggingEvent loggingEvent) { } } - private Event buildEvent(LoggingEvent loggingEvent) { + protected Event buildEvent(LoggingEvent loggingEvent) { EventBuilder eventBuilder = new EventBuilder() .setTimestamp(new Date(loggingEvent.getTimeStamp())) .setMessage(loggingEvent.getRenderedMessage()) From a78ce500304d8562394b7581d7c2f95d69d41156 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Jul 2013 16:52:15 +0100 Subject: [PATCH 0653/2152] Improve the possibility of inheritance with log4j2 --- .../kencochrane/raven/log4j2/SentryAppender.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 23f3c287349..24662001fb2 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -32,12 +32,12 @@ public class SentryAppender extends AbstractAppender { * Default name for the appender. */ public static final String APPENDER_NAME = "raven"; - private static final String LOG4J_NDC = "log4j2-NDC"; - private static final String LOG4J_MARKER = "log4j2-Marker"; + protected static final String LOG4J_NDC = "log4j2-NDC"; + protected static final String LOG4J_MARKER = "log4j2-Marker"; + protected Raven raven; + protected String dsn; + protected String ravenFactory; private final boolean propagateClose; - private Raven raven; - private String dsn; - private String ravenFactory; public SentryAppender() { this(APPENDER_NAME, null, true); @@ -84,7 +84,7 @@ public static SentryAppender createAppender(@PluginAttr("name") final String nam return sentryAppender; } - private static Event.Level formatLevel(Level level) { + protected static Event.Level formatLevel(Level level) { switch (level) { case FATAL: return Event.Level.FATAL; @@ -143,7 +143,7 @@ protected void initRaven() { } } - private Event buildEvent(LogEvent event) { + protected Event buildEvent(LogEvent event) { Message eventMessage = event.getMessage(); EventBuilder eventBuilder = new EventBuilder() .setTimestamp(new Date(event.getMillis())) From 751752405cc4c91a7b369d6d4e0d747c650523dc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Jul 2013 16:52:15 +0100 Subject: [PATCH 0654/2152] Improve the possibility of inheritance with logback --- .../kencochrane/raven/logback/SentryAppender.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index bc8c119922b..9b572b894f9 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -22,11 +22,11 @@ * Appender for logback in charge of sending the logged events to a Sentry server. */ public class SentryAppender extends AppenderBase { - private static final String LOGBACK_MARKER = "logback-Marker"; + protected static final String LOGBACK_MARKER = "logback-Marker"; + protected Raven raven; + protected String dsn; + protected String ravenFactory; private final boolean propagateClose; - private Raven raven; - private String dsn; - private String ravenFactory; public SentryAppender() { propagateClose = true; @@ -49,7 +49,7 @@ private static List formatArguments(Object[] argumentArray) { return arguments; } - private static Event.Level formatLevel(Level level) { + protected static Event.Level formatLevel(Level level) { if (level.isGreaterOrEqual(Level.ERROR)) { return Event.Level.ERROR; } else if (level.isGreaterOrEqual(Level.WARN)) { @@ -95,7 +95,7 @@ protected void initRaven() { } } - private Event buildEvent(ILoggingEvent iLoggingEvent) { + protected Event buildEvent(ILoggingEvent iLoggingEvent) { EventBuilder eventBuilder = new EventBuilder() .setTimestamp(new Date(iLoggingEvent.getTimeStamp())) .setMessage(iLoggingEvent.getFormattedMessage()) From a8cd7721b851cfcb653a10254d54e837e6453c83 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Jul 2013 16:52:15 +0100 Subject: [PATCH 0655/2152] Improve the possibility of inheritance with JUL --- .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 3bf754592b3..e74b00dcf04 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -18,8 +18,8 @@ * Logging handler in charge of sending the java.util.logging records to a Sentry server. */ public class SentryHandler extends Handler { + protected Raven raven; private final boolean propagateClose; - private Raven raven; private boolean guard = false; public SentryHandler() { @@ -35,7 +35,7 @@ public SentryHandler(Raven raven, boolean propagateClose) { this.propagateClose = propagateClose; } - private static Event.Level getLevel(Level level) { + protected static Event.Level getLevel(Level level) { if (level.intValue() >= Level.SEVERE.intValue()) return Event.Level.ERROR; else if (level.intValue() >= Level.WARNING.intValue()) @@ -86,7 +86,7 @@ protected void initRaven() { } } - private Event buildEvent(LogRecord record) { + protected Event buildEvent(LogRecord record) { EventBuilder eventBuilder = new EventBuilder() .setLevel(getLevel(record.getLevel())) .setTimestamp(new Date(record.getMillis())) From 60ad7c175226bc2ebfb94f22b1b30aa38658d563 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Jul 2013 16:58:47 +0100 Subject: [PATCH 0656/2152] Use a constant naming convention for formatMessageParameters --- .../kencochrane/raven/log4j2/SentryAppender.java | 13 ++++++------- .../kencochrane/raven/logback/SentryAppender.java | 8 ++++---- .../net/kencochrane/raven/jul/SentryHandler.java | 4 ++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 24662001fb2..de6064664e6 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -106,6 +106,12 @@ private static String formatCulprit(StackTraceElement stackTraceElement) { return stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + " at line " + stackTraceElement.getLineNumber(); } + protected static List formatMessageParameters(Object[] parameters) { + List stringParameters = new ArrayList(parameters.length); + for (Object parameter : parameters) + stringParameters.add((parameter != null) ? parameter.toString() : null); + return stringParameters; + } /** * {@inheritDoc} @@ -190,13 +196,6 @@ protected Event buildEvent(LogEvent event) { return eventBuilder.build(); } - private List formatMessageParameters(Object[] parameters) { - List stringParameters = new ArrayList(parameters.length); - for (Object parameter : parameters) - stringParameters.add((parameter != null) ? parameter.toString() : null); - return stringParameters; - } - public void setDsn(String dsn) { this.dsn = dsn; } diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 9b572b894f9..1a6b07b9ce4 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -41,9 +41,9 @@ public SentryAppender(Raven raven, boolean propagateClose) { this.propagateClose = propagateClose; } - private static List formatArguments(Object[] argumentArray) { - List arguments = new ArrayList(argumentArray.length); - for (Object argument : argumentArray) { + protected static List formatMessageParameters(Object[] parameters) { + List arguments = new ArrayList(parameters.length); + for (Object argument : parameters) { arguments.add((argument != null) ? argument.toString() : null); } return arguments; @@ -104,7 +104,7 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { if (iLoggingEvent.getArgumentArray() != null) eventBuilder.addSentryInterface(new MessageInterface(iLoggingEvent.getMessage(), - formatArguments(iLoggingEvent.getArgumentArray()))); + formatMessageParameters(iLoggingEvent.getArgumentArray()))); if (iLoggingEvent.getThrowableProxy() != null) { Throwable throwable = ((ThrowableProxy) iLoggingEvent.getThrowableProxy()).getThrowable(); diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index e74b00dcf04..448b76b4e57 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -47,7 +47,7 @@ else if (level.intValue() >= Level.ALL.intValue()) else return null; } - private static List formatParameters(Object[] parameters) { + protected static List formatMessageParameters(Object[] parameters) { List formattedParameters = new ArrayList(parameters.length); for (Object parameter : parameters) formattedParameters.add((parameter != null) ? parameter.toString() : null); @@ -106,7 +106,7 @@ protected Event buildEvent(LogRecord record) { if (record.getParameters() != null) { eventBuilder.addSentryInterface(new MessageInterface(record.getMessage(), - formatParameters(record.getParameters()))); + formatMessageParameters(record.getParameters()))); } else { eventBuilder.setMessage(record.getMessage()); } From 1012999c8e8570b606868c8fdf286ada55ae884d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Jul 2013 17:01:40 +0100 Subject: [PATCH 0657/2152] Use constant naming for getEventPosition --- .../raven/log4j2/SentryAppender.java | 10 +++++----- .../raven/logback/SentryAppender.java | 20 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index de6064664e6..4e24238aad5 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -102,10 +102,11 @@ protected static Event.Level formatLevel(Level level) { } } - private static String formatCulprit(StackTraceElement stackTraceElement) { - return stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() - + " at line " + stackTraceElement.getLineNumber(); + protected static String getEventPosition(LogEvent event) { + StackTraceElement stackTraceElement = event.getSource(); + return stackTraceElement.getClassName() + stackTraceElement.getMethodName() + stackTraceElement.getLineNumber(); } + protected static List formatMessageParameters(Object[] parameters) { List stringParameters = new ArrayList(parameters.length); for (Object parameter : parameters) @@ -163,8 +164,7 @@ protected Event buildEvent(LogEvent event) { } else if (event.getSource() != null) { // When it's a message try to rely on the position of the log (the same message can be logged from // different places, or a same place can log a message in different ways). - String source = formatCulprit(event.getSource()); - eventBuilder.generateChecksum(source); + eventBuilder.generateChecksum(getEventPosition(event)); } if (event.getSource() != null) { diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 1a6b07b9ce4..0f4af5fa8f0 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -61,6 +61,16 @@ protected static Event.Level formatLevel(Level level) { } else return null; } + protected static String getEventPosition(ILoggingEvent iLoggingEvent) { + StringBuilder sb = new StringBuilder(); + for (StackTraceElement stackTraceElement : iLoggingEvent.getCallerData()) { + sb.append(stackTraceElement.getClassName()) + .append(stackTraceElement.getMethodName()) + .append(stackTraceElement.getLineNumber()); + } + return sb.toString(); + } + /** * {@inheritDoc} *

    @@ -134,16 +144,6 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { return eventBuilder.build(); } - private String getEventPosition(ILoggingEvent iLoggingEvent) { - StringBuilder sb = new StringBuilder(); - for (StackTraceElement stackTraceElement : iLoggingEvent.getCallerData()) { - sb.append(stackTraceElement.getClassName()) - .append(stackTraceElement.getMethodName()) - .append(stackTraceElement.getLineNumber()); - } - return sb.toString(); - } - public void setDsn(String dsn) { this.dsn = dsn; } From b2161fcc935e89126902baf4ddccd36f202fb167 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Jul 2013 17:06:20 +0100 Subject: [PATCH 0658/2152] Improve documentation in appenders and handlers --- .../raven/log4j/SentryAppender.java | 38 +++++++++++++ .../raven/log4j2/SentryAppender.java | 53 +++++++++++++++++++ .../raven/logback/SentryAppender.java | 50 +++++++++++++++++ .../kencochrane/raven/jul/SentryHandler.java | 26 +++++++++ 4 files changed, 167 insertions(+) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 38a872528d1..9cc847abcb8 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -20,9 +20,29 @@ * Appender for log4j in charge of sending the logged events to a Sentry server. */ public class SentryAppender extends AppenderSkeleton { + /** + * Name of the {@link Event#extra} property containing NDC details. + */ protected static final String LOG4J_NDC = "log4J-NDC"; + /** + * Current instance of {@link Raven}. + * + * @see #initRaven() + */ protected Raven raven; + /** + * DSN property of the appender. + *

    + * Might be null in which case the DSN should be detected automatically. + *

    + */ protected String dsn; + /** + * Name of the {@link RavenFactory} being used. + *

    + * Might be null in which case the factory should be defined automatically. + *

    + */ protected String ravenFactory; private final boolean propagateClose; private boolean guard; @@ -40,6 +60,12 @@ public SentryAppender(Raven raven, boolean propagateClose) { this.propagateClose = propagateClose; } + /** + * Transforms a {@link Level} into an {@link Event.Level}. + * + * @param level original level as defined in log4j. + * @return log level used within raven. + */ protected static Event.Level formatLevel(Level level) { if (level.isGreaterOrEqual(Level.FATAL)) { return Event.Level.FATAL; @@ -54,6 +80,12 @@ protected static Event.Level formatLevel(Level level) { } else return null; } + /** + * Transforms the location info of a log into a stacktrace element (stackframe). + * + * @param location details on the location of the log. + * @return a stackframe. + */ protected static StackTraceElement asStackTraceElement(LocationInfo location) { String fileName = (LocationInfo.NA.equals(location.getFileName())) ? null : location.getFileName(); int line = (LocationInfo.NA.equals(location.getLineNumber())) ? -1 : Integer.parseInt(location.getLineNumber()); @@ -98,6 +130,12 @@ protected synchronized void append(LoggingEvent loggingEvent) { } } + /** + * Builds an Event based on the logging event. + * + * @param loggingEvent Log generated. + * @return Event containing details provided by the logging system. + */ protected Event buildEvent(LoggingEvent loggingEvent) { EventBuilder eventBuilder = new EventBuilder() .setTimestamp(new Date(loggingEvent.getTimeStamp())) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 4e24238aad5..542b1b8a8ac 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -32,10 +32,33 @@ public class SentryAppender extends AbstractAppender { * Default name for the appender. */ public static final String APPENDER_NAME = "raven"; + /** + * Name of the {@link Event#extra} property containing NDC details. + */ protected static final String LOG4J_NDC = "log4j2-NDC"; + /** + * Name of the {@link Event#extra} property containing Marker details. + */ protected static final String LOG4J_MARKER = "log4j2-Marker"; + /** + * Current instance of {@link Raven}. + * + * @see #initRaven() + */ protected Raven raven; + /** + * DSN property of the appender. + *

    + * Might be null in which case the DSN should be detected automatically. + *

    + */ protected String dsn; + /** + * Name of the {@link RavenFactory} being used. + *

    + * Might be null in which case the factory should be defined automatically. + *

    + */ protected String ravenFactory; private final boolean propagateClose; @@ -84,6 +107,12 @@ public static SentryAppender createAppender(@PluginAttr("name") final String nam return sentryAppender; } + /** + * Transforms a {@link Level} into an {@link Event.Level}. + * + * @param level original level as defined in log4j2. + * @return log level used within raven. + */ protected static Event.Level formatLevel(Level level) { switch (level) { case FATAL: @@ -102,11 +131,29 @@ protected static Event.Level formatLevel(Level level) { } } + /** + * Gets the position of the event as a String. + *

    + * Allows to generate a checksum when there is no stacktrace but the position of the log can be found. + *

    + * + * @param event event without stacktrace but with a position. + * @return a string version of the position. + */ protected static String getEventPosition(LogEvent event) { StackTraceElement stackTraceElement = event.getSource(); return stackTraceElement.getClassName() + stackTraceElement.getMethodName() + stackTraceElement.getLineNumber(); } + /** + * Extracts message parameters into a List of Strings. + *

    + * null parameters are kept as null. + *

    + * + * @param parameters parameters provided to the logging system. + * @return the parameters formatted as Strings in a List. + */ protected static List formatMessageParameters(Object[] parameters) { List stringParameters = new ArrayList(parameters.length); for (Object parameter : parameters) @@ -150,6 +197,12 @@ protected void initRaven() { } } + /** + * Builds an Event based on the logging event. + * + * @param event Log generated. + * @return Event containing details provided by the logging system. + */ protected Event buildEvent(LogEvent event) { Message eventMessage = event.getMessage(); EventBuilder eventBuilder = new EventBuilder() diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 0f4af5fa8f0..83dffa29908 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -22,9 +22,29 @@ * Appender for logback in charge of sending the logged events to a Sentry server. */ public class SentryAppender extends AppenderBase { + /** + * Name of the {@link Event#extra} property containing Maker details. + */ protected static final String LOGBACK_MARKER = "logback-Marker"; + /** + * Current instance of {@link Raven}. + * + * @see #initRaven() + */ protected Raven raven; + /** + * DSN property of the appender. + *

    + * Might be null in which case the DSN should be detected automatically. + *

    + */ protected String dsn; + /** + * Name of the {@link RavenFactory} being used. + *

    + * Might be null in which case the factory should be defined automatically. + *

    + */ protected String ravenFactory; private final boolean propagateClose; @@ -41,6 +61,15 @@ public SentryAppender(Raven raven, boolean propagateClose) { this.propagateClose = propagateClose; } + /** + * Extracts message parameters into a List of Strings. + *

    + * null parameters are kept as null. + *

    + * + * @param parameters parameters provided to the logging system. + * @return the parameters formatted as Strings in a List. + */ protected static List formatMessageParameters(Object[] parameters) { List arguments = new ArrayList(parameters.length); for (Object argument : parameters) { @@ -49,6 +78,12 @@ protected static List formatMessageParameters(Object[] parameters) { return arguments; } + /** + * Transforms a {@link Level} into an {@link Event.Level}. + * + * @param level original level as defined in logback. + * @return log level used within raven. + */ protected static Event.Level formatLevel(Level level) { if (level.isGreaterOrEqual(Level.ERROR)) { return Event.Level.ERROR; @@ -61,6 +96,15 @@ protected static Event.Level formatLevel(Level level) { } else return null; } + /** + * Gets the position of the event as a String. + *

    + * Allows to generate a checksum when there is no stacktrace but the position of the log can be found. + *

    + * + * @param iLoggingEvent event without stacktrace but with a position. + * @return a string version of the position. + */ protected static String getEventPosition(ILoggingEvent iLoggingEvent) { StringBuilder sb = new StringBuilder(); for (StackTraceElement stackTraceElement : iLoggingEvent.getCallerData()) { @@ -105,6 +149,12 @@ protected void initRaven() { } } + /** + * Builds an Event based on the logging event. + * + * @param iLoggingEvent Log generated. + * @return Event containing details provided by the logging system. + */ protected Event buildEvent(ILoggingEvent iLoggingEvent) { EventBuilder eventBuilder = new EventBuilder() .setTimestamp(new Date(iLoggingEvent.getTimeStamp())) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 448b76b4e57..99b0d013144 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -18,6 +18,11 @@ * Logging handler in charge of sending the java.util.logging records to a Sentry server. */ public class SentryHandler extends Handler { + /** + * Current instance of {@link Raven}. + * + * @see #initRaven() + */ protected Raven raven; private final boolean propagateClose; private boolean guard = false; @@ -35,6 +40,12 @@ public SentryHandler(Raven raven, boolean propagateClose) { this.propagateClose = propagateClose; } + /** + * Transforms a {@link Level} into an {@link Event.Level}. + * + * @param level original level as defined in JUL. + * @return log level used within raven. + */ protected static Event.Level getLevel(Level level) { if (level.intValue() >= Level.SEVERE.intValue()) return Event.Level.ERROR; @@ -47,6 +58,15 @@ else if (level.intValue() >= Level.ALL.intValue()) else return null; } + /** + * Extracts message parameters into a List of Strings. + *

    + * null parameters are kept as null. + *

    + * + * @param parameters parameters provided to the logging system. + * @return the parameters formatted as Strings in a List. + */ protected static List formatMessageParameters(Object[] parameters) { List formattedParameters = new ArrayList(parameters.length); for (Object parameter : parameters) @@ -86,6 +106,12 @@ protected void initRaven() { } } + /** + * Builds an Event based on the log record. + * + * @param record Log generated. + * @return Event containing details provided by the logging system. + */ protected Event buildEvent(LogRecord record) { EventBuilder eventBuilder = new EventBuilder() .setLevel(getLevel(record.getLevel())) From d69f13b4882ce6b63cba73c5a0d93636ccd17102 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Jul 2013 17:07:00 +0100 Subject: [PATCH 0659/2152] Add the thread name to the extras in the logs --- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 7 ++++++- .../java/net/kencochrane/raven/log4j2/SentryAppender.java | 7 ++++++- .../java/net/kencochrane/raven/logback/SentryAppender.java | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 9cc847abcb8..d8fac483e9f 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -24,6 +24,10 @@ public class SentryAppender extends AppenderSkeleton { * Name of the {@link Event#extra} property containing NDC details. */ protected static final String LOG4J_NDC = "log4J-NDC"; + /** + * Name of the {@link Event#extra} property containing the Thread name. + */ + protected static final String THREAD_NAME = "Raven-Threadname"; /** * Current instance of {@link Raven}. * @@ -141,7 +145,8 @@ protected Event buildEvent(LoggingEvent loggingEvent) { .setTimestamp(new Date(loggingEvent.getTimeStamp())) .setMessage(loggingEvent.getRenderedMessage()) .setLogger(loggingEvent.getLoggerName()) - .setLevel(formatLevel(loggingEvent.getLevel())); + .setLevel(formatLevel(loggingEvent.getLevel())) + .addExtra(THREAD_NAME, loggingEvent.getThreadName()); if (loggingEvent.getThrowableInformation() != null) { Throwable throwable = loggingEvent.getThrowableInformation().getThrowable(); diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 542b1b8a8ac..fa3b408b434 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -40,6 +40,10 @@ public class SentryAppender extends AbstractAppender { * Name of the {@link Event#extra} property containing Marker details. */ protected static final String LOG4J_MARKER = "log4j2-Marker"; + /** + * Name of the {@link Event#extra} property containing the Thread name. + */ + protected static final String THREAD_NAME = "Raven-Threadname"; /** * Current instance of {@link Raven}. * @@ -209,7 +213,8 @@ protected Event buildEvent(LogEvent event) { .setTimestamp(new Date(event.getMillis())) .setMessage(eventMessage.getFormattedMessage()) .setLogger(event.getLoggerName()) - .setLevel(formatLevel(event.getLevel())); + .setLevel(formatLevel(event.getLevel())) + .addExtra(THREAD_NAME, event.getThreadName()); if (event.getThrown() != null) { Throwable throwable = event.getThrown(); diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 83dffa29908..8c977968a24 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -26,6 +26,10 @@ public class SentryAppender extends AppenderBase { * Name of the {@link Event#extra} property containing Maker details. */ protected static final String LOGBACK_MARKER = "logback-Marker"; + /** + * Name of the {@link Event#extra} property containing the Thread name. + */ + protected static final String THREAD_NAME = "Raven-Threadname"; /** * Current instance of {@link Raven}. * @@ -160,7 +164,8 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { .setTimestamp(new Date(iLoggingEvent.getTimeStamp())) .setMessage(iLoggingEvent.getFormattedMessage()) .setLogger(iLoggingEvent.getLoggerName()) - .setLevel(formatLevel(iLoggingEvent.getLevel())); + .setLevel(formatLevel(iLoggingEvent.getLevel())) + .addExtra(THREAD_NAME, iLoggingEvent.getThreadName()); if (iLoggingEvent.getArgumentArray() != null) eventBuilder.addSentryInterface(new MessageInterface(iLoggingEvent.getMessage(), From 7d658c42932615ee5cb68b182af07315c29a4216 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 8 Jul 2013 17:07:18 +0100 Subject: [PATCH 0660/2152] Retrieve the handler properties in the constructor --- .../kencochrane/raven/jul/SentryHandler.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 99b0d013144..827cf22dd05 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -24,11 +24,26 @@ public class SentryHandler extends Handler { * @see #initRaven() */ protected Raven raven; + /** + * DSN property of the appender. + *

    + * Might be null in which case the DSN should be detected automatically. + *

    + */ + protected String dsn; + /** + * Name of the {@link RavenFactory} being used. + *

    + * Might be null in which case the factory should be defined automatically. + *

    + */ + protected String ravenFactory; private final boolean propagateClose; private boolean guard = false; public SentryHandler() { propagateClose = true; + retrieveProperties(); } public SentryHandler(Raven raven) { @@ -74,6 +89,15 @@ protected static List formatMessageParameters(Object[] parameters) { return formattedParameters; } + /** + * Retrieves the properties of the logger. + */ + protected void retrieveProperties() { + LogManager manager = LogManager.getLogManager(); + dsn = manager.getProperty(SentryHandler.class.getName() + ".dsn"); + ravenFactory = manager.getProperty(SentryHandler.class.getName() + ".ravenFactory"); + } + @Override public synchronized void publish(LogRecord record) { // Do not log the event if the current thread has been spawned by raven or if the event has been created during From 41cda816e95b6b4e413e91809a657245d9b09074 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 9 Jul 2013 11:09:37 +0100 Subject: [PATCH 0661/2152] Use stacktraceinterface to generate checksums for message logs --- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 6 +++--- .../java/net/kencochrane/raven/log4j2/SentryAppender.java | 6 +++--- .../java/net/kencochrane/raven/logback/SentryAppender.java | 5 ++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index d8fac483e9f..638ea8f591f 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -6,6 +6,7 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; import org.apache.log4j.spi.ErrorCode; @@ -152,9 +153,8 @@ protected Event buildEvent(LoggingEvent loggingEvent) { Throwable throwable = loggingEvent.getThrowableInformation().getThrowable(); eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); } else if (loggingEvent.getLocationInformation().fullInfo != null) { - // When it's a message try to rely on the position of the log (the same message can be logged from - // different places, or a same place can log a message in different ways). - eventBuilder.generateChecksum(loggingEvent.getLocationInformation().fullInfo); + StackTraceElement[] stackTrace = {asStackTraceElement(loggingEvent.getLocationInformation())}; + eventBuilder.addSentryInterface(new StackTraceInterface(stackTrace)); } // Set culprit diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index fa3b408b434..c6121848aa9 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -7,6 +7,7 @@ import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; @@ -220,9 +221,8 @@ protected Event buildEvent(LogEvent event) { Throwable throwable = event.getThrown(); eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); } else if (event.getSource() != null) { - // When it's a message try to rely on the position of the log (the same message can be logged from - // different places, or a same place can log a message in different ways). - eventBuilder.generateChecksum(getEventPosition(event)); + StackTraceElement[] stackTrace = {event.getSource()}; + eventBuilder.addSentryInterface(new StackTraceInterface(stackTrace)); } if (event.getSource() != null) { diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 8c977968a24..22afa3c3d22 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -11,6 +11,7 @@ import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; import java.io.IOException; import java.util.ArrayList; @@ -175,9 +176,7 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { Throwable throwable = ((ThrowableProxy) iLoggingEvent.getThrowableProxy()).getThrowable(); eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); } else if (iLoggingEvent.getCallerData().length > 0) { - // When there is no exceptions try to rely on the position of the log (the same message can be logged from - // different places, or a same place can log a message in different ways). - eventBuilder.generateChecksum(getEventPosition(iLoggingEvent)); + eventBuilder.addSentryInterface(new StackTraceInterface(iLoggingEvent.getCallerData())); } if (iLoggingEvent.getCallerData().length > 0) { From ec90f36651da5090679fb6eb27baf8f6d168a657 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 9 Jul 2013 11:12:02 +0100 Subject: [PATCH 0662/2152] Get actual message with JUL from the resource bundle --- .../java/net/kencochrane/raven/jul/SentryHandler.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 827cf22dd05..adcf6ffcd9f 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -154,9 +154,14 @@ protected Event buildEvent(LogRecord record) { eventBuilder.addSentryInterface(new ExceptionInterface(record.getThrown())); } - if (record.getParameters() != null) { - eventBuilder.addSentryInterface(new MessageInterface(record.getMessage(), - formatMessageParameters(record.getParameters()))); + if (record.getResourceBundle().containsKey(record.getMessage())) { + String message = record.getResourceBundle().getString(record.getMessage()); + if (record.getParameters() != null) { + eventBuilder.addSentryInterface( + new MessageInterface(message, formatMessageParameters(record.getParameters()))); + } else { + eventBuilder.setMessage(message); + } } else { eventBuilder.setMessage(record.getMessage()); } From 4e95104efb1c243e05f9eb3c4bc32e464b867368 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 9 Jul 2013 11:15:22 +0100 Subject: [PATCH 0663/2152] Reorganise SentryHandler code --- .../kencochrane/raven/jul/SentryHandler.java | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index adcf6ffcd9f..862438601da 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -142,18 +142,6 @@ protected Event buildEvent(LogRecord record) { .setTimestamp(new Date(record.getMillis())) .setLogger(record.getLoggerName()); - if (record.getSourceClassName() != null && record.getSourceMethodName() != null) { - StackTraceElement fakeFrame = new StackTraceElement(record.getSourceClassName(), - record.getSourceMethodName(), null, -1); - eventBuilder.setCulprit(fakeFrame); - } else { - eventBuilder.setCulprit(record.getLoggerName()); - } - - if (record.getThrown() != null) { - eventBuilder.addSentryInterface(new ExceptionInterface(record.getThrown())); - } - if (record.getResourceBundle().containsKey(record.getMessage())) { String message = record.getResourceBundle().getString(record.getMessage()); if (record.getParameters() != null) { @@ -166,6 +154,17 @@ protected Event buildEvent(LogRecord record) { eventBuilder.setMessage(record.getMessage()); } + if (record.getThrown() != null) + eventBuilder.addSentryInterface(new ExceptionInterface(record.getThrown())); + + if (record.getSourceClassName() != null && record.getSourceMethodName() != null) { + StackTraceElement fakeFrame = new StackTraceElement(record.getSourceClassName(), + record.getSourceMethodName(), null, -1); + eventBuilder.setCulprit(fakeFrame); + } else { + eventBuilder.setCulprit(record.getLoggerName()); + } + raven.runBuilderHelpers(eventBuilder); return eventBuilder.build(); } From 822b38a4c881214c7aeefb3a7e8091b3e04507b2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 9 Jul 2013 11:24:57 +0100 Subject: [PATCH 0664/2152] Format message if it comes from a bundle --- .../net/kencochrane/raven/jul/SentryHandler.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 862438601da..1f280df0847 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -9,6 +9,7 @@ import net.kencochrane.raven.event.interfaces.MessageInterface; import java.io.IOException; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -144,12 +145,11 @@ protected Event buildEvent(LogRecord record) { if (record.getResourceBundle().containsKey(record.getMessage())) { String message = record.getResourceBundle().getString(record.getMessage()); - if (record.getParameters() != null) { - eventBuilder.addSentryInterface( - new MessageInterface(message, formatMessageParameters(record.getParameters()))); - } else { - eventBuilder.setMessage(message); - } + Object[] parameters = record.getParameters(); + eventBuilder.setMessage(MessageFormat.format(message, parameters)); + + if (parameters != null) + eventBuilder.addSentryInterface(new MessageInterface(message, formatMessageParameters(parameters))); } else { eventBuilder.setMessage(record.getMessage()); } From d2a30d17b5188a5b640ee627e7d436cab6774f52 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 9 Jul 2013 11:25:29 +0100 Subject: [PATCH 0665/2152] Improve indentation/code organisation in appenders --- .../raven/log4j2/SentryAppender.java | 18 ++++++++---------- .../raven/logback/SentryAppender.java | 7 +++---- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index c6121848aa9..2be3a682847 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -217,6 +217,11 @@ protected Event buildEvent(LogEvent event) { .setLevel(formatLevel(event.getLevel())) .addExtra(THREAD_NAME, event.getThreadName()); + if (!eventMessage.getFormattedMessage().equals(eventMessage.getFormat())) { + eventBuilder.addSentryInterface(new MessageInterface(eventMessage.getFormat(), + formatMessageParameters(eventMessage.getParameters()))); + } + if (event.getThrown() != null) { Throwable throwable = event.getThrown(); eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); @@ -231,26 +236,19 @@ protected Event buildEvent(LogEvent event) { eventBuilder.setCulprit(event.getLoggerName()); } - if (!eventMessage.getFormattedMessage().equals(eventMessage.getFormat())) { - eventBuilder.addSentryInterface(new MessageInterface(eventMessage.getFormat(), - formatMessageParameters(eventMessage.getParameters()))); - } - - if (event.getContextStack() != null) { + if (event.getContextStack() != null) eventBuilder.addExtra(LOG4J_NDC, event.getContextStack().asList()); - } if (event.getContextMap() != null) { for (Map.Entry mdcEntry : event.getContextMap().entrySet()) { eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); } } - if (event.getMarker() != null) { + + if (event.getMarker() != null) eventBuilder.addExtra(LOG4J_MARKER, event.getMarker()); - } raven.runBuilderHelpers(eventBuilder); - return eventBuilder.build(); } diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 22afa3c3d22..f9cff90ef89 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -168,9 +168,10 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { .setLevel(formatLevel(iLoggingEvent.getLevel())) .addExtra(THREAD_NAME, iLoggingEvent.getThreadName()); - if (iLoggingEvent.getArgumentArray() != null) + if (iLoggingEvent.getArgumentArray() != null) { eventBuilder.addSentryInterface(new MessageInterface(iLoggingEvent.getMessage(), formatMessageParameters(iLoggingEvent.getArgumentArray()))); + } if (iLoggingEvent.getThrowableProxy() != null) { Throwable throwable = ((ThrowableProxy) iLoggingEvent.getThrowableProxy()).getThrowable(); @@ -189,12 +190,10 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); } - if (iLoggingEvent.getMarker() != null) { + if (iLoggingEvent.getMarker() != null) eventBuilder.addExtra(LOGBACK_MARKER, iLoggingEvent.getMarker()); - } raven.runBuilderHelpers(eventBuilder); - return eventBuilder.build(); } From 33e9e15c74ab9cded20f291cdb5e2aa4383b43ae Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 9 Jul 2013 11:55:50 +0100 Subject: [PATCH 0666/2152] Only use the current position as stacktrace if it's complete --- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 638ea8f591f..d090a0d3289 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -153,8 +153,11 @@ protected Event buildEvent(LoggingEvent loggingEvent) { Throwable throwable = loggingEvent.getThrowableInformation().getThrowable(); eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); } else if (loggingEvent.getLocationInformation().fullInfo != null) { - StackTraceElement[] stackTrace = {asStackTraceElement(loggingEvent.getLocationInformation())}; - eventBuilder.addSentryInterface(new StackTraceInterface(stackTrace)); + LocationInfo location = loggingEvent.getLocationInformation(); + if (!LocationInfo.NA.equals(location.getFileName()) && !LocationInfo.NA.equals(location.getLineNumber())) { + StackTraceElement[] stackTrace = {asStackTraceElement(location)}; + eventBuilder.addSentryInterface(new StackTraceInterface(stackTrace)); + } } // Set culprit From 0403df927ac54aa20c35537beb60839a4a120342 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 9 Jul 2013 14:50:52 +0100 Subject: [PATCH 0667/2152] Ensure that the parametrised message is indeed formatted. --- .../kencochrane/raven/log4j/SentryAppenderTest.java | 5 +++++ .../raven/log4j2/SentryAppenderTest.java | 5 +++++ .../raven/logback/SentryAppenderTest.java | 5 +++++ .../net/kencochrane/raven/AbstractLoggerTest.java | 13 ++++++++++++- .../kencochrane/raven/jul/SentryHandlerTest.java | 5 +++++ 5 files changed, 32 insertions(+), 1 deletion(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 8c96bc32f38..ddf4e4b7bc2 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -40,6 +40,11 @@ public String getCurrentLoggerName() { return logger.getName(); } + @Override + public String getUnformattedMessage() { + throw new UnsupportedOperationException(); + } + @Override @Test public void testLogLevelConversions() throws Exception { diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index d853c858472..6d270cff376 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -42,6 +42,11 @@ public String getCurrentLoggerName() { return LOGGER_NAME; } + @Override + public String getUnformattedMessage() { + return "Some content {} {} {}"; + } + @Test @Override public void testLogLevelConversions() throws Exception { diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java index 893b51be83f..6de2721b1ac 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -39,6 +39,11 @@ public void logAnyLevel(String message, List parameters) { logger.info(message, parameters.toArray()); } + @Override + public String getUnformattedMessage() { + return "Some content {} {} {}"; + } + @Override public String getCurrentLoggerName() { return logger.getName(); diff --git a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java index 72832c2661f..e40e96c777c 100644 --- a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java @@ -52,6 +52,13 @@ protected Raven getMockRaven() { public abstract String getCurrentLoggerName(); + /** + * Returns the format of the parametrised message "Some content %s %s %s" with '%s' being the wildcard. + * + * @return the format of the parametrised message. + */ + public abstract String getUnformattedMessage(); + @Test public abstract void testLogLevelConversions() throws Exception; @@ -85,7 +92,7 @@ public void testLogException() throws Exception { @Test public void testLogParametrisedMessage() throws Exception { ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - String message = "Some content %s"; + String message = getUnformattedMessage(); List parameters = Arrays.asList(null, UUID.randomUUID().toString(), UUID.randomUUID().toString()); Event event; @@ -99,5 +106,9 @@ public void testLogParametrisedMessage() throws Exception { assertThat(messageInterface.getMessage(), is(message)); assertThat(messageInterface.getParams(), is(parameters)); + assertThat(event.getMessage(), is( + "Some content " + parameters.get(0) + + " " + parameters.get(1) + + " " + parameters.get(2))); } } diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java index 212eaadf7e9..7ce6dee6347 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java @@ -44,6 +44,11 @@ public String getCurrentLoggerName() { return logger.getName(); } + @Override + public String getUnformattedMessage() { + return "Some content {0} {1} {2}"; + } + @Override @Test public void testLogLevelConversions() throws Exception { From 158b89ac00d6cfeba8c648a2c9db0af3c3bec7f1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 9 Jul 2013 14:51:38 +0100 Subject: [PATCH 0668/2152] Always format the message if parameters are provided --- .../kencochrane/raven/jul/SentryHandler.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 1f280df0847..b73e8b1d164 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -143,16 +143,15 @@ protected Event buildEvent(LogRecord record) { .setTimestamp(new Date(record.getMillis())) .setLogger(record.getLoggerName()); - if (record.getResourceBundle().containsKey(record.getMessage())) { - String message = record.getResourceBundle().getString(record.getMessage()); - Object[] parameters = record.getParameters(); - eventBuilder.setMessage(MessageFormat.format(message, parameters)); - - if (parameters != null) - eventBuilder.addSentryInterface(new MessageInterface(message, formatMessageParameters(parameters))); - } else { - eventBuilder.setMessage(record.getMessage()); + String message = record.getMessage(); + if (record.getResourceBundle() != null && record.getResourceBundle().containsKey(record.getMessage())) { + message = record.getResourceBundle().getString(record.getMessage()); + } + if (record.getParameters() != null) { + eventBuilder.addSentryInterface(new MessageInterface(message, formatMessageParameters(record.getParameters()))); + message = MessageFormat.format(message, record.getParameters()); } + eventBuilder.setMessage(message); if (record.getThrown() != null) eventBuilder.addSentryInterface(new ExceptionInterface(record.getThrown())); From afb020b40f9b796d2bf83621a53a64f2f4dc9db1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 9 Jul 2013 14:52:27 +0100 Subject: [PATCH 0669/2152] Make mockRaven protected to simplify inheritance --- .../src/test/java/net/kencochrane/raven/AbstractLoggerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java index e40e96c777c..10ff680daf0 100644 --- a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java @@ -23,7 +23,7 @@ @RunWith(MockitoJUnitRunner.class) public abstract class AbstractLoggerTest { @Mock - private Raven mockRaven; + protected Raven mockRaven; @Test public void testSimpleMessageLogging() throws Exception { From 35a758d6ad40854419cf6c14266f1211c183e33c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 9 Jul 2013 16:21:01 +0100 Subject: [PATCH 0670/2152] Add integration tests to test chained exceptions --- .../net/kencochrane/raven/log4j/SentryAppenderIT.java | 8 ++++++++ .../net/kencochrane/raven/log4j2/SentryAppenderIT.java | 8 ++++++++ .../net/kencochrane/raven/logback/SentryAppenderIT.java | 8 ++++++++ .../java/net/kencochrane/raven/jul/SentryHandlerIT.java | 9 +++++++++ 4 files changed, 33 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java index 1cee3d3fde7..c444a17c5d2 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java @@ -30,4 +30,12 @@ public void testInfoLog() { logger.info("This is a test"); assertThat(sentryStub.getEventCount(), is(1)); } + + @Test + public void testChainedExceptions() { + assertThat(sentryStub.getEventCount(), is(0)); + logger.error("This is an exception", + new UnsupportedOperationException("Test", new UnsupportedOperationException())); + assertThat(sentryStub.getEventCount(), is(1)); + } } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java index 0cf952155de..ed1539e29be 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java @@ -31,4 +31,12 @@ public void testInfoLog() { logger.info("This is a test"); assertThat(sentryStub.getEventCount(), is(1)); } + + @Test + public void testChainedExceptions() { + assertThat(sentryStub.getEventCount(), is(0)); + logger.error("This is an exception", + new UnsupportedOperationException("Test", new UnsupportedOperationException())); + assertThat(sentryStub.getEventCount(), is(1)); + } } diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java index 2ea345275da..021e0478f85 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java @@ -31,4 +31,12 @@ public void testInfoLog() { logger.info("This is a test"); assertThat(sentryStub.getEventCount(), is(1)); } + + @Test + public void testChainedExceptions() { + assertThat(sentryStub.getEventCount(), is(0)); + logger.error("This is an exception", + new UnsupportedOperationException("Test", new UnsupportedOperationException())); + assertThat(sentryStub.getEventCount(), is(1)); + } } diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java index 8b2d7c4ddc9..7c757952dcc 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java @@ -5,6 +5,7 @@ import org.junit.Before; import org.junit.Test; +import java.util.logging.Level; import java.util.logging.Logger; import static org.hamcrest.MatcherAssert.assertThat; @@ -31,4 +32,12 @@ public void testInfoLog() { logger.info("This is a test"); assertThat(sentryStub.getEventCount(), is(1)); } + + @Test + public void testChainedExceptions() { + assertThat(sentryStub.getEventCount(), is(0)); + logger.log(Level.SEVERE, "This is an exception", + new UnsupportedOperationException("Test", new UnsupportedOperationException())); + assertThat(sentryStub.getEventCount(), is(1)); + } } From abc46da9eb1d90d279f32e8e2c3f4bad98503080 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 14:06:13 +0100 Subject: [PATCH 0671/2152] Update the description of the project --- README.md | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index dc5ffb567a6..bbd389be219 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,22 @@ [![Build Status](https://secure.travis-ci.org/kencochrane/raven-java.png?branch=master)](http://travis-ci.org/kencochrane/raven-java) -Raven is a Java client for [Sentry](https://www.getsentry.com/). -Besides the regular client you can use within your application code, this -project also provides tools allowing the most popular logging frameworks -to send the logs directly to sentry: - - - `java.util.logging` is supported out of the box. - - `raven-log4j` adds the support for - [log4j](https://logging.apache.org/log4j/1.2/). - - `raven-log4j2` adds the support for - [log4j2](https://logging.apache.org/log4j/2.x/). - - `raven-logback` adds the support for [logback](http://logback.qos.ch/). - -Raven supports both HTTP(S) and UDP transport of events. - +Raven is the Java client for [Sentry](https://www.getsentry.com/). +Raven relies on the most popular logging libraries to capture and convert logs +before sending details to a Sentry instance. + + - [`java.util.logging`](http://docs.oracle.com/javase/7/docs/technotes/guides/logging/index.html) + support is provided by the main project [`raven`](raven/README.md) + - [log4j](https://logging.apache.org/log4j/1.2/) support is provided in [`raven-log4j`](raven-log4j/README.md) + - [log4j2](https://logging.apache.org/log4j/2.x/) can be used with [`raven-log4j2`](raven-log4j2/README.md) + - [logback](http://logback.qos.ch/) support is provided in [`raven-logback`](raven-logback/README.md) + +While it's **strongly recommended to use one of the supported logging +frameworks** to capture and send messages to Sentry, a it is possible to do so +manually with the main project [`raven`](raven/README.md). + +Raven supports both HTTP(S) and UDP as transport protocols to the Sentry +instance. ## Sentry Protocol and supported versions ### Sentry Protocol versions From ef5f0bb9eec135ffae6899d6ce9393aa1c94a5ef Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 14:08:43 +0100 Subject: [PATCH 0672/2152] Update details on protocol/raven/sentry versions --- README.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index bbd389be219..4fa6c4e95d3 100644 --- a/README.md +++ b/README.md @@ -19,22 +19,20 @@ manually with the main project [`raven`](raven/README.md). Raven supports both HTTP(S) and UDP as transport protocols to the Sentry instance. -## Sentry Protocol and supported versions -### Sentry Protocol versions -Since the version 3.0, Raven the versionning system is based on the -protocol version of Sentry. This means that Raven-3.x only supports -the version 3 of Sentry's protocol while Raven-4.x only supports -the version 4. -Sentry only supports the last two major releases of the protocol, for this -reason, only the last two major versions of Raven are maintained. +## Sentry Protocol and Raven versions +Since 2.0, the major version of raven matches the version of the Sentry protocol. -### Sentry versions +| Raven version | Sentry version | Protocol version | +| ------------- | -------------- | ---------------- | +| Raven 2.x | >= 2.0 | V2 | +| Raven 3.x | >= 5.1 | V3 | +| Raven 4.x(DEV)| >= 5.5(DEV) | V4 | - - Sentry protocol v4 is not yet available (use Raven-4.x) - - Sentry protocol v3 is available since Sentry 5.1 (use Raven-3.x) - - Sentry protocol v2 is available since Sentry 2.0 (use Raven-2.x) +Each release of Sentry supports the last two version of the protocol +(i.e. Sentry 5.4.5 supports both the protocol V3 and V2), for this reason, only +the two last stable version of Raven are actively maintained. ## Build and Installation **See From 6a2ffbc3033a97f06a0a57bec24e2884c06bcb1c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 17:07:21 +0100 Subject: [PATCH 0673/2152] Create a README for the main raven module --- raven/README.md | 128 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 raven/README.md diff --git a/raven/README.md b/raven/README.md new file mode 100644 index 00000000000..99d5611e199 --- /dev/null +++ b/raven/README.md @@ -0,0 +1,128 @@ +# Raven (module) +Main module of the Raven project in java. It provides a client to send messages +to a Sentry server as well as an implementation of an [`Handler`](http://docs.oracle.com/javase/7/docs/api/java/util/logging/Handler.html) +for `java.util.logging`. + +## Installation + +### Maven + + net.kencochrane.raven + raven + 4.0 + + +### Other dependency managers +Details in the [central Maven repository](http://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.0%7Cjar). + +### Manual dependency management +Relies on: + + - [raven-4.0.jar](http://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.0%7Cjar) + - [slf4j-api-1.7.5.jar](http://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.5%7Cjar) + - [commons-codec-1.8.jar](http://search.maven.org/#artifactdetails%7Ccommons-codec%7Ccommons-codec%7C1.8%7Cjar) + - [jackson-core-2.2.2.jar](http://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.2.2%7Cjar) + + +## Usage (`java.util.logging`) +### Configuration +In the `logging.properties` file set: + + level=INFO + handlers=net.kencochrane.raven.jul.SentryHandler + net.kencochrane.raven.jul.SentryHandler.dsn=http://publicKey:secretKey@host:port/1?options + +When starting your application, add the `java.util.logging.config.file` to the +system properties, with the full path to the `logging.properties` as its value. + + $ java -Djava.util.logging.config.file=/path/to/app.properties MainClass + +### In practice + + import java.util.logging.Level; + import java.util.logging.Logger; + + public class MyClass { + private static final Logger logger = Logger.getLogger(MyClass.class.getName()); + + void logSimpleMessage() { + // This adds a simple message to the logs + logger.log(Level.INFO, "This is a test"); + } + + void logException() { + try { + unsafeMethod(); + } catch (Exception e) { + // This adds an exception to the logs + logger.log(Level.SEVERE, "Exception caught", e); + } + } + + void unsafeMethod() { + throw new UnsupportedOperationException("You shouldn't call that"); + } + } + +### Unsupported features +`java.util.logging` does not support either MDC nor NDC, meaning that it is not +possible to attach additional/custom context values to the logs. +In other terms, it is not possible to use the "extra" field supported by Sentry. + + +## Manual usage (NOT RECOMMENDED) +It is possible to use the client manually rather than using a logging framework +in order to send messages to Sentry. It is not recommended to use this solution +as the API is more verbose and requires the developer to specify the value of +each field sent to Sentry. + +### In practice + + import net.kencochrane.raven.Raven; + import net.kencochrane.raven.RavenFactory; + import net.kencochrane.raven.event.Event; + import net.kencochrane.raven.event.EventBuilder; + import net.kencochrane.raven.event.interfaces.ExceptionInterface; + import net.kencochrane.raven.event.interfaces.MessageInterface; + + public class MyClass { + private static Raven raven; + + public static void main(String... args) { + // Creation of the client with a specific DSN + String dsn = args[0]; + raven = RavenFactory.ravenInstance(dsn); + + // It is also possible to use the DSN detection system like this + raven = RavenFactory.ravenInstance(); + } + + void logSimpleMessage() { + // This adds a simple message to the logs + EventBuilder eventBuilder = new EventBuilder() + .setMessage("This is a test") + .setLevel(Event.Level.INFO) + .setLogger(MyClass.class.getName()); + raven.runBuilderHelpers(eventBuilder); // Optional + raven.sendEvent(eventBuilder.build()); + } + + void logException() { + try { + unsafeMethod(); + } catch (Exception e) { + // This adds an exception to the logs + EventBuilder eventBuilder = new EventBuilder() + .setMessage("Exception caught") + .setLevel(Event.Level.ERROR) + .setLogger(MyClass.class.getName()) + .addSentryInterface(new ExceptionInterface(e)); + raven.runBuilderHelpers(eventBuilder); // Optional + raven.sendEvent(eventBuilder.build()); + } + } + + void unsafeMethod() { + throw new UnsupportedOperationException("You shouldn't call that"); + } + } From 92107f8cf715c786009ad4d407455d516ad674ba Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 17:12:04 +0100 Subject: [PATCH 0674/2152] Fix code colouring --- raven/README.md | 142 +++++++++++++++++++++++++----------------------- 1 file changed, 74 insertions(+), 68 deletions(-) diff --git a/raven/README.md b/raven/README.md index 99d5611e199..6d144114236 100644 --- a/raven/README.md +++ b/raven/README.md @@ -6,11 +6,13 @@ for `java.util.logging`. ## Installation ### Maven - - net.kencochrane.raven - raven - 4.0 - +```xml + + net.kencochrane.raven + raven + 4.0 + +``` ### Other dependency managers Details in the [central Maven repository](http://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.0%7Cjar). @@ -28,9 +30,11 @@ Relies on: ### Configuration In the `logging.properties` file set: - level=INFO - handlers=net.kencochrane.raven.jul.SentryHandler - net.kencochrane.raven.jul.SentryHandler.dsn=http://publicKey:secretKey@host:port/1?options +```properties +level=INFO +handlers=net.kencochrane.raven.jul.SentryHandler +net.kencochrane.raven.jul.SentryHandler.dsn=http://publicKey:secretKey@host:port/1?options +``` When starting your application, add the `java.util.logging.config.file` to the system properties, with the full path to the `logging.properties` as its value. @@ -38,31 +42,32 @@ system properties, with the full path to the `logging.properties` as its value. $ java -Djava.util.logging.config.file=/path/to/app.properties MainClass ### In practice +```java +import java.util.logging.Level; +import java.util.logging.Logger; - import java.util.logging.Level; - import java.util.logging.Logger; +public class MyClass { + private static final Logger logger = Logger.getLogger(MyClass.class.getName()); - public class MyClass { - private static final Logger logger = Logger.getLogger(MyClass.class.getName()); - - void logSimpleMessage() { - // This adds a simple message to the logs - logger.log(Level.INFO, "This is a test"); - } + void logSimpleMessage() { + // This adds a simple message to the logs + logger.log(Level.INFO, "This is a test"); + } - void logException() { - try { - unsafeMethod(); - } catch (Exception e) { - // This adds an exception to the logs - logger.log(Level.SEVERE, "Exception caught", e); - } + void logException() { + try { + unsafeMethod(); + } catch (Exception e) { + // This adds an exception to the logs + logger.log(Level.SEVERE, "Exception caught", e); } + } - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call that"); - } + void unsafeMethod() { + throw new UnsupportedOperationException("You shouldn't call that"); } +} +``` ### Unsupported features `java.util.logging` does not support either MDC nor NDC, meaning that it is not @@ -77,52 +82,53 @@ as the API is more verbose and requires the developer to specify the value of each field sent to Sentry. ### In practice +```java +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.MessageInterface; + +public class MyClass { + private static Raven raven; + + public static void main(String... args) { + // Creation of the client with a specific DSN + String dsn = args[0]; + raven = RavenFactory.ravenInstance(dsn); + + // It is also possible to use the DSN detection system like this + raven = RavenFactory.ravenInstance(); + } - import net.kencochrane.raven.Raven; - import net.kencochrane.raven.RavenFactory; - import net.kencochrane.raven.event.Event; - import net.kencochrane.raven.event.EventBuilder; - import net.kencochrane.raven.event.interfaces.ExceptionInterface; - import net.kencochrane.raven.event.interfaces.MessageInterface; - - public class MyClass { - private static Raven raven; - - public static void main(String... args) { - // Creation of the client with a specific DSN - String dsn = args[0]; - raven = RavenFactory.ravenInstance(dsn); - - // It is also possible to use the DSN detection system like this - raven = RavenFactory.ravenInstance(); - } + void logSimpleMessage() { + // This adds a simple message to the logs + EventBuilder eventBuilder = new EventBuilder() + .setMessage("This is a test") + .setLevel(Event.Level.INFO) + .setLogger(MyClass.class.getName()); + raven.runBuilderHelpers(eventBuilder); // Optional + raven.sendEvent(eventBuilder.build()); + } - void logSimpleMessage() { - // This adds a simple message to the logs + void logException() { + try { + unsafeMethod(); + } catch (Exception e) { + // This adds an exception to the logs EventBuilder eventBuilder = new EventBuilder() - .setMessage("This is a test") - .setLevel(Event.Level.INFO) - .setLogger(MyClass.class.getName()); + .setMessage("Exception caught") + .setLevel(Event.Level.ERROR) + .setLogger(MyClass.class.getName()) + .addSentryInterface(new ExceptionInterface(e)); raven.runBuilderHelpers(eventBuilder); // Optional raven.sendEvent(eventBuilder.build()); } + } - void logException() { - try { - unsafeMethod(); - } catch (Exception e) { - // This adds an exception to the logs - EventBuilder eventBuilder = new EventBuilder() - .setMessage("Exception caught") - .setLevel(Event.Level.ERROR) - .setLogger(MyClass.class.getName()) - .addSentryInterface(new ExceptionInterface(e)); - raven.runBuilderHelpers(eventBuilder); // Optional - raven.sendEvent(eventBuilder.build()); - } - } - - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call that"); - } + void unsafeMethod() { + throw new UnsupportedOperationException("You shouldn't call that"); } +} +``` From 878a270ae4fe0ed253bd4fcc25583eeb979f2f8f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 17:17:52 +0100 Subject: [PATCH 0675/2152] Add info on an implementation of slf4j --- raven/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/raven/README.md b/raven/README.md index 6d144114236..fc25ea2c825 100644 --- a/raven/README.md +++ b/raven/README.md @@ -22,6 +22,11 @@ Relies on: - [raven-4.0.jar](http://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.0%7Cjar) - [slf4j-api-1.7.5.jar](http://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.5%7Cjar) + it is also recommended to have an implementation of the slf4j-api in order to + get the potential logs from Raven. For example if the connection isn't working + or if a problem occured during the creation of the log. + [slf4j-jdk14-1.7.5.jar](http://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-jdk14%7C1.7.5%7Cjar) + is recommended if `java.util.logging` is used. - [commons-codec-1.8.jar](http://search.maven.org/#artifactdetails%7Ccommons-codec%7Ccommons-codec%7C1.8%7Cjar) - [jackson-core-2.2.2.jar](http://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.2.2%7Cjar) From c0149a2ba5d8318f7633bc0002ec0285453eee97 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 17:18:51 +0100 Subject: [PATCH 0676/2152] Use the same class name as in the later examples --- raven/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/README.md b/raven/README.md index fc25ea2c825..1de90213261 100644 --- a/raven/README.md +++ b/raven/README.md @@ -44,7 +44,7 @@ net.kencochrane.raven.jul.SentryHandler.dsn=http://publicKey:secretKey@host:port When starting your application, add the `java.util.logging.config.file` to the system properties, with the full path to the `logging.properties` as its value. - $ java -Djava.util.logging.config.file=/path/to/app.properties MainClass + $ java -Djava.util.logging.config.file=/path/to/app.properties MyClass ### In practice ```java From 82dd66f0500453133e1c1b881fad1e52176202d7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 17:26:41 +0100 Subject: [PATCH 0677/2152] Use HTTPS links when possible --- raven/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/raven/README.md b/raven/README.md index 1de90213261..e4747479149 100644 --- a/raven/README.md +++ b/raven/README.md @@ -15,20 +15,20 @@ for `java.util.logging`. ``` ### Other dependency managers -Details in the [central Maven repository](http://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.0%7Cjar). ### Manual dependency management Relies on: - - [raven-4.0.jar](http://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.0%7Cjar) - - [slf4j-api-1.7.5.jar](http://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.5%7Cjar) + - [raven-4.0.jar](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.0%7Cjar) + - [slf4j-api-1.7.5.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.5%7Cjar) it is also recommended to have an implementation of the slf4j-api in order to get the potential logs from Raven. For example if the connection isn't working or if a problem occured during the creation of the log. - [slf4j-jdk14-1.7.5.jar](http://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-jdk14%7C1.7.5%7Cjar) + [slf4j-jdk14-1.7.5.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-jdk14%7C1.7.5%7Cjar) is recommended if `java.util.logging` is used. - - [commons-codec-1.8.jar](http://search.maven.org/#artifactdetails%7Ccommons-codec%7Ccommons-codec%7C1.8%7Cjar) - - [jackson-core-2.2.2.jar](http://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.2.2%7Cjar) + - [commons-codec-1.8.jar](https://search.maven.org/#artifactdetails%7Ccommons-codec%7Ccommons-codec%7C1.8%7Cjar) + - [jackson-core-2.2.2.jar](https://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.2.2%7Cjar) ## Usage (`java.util.logging`) @@ -38,7 +38,7 @@ In the `logging.properties` file set: ```properties level=INFO handlers=net.kencochrane.raven.jul.SentryHandler -net.kencochrane.raven.jul.SentryHandler.dsn=http://publicKey:secretKey@host:port/1?options +net.kencochrane.raven.jul.SentryHandler.dsn=https://publicKey:secretKey@host:port/1?options ``` When starting your application, add the `java.util.logging.config.file` to the From 851114d120fcc4d6d6540e34c90cedc72ae7e20b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 17:37:58 +0100 Subject: [PATCH 0678/2152] Set the log level to ALL (just in case) --- raven/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/README.md b/raven/README.md index e4747479149..e8d9f8f83b5 100644 --- a/raven/README.md +++ b/raven/README.md @@ -36,7 +36,7 @@ Relies on: In the `logging.properties` file set: ```properties -level=INFO +level=ALL handlers=net.kencochrane.raven.jul.SentryHandler net.kencochrane.raven.jul.SentryHandler.dsn=https://publicKey:secretKey@host:port/1?options ``` From ffb4df176a9609941073c766ad9b6f504934a7ae Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 17:41:15 +0100 Subject: [PATCH 0679/2152] Fix links to use HTTPS --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4fa6c4e95d3..87db82e2a7f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Raven -[![Build Status](https://secure.travis-ci.org/kencochrane/raven-java.png?branch=master)](http://travis-ci.org/kencochrane/raven-java) +[![Build Status](https://secure.travis-ci.org/kencochrane/raven-java.png?branch=master)](https://travis-ci.org/kencochrane/raven-java) Raven is the Java client for [Sentry](https://www.getsentry.com/). Raven relies on the most popular logging libraries to capture and convert logs From 31fcb08b627a65d261f096d51d6680ddab9bab6c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 17:41:25 +0100 Subject: [PATCH 0680/2152] Do not overuse the code blocks and link to projects rather than files --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 87db82e2a7f..fbbc348c5a9 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ Raven relies on the most popular logging libraries to capture and convert logs before sending details to a Sentry instance. - [`java.util.logging`](http://docs.oracle.com/javase/7/docs/technotes/guides/logging/index.html) - support is provided by the main project [`raven`](raven/README.md) - - [log4j](https://logging.apache.org/log4j/1.2/) support is provided in [`raven-log4j`](raven-log4j/README.md) - - [log4j2](https://logging.apache.org/log4j/2.x/) can be used with [`raven-log4j2`](raven-log4j2/README.md) - - [logback](http://logback.qos.ch/) support is provided in [`raven-logback`](raven-logback/README.md) + support is provided by the main project [raven](raven) + - [log4j](https://logging.apache.org/log4j/1.2/) support is provided in [raven-log4j](raven-log4j) + - [log4j2](https://logging.apache.org/log4j/2.x/) can be used with [raven-log4j2](raven-log4j2) + - [logback](http://logback.qos.ch/) support is provided in [raven-logback](raven-logback) While it's **strongly recommended to use one of the supported logging frameworks** to capture and send messages to Sentry, a it is possible to do so From e081feaeeffa4f37a9b7d81a72a02d2390f44564 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 18:02:32 +0100 Subject: [PATCH 0681/2152] Add a readme file to the log4j module --- raven-log4j/README.md | 69 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 raven-log4j/README.md diff --git a/raven-log4j/README.md b/raven-log4j/README.md new file mode 100644 index 00000000000..c4e956f499a --- /dev/null +++ b/raven-log4j/README.md @@ -0,0 +1,69 @@ +# Raven-log4j +[log4j](https://logging.apache.org/log4j/1.2/) support for Raven. +It provides an [`Appender`](https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Appender.html) +for log4j to send the logged events to Sentry. + +## Installation + +### Maven +```xml + + net.kencochrane.raven + raven-log4j + 4.0 + +``` + +### Other dependency managers +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j%7C4.0%7Cjar). + +### Manual dependency management +Relies on: + + - [raven dependencies](../raven) + - [log4j-1.2.17.jar](https://search.maven.org/#artifactdetails%7Clog4j%7Clog4j%7C1.2.17%7Cjar) + + +## Usage +### Configuration +In the `log4j.properties` file set: + +```properties +log4j.rootLogger=ALL, SentryAppender +log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender +log4j.appender.SentryAppender.dsn=https://publicKey:secretKey@host:port/1?options +``` + +### In practice +```java +import org.apache.log4j.Logger; + +public class MyClass { + private static final Logger logger = Logger.getLogger(MyClass.class); + + void logSimpleMessage() { + // This adds a simple message to the logs + logger.info("This is a test"); + } + + void logException() { + try { + unsafeMethod(); + } catch (Exception e) { + // This adds an exception to the logs + logger.error("Exception caught", e); + } + } + + void unsafeMethod() { + throw new UnsupportedOperationException("You shouldn't call that"); + } +} +``` + +## Asynchronous logging +It is not recommended to attempt to set up `SentryAppender` within an +[AsyncAppender](https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/AsyncAppender.html). +While this is a common solution to avoid blocking the current thread until the +event is sent to Sentry, it is recommended to rely instead on the asynchronous +connection provided by Raven. From 6a9119fddfc244f4d3d2d4eefdec78a3dcfb490d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 18:20:25 +0100 Subject: [PATCH 0682/2152] Remove manual usage from INSTALL.md --- INSTALL.md | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 4c3188bbaff..857f1dd2d17 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -51,22 +51,6 @@ TODO ## Using Raven -### Manual usage - - import net.kencochrane.raven.Raven; - import net.kencochrane.raven.event.EventBuilder; - - public class Example { - public static void main(String[] args) { - // The DSN from Sentry: "http://public:private@host:port/1" - String rawDsn = args[0]; - Raven client = new Raven(rawDsn); - EventBuilder eventBuilder = new EventBuilder() - .setMessage("Hello from Raven!"); - client.sendEvent(eventBuilder.build()); - } - } - It is also possible to create a client without directly providing a DSN, the DSN will then be determined at runtime (when the client is created). @@ -77,18 +61,6 @@ The client will lookup for the first DSN configuration provided: (`export SENTRY_DSN=yoursentrydsn` or `setenv SENTRYDSN yoursentrydsn`) - The system property `SENTRY_DSN` (`-DSENTRY_DSN=yoursentrydsn`) - import net.kencochrane.raven.Raven; - import net.kencochrane.raven.event.EventBuilder; - - public class Example { - public static void main(String[] args) { - Raven client = new Raven(); - EventBuilder eventBuilder = new EventBuilder() - .setMessage("Hello from Raven!"); - client.sendEvent(eventBuilder.build()); - } - } - ### Using `java.util.logging` To use the `SentryHandler` with `java.util.loggin` use this `logging.properties` From 538cf24aa77193d967767015b53a2970c266908f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 18:21:33 +0100 Subject: [PATCH 0683/2152] Remove java.util.logging usage from INSTALL.md --- INSTALL.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 857f1dd2d17..41829e46f0a 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -61,14 +61,6 @@ The client will lookup for the first DSN configuration provided: (`export SENTRY_DSN=yoursentrydsn` or `setenv SENTRYDSN yoursentrydsn`) - The system property `SENTRY_DSN` (`-DSENTRY_DSN=yoursentrydsn`) -### Using `java.util.logging` -To use the `SentryHandler` with `java.util.loggin` use this `logging.properties` - - level=INFO - handlers=net.kencochrane.raven.jul.SentryHandler - net.kencochrane.raven.jul.SentryHandler.dsn=http://publicKey:secretKey@host:port/1?options - - ### Using log4j To use the `SentryAppender` with log4j use this configuration: From 5991f2b453b76ef643555238a38a4519952d2587 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 18:21:53 +0100 Subject: [PATCH 0684/2152] Remove log4j usage from INSTALL.md --- INSTALL.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 41829e46f0a..f13818c4038 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -61,20 +61,6 @@ The client will lookup for the first DSN configuration provided: (`export SENTRY_DSN=yoursentrydsn` or `setenv SENTRYDSN yoursentrydsn`) - The system property `SENTRY_DSN` (`-DSENTRY_DSN=yoursentrydsn`) -### Using log4j -To use the `SentryAppender` with log4j use this configuration: - - log4j.rootLogger=DEBUG, SentryAppender - log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender - log4j.appender.SentryAppender.dsn=http://publicKey:secretKey@host:port/1?options - -#### Asynchronous logging with AsyncAppender -It is not recommended to attempt to set up a `SentryAppender` with an -[AsyncAppender](http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/AsyncAppender.html). -While this is a common solution to avoid blocking the current thread until the -event is sent to Sentry, it is recommended to rely instead on the asynchronous -connection within Raven. - ### Using log4j2 To use the `SentryAppender` with log4j2 use this configuration: From 40dc16443a95bccf4cf0fddbe75d1c5fc6c9b231 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 18:50:19 +0100 Subject: [PATCH 0685/2152] Add details about log4j2 configuration --- raven-log4j2/README.md | 82 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 raven-log4j2/README.md diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md new file mode 100644 index 00000000000..caa0839ea02 --- /dev/null +++ b/raven-log4j2/README.md @@ -0,0 +1,82 @@ +# Raven-log4j2 +[log4j2](https://logging.apache.org/log4j/2.x/) support for Raven. +It provides an [`Appender`](https://logging.apache.org/log4j/2.x/log4j-core/apidocs/org/apache/logging/log4j/core/Appender.html) +for log4j to send the logged events to Sentry. + +## Installation + +### Maven +```xml + + net.kencochrane.raven + raven-log4j2 + 4.0 + +``` + +### Other dependency managers +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j2%7C4.0%7Cjar). + +### Manual dependency management +Relies on: + + - [raven dependencies](../raven) + - [log4j-api-2.0-beta7.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7C2.0-beta7%7Cjar) + - [log4j-core-2.0-beta7.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7C2.0-beta7%7Cjar) + + +## Usage +### Configuration +In the `log4j2.xml` file set: + +```xml + + + + + + https://publicKey:secretKey@host:port/1?options + + + + + + + + + + +``` + +### In practice +```java +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class MyClass { + private static final Logger logger = LogManager.getLogger(MyClass.class); + + void logSimpleMessage() { + // This adds a simple message to the logs + logger.info("This is a test"); + } + + void logException() { + try { + unsafeMethod(); + } catch (Exception e) { + // This adds an exception to the logs + logger.error("Exception caught", e); + } + } + + void unsafeMethod() { + throw new UnsupportedOperationException("You shouldn't call that"); + } +} +``` + +### Extras +**TODO:** +The log4j2 module for raven takes advantage of the [marker system](https://logging.apache.org/log4j/2.x/manual/markers.html). +It is also possible use both the [MDC and the NDC systems provided by log4j2](https://logging.apache.org/log4j/2.x/manual/thread-context.html) From 414fc294e6706d8cfa3bca1ddea57071d06a5357 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 18:53:39 +0100 Subject: [PATCH 0686/2152] Use the proper name for Log4j 2 --- raven-log4j2/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index caa0839ea02..bb830e404ad 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -1,7 +1,7 @@ -# Raven-log4j2 -[log4j2](https://logging.apache.org/log4j/2.x/) support for Raven. +# Raven-Log4j 2 +[Log4j 2](https://logging.apache.org/log4j/2.x/) support for Raven. It provides an [`Appender`](https://logging.apache.org/log4j/2.x/log4j-core/apidocs/org/apache/logging/log4j/core/Appender.html) -for log4j to send the logged events to Sentry. +for Log4j 2 to send the logged events to Sentry. ## Installation @@ -78,5 +78,5 @@ public class MyClass { ### Extras **TODO:** -The log4j2 module for raven takes advantage of the [marker system](https://logging.apache.org/log4j/2.x/manual/markers.html). -It is also possible use both the [MDC and the NDC systems provided by log4j2](https://logging.apache.org/log4j/2.x/manual/thread-context.html) +The Log4j 2 module for raven takes advantage of the [marker system](https://logging.apache.org/log4j/2.x/manual/markers.html). +It is also possible use both the [MDC and the NDC systems provided by Log4j 2](https://logging.apache.org/log4j/2.x/manual/thread-context.html) From 04dd6bcb5c6ba229aa63973c75bb7477d7853da3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 19:05:38 +0100 Subject: [PATCH 0687/2152] Add details on the slf4j implementation to use --- raven-log4j/README.md | 2 ++ raven-log4j2/README.md | 2 ++ raven/README.md | 10 ++++------ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index c4e956f499a..ae23828e470 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -22,6 +22,8 @@ Relies on: - [raven dependencies](../raven) - [log4j-1.2.17.jar](https://search.maven.org/#artifactdetails%7Clog4j%7Clog4j%7C1.2.17%7Cjar) + - [slf4j-log4j12-1.7.5.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-log4j12%7C1.7.5%7Cjar) + is recommended as the implementation of slf4j (instead of slf4j-jdk14). ## Usage diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index bb830e404ad..3a27ace8df2 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -23,6 +23,8 @@ Relies on: - [raven dependencies](../raven) - [log4j-api-2.0-beta7.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7C2.0-beta7%7Cjar) - [log4j-core-2.0-beta7.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7C2.0-beta7%7Cjar) + - [log4j-slf4j-impl-2.0-beta7.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7C2.0-beta7%7Cjar) + is recommended as the implementation of slf4j (instead of slf4j-jdk14). ## Usage diff --git a/raven/README.md b/raven/README.md index e8d9f8f83b5..5efdb22d89e 100644 --- a/raven/README.md +++ b/raven/README.md @@ -21,14 +21,12 @@ Details in the [central Maven repository](https://search.maven.org/#artifactdeta Relies on: - [raven-4.0.jar](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.0%7Cjar) - - [slf4j-api-1.7.5.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.5%7Cjar) - it is also recommended to have an implementation of the slf4j-api in order to - get the potential logs from Raven. For example if the connection isn't working - or if a problem occured during the creation of the log. - [slf4j-jdk14-1.7.5.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-jdk14%7C1.7.5%7Cjar) - is recommended if `java.util.logging` is used. - [commons-codec-1.8.jar](https://search.maven.org/#artifactdetails%7Ccommons-codec%7Ccommons-codec%7C1.8%7Cjar) - [jackson-core-2.2.2.jar](https://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.2.2%7Cjar) + - [slf4j-api-1.7.5.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.5%7Cjar) + - [slf4j-jdk14-1.7.5.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-jdk14%7C1.7.5%7Cjar) + is recommended as the implementation of slf4j to capture the log events + generated by Raven (connection errors, ...) if `java.util.logging` is used. ## Usage (`java.util.logging`) From b27a72490f458ce6964a7f5911ae453d5613a066 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 19:10:02 +0100 Subject: [PATCH 0688/2152] Remove redundant documentation --- INSTALL.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index f13818c4038..cea1dc57402 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -18,19 +18,6 @@ sources. $ mvn clean install -DskipTests=true ### Maven dependency -To add raven as a dependency, simply add this to the pom.xml file: - - - net.kencochrane.raven - raven - 4.0 - - -Other modules can be added the same way (replacing the `artifactId`) with the -name of the module. -It is preferable to use the scope `runtime` if the API is used through a -logging system. - If the version is a snapshot it will be necessary to specify the Sonatype Nexus snapshot repository: From 5c100210144b22fb076c030373d0ecf7b58d6a37 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 19:10:14 +0100 Subject: [PATCH 0689/2152] Remove empty section of documentation --- INSTALL.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index cea1dc57402..fc34cd6a431 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -33,9 +33,6 @@ Sonatype Nexus snapshot repository: -### Manual installation -TODO - ## Using Raven It is also possible to create a client without directly providing a DSN, From 617a05948d1d0b6116bc9c6e7a429933e4532705 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 19:10:29 +0100 Subject: [PATCH 0690/2152] Remove Log4j 2 section from the install --- INSTALL.md | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index fc34cd6a431..26292323dc6 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -45,27 +45,6 @@ The client will lookup for the first DSN configuration provided: (`export SENTRY_DSN=yoursentrydsn` or `setenv SENTRYDSN yoursentrydsn`) - The system property `SENTRY_DSN` (`-DSENTRY_DSN=yoursentrydsn`) -### Using log4j2 -To use the `SentryAppender` with log4j2 use this configuration: - - - - - - - http://publicKey:secretKey@host:port/1?options - - - - - - - - - - - - ### Using logback To use the `SentryAppender` with logback use this configuration: From 51843c5d4a59fdc302d71ba56b8865d99bf46dfe Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 21:20:52 +0100 Subject: [PATCH 0691/2152] Add README.md to logback --- raven-logback/README.md | 74 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 raven-logback/README.md diff --git a/raven-logback/README.md b/raven-logback/README.md new file mode 100644 index 00000000000..fa138ec12ce --- /dev/null +++ b/raven-logback/README.md @@ -0,0 +1,74 @@ +# Raven-logback +[logback](http://logback.qos.ch/) support for Raven. +It provides an [`Appender`](http://logback.qos.ch/apidocs/ch/qos/logback/core/Appender.html) +for logback to send the logged events to Sentry. + +## Installation + +### Maven +```xml + + net.kencochrane.raven + raven-logback + 4.0 + +``` + +### Other dependency managers +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-logback%7C4.0%7Cjar). + +### Manual dependency management +Relies on: + + - [raven dependencies](../raven) + - [logback-core-1.0.13.jar](https://search.maven.org/#artifactdetails%7Cch.qos.logback%7Clogback-core%7C1.0.13%7Cjar) + - [logback-classic-1.0.13.jar](https://search.maven.org/#artifactdetails%7Cch.qos.logback%7Clogback-classic%7C1.0.13%7Cjar) + will act as the implementation of slf4j (instead of slf4j-jdk14). + +## Usage +### Configuration +In the `logback.xml` file set: + +```xml + + + https://publicKey:secretKey@host:port/1?options + + + + + +``` + +### In practice +```java +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MyClass { + private static final Logger logger = LoggerFactory.getLogger(MyClass.class); + + void logSimpleMessage() { + // This adds a simple message to the logs + logger.info("This is a test"); + } + + void logException() { + try { + unsafeMethod(); + } catch (Exception e) { + // This adds an exception to the logs + logger.error("Exception caught", e); + } + } + + void unsafeMethod() { + throw new UnsupportedOperationException("You shouldn't call that"); + } +} +``` + +### Extras +**TODO:** +The logback module for raven takes advantage of the [marker system](http://www.slf4j.org/faq.html#fatal). +It is also possible use the [MDC system provided by logback](http://logback.qos.ch/manual/mdc.html). From b6a4b9fd992a14cb32dcf673a419c2a484c19793 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 21:21:16 +0100 Subject: [PATCH 0692/2152] Remove logback from install.md --- INSTALL.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 26292323dc6..86dcf7dd39a 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -45,20 +45,5 @@ The client will lookup for the first DSN configuration provided: (`export SENTRY_DSN=yoursentrydsn` or `setenv SENTRYDSN yoursentrydsn`) - The system property `SENTRY_DSN` (`-DSENTRY_DSN=yoursentrydsn`) -### Using logback -To use the `SentryAppender` with logback use this configuration: - - - - - http://publicKey:secretKey@host:port/1?options - - - - - - - - ### Capturing the HTTP environment **TODO** From 46a70f749b43e179319157fca7f28cb35bd6ed43 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 9 Jun 2013 21:21:46 +0100 Subject: [PATCH 0693/2152] Simplify link to the raven module --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fbbc348c5a9..8f63bebc463 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ before sending details to a Sentry instance. While it's **strongly recommended to use one of the supported logging frameworks** to capture and send messages to Sentry, a it is possible to do so -manually with the main project [`raven`](raven/README.md). +manually with the main project [raven](raven). Raven supports both HTTP(S) and UDP as transport protocols to the Sentry instance. From 2e7c91cad21e60f83c1e4bbdfe6e296fc73ebcf9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 23 Jun 2013 23:33:20 +0100 Subject: [PATCH 0694/2152] Add documentation to use snapshots --- README.md | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8f63bebc463..26813e01e46 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,26 @@ Each release of Sentry supports the last two version of the protocol (i.e. Sentry 5.4.5 supports both the protocol V3 and V2), for this reason, only the two last stable version of Raven are actively maintained. -## Build and Installation -**See -[INSTALL.md](https://github.com/kencochrane/raven-java/blob/master/INSTALL.md)** - +### Snapshot versions +While the stable versions of raven are available on the +[central Maven Repository](https://search.maven.org), newer (but less stable) +versions (AKA snapshots) are available in Sonatype's snapshot repository. + +To use it with maven, add the following repository: + +```xml + + sonatype-nexus-snapshots + Sonatype Nexus Snapshots + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + +``` ## Connection and protocol It is possible to send events to Sentry over different protocols, depending From 15319382b6c1fc7bf2af2ec59723f7303c449bef Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 23 Jun 2013 23:33:43 +0100 Subject: [PATCH 0695/2152] Add documentation for Android issues --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 26813e01e46..8a7903eec13 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,22 @@ To use it with maven, add the following repository: ``` +## Android + +Raven works on Android, and relies on the [ServiceLoader](https://developer.android.com/reference/java/util/ServiceLoader.html) +system which uses the content of `META-INF/services`. +This is used to declare the `RavenFactory` implementations (to allow more control over the automatically generated +instances of `Raven`) in `META-INF/services/net.kencochrane.raven.RavenFactory`. + +Unfortunately, when the APK is build, the content of `META-INF/services` of the dependencies is lost, this prevent Raven +to work properly. A few solutions exist for that problem. + + - Use [maven-android-plugin](https://code.google.com/p/maven-android-plugin/) which has already solved this +[problem](https://code.google.com/p/maven-android-plugin/issues/detail?id=97) + - Create manually a `META-INF/services/net.kencochrane.raven.RavenFactory` for the project which will contain the + canonical name of of implementation of `RavenFactory` (ie. `net.kencochrane.raven.DefaultRavenFactory`). + - Register manually the `RavenFactory` when the application starts with `RavenFactory.registerFactory(new DefaultRavenFactory())`. + ## Connection and protocol It is possible to send events to Sentry over different protocols, depending on the security and performance requirements. From 6c745bf6af17f5b98ddfe5428fbf27ade0bce9ba Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 24 Jun 2013 09:30:21 +0100 Subject: [PATCH 0696/2152] Use proper code highlighting --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a7903eec13..fc4d6345e58 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,11 @@ to work properly. A few solutions exist for that problem. [problem](https://code.google.com/p/maven-android-plugin/issues/detail?id=97) - Create manually a `META-INF/services/net.kencochrane.raven.RavenFactory` for the project which will contain the canonical name of of implementation of `RavenFactory` (ie. `net.kencochrane.raven.DefaultRavenFactory`). - - Register manually the `RavenFactory` when the application starts with `RavenFactory.registerFactory(new DefaultRavenFactory())`. + - Register manually the `RavenFactory` when the application starts: + + ```java + RavenFactory.registerFactory(new DefaultRavenFactory()); + ``` ## Connection and protocol It is possible to send events to Sentry over different protocols, depending From 30cd1e73541eeea006ae41b194b55f99a83596fe Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 27 Jun 2013 00:49:09 +0100 Subject: [PATCH 0697/2152] Add documentation for the simplified API --- raven/README.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/raven/README.md b/raven/README.md index 5efdb22d89e..e115997b647 100644 --- a/raven/README.md +++ b/raven/README.md @@ -88,6 +88,47 @@ each field sent to Sentry. ```java import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; + + +public class MyClass { + private static Raven raven; + + public static void main(String... args) { + // Creation of the client with a specific DSN + String dsn = args[0]; + raven = RavenFactory.ravenInstance(dsn); + + // It is also possible to use the DSN detection system like this + raven = RavenFactory.ravenInstance(); + } + + void logSimpleMessage() { + // This adds a simple message to the logs + raven.sendMessage("This is a test"); + } + + void logException() { + try { + unsafeMethod(); + } catch (Exception e) { + // This adds an exception to the logs + raven.sendException(e); + } + } + + void unsafeMethod() { + throw new UnsupportedOperationException("You shouldn't call that"); + } +} +``` + +### In practice (advanced) + +For more complex messages, it will be necessary to build an `Event` with the `EventBuilder` class. + +```java +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; @@ -135,3 +176,5 @@ public class MyClass { } } ``` + +This gives more control over the content of the `Event` and gives access to the complete API supported by Sentry. From e403071bed13339cb68ca150275b88a7ab8f9df3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 4 Jul 2013 12:13:17 +0100 Subject: [PATCH 0698/2152] Replace dependency to commons-codec with guava --- raven/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/README.md b/raven/README.md index e115997b647..9540dec2d64 100644 --- a/raven/README.md +++ b/raven/README.md @@ -21,7 +21,7 @@ Details in the [central Maven repository](https://search.maven.org/#artifactdeta Relies on: - [raven-4.0.jar](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.0%7Cjar) - - [commons-codec-1.8.jar](https://search.maven.org/#artifactdetails%7Ccommons-codec%7Ccommons-codec%7C1.8%7Cjar) + - [guava-14.0.1.jar](https://search.maven.org/#artifactdetails%7Ccom.google.guava%7Cguava%7C14.0.1%7Cjar) - [jackson-core-2.2.2.jar](https://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.2.2%7Cjar) - [slf4j-api-1.7.5.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.5%7Cjar) - [slf4j-jdk14-1.7.5.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-jdk14%7C1.7.5%7Cjar) From e1657e591760a10f53b2b94ac39d5f71fc2f8f11 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Jul 2013 19:23:13 +0100 Subject: [PATCH 0699/2152] Remove unused INSTALL.md --- INSTALL.md | 49 ------------------------------------------------- 1 file changed, 49 deletions(-) delete mode 100644 INSTALL.md diff --git a/INSTALL.md b/INSTALL.md deleted file mode 100644 index 86dcf7dd39a..00000000000 --- a/INSTALL.md +++ /dev/null @@ -1,49 +0,0 @@ -# Installation and setup of Raven - -## Installing Raven -Currently there are 6 modules in the project: - - - `raven`, the core of the project, providing the client and support for JUL - - `raven-log4j`, Appender for log4j - - `raven-log4j2`, Appender for log4j2 - - `raven-logback`, Appender for Logback - - `sentry-stub`, Sentry server stub, allowing to test the protocol - -### Build -It's possible to get the latest version of Raven by building it from the -sources. - - $ git clone https://github.com/kencochrane/raven-java.git - $ cd raven-java - $ mvn clean install -DskipTests=true - -### Maven dependency -If the version is a snapshot it will be necessary to specify the -Sonatype Nexus snapshot repository: - - - sonatype-nexus-snapshots - Sonatype Nexus Snapshots - https://oss.sonatype.org/content/repositories/snapshots - - false - - - true - - - -## Using Raven - -It is also possible to create a client without directly providing a DSN, -the DSN will then be determined at runtime (when the client is created). - -The client will lookup for the first DSN configuration provided: - - - JNDI in `java:comp/env/sentry/dsn` - - The environment variable `SENTRY_DSN` - (`export SENTRY_DSN=yoursentrydsn` or `setenv SENTRYDSN yoursentrydsn`) - - The system property `SENTRY_DSN` (`-DSENTRY_DSN=yoursentrydsn`) - -### Capturing the HTTP environment -**TODO** From 8c5598a6f0cb5a4ec5c1213620f195ab809bed2a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Jul 2013 19:23:33 +0100 Subject: [PATCH 0700/2152] Format and remove useless content from README --- README.md | 89 ++++++++++++++----------------------------------------- 1 file changed, 23 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index fc4d6345e58..f14be5bc4a1 100644 --- a/README.md +++ b/README.md @@ -57,18 +57,23 @@ To use it with maven, add the following repository: ## Android -Raven works on Android, and relies on the [ServiceLoader](https://developer.android.com/reference/java/util/ServiceLoader.html) +Raven works on Android, and relies on the +[ServiceLoader](https://developer.android.com/reference/java/util/ServiceLoader.html) system which uses the content of `META-INF/services`. -This is used to declare the `RavenFactory` implementations (to allow more control over the automatically generated -instances of `Raven`) in `META-INF/services/net.kencochrane.raven.RavenFactory`. +This is used to declare the `RavenFactory` implementations (to allow more +control over the automatically generated instances of `Raven`) in +`META-INF/services/net.kencochrane.raven.RavenFactory`. -Unfortunately, when the APK is build, the content of `META-INF/services` of the dependencies is lost, this prevent Raven -to work properly. A few solutions exist for that problem. +Unfortunately, when the APK is build, the content of `META-INF/services` of +the dependencies is lost, this prevent Raven to work properly. +Solutions exist for that problem: - - Use [maven-android-plugin](https://code.google.com/p/maven-android-plugin/) which has already solved this + - Use [maven-android-plugin](https://code.google.com/p/maven-android-plugin/) + which has already solved this [problem](https://code.google.com/p/maven-android-plugin/issues/detail?id=97) - - Create manually a `META-INF/services/net.kencochrane.raven.RavenFactory` for the project which will contain the - canonical name of of implementation of `RavenFactory` (ie. `net.kencochrane.raven.DefaultRavenFactory`). + - Create manually a `META-INF/services/net.kencochrane.raven.RavenFactory` for + the project which will contain the canonical name of of implementation of + `RavenFactory` (ie. `net.kencochrane.raven.DefaultRavenFactory`). - Register manually the `RavenFactory` when the application starts: ```java @@ -127,9 +132,9 @@ option is enabled. ### Async connection In order to avoid performance issues due to a large amount of logs being generated or a slow connection to the Sentry server, an asynchronous connection -is set up, using a low priority thread pool to submit events to Sentry. +is set up, using a low priority thread pool to submit events to Sentry. -To disable the async mode, add `raven.async=false` to your DSN: +To disable the async mode, add `raven.async=false` to the DSN: http://public:private@host:port/1?raven.async=false @@ -178,7 +183,7 @@ Usually when a StackTrace is printed, the result looks like this: HighLevelException: MidLevelException: LowLevelException at Main.a(Main.java:13) - at Junk.main(Main.java:4) + at Main.main(Main.java:4) Caused by: MidLevelException: LowLevelException at Main.c(Main.java:23) at Main.b(Main.java:17) @@ -206,13 +211,13 @@ what the problem was and will just create a longer stacktrace. Currently this is not configurable (see #49) and some packages are ignored by default: -- com.sun.* -- java.* -- javax.* -- org.omg.* -- sun.* -- junit.* -- com.intellij.rt.* +- `com.sun.*` +- `java.*` +- `javax.*` +- `org.omg.*` +- `sun.*` +- `junit.*` +- `com.intellij.rt.*` ### Compression By default the content sent to Sentry is compressed and encoded in base64 before @@ -238,51 +243,3 @@ By default the connection will set up its own timeout, but it's possible to manually set one with `raven.timeout` (in milliseconds): http://public:private@host:port/1?raven.timeout=10000 - - -## History - -- 4.0 - - Support of the Sentry protocol V4 -- 3.0 - - Support of the Sentry protocol V3 - - Rewritten - - Added log4j2 appender - - Support of JNDI -- 2.0 - - Version increment to reduce confusion about releases - - Added Logback appender (thanks to [ccouturi](https://github.com/ccouturi)) -- 1.0 - - Rewrite - - Support tags - - Added support for JSON processors (see bundled `ServletJSONProcessor`) -- 0.6 - - Added support for sending messages through UDP -- 0.5 - - Added async support - - Fixed issue with parsing of path and port in DSN -- 0.4 - - Added the ability to get the SENTRY_DSN from the ENV - - Added RavenClient.captureMessage - - Added RavenClient.captureException -- 0.3 - - Added Maven support - - Merged with log4sentry project by Kevin Wetzels - - Added Proxy support - - Added full stack trace to logs - -- 0.2 - - code refactor and cleanup - -- 0.1 - - initial version - -## Contributors - -- [Ken Cochrane](https://github.com/kencochrane) -- [Kevin Wetzels](https://github.com/roam) -- [David Cramer](https://github.com/dcramer) -- [Mark Philpot](https://github.com/griphiam) -- [Brad Chen](https://github.com/vvasabi) -- [ccouturi](https://github.com/ccouturi) -- [Colin Hebert](https://github.com/ColinHebert) From 1841fe366edf22cae2fc75e1b506ae955cdf41d7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Jul 2013 19:24:05 +0100 Subject: [PATCH 0701/2152] Only log warnings in examples (to avoid hammering sentry) --- raven-log4j/README.md | 2 +- raven-logback/README.md | 2 +- raven/README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index ae23828e470..789da1cb423 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -31,7 +31,7 @@ Relies on: In the `log4j.properties` file set: ```properties -log4j.rootLogger=ALL, SentryAppender +log4j.rootLogger=WARN, SentryAppender log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender log4j.appender.SentryAppender.dsn=https://publicKey:secretKey@host:port/1?options ``` diff --git a/raven-logback/README.md b/raven-logback/README.md index fa138ec12ce..5f44172efd8 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -34,7 +34,7 @@ In the `logback.xml` file set: https://publicKey:secretKey@host:port/1?options - +
    diff --git a/raven/README.md b/raven/README.md index 9540dec2d64..3a09ec11650 100644 --- a/raven/README.md +++ b/raven/README.md @@ -34,7 +34,7 @@ Relies on: In the `logging.properties` file set: ```properties -level=ALL +level=WARN handlers=net.kencochrane.raven.jul.SentryHandler net.kencochrane.raven.jul.SentryHandler.dsn=https://publicKey:secretKey@host:port/1?options ``` From ce9947c8917cff610db8f40608ef6f75aad699d6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Jul 2013 19:24:27 +0100 Subject: [PATCH 0702/2152] Add the marker as a tag rather than an extra --- .../main/java/net/kencochrane/raven/log4j2/SentryAppender.java | 2 +- .../main/java/net/kencochrane/raven/logback/SentryAppender.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 2be3a682847..07fce5ec37d 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -246,7 +246,7 @@ protected Event buildEvent(LogEvent event) { } if (event.getMarker() != null) - eventBuilder.addExtra(LOG4J_MARKER, event.getMarker()); + eventBuilder.addTag(LOG4J_MARKER, event.getMarker().getName()); raven.runBuilderHelpers(eventBuilder); return eventBuilder.build(); diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index f9cff90ef89..9598b7d7797 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -191,7 +191,7 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { } if (iLoggingEvent.getMarker() != null) - eventBuilder.addExtra(LOGBACK_MARKER, iLoggingEvent.getMarker()); + eventBuilder.addTag(LOGBACK_MARKER, iLoggingEvent.getMarker().getName()); raven.runBuilderHelpers(eventBuilder); return eventBuilder.build(); From b765a62bb86f1011acd69df07b6aced5c14afa1e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Jul 2013 19:24:58 +0100 Subject: [PATCH 0703/2152] Give unique name to the raven thread pool --- .../main/java/net/kencochrane/raven/DefaultRavenFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 92e4a33a80f..00ac440d0d9 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -204,7 +204,7 @@ protected static final class DaemonThreadFactory implements ThreadFactory { private DaemonThreadFactory(int priority) { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); - namePrefix = "pool-" + POOL_NUMBER.getAndIncrement() + "-thread-"; + namePrefix = "raven-pool-" + POOL_NUMBER.getAndIncrement() + "-thread-"; this.priority = priority; } From b3ab754d0ee989ef6b4b9ae34228d7344ef27973 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 10 Jul 2013 19:25:23 +0100 Subject: [PATCH 0704/2152] Improve documentation on modules --- raven-log4j2/README.md | 10 ++++++---- raven-logback/README.md | 10 ++++++---- raven/README.md | 6 ++++-- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index 3a27ace8df2..3490a27f8d8 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -78,7 +78,9 @@ public class MyClass { } ``` -### Extras -**TODO:** -The Log4j 2 module for raven takes advantage of the [marker system](https://logging.apache.org/log4j/2.x/manual/markers.html). -It is also possible use both the [MDC and the NDC systems provided by Log4j 2](https://logging.apache.org/log4j/2.x/manual/thread-context.html) +### Additional data and information +It's possible to add extra details to events captured by the Log4j 2 module +thanks to the [marker system](https://logging.apache.org/log4j/2.x/manual/markers.html) +which will add a tag `log4j2-Marker`. +Both [the MDC and the NDC systems provided by Log4j 2](https://logging.apache.org/log4j/2.x/manual/thread-context.html) +are usable, allowing to attach extras information to the event. diff --git a/raven-logback/README.md b/raven-logback/README.md index 5f44172efd8..a07353618cf 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -68,7 +68,9 @@ public class MyClass { } ``` -### Extras -**TODO:** -The logback module for raven takes advantage of the [marker system](http://www.slf4j.org/faq.html#fatal). -It is also possible use the [MDC system provided by logback](http://logback.qos.ch/manual/mdc.html). +### Additional data and information +It's possible to add extra details to events captured by the logback module +thanks to the [marker system](http://www.slf4j.org/faq.html#fatal) which will +add a tag `logback-Marker`. +[The MDC system provided by Log4j 2](http://logback.qos.ch/manual/mdc.html) +allows to add extra information to the event. diff --git a/raven/README.md b/raven/README.md index 3a09ec11650..53544479c4c 100644 --- a/raven/README.md +++ b/raven/README.md @@ -124,7 +124,8 @@ public class MyClass { ### In practice (advanced) -For more complex messages, it will be necessary to build an `Event` with the `EventBuilder` class. +For more complex messages, it will be necessary to build an `Event` with the +`EventBuilder` class. ```java import net.kencochrane.raven.Raven; @@ -177,4 +178,5 @@ public class MyClass { } ``` -This gives more control over the content of the `Event` and gives access to the complete API supported by Sentry. +This gives more control over the content of the `Event` and gives access to the +complete API supported by Sentry. From 7d39205b22af3c17f68bde4ee65c5e257033af2e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 15:34:13 +0100 Subject: [PATCH 0705/2152] Remove unused method --- .../raven/logback/SentryAppender.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 9598b7d7797..29dc24a7b5c 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -101,25 +101,6 @@ protected static Event.Level formatLevel(Level level) { } else return null; } - /** - * Gets the position of the event as a String. - *

    - * Allows to generate a checksum when there is no stacktrace but the position of the log can be found. - *

    - * - * @param iLoggingEvent event without stacktrace but with a position. - * @return a string version of the position. - */ - protected static String getEventPosition(ILoggingEvent iLoggingEvent) { - StringBuilder sb = new StringBuilder(); - for (StackTraceElement stackTraceElement : iLoggingEvent.getCallerData()) { - sb.append(stackTraceElement.getClassName()) - .append(stackTraceElement.getMethodName()) - .append(stackTraceElement.getLineNumber()); - } - return sb.toString(); - } - /** * {@inheritDoc} *

    From ded6cc8bc5c8969a6252c27264e02ff5f9a4d556 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 15:51:03 +0100 Subject: [PATCH 0706/2152] Catch exceptions thrown while logging events --- .../net/kencochrane/raven/log4j/SentryAppender.java | 3 +++ .../kencochrane/raven/log4j2/SentryAppender.java | 12 ++++++++---- .../kencochrane/raven/logback/SentryAppender.java | 13 +++++++++---- .../net/kencochrane/raven/jul/SentryHandler.java | 2 ++ 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index d090a0d3289..b0839da30ab 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -130,6 +130,9 @@ protected synchronized void append(LoggingEvent loggingEvent) { guard = true; Event event = buildEvent(loggingEvent); raven.sendEvent(event); + } catch (Exception e) { + getErrorHandler().error("An exception occurred while creating a new event in Raven", e, + ErrorCode.WRITE_FAILURE); } finally { guard = false; } diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 07fce5ec37d..c53690738e7 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -182,10 +182,14 @@ public void append(LogEvent logEvent) { if (Raven.RAVEN_THREAD.get()) return; - if (raven == null) - initRaven(); - Event event = buildEvent(logEvent); - raven.sendEvent(event); + try { + if (raven == null) + initRaven(); + Event event = buildEvent(logEvent); + raven.sendEvent(event); + } catch (Exception e) { + error("An exception occurred while creating a new event in Raven", logEvent, e); + } } /** diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 29dc24a7b5c..55eb86e7fa8 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -115,10 +115,15 @@ protected void append(ILoggingEvent iLoggingEvent) { if (Raven.RAVEN_THREAD.get()) return; - if (raven == null) - initRaven(); - Event event = buildEvent(iLoggingEvent); - raven.sendEvent(event); + try { + if (raven == null) + initRaven(); + + Event event = buildEvent(iLoggingEvent); + raven.sendEvent(event); + } catch (Exception e) { + addError("An exception occurred while creating a new event in Raven", e); + } } /** diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index b73e8b1d164..d02a868bd02 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -112,6 +112,8 @@ public synchronized void publish(LogRecord record) { initRaven(); Event event = buildEvent(record); raven.sendEvent(event); + } catch (Exception e) { + reportError("An exception occurred while creating a new event in Raven", e, ErrorManager.WRITE_FAILURE); } finally { guard = false; } From 53943b5fb17ee92ffdfd670f67f2944f750cd6d3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 15:52:15 +0100 Subject: [PATCH 0707/2152] Fix message text --- .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 4 ++-- .../java/net/kencochrane/raven/log4j2/SentryAppender.java | 4 ++-- .../java/net/kencochrane/raven/logback/SentryAppender.java | 4 ++-- .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index b0839da30ab..2bb7165a986 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -114,7 +114,7 @@ protected void initRaven() { raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); } catch (Exception e) { - getErrorHandler().error("An exception occurred during the creation of a raven instance", e, + getErrorHandler().error("An exception occurred during the creation of a Raven instance", e, ErrorCode.FILE_OPEN_FAILURE); } } @@ -196,7 +196,7 @@ public void close() { if (propagateClose) raven.getConnection().close(); } catch (IOException e) { - getErrorHandler().error("An exception occurred while closing the raven connection", e, + getErrorHandler().error("An exception occurred while closing the Raven connection", e, ErrorCode.CLOSE_FAILURE); } } diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index c53690738e7..4c33de01dfd 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -202,7 +202,7 @@ protected void initRaven() { raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); } catch (Exception e) { - error("An exception occurred during the creation of a raven instance", e); + error("An exception occurred during the creation of a Raven instance", e); } } @@ -272,7 +272,7 @@ public void stop() { if (propagateClose && raven != null) raven.getConnection().close(); } catch (IOException e) { - error("An exception occurred while closing the raven connection", e); + error("An exception occurred while closing the Raven connection", e); } } } diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 55eb86e7fa8..1f291f3859b 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -136,7 +136,7 @@ protected void initRaven() { raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); } catch (Exception e) { - addError("An exception occurred during the creation of a raven instance", e); + addError("An exception occurred during the creation of a Raven instance", e); } } @@ -199,7 +199,7 @@ public void stop() { if (propagateClose && raven != null) raven.getConnection().close(); } catch (IOException e) { - addError("An exception occurred while closing the raven connection", e); + addError("An exception occurred while closing the Raven connection", e); } } } diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index d02a868bd02..9e41bcee66c 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -129,7 +129,7 @@ protected void initRaven() { raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); } catch (Exception e) { - reportError("An exception occurred during the creation of a raven instance", e, ErrorManager.OPEN_FAILURE); + reportError("An exception occurred during the creation of a Raven instance", e, ErrorManager.OPEN_FAILURE); } } @@ -180,7 +180,7 @@ public void close() throws SecurityException { if (propagateClose) raven.getConnection().close(); } catch (IOException e) { - reportError("An exception occurred while closing the raven connection", e, ErrorManager.CLOSE_FAILURE); + reportError("An exception occurred while closing the Raven connection", e, ErrorManager.CLOSE_FAILURE); } } } From 4aad24e64dc8a72c5ba936c42be97911a70b8413 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 15:52:32 +0100 Subject: [PATCH 0708/2152] Handle exceptions due to the DSN separately --- .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 2bb7165a986..5ce0815f1ad 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -3,6 +3,7 @@ import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; +import net.kencochrane.raven.dsn.InvalidDsnException; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; @@ -113,6 +114,9 @@ protected void initRaven() { dsn = Dsn.dsnLookup(); raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + } catch (InvalidDsnException e) { + getErrorHandler().error("An exception occurred during the retrieval of the DSN for Raven", e, + ErrorCode.ADDRESS_PARSE_FAILURE); } catch (Exception e) { getErrorHandler().error("An exception occurred during the creation of a Raven instance", e, ErrorCode.FILE_OPEN_FAILURE); From d1a268b6216ff13ef320580822c296f13811b064 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 16:34:04 +0100 Subject: [PATCH 0709/2152] Make extra fields names public --- .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 5ce0815f1ad..38255c36e9f 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -25,11 +25,11 @@ public class SentryAppender extends AppenderSkeleton { /** * Name of the {@link Event#extra} property containing NDC details. */ - protected static final String LOG4J_NDC = "log4J-NDC"; + public static final String LOG4J_NDC = "log4J-NDC"; /** * Name of the {@link Event#extra} property containing the Thread name. */ - protected static final String THREAD_NAME = "Raven-Threadname"; + public static final String THREAD_NAME = "Raven-Threadname"; /** * Current instance of {@link Raven}. * From a152e3d0797f955c167c58fe3d7e6f8b292abadf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 17:00:07 +0100 Subject: [PATCH 0710/2152] Add test to check that thread name is added to the event --- .../raven/log4j/SentryAppenderTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index ddf4e4b7bc2..2ca69ae4ff5 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -4,11 +4,17 @@ import net.kencochrane.raven.event.Event; import org.apache.log4j.Level; import org.apache.log4j.Logger; +import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import java.util.List; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasEntry; +import static org.mockito.Mockito.*; + public class SentryAppenderTest extends AbstractLoggerTest { private Logger logger; @@ -65,4 +71,15 @@ private void assertLevelConverted(Event.Level expectedLevel, Level level) { public void testLogParametrisedMessage() throws Exception { // Parametrised messages aren't supported } + + @Test + public void testThreadNameAddedToExtra() { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + logger.info("testMessage"); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getExtra(), + Matchers.hasEntry(SentryAppender.THREAD_NAME, Thread.currentThread().getName())); + } } From bec13e019dcc18747ab496b25f75ea8b4eddf267 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 17:00:53 +0100 Subject: [PATCH 0711/2152] Add test to check that MDC details are added to the Event --- .../raven/log4j/SentryAppenderTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 2ca69ae4ff5..b564e5c1a22 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -4,12 +4,14 @@ import net.kencochrane.raven.event.Event; import org.apache.log4j.Level; import org.apache.log4j.Logger; +import org.apache.log4j.MDC; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import java.util.List; +import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasEntry; @@ -82,4 +84,18 @@ public void testThreadNameAddedToExtra() { assertThat(eventCaptor.getValue().getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, Thread.currentThread().getName())); } + + @Test + public void testMdcAddedToExtra() { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + String extraKey = UUID.randomUUID().toString(); + Object extraValue = mock(Object.class); + + MDC.put(extraKey, extraValue); + + logger.info("testMessage"); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getExtra(), hasEntry(extraKey, extraValue)); + } } From 61ef2abd0c7b3ca39618114ff5207003295440dd Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 17:01:13 +0100 Subject: [PATCH 0712/2152] Add test to check that NDC details are added to the event --- .../raven/log4j/SentryAppenderTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index b564e5c1a22..8df238968bb 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -5,6 +5,7 @@ import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.MDC; +import org.apache.log4j.NDC; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; @@ -15,6 +16,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasKey; import static org.mockito.Mockito.*; public class SentryAppenderTest extends AbstractLoggerTest { @@ -98,4 +100,28 @@ public void testMdcAddedToExtra() { verify(mockRaven).sendEvent(eventCaptor.capture()); assertThat(eventCaptor.getValue().getExtra(), hasEntry(extraKey, extraValue)); } + + @Test + @SuppressWarnings("unchecked") + public void testNdcAddedToExtra() { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + String extra = UUID.randomUUID().toString(); + String extra2 = UUID.randomUUID().toString(); + + NDC.push(extra); + logger.info("testMessage"); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getExtra(), hasKey(SentryAppender.LOG4J_NDC)); + assertThat(eventCaptor.getValue().getExtra().get(SentryAppender.LOG4J_NDC), Matchers.is(extra)); + + reset(mockRaven); + NDC.push(extra2); + logger.info("testMessage"); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getExtra(), hasKey(SentryAppender.LOG4J_NDC)); + assertThat(eventCaptor.getValue().getExtra().get(SentryAppender.LOG4J_NDC), + Matchers.is(extra + " " + extra2)); + } } From 6fc08e86859aed2d9813332ff1975082b76820c8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 17:07:17 +0100 Subject: [PATCH 0713/2152] Add documentation of the usage of MDC/NDC with log4j --- raven-log4j/README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 789da1cb423..3335611da7d 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -25,7 +25,6 @@ Relies on: - [slf4j-log4j12-1.7.5.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-log4j12%7C1.7.5%7Cjar) is recommended as the implementation of slf4j (instead of slf4j-jdk14). - ## Usage ### Configuration In the `log4j.properties` file set: @@ -36,9 +35,17 @@ log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender log4j.appender.SentryAppender.dsn=https://publicKey:secretKey@host:port/1?options ``` +### Additional data and information +It's possible to add extra details to events captured by the Log4j module +thanks to both [the MDC](https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/MDC.html) +and [the NDC](https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/NDC.html) systems provided by Log4j are +usable, allowing to attach extras information to the event. + ### In practice ```java import org.apache.log4j.Logger; +import org.apache.log4j.MDC; +import org.apache.log4j.NDC; public class MyClass { private static final Logger logger = Logger.getLogger(MyClass.class); @@ -48,6 +55,14 @@ public class MyClass { logger.info("This is a test"); } + void logWithExtras() { + // This adds a message with extras to the logs + MDC.put("extra_key", "extra_value"); + // NDC extras are sent under 'log4J-NDC' + NDC.push("Extra_details"); + logger.info("This is a test"); + } + void logException() { try { unsafeMethod(); From 6710e0c99b792b8460d97cc95fd2973ec22fb6e1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 17:12:14 +0100 Subject: [PATCH 0714/2152] Fix documentation of MDC/NDC for log4j --- raven-log4j/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 3335611da7d..38a263deb5d 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -56,10 +56,11 @@ public class MyClass { } void logWithExtras() { - // This adds a message with extras to the logs + // MDC extras MDC.put("extra_key", "extra_value"); // NDC extras are sent under 'log4J-NDC' NDC.push("Extra_details"); + // This adds a message with extras to the logs logger.info("This is a test"); } From 6f3f3dc39402b1c777fe8d021424797f02d6ac25 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 17:33:45 +0100 Subject: [PATCH 0715/2152] Make the extra/tag keys public for log4j2 --- .../java/net/kencochrane/raven/log4j2/SentryAppender.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 4c33de01dfd..45e1aa85627 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -36,15 +36,15 @@ public class SentryAppender extends AbstractAppender { /** * Name of the {@link Event#extra} property containing NDC details. */ - protected static final String LOG4J_NDC = "log4j2-NDC"; + public static final String LOG4J_NDC = "log4j2-NDC"; /** * Name of the {@link Event#extra} property containing Marker details. */ - protected static final String LOG4J_MARKER = "log4j2-Marker"; + public static final String LOG4J_MARKER = "log4j2-Marker"; /** * Name of the {@link Event#extra} property containing the Thread name. */ - protected static final String THREAD_NAME = "Raven-Threadname"; + public static final String THREAD_NAME = "Raven-Threadname"; /** * Current instance of {@link Raven}. * From 71b867c2a281a968150d79540a9bb5654415e2de Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 17:34:51 +0100 Subject: [PATCH 0716/2152] Add a way to use a Marker during tests --- .../raven/log4j2/SentryAppenderTest.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 6d270cff376..c50645b19a0 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -3,6 +3,7 @@ import net.kencochrane.raven.AbstractLoggerTest; import net.kencochrane.raven.event.Event; import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.FormattedMessage; @@ -24,17 +25,17 @@ public void setUp() throws Exception { @Override public void logAnyLevel(String message) { - logEvent(Level.INFO, null, message, null); + logEvent(Level.INFO, null, null, message, null); } @Override public void logAnyLevel(String message, Throwable exception) { - logEvent(Level.INFO, exception, message, null); + logEvent(Level.INFO, null, exception, message, null); } @Override public void logAnyLevel(String message, List parameters) { - logEvent(Level.INFO, null, message, parameters); + logEvent(Level.INFO, null, null, message, parameters); } @Override @@ -59,18 +60,19 @@ public void testLogLevelConversions() throws Exception { } private void assertLevelConverted(Event.Level expectedLevel, Level level) { - logEvent(level, null, "", null); + logEvent(level, null, null, "", null); assertLogLevel(expectedLevel); } - private void logEvent(Level level, Throwable exception, String messageString, List messageParameters) { + private void logEvent(Level level, Marker marker, Throwable exception, String messageString, + List messageParameters) { Message message; if (messageParameters != null) message = new FormattedMessage(messageString, messageParameters.toArray()); else message = new SimpleMessage(messageString); - LogEvent event = new Log4jLogEvent(LOGGER_NAME, null, SentryAppenderTest.class.getName(), level, + LogEvent event = new Log4jLogEvent(LOGGER_NAME, marker, SentryAppenderTest.class.getName(), level, message, exception); sentryAppender.append(event); } From 98cea2bc2075222aac03ec8c179312d25084ac42 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 17:36:17 +0100 Subject: [PATCH 0717/2152] Add tests to ensure that the Thread name is provided as an extra --- .../raven/log4j2/SentryAppenderTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index c50645b19a0..fe96a7c6033 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -9,10 +9,14 @@ import org.apache.logging.log4j.message.FormattedMessage; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.SimpleMessage; +import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import java.util.List; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.verify; public class SentryAppenderTest extends AbstractLoggerTest { private static final String LOGGER_NAME = SentryAppenderTest.class.getName(); @@ -64,6 +68,17 @@ private void assertLevelConverted(Event.Level expectedLevel, Level level) { assertLogLevel(expectedLevel); } + @Test + public void testThreadNameAddedToExtra() { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + logEvent(Level.INFO, null, null, "testMessage", null); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getExtra(), + Matchers.hasEntry(SentryAppender.THREAD_NAME, Thread.currentThread().getName())); + } + private void logEvent(Level level, Marker marker, Throwable exception, String messageString, List messageParameters) { Message message; From 1ba1bcabf7dc19ef201719e59b902738f7b004ba Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 17:37:24 +0100 Subject: [PATCH 0718/2152] Add test to check that the Marker is provided as a tag --- .../raven/log4j2/SentryAppenderTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index fe96a7c6033..eeb016d3e0e 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -4,6 +4,7 @@ import net.kencochrane.raven.event.Event; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.FormattedMessage; @@ -15,6 +16,8 @@ import org.mockito.ArgumentCaptor; import java.util.List; +import java.util.UUID; + import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Mockito.verify; @@ -79,6 +82,18 @@ public void testThreadNameAddedToExtra() { Matchers.hasEntry(SentryAppender.THREAD_NAME, Thread.currentThread().getName())); } + @Test + public void testMarkerAddedToTag() { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + String markerName = UUID.randomUUID().toString(); + + logEvent(Level.INFO, MarkerManager.getMarker(markerName), null, "testMessage", null); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getTags(), + Matchers.hasEntry(SentryAppender.LOG4J_MARKER, markerName)); + } + private void logEvent(Level level, Marker marker, Throwable exception, String messageString, List messageParameters) { Message message; From 2c87e27056f5979b4e7ffd8d4c446babfe2dae0a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 17:38:15 +0100 Subject: [PATCH 0719/2152] Add test to check that MDC details are available as extras --- .../raven/log4j2/SentryAppenderTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index eeb016d3e0e..a9154393e44 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -5,6 +5,7 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.FormattedMessage; @@ -94,6 +95,20 @@ public void testMarkerAddedToTag() { Matchers.hasEntry(SentryAppender.LOG4J_MARKER, markerName)); } + @Test + public void testMdcAddedToExtra() { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + String extraKey = UUID.randomUUID().toString(); + String extraValue = UUID.randomUUID().toString(); + + ThreadContext.put(extraKey, extraValue); + + logEvent(Level.INFO, null, null, "testMessage", null); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getExtra(), Matchers.hasEntry(extraKey, extraValue)); + } + private void logEvent(Level level, Marker marker, Throwable exception, String messageString, List messageParameters) { Message message; From 9c726a901b283e4775fb20f667b0c4c8ff5a5b3c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 17:38:15 +0100 Subject: [PATCH 0720/2152] Add test to check that NDC details are available as extras --- .../raven/log4j2/SentryAppenderTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index a9154393e44..69b6d6d9a31 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -16,10 +16,14 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; +import java.util.Collection; import java.util.List; import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasKey; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; public class SentryAppenderTest extends AbstractLoggerTest { @@ -109,6 +113,31 @@ public void testMdcAddedToExtra() { assertThat(eventCaptor.getValue().getExtra(), Matchers.hasEntry(extraKey, extraValue)); } + @Test + @SuppressWarnings("unchecked") + public void testNdcAddedToExtra() { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + String extra = UUID.randomUUID().toString(); + String extra2 = UUID.randomUUID().toString(); + + ThreadContext.push(extra); + logEvent(Level.INFO, null, null, "testMessage", null); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getExtra(), hasKey(SentryAppender.LOG4J_NDC)); + assertThat((Collection) eventCaptor.getValue().getExtra().get(SentryAppender.LOG4J_NDC), + contains(extra)); + + reset(mockRaven); + ThreadContext.push(extra2); + logEvent(Level.INFO, null, null, "testMessage", null); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getExtra(), hasKey(SentryAppender.LOG4J_NDC)); + assertThat((Collection) eventCaptor.getValue().getExtra().get(SentryAppender.LOG4J_NDC), + contains(extra, extra2)); + } + private void logEvent(Level level, Marker marker, Throwable exception, String messageString, List messageParameters) { Message message; From b8c30b41926d0f5467fc71df66590ec0982229d0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 17:45:09 +0100 Subject: [PATCH 0721/2152] Add documentation on tags and extras for log4j2 --- raven-log4j2/README.md | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index 3490a27f8d8..c1ff1c60092 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -50,19 +50,43 @@ In the `log4j2.xml` file set: ``` +### Additional data and information +It's possible to add extra details to events captured by the Log4j 2 module +thanks to the [marker system](https://logging.apache.org/log4j/2.x/manual/markers.html) +which will add a tag `log4j2-Marker`. +Both [the MDC and the NDC systems provided by Log4j 2](https://logging.apache.org/log4j/2.x/manual/thread-context.html) +are usable, allowing to attach extras information to the event. + ### In practice ```java import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; public class MyClass { private static final Logger logger = LogManager.getLogger(MyClass.class); + private static final Marker MARKER = MarkerManager.getMarker("myMarker"); void logSimpleMessage() { // This adds a simple message to the logs logger.info("This is a test"); } + void logWithTag() { + // This adds a message with a tag to the logs named 'log4j2-Marker' + logger.info(MARKER, "This is a test"); + } + + void logWithExtras() { + // MDC extras + ThreadContext.put("extra_key", "extra_value"); + // NDC extras are sent under 'log4j2-NDC' + ThreadContext.push("Extra_details"); + // This adds a message with extras to the logs + logger.info("This is a test"); + } + void logException() { try { unsafeMethod(); @@ -77,10 +101,3 @@ public class MyClass { } } ``` - -### Additional data and information -It's possible to add extra details to events captured by the Log4j 2 module -thanks to the [marker system](https://logging.apache.org/log4j/2.x/manual/markers.html) -which will add a tag `log4j2-Marker`. -Both [the MDC and the NDC systems provided by Log4j 2](https://logging.apache.org/log4j/2.x/manual/thread-context.html) -are usable, allowing to attach extras information to the event. From c9eec2b081bac9d352f064843860a16500063a88 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 18:22:34 +0100 Subject: [PATCH 0722/2152] Add a test to ensure that logback get the Thread name --- .../raven/logback/SentryAppenderTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java index 6de2721b1ac..cf4b4b37a20 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -7,11 +7,16 @@ import ch.qos.logback.core.Appender; import net.kencochrane.raven.AbstractLoggerTest; import net.kencochrane.raven.event.Event; +import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import java.util.List; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.verify; + public class SentryAppenderTest extends AbstractLoggerTest { private Logger logger; @@ -74,4 +79,15 @@ private void assertLevelConverted(Event.Level expectedLevel, Level level) { assertLogLevel(expectedLevel); } + + @Test + public void testThreadNameAddedToExtra() { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + logger.info("testMessage"); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getExtra(), + Matchers.hasEntry(SentryAppender.THREAD_NAME, Thread.currentThread().getName())); + } } From 81152394e80345d0f843866440873330cbc07038 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 18:24:24 +0100 Subject: [PATCH 0723/2152] Add test to check that Markers are stored as tags --- .../raven/logback/SentryAppenderTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java index cf4b4b37a20..466315f9d69 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -11,6 +11,7 @@ import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.slf4j.MarkerFactory; import java.util.List; @@ -90,4 +91,16 @@ public void testThreadNameAddedToExtra() { assertThat(eventCaptor.getValue().getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, Thread.currentThread().getName())); } + + @Test + public void testMarkerAddedToTag() { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + String markerName = UUID.randomUUID().toString(); + + logger.info(MarkerFactory.getMarker(markerName), "testMessage"); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getTags(), + Matchers.hasEntry(SentryAppender.LOGBACK_MARKER, markerName)); + } } From 965298fec49f1b439bb3d2c9cf51e143ccf17f11 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 18:25:02 +0100 Subject: [PATCH 0724/2152] Add test to verify that MDC data are added to the extras --- .../raven/logback/SentryAppenderTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java index 466315f9d69..9ce252bd628 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -11,9 +11,11 @@ import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.slf4j.MDC; import org.slf4j.MarkerFactory; import java.util.List; +import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Mockito.verify; @@ -103,4 +105,17 @@ public void testMarkerAddedToTag() { assertThat(eventCaptor.getValue().getTags(), Matchers.hasEntry(SentryAppender.LOGBACK_MARKER, markerName)); } + + @Test + public void testMdcAddedToExtra() { + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + String extraKey = UUID.randomUUID().toString(); + String extraValue = UUID.randomUUID().toString(); + + MDC.put(extraKey, extraValue); + logger.info("testMessage"); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getExtra(), Matchers.hasEntry(extraKey, extraValue)); + } } From 27dc9b70fbcc988ec881f0248d9a61b314e92e3f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 18:25:44 +0100 Subject: [PATCH 0725/2152] Make the extra/tag keys public --- .../java/net/kencochrane/raven/logback/SentryAppender.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 1f291f3859b..57c371eb20f 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -26,11 +26,11 @@ public class SentryAppender extends AppenderBase { /** * Name of the {@link Event#extra} property containing Maker details. */ - protected static final String LOGBACK_MARKER = "logback-Marker"; + public static final String LOGBACK_MARKER = "logback-Marker"; /** * Name of the {@link Event#extra} property containing the Thread name. */ - protected static final String THREAD_NAME = "Raven-Threadname"; + public static final String THREAD_NAME = "Raven-Threadname"; /** * Current instance of {@link Raven}. * From 42d6adba3e69046fd37faa3e84e01a1fbb99dbbc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 18:26:07 +0100 Subject: [PATCH 0726/2152] Update documentation to describe MDC and Marker details to events --- raven-logback/README.md | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/raven-logback/README.md b/raven-logback/README.md index a07353618cf..ef441e3dd27 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -40,19 +40,41 @@ In the `logback.xml` file set: ``` +### Additional data and information +It's possible to add extra details to events captured by the logback module +thanks to the [marker system](http://www.slf4j.org/faq.html#fatal) which will +add a tag `logback-Marker`. +[The MDC system provided by Log4j 2](http://logback.qos.ch/manual/mdc.html) +allows to add extra information to the event. + ### In practice ```java import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.slf4j.MarkerFactory; public class MyClass { private static final Logger logger = LoggerFactory.getLogger(MyClass.class); + private static final Marker MARKER = MarkerFactory.getMarker("myMarker"); void logSimpleMessage() { // This adds a simple message to the logs logger.info("This is a test"); } + void logWithTag() { + // This adds a message with a tag to the logs named 'logback-Marker' + logger.info(MARKER, "This is a test"); + } + + void logWithExtras() { + // MDC extras + MDC.put("extra_key", "extra_value"); + // This adds a message with extras to the logs + logger.info("This is a test"); + } + void logException() { try { unsafeMethod(); @@ -67,10 +89,3 @@ public class MyClass { } } ``` - -### Additional data and information -It's possible to add extra details to events captured by the logback module -thanks to the [marker system](http://www.slf4j.org/faq.html#fatal) which will -add a tag `logback-Marker`. -[The MDC system provided by Log4j 2](http://logback.qos.ch/manual/mdc.html) -allows to add extra information to the event. From 56061758f0996cf6528048223da0d1bec5da4e42 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 19:05:35 +0100 Subject: [PATCH 0727/2152] Re enable HttpErrorLogged test --- .../net/kencochrane/raven/connection/HttpConnectionTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index 37815dc5ba2..2ba8fedf777 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -107,8 +107,6 @@ public void testAuthHeaderSent() throws Exception { verify(mockUrlConnection).setRequestProperty("X-Sentry-Auth", expectedAuthRequest); } - //TODO: This test is ignored since the logs are sent through SLF4J rather than JUL. - @Ignore @Test public void testHttpErrorLogged() throws Exception { final String httpErrorMessage = UUID.randomUUID().toString(); From 8eaa97e8b227a78ba5d9225abb0d28265ab47c63 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Jul 2013 13:49:24 +0100 Subject: [PATCH 0728/2152] Enable checkstyle on verify step --- pom.xml | 20 ++++++++++++++++++++ raven-log4j/pom.xml | 4 ++++ raven-log4j2/pom.xml | 4 ++++ raven-logback/pom.xml | 4 ++++ raven/pom.xml | 4 ++++ 5 files changed, 36 insertions(+) diff --git a/pom.xml b/pom.xml index 13fb716a6eb..6ba13c68314 100644 --- a/pom.xml +++ b/pom.xml @@ -299,6 +299,26 @@ maven-failsafe-plugin 2.14.1 + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.10 + + src/checkstyle/checkstyle.xml + true + + + + checkstyle + + ${skipTests} + + + check + + + + diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index e6ad2838b2f..c1dc93bb28d 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -76,6 +76,10 @@ org.eclipse.jetty jetty-maven-plugin + + org.apache.maven.plugins + maven-checkstyle-plugin + diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 3885d4b3959..29faa81b778 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -81,6 +81,10 @@ org.eclipse.jetty jetty-maven-plugin + + org.apache.maven.plugins + maven-checkstyle-plugin + diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 1de5c6ded64..54ee9ad14ad 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -76,6 +76,10 @@ org.eclipse.jetty jetty-maven-plugin + + org.apache.maven.plugins + maven-checkstyle-plugin + diff --git a/raven/pom.xml b/raven/pom.xml index c89b4b7774a..16034b70e1b 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -98,6 +98,10 @@ org.eclipse.jetty jetty-maven-plugin + + org.apache.maven.plugins + maven-checkstyle-plugin + From 7af6ac5b03381e54696f313f9c15801d765327e3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Jul 2013 13:49:58 +0100 Subject: [PATCH 0729/2152] Remove unnecessary configuration --- sentry-stub/pom.xml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index ab8c4da5a3b..aa0f969fdc8 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -33,23 +33,4 @@ provided - - - - - org.eclipse.jetty - jetty-maven-plugin - - - start-sentry-stub - none - - - stop-sentry-stub - none - - - - - From 26e4fd1c29d900b3dd66af3dd23113352ac449ee Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Jul 2013 13:50:11 +0100 Subject: [PATCH 0730/2152] Document InvalidDsnException --- .../net/kencochrane/raven/dsn/InvalidDsnException.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/dsn/InvalidDsnException.java b/raven/src/main/java/net/kencochrane/raven/dsn/InvalidDsnException.java index 2584fbb2550..bc5c0f53036 100644 --- a/raven/src/main/java/net/kencochrane/raven/dsn/InvalidDsnException.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/InvalidDsnException.java @@ -1,5 +1,12 @@ package net.kencochrane.raven.dsn; +/** + * Exception thrown whenever the given {@link Dsn} as been detected as invalid. + *

    + * The invalidity of the DSN can either be due on the content of the DSN (invalid or missing parameters) or the sentry + * server issuing an authentication error. + *

    + */ public class InvalidDsnException extends RuntimeException { public InvalidDsnException() { } From e7588de7b4755d7ce79ab684c8254b9cffc52397 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Jul 2013 13:50:53 +0100 Subject: [PATCH 0731/2152] Rename the params field in parameters --- .../raven/event/interfaces/MessageInterface.java | 10 +++++----- .../raven/marshaller/json/MessageInterfaceBinding.java | 2 +- .../java/net/kencochrane/raven/AbstractLoggerTest.java | 2 +- .../marshaller/json/MessageInterfaceBindingTest.java | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java index 8a767dfb678..96242933fca 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java @@ -14,7 +14,7 @@ public class MessageInterface implements SentryInterface { */ public static final String MESSAGE_INTERFACE = "sentry.interfaces.Message"; private final String message; - private final List params; + private final List parameters; /** * Creates a non parametrised message. @@ -35,9 +35,9 @@ public MessageInterface(String message, String... params) { this(message, Arrays.asList(params)); } - public MessageInterface(String message, List params) { + public MessageInterface(String message, List parameters) { this.message = message; - this.params = Collections.unmodifiableList(new ArrayList(params)); + this.parameters = Collections.unmodifiableList(new ArrayList(parameters)); } @Override @@ -49,7 +49,7 @@ public String getMessage() { return message; } - public List getParams() { - return params; + public List getParameters() { + return parameters; } } diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBinding.java index 6f93bd149f3..a07643e6486 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBinding.java @@ -35,7 +35,7 @@ public void writeInterface(JsonGenerator generator, MessageInterface messageInte generator.writeStartObject(); generator.writeStringField(MESSAGE_PARAMETER, formatMessage(messageInterface.getMessage())); generator.writeArrayFieldStart(PARAMS_PARAMETER); - for (String parameter : messageInterface.getParams()) { + for (String parameter : messageInterface.getParameters()) { generator.writeString(parameter); } generator.writeEndArray(); diff --git a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java index 10ff680daf0..62dda00783d 100644 --- a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java @@ -105,7 +105,7 @@ public void testLogParametrisedMessage() throws Exception { (MessageInterface) event.getSentryInterfaces().get(MessageInterface.MESSAGE_INTERFACE); assertThat(messageInterface.getMessage(), is(message)); - assertThat(messageInterface.getParams(), is(parameters)); + assertThat(messageInterface.getParameters(), is(parameters)); assertThat(event.getMessage(), is( "Some content " + parameters.get(0) + " " + parameters.get(1) diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java index 2b35fa4366c..3e80602aec0 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java @@ -35,7 +35,7 @@ public void testSimpleMessage() throws Exception { String message = UUID.randomUUID().toString(); List parameters = Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()); when(mockMessageInterface.getMessage()).thenReturn(message); - when(mockMessageInterface.getParams()).thenReturn(parameters); + when(mockMessageInterface.getParameters()).thenReturn(parameters); JsonGenerator jSonGenerator = getJsonGenerator(); interfaceBinding.writeInterface(jSonGenerator, mockMessageInterface); From eb04f6562df65e9649375ff1e511a5128b03842d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Jul 2013 13:51:10 +0100 Subject: [PATCH 0732/2152] Remove unused import --- .../net/kencochrane/raven/marshaller/json/JsonMarshaller.java | 1 - 1 file changed, 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index fb9ec088acd..54c519294ac 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; -import com.google.common.io.BaseEncoding; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.interfaces.SentryInterface; import net.kencochrane.raven.marshaller.Marshaller; From cdd73cf9ff4e693961caff7408b89fec80be6e60 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Jul 2013 13:51:19 +0100 Subject: [PATCH 0733/2152] Document JSonMarshaller --- .../kencochrane/raven/marshaller/json/JsonMarshaller.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 54c519294ac..8f47a01ab41 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -18,6 +18,13 @@ import static com.google.common.io.BaseEncoding.base64; +/** + * Event marshaller using JSON to send the data. + *

    + * The content can also be compressed with {@link DeflaterOutputStream} in which case the binary result is encoded + * in base 64. + *

    + */ public class JsonMarshaller implements Marshaller { /** * Hexadecimal string representing a uuid4 value. From 72c0fdb0a35f0a96bb3bc0d4f44a63630a38aba9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Jul 2013 13:52:31 +0100 Subject: [PATCH 0734/2152] Document HttpInterfaceBinding --- .../raven/marshaller/json/HttpInterfaceBinding.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java index c5fde78b6e0..3ed7fbcdeca 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java @@ -9,6 +9,9 @@ import java.util.Collections; import java.util.Map; +/** + * Binding system allowing to convert an {@link HttpInterface} into a JSON stream. + */ public class HttpInterfaceBinding implements InterfaceBinding { private static final String URL = "url"; private static final String METHOD = "method"; From bd054463dbc5ce2cd80de126690f92b7785cd62d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Jul 2013 13:52:40 +0100 Subject: [PATCH 0735/2152] Document InterfaceBinding --- .../raven/marshaller/json/InterfaceBinding.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/InterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/InterfaceBinding.java index dee49030a1a..3ff980bce39 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/InterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/InterfaceBinding.java @@ -5,6 +5,18 @@ import java.io.IOException; +/** + * An interface binding allows to encore a {@link SentryInterface} of a specific type into a JSON stream. + * + * @param type of {@link SentryInterface} supported by the binder. + */ public interface InterfaceBinding { + /** + * Encodes the content of a sentry interface into a JSON stream. + * + * @param generator JSON generator allowing to write JSON content. + * @param sentryInterface interface to encode. + * @throws IOException thrown in case of failure during the generation of JSON content. + */ void writeInterface(JsonGenerator generator, T sentryInterface) throws IOException; } From 496e79b1c541aef6f6b131496c79423f39c4aaac Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 12 Jul 2013 13:53:14 +0100 Subject: [PATCH 0736/2152] Create temporary variable instead of making an extra long line --- .../src/main/java/net/kencochrane/raven/jul/SentryHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 9e41bcee66c..d44492db4a7 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -150,7 +150,8 @@ protected Event buildEvent(LogRecord record) { message = record.getResourceBundle().getString(record.getMessage()); } if (record.getParameters() != null) { - eventBuilder.addSentryInterface(new MessageInterface(message, formatMessageParameters(record.getParameters()))); + List parameters = formatMessageParameters(record.getParameters()); + eventBuilder.addSentryInterface(new MessageInterface(message, parameters)); message = MessageFormat.format(message, record.getParameters()); } eventBuilder.setMessage(message); From f28f92ded74579f67da10784e37ca027fc3788e8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Jul 2013 10:56:53 +0100 Subject: [PATCH 0737/2152] Update log4j2 to the last version available --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6ba13c68314..f36eeddab4e 100644 --- a/pom.xml +++ b/pom.xml @@ -111,7 +111,7 @@ 3.0.1 1.0.13 1.2.17 - 2.0-beta7 + 2.0-beta8 4.11 1.9.5 1.3 From bdbb59576246918c66d7f16c71ec24f6b5941c61 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Jul 2013 15:56:13 +0100 Subject: [PATCH 0738/2152] Make createRavenInstance publicly accessible --- .../main/java/net/kencochrane/raven/DefaultRavenFactory.java | 2 +- raven/src/main/java/net/kencochrane/raven/RavenFactory.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 00ac440d0d9..f83fd52d6d6 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -65,7 +65,7 @@ public class DefaultRavenFactory extends RavenFactory { private static final String FALSE = Boolean.FALSE.toString(); @Override - protected Raven createRavenInstance(Dsn dsn) { + public Raven createRavenInstance(Dsn dsn) { Raven raven = new Raven(); raven.setConnection(createConnection(dsn)); try { diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index a3fce9441a8..1d1ec2c285e 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -56,5 +56,5 @@ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { throw new IllegalStateException("Couldn't create a raven instance for '" + dsn + "'"); } - protected abstract Raven createRavenInstance(Dsn dsn); + public abstract Raven createRavenInstance(Dsn dsn); } From 334d4ec08d34aa5e968512e1b4cda434e622155b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Jul 2013 15:56:29 +0100 Subject: [PATCH 0739/2152] Remove unused method getEventPosition --- .../kencochrane/raven/log4j2/SentryAppender.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 45e1aa85627..dd4617b8b70 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -136,20 +136,6 @@ protected static Event.Level formatLevel(Level level) { } } - /** - * Gets the position of the event as a String. - *

    - * Allows to generate a checksum when there is no stacktrace but the position of the log can be found. - *

    - * - * @param event event without stacktrace but with a position. - * @return a string version of the position. - */ - protected static String getEventPosition(LogEvent event) { - StackTraceElement stackTraceElement = event.getSource(); - return stackTraceElement.getClassName() + stackTraceElement.getMethodName() + stackTraceElement.getLineNumber(); - } - /** * Extracts message parameters into a List of Strings. *

    From b55a815e185d7f122d3dd109f103db5fed4fc5d9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Jul 2013 15:56:45 +0100 Subject: [PATCH 0740/2152] Log the required factory name if the factory couldn't be created --- raven/src/main/java/net/kencochrane/raven/RavenFactory.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index 1d1ec2c285e..80602226688 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -53,7 +53,8 @@ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { } } - throw new IllegalStateException("Couldn't create a raven instance for '" + dsn + "'"); + throw new IllegalStateException("Couldn't create a raven instance of '" + ravenFactoryName + + "' for '" + dsn + "'"); } public abstract Raven createRavenInstance(Dsn dsn); From 50f32e0c221c2fd9f7ea977c5dbf4269befcb347 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Jul 2013 17:00:19 +0100 Subject: [PATCH 0741/2152] Add logs to track the usage of RavenFactory --- .../main/java/net/kencochrane/raven/RavenFactory.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index 80602226688..d0ff2af5f0b 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -1,6 +1,8 @@ package net.kencochrane.raven; import net.kencochrane.raven.dsn.Dsn; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.LinkedList; @@ -15,6 +17,7 @@ public abstract class RavenFactory { private static final ServiceLoader AUTO_REGISTERED_FACTORIES = ServiceLoader.load(RavenFactory.class); private static final Collection MANUALLY_REGISTERED_FACTORIES = new LinkedList(); + private static final Logger logger = LoggerFactory.getLogger(RavenFactory.class); public static void registerFactory(RavenFactory ravenFactory) { MANUALLY_REGISTERED_FACTORIES.add(ravenFactory); @@ -37,9 +40,12 @@ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { if (ravenFactoryName != null && !ravenFactoryName.equals(ravenFactory.getClass().getName())) continue; + logger.info("Found an appropriate Raven factory for '{}': '{}'", ravenFactoryName, ravenFactory); Raven raven = ravenFactory.createRavenInstance(dsn); if (raven != null) { return raven; + } else { + logger.warn("The raven factory '{}' couldn't create an instance of Raven", ravenFactory); } } @@ -47,9 +53,12 @@ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { if (ravenFactoryName != null && !ravenFactoryName.equals(ravenFactory.getClass().getName())) continue; + logger.info("Found an appropriate Raven factory for '{}': '{}'", ravenFactoryName, ravenFactory); Raven raven = ravenFactory.createRavenInstance(dsn); if (raven != null) { return raven; + } else { + logger.warn("The raven factory '{}' couldn't create an instance of Raven", ravenFactory); } } From 2917d6dab2ee70e8f2305bbf00addb419e3c33c4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Jul 2013 17:03:56 +0100 Subject: [PATCH 0742/2152] Set the RavenFactory manually for unit/integration tests --- raven-log4j2/src/test/resources/log4j2.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/raven-log4j2/src/test/resources/log4j2.xml b/raven-log4j2/src/test/resources/log4j2.xml index 6f0d999b13d..62b90fa8b72 100644 --- a/raven-log4j2/src/test/resources/log4j2.xml +++ b/raven-log4j2/src/test/resources/log4j2.xml @@ -6,6 +6,7 @@ http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false + net.kencochrane.raven.DefaultRavenFactory From d395e08c9a7b9901bb94c08d5637f66ef0767aae Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 15 Jul 2013 17:05:12 +0100 Subject: [PATCH 0743/2152] Update the unit tests for log4j2 --- .../raven/log4j2/SentryAppenderTest.java | 268 +++++++++++++----- 1 file changed, 197 insertions(+), 71 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 69b6d6d9a31..62f9696bdba 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -1,67 +1,95 @@ package net.kencochrane.raven.log4j2; -import net.kencochrane.raven.AbstractLoggerTest; +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.MessageInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.DefaultErrorHandler; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.FormattedMessage; -import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.spi.DefaultThreadContextStack; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; -import java.util.Collection; -import java.util.List; -import java.util.UUID; +import java.util.*; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.hasKey; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; +import static org.hamcrest.Matchers.*; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; -public class SentryAppenderTest extends AbstractLoggerTest { - private static final String LOGGER_NAME = SentryAppenderTest.class.getName(); +@RunWith(MockitoJUnitRunner.class) +public class SentryAppenderTest { + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Raven mockRaven; + @Mock + private DefaultErrorHandler mockErrorHandler; private SentryAppender sentryAppender; @Before public void setUp() throws Exception { - sentryAppender = new SentryAppender(getMockRaven()); + sentryAppender = new SentryAppender(mockRaven); + setMockErrorHandlerOnAppender(sentryAppender); } - @Override - public void logAnyLevel(String message) { - logEvent(Level.INFO, null, null, message, null); - } + private void setMockErrorHandlerOnAppender(final SentryAppender sentryAppender){ + sentryAppender.setHandler(mockErrorHandler); - @Override - public void logAnyLevel(String message, Throwable exception) { - logEvent(Level.INFO, null, exception, message, null); - } + Answer answer = new Answer() { + private final DefaultErrorHandler actualErrorHandler = new DefaultErrorHandler(sentryAppender); - @Override - public void logAnyLevel(String message, List parameters) { - logEvent(Level.INFO, null, null, message, parameters); + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + invocation.getMethod().invoke(actualErrorHandler, invocation.getArguments()); + return null; + } + }; + doAnswer(answer).when(mockErrorHandler).error(anyString()); + doAnswer(answer).when(mockErrorHandler).error(anyString(), any(Throwable.class)); + doAnswer(answer).when(mockErrorHandler).error(anyString(), any(LogEvent.class), any(Throwable.class)); } - @Override - public String getCurrentLoggerName() { - return LOGGER_NAME; - } + @Test + public void testSimpleMessageLogging() throws Exception { + String messageContent = UUID.randomUUID().toString(); + String loggerName = UUID.randomUUID().toString(); + String threadName = UUID.randomUUID().toString(); + Date date = new Date(1373883196416L); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + Event event; + + sentryAppender.append(new Log4jLogEvent(loggerName, null, null, Level.INFO, new SimpleMessage(messageContent), + null, null, null, threadName, null, date.getTime())); + + verify(mockRaven).runBuilderHelpers(any(EventBuilder.class)); + verify(mockRaven).sendEvent(eventCaptor.capture()); + event = eventCaptor.getValue(); + assertThat(event.getMessage(), is(messageContent)); + assertThat(event.getLogger(), is(loggerName)); + assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); + assertThat(event.getTimestamp(), is(date)); - @Override - public String getUnformattedMessage() { - return "Some content {} {} {}"; + assertNoErrors(); } @Test - @Override public void testLogLevelConversions() throws Exception { assertLevelConverted(Event.Level.DEBUG, Level.TRACE); assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); @@ -72,82 +100,180 @@ public void testLogLevelConversions() throws Exception { } private void assertLevelConverted(Event.Level expectedLevel, Level level) { - logEvent(level, null, null, "", null); - assertLogLevel(expectedLevel); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + sentryAppender.append(new Log4jLogEvent(null, null, null, level, new SimpleMessage(""), null)); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getLevel(), is(expectedLevel)); + assertNoErrors(); + reset(mockRaven); } @Test - public void testThreadNameAddedToExtra() { + public void testExceptionLogging() throws Exception { + Exception exception = new Exception(); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - logEvent(Level.INFO, null, null, "testMessage", null); + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.ERROR, new SimpleMessage(""), exception)); verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getExtra(), - Matchers.hasEntry(SentryAppender.THREAD_NAME, Thread.currentThread().getName())); + ExceptionInterface exceptionInterface = (ExceptionInterface) eventCaptor.getValue().getSentryInterfaces() + .get(ExceptionInterface.EXCEPTION_INTERFACE); + Throwable capturedException = exceptionInterface.getThrowable(); + + assertThat(capturedException.getMessage(), is(exception.getMessage())); + assertThat(capturedException.getStackTrace(), is(exception.getStackTrace())); + assertNoErrors(); } @Test - public void testMarkerAddedToTag() { + public void testLogParametrisedMessage() throws Exception { + String messagePattern = "Formatted message {} {} {}"; + List parameters = Arrays.asList("first parameter", new Object[0], null); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, + new FormattedMessage(messagePattern, parameters.toArray()), null)); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + MessageInterface messageInterface = (MessageInterface) eventCaptor.getValue().getSentryInterfaces() + .get(MessageInterface.MESSAGE_INTERFACE); + + assertThat(eventCaptor.getValue().getMessage(), is("Formatted message first parameter [] null")); + assertThat(messageInterface.getMessage(), is(messagePattern)); + assertThat(messageInterface.getParameters(), is(Arrays.asList( + parameters.get(0).toString(), + parameters.get(1).toString(), + null))); + assertNoErrors(); + } + + @Test + public void testMarkerAddedToTag() throws Exception { String markerName = UUID.randomUUID().toString(); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - logEvent(Level.INFO, MarkerManager.getMarker(markerName), null, "testMessage", null); + sentryAppender.append(new Log4jLogEvent(null, MarkerManager.getMarker(markerName), null, + Level.INFO, new SimpleMessage(""), null)); verify(mockRaven).sendEvent(eventCaptor.capture()); assertThat(eventCaptor.getValue().getTags(), Matchers.hasEntry(SentryAppender.LOG4J_MARKER, markerName)); + assertNoErrors(); } @Test - public void testMdcAddedToExtra() { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + public void testMdcAddedToExtra() throws Exception { String extraKey = UUID.randomUUID().toString(); String extraValue = UUID.randomUUID().toString(); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); ThreadContext.put(extraKey, extraValue); - - logEvent(Level.INFO, null, null, "testMessage", null); + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, + Collections.singletonMap(extraKey, extraValue), null, null, null, 0)); verify(mockRaven).sendEvent(eventCaptor.capture()); assertThat(eventCaptor.getValue().getExtra(), Matchers.hasEntry(extraKey, extraValue)); + assertNoErrors(); } @Test @SuppressWarnings("unchecked") - public void testNdcAddedToExtra() { + public void testNdcAddedToExtra() throws Exception { ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - String extra = UUID.randomUUID().toString(); - String extra2 = UUID.randomUUID().toString(); + ThreadContext.ContextStack contextStack = new DefaultThreadContextStack(true); - ThreadContext.push(extra); - logEvent(Level.INFO, null, null, "testMessage", null); + contextStack.push(UUID.randomUUID().toString()); + contextStack.push(UUID.randomUUID().toString()); + contextStack.push(UUID.randomUUID().toString()); + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, + null, contextStack, null, null, 0)); verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getExtra(), hasKey(SentryAppender.LOG4J_NDC)); - assertThat((Collection) eventCaptor.getValue().getExtra().get(SentryAppender.LOG4J_NDC), - contains(extra)); + assertThat((List) eventCaptor.getValue().getExtra().get(SentryAppender.LOG4J_NDC), + equalTo(contextStack.asList())); + assertNoErrors(); + } - reset(mockRaven); - ThreadContext.push(extra2); - logEvent(Level.INFO, null, null, "testMessage", null); + @Test + public void testSourceUsedAsStacktrace() throws Exception { + StackTraceElement location = new StackTraceElement(UUID.randomUUID().toString(), + UUID.randomUUID().toString(), + UUID.randomUUID().toString(), 42); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, + null, null, null, location, 0)); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + StackTraceInterface stackTraceInterface = (StackTraceInterface) eventCaptor.getValue().getSentryInterfaces() + .get(StackTraceInterface.STACKTRACE_INTERFACE); + assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); + assertThat(stackTraceInterface.getStackTrace()[0], is(location)); + assertNoErrors(); + } + + @Test + public void testCulpritWithSource() throws Exception { + StackTraceElement location = new StackTraceElement("a", "b", "c", 42); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, + null, null, null, location, 0)); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getCulprit(), is("a.b(c:42)")); + assertNoErrors(); + } + + @Test + public void testCulpritWithoutSource() throws Exception { + String loggerName = UUID.randomUUID().toString(); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + sentryAppender.append(new Log4jLogEvent(loggerName, null, null, Level.INFO, new SimpleMessage(""), null)); verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getExtra(), hasKey(SentryAppender.LOG4J_NDC)); - assertThat((Collection) eventCaptor.getValue().getExtra().get(SentryAppender.LOG4J_NDC), - contains(extra, extra2)); - } - - private void logEvent(Level level, Marker marker, Throwable exception, String messageString, - List messageParameters) { - Message message; - if (messageParameters != null) - message = new FormattedMessage(messageString, messageParameters.toArray()); - else - message = new SimpleMessage(messageString); - - LogEvent event = new Log4jLogEvent(LOGGER_NAME, marker, SentryAppenderTest.class.getName(), level, - message, exception); - sentryAppender.append(event); + assertThat(eventCaptor.getValue().getCulprit(), is(loggerName)); + assertNoErrors(); + } + + @Test + public void testClose() throws Exception { + sentryAppender.stop(); + verify(mockRaven.getConnection(), never()).close(); + + sentryAppender = new SentryAppender(mockRaven, true); + setMockErrorHandlerOnAppender(sentryAppender); + + sentryAppender.stop(); + verify(mockRaven.getConnection()).close(); + assertNoErrors(); + } + + @Test + public void testLazyInitialisation() throws Exception { + sentryAppender = new SentryAppender(); + setMockErrorHandlerOnAppender(sentryAppender); + sentryAppender.setDsn("proto://private:public@host/1"); + + RavenFactory ravenFactory = mock(RavenFactory.class); + when(ravenFactory.createRavenInstance(any(Dsn.class))).thenReturn(mockRaven); + RavenFactory.registerFactory(ravenFactory); + sentryAppender.setRavenFactory(ravenFactory.getClass().getName()); + + sentryAppender.start(); + verify(ravenFactory, never()).createRavenInstance(any(Dsn.class)); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); + verify(ravenFactory).createRavenInstance(any(Dsn.class)); + assertNoErrors(); + } + + private void assertNoErrors() { + verify(mockErrorHandler, never()).error(anyString()); + verify(mockErrorHandler, never()).error(anyString(), any(Throwable.class)); + verify(mockErrorHandler, never()).error(anyString(), any(LogEvent.class), any(Throwable.class)); } } From 98098739022b8ace826b0ee5ea84336cbf3ef8fd Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 11:12:24 +0100 Subject: [PATCH 0744/2152] Remove unecessary usage of collections --- .../kencochrane/raven/log4j2/SentryAppenderTest.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 62f9696bdba..385a9112a55 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -130,11 +130,11 @@ public void testExceptionLogging() throws Exception { @Test public void testLogParametrisedMessage() throws Exception { String messagePattern = "Formatted message {} {} {}"; - List parameters = Arrays.asList("first parameter", new Object[0], null); + Object[] parameters = {"first parameter", new Object[0], null}; ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, - new FormattedMessage(messagePattern, parameters.toArray()), null)); + new FormattedMessage(messagePattern, parameters), null)); verify(mockRaven).sendEvent(eventCaptor.capture()); MessageInterface messageInterface = (MessageInterface) eventCaptor.getValue().getSentryInterfaces() @@ -142,10 +142,8 @@ public void testLogParametrisedMessage() throws Exception { assertThat(eventCaptor.getValue().getMessage(), is("Formatted message first parameter [] null")); assertThat(messageInterface.getMessage(), is(messagePattern)); - assertThat(messageInterface.getParameters(), is(Arrays.asList( - parameters.get(0).toString(), - parameters.get(1).toString(), - null))); + assertThat(messageInterface.getParameters(), + is(Arrays.asList(parameters[0].toString(), parameters[1].toString(), null))); assertNoErrors(); } From 48b8c9f7d0d8284c8b2e42394356ca49cd361116 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 11:12:39 +0100 Subject: [PATCH 0745/2152] Format code and remove unused statements --- .../java/net/kencochrane/raven/log4j2/SentryAppenderTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 385a9112a55..bd00ae7e602 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -49,7 +49,7 @@ public void setUp() throws Exception { setMockErrorHandlerOnAppender(sentryAppender); } - private void setMockErrorHandlerOnAppender(final SentryAppender sentryAppender){ + private void setMockErrorHandlerOnAppender(final SentryAppender sentryAppender) { sentryAppender.setHandler(mockErrorHandler); Answer answer = new Answer() { @@ -167,7 +167,6 @@ public void testMdcAddedToExtra() throws Exception { String extraValue = UUID.randomUUID().toString(); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - ThreadContext.put(extraKey, extraValue); sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, Collections.singletonMap(extraKey, extraValue), null, null, null, 0)); From 0ba4965f831c8f0f6097693f3bcbe42c44528dc8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 11:13:01 +0100 Subject: [PATCH 0746/2152] Rewrite logback unit tests --- .../raven/logback/SentryAppenderTest.java | 180 ++++++++++++------ 1 file changed, 123 insertions(+), 57 deletions(-) diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java index 9ce252bd628..4c6c3637f59 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -1,64 +1,78 @@ package net.kencochrane.raven.logback; import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.Logger; -import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.core.Appender; -import net.kencochrane.raven.AbstractLoggerTest; +import ch.qos.logback.classic.spi.ThrowableProxy; +import ch.qos.logback.core.BasicStatusManager; +import ch.qos.logback.core.Context; +import ch.qos.logback.core.status.OnConsoleStatusListener; +import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.MessageInterface; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; import org.slf4j.MDC; +import org.slf4j.Marker; import org.slf4j.MarkerFactory; +import org.slf4j.helpers.MessageFormatter; -import java.util.List; -import java.util.UUID; +import java.util.*; import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.Mockito.verify; +import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; -public class SentryAppenderTest extends AbstractLoggerTest { - private Logger logger; +@RunWith(MockitoJUnitRunner.class) +public class SentryAppenderTest { + @Mock + private Raven mockRaven; + private SentryAppender sentryAppender; @Before public void setUp() throws Exception { - logger = new LoggerContext().getLogger(SentryAppenderTest.class); - logger.setLevel(Level.ALL); - Appender appender = new SentryAppender(getMockRaven()); - appender.start(); - logger.addAppender(appender); + sentryAppender = new SentryAppender(mockRaven); + Context mockContext = mock(Context.class); + sentryAppender.setContext(mockContext); + BasicStatusManager statusManager = new BasicStatusManager(); + OnConsoleStatusListener listener = new OnConsoleStatusListener(); + listener.start(); + statusManager.add(listener); + when(mockContext.getStatusManager()).thenReturn(statusManager); } - @Override - public void logAnyLevel(String message) { - logger.info(message); - } - - @Override - public void logAnyLevel(String message, Throwable exception) { - logger.error(message, exception); - } + @Test + public void testSimpleMessageLogging() throws Exception { + String message = UUID.randomUUID().toString(); + String loggerName = UUID.randomUUID().toString(); + String threadName = UUID.randomUUID().toString(); + Date date = new Date(1373883196416L); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + Event event; - @Override - public void logAnyLevel(String message, List parameters) { - logger.info(message, parameters.toArray()); - } + ILoggingEvent loggingEvent = newLoggingEvent(loggerName, null, Level.INFO, message, null, null, null, + threadName, null, date.getTime()); + sentryAppender.append(loggingEvent); - @Override - public String getUnformattedMessage() { - return "Some content {} {} {}"; - } + verify(mockRaven).runBuilderHelpers(any(EventBuilder.class)); + verify(mockRaven).sendEvent(eventCaptor.capture()); + event = eventCaptor.getValue(); + assertThat(event.getMessage(), is(message)); + assertThat(event.getLogger(), is(loggerName)); + assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); + assertThat(event.getTimestamp(), is(date)); - @Override - public String getCurrentLoggerName() { - return logger.getName(); + assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); } @Test - @Override public void testLogLevelConversions() throws Exception { assertLevelConverted(Event.Level.DEBUG, Level.TRACE); assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); @@ -68,54 +82,106 @@ public void testLogLevelConversions() throws Exception { } private void assertLevelConverted(Event.Level expectedLevel, Level level) { - if (level.isGreaterOrEqual(Level.ERROR)) { - logger.error("message"); - } else if (level.isGreaterOrEqual(Level.WARN)) { - logger.warn("message"); - } else if (level.isGreaterOrEqual(Level.INFO)) { - logger.info("message"); - } else if (level.isGreaterOrEqual(Level.DEBUG)) { - logger.debug("message"); - } else if (level.isGreaterOrEqual(Level.TRACE)) { - logger.trace("message"); - } - - assertLogLevel(expectedLevel); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + sentryAppender.append(newLoggingEvent(null, null, level, null, null, null)); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getLevel(), is(expectedLevel)); + assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); + reset(mockRaven); } @Test - public void testThreadNameAddedToExtra() { + public void testExceptionLogging() throws Exception { + Exception exception = new Exception(); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - logger.info("testMessage"); + sentryAppender.append(newLoggingEvent(null, null, Level.ERROR, null, null, exception)); verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getExtra(), - Matchers.hasEntry(SentryAppender.THREAD_NAME, Thread.currentThread().getName())); + ExceptionInterface exceptionInterface = (ExceptionInterface) eventCaptor.getValue().getSentryInterfaces() + .get(ExceptionInterface.EXCEPTION_INTERFACE); + Throwable capturedException = exceptionInterface.getThrowable(); + + assertThat(capturedException.getMessage(), is(exception.getMessage())); + assertThat(capturedException.getStackTrace(), is(exception.getStackTrace())); + assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); } @Test - public void testMarkerAddedToTag() { + public void testLogParametrisedMessage() throws Exception { + String messagePattern = "Formatted message {} {} {}"; + Object[] parameters = {"first parameter", new Object[0], null}; ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + sentryAppender.append(newLoggingEvent(null, null, Level.INFO, messagePattern, parameters, null)); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + MessageInterface messageInterface = (MessageInterface) eventCaptor.getValue().getSentryInterfaces() + .get(MessageInterface.MESSAGE_INTERFACE); + + assertThat(eventCaptor.getValue().getMessage(), is("Formatted message first parameter [] null")); + assertThat(messageInterface.getMessage(), is(messagePattern)); + assertThat(messageInterface.getParameters(), + is(Arrays.asList(parameters[0].toString(), parameters[1].toString(), null))); + assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); + } + + @Test + public void testMarkerAddedToTag() throws Exception { String markerName = UUID.randomUUID().toString(); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - logger.info(MarkerFactory.getMarker(markerName), "testMessage"); + sentryAppender.append(newLoggingEvent(null, MarkerFactory.getMarker(markerName), Level.INFO, null, null, null)); verify(mockRaven).sendEvent(eventCaptor.capture()); assertThat(eventCaptor.getValue().getTags(), Matchers.hasEntry(SentryAppender.LOGBACK_MARKER, markerName)); + assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); } @Test - public void testMdcAddedToExtra() { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + public void testMdcAddedToExtra() throws Exception { String extraKey = UUID.randomUUID().toString(); String extraValue = UUID.randomUUID().toString(); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); MDC.put(extraKey, extraValue); - logger.info("testMessage"); + sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null, + Collections.singletonMap(extraKey, extraValue), null, null, 0)); verify(mockRaven).sendEvent(eventCaptor.capture()); assertThat(eventCaptor.getValue().getExtra(), Matchers.hasEntry(extraKey, extraValue)); + assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); + } + + private ILoggingEvent newLoggingEvent(String loggerName, Marker marker, Level level, String message, + Object[] argumentArray, Throwable t) { + return newLoggingEvent(loggerName, marker, level, message, argumentArray, t, + null, null, null, System.currentTimeMillis()); + } + + private ILoggingEvent newLoggingEvent(String loggerName, Marker marker, Level level, + String message, Object[] argumentArray, Throwable throwable, + Map mdcPropertyMap, String threadName, + StackTraceElement[] callerData, long timestamp) { + ILoggingEvent iLoggingEvent = mock(ILoggingEvent.class); + when(iLoggingEvent.getThreadName()).thenReturn(threadName); + when(iLoggingEvent.getLevel()).thenReturn(level); + when(iLoggingEvent.getMessage()).thenReturn(message); + when(iLoggingEvent.getArgumentArray()).thenReturn(argumentArray); + when(iLoggingEvent.getFormattedMessage()).thenReturn( + argumentArray != null ? MessageFormatter.arrayFormat(message, argumentArray).getMessage() : message); + when(iLoggingEvent.getLoggerName()).thenReturn(loggerName); + when(iLoggingEvent.getThrowableProxy()).thenReturn(throwable != null ? new ThrowableProxy(throwable) : null); + when(iLoggingEvent.getCallerData()).thenReturn(callerData != null ? callerData : new StackTraceElement[0]); + when(iLoggingEvent.hasCallerData()).thenReturn(callerData != null && callerData.length > 0); + when(iLoggingEvent.getMarker()).thenReturn(marker); + when(iLoggingEvent.getMDCPropertyMap()) + .thenReturn(mdcPropertyMap != null ? mdcPropertyMap : Collections.emptyMap()); + when(iLoggingEvent.getTimeStamp()).thenReturn(timestamp); + + return iLoggingEvent; } } From e3aee20cd461b75ce2aef27dede5acf6caa6a809 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 11:27:56 +0100 Subject: [PATCH 0747/2152] Add more tests for the Logback appender --- .../raven/logback/SentryAppenderTest.java | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java index 4c6c3637f59..d92d56d4238 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -11,10 +11,12 @@ import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; @@ -32,13 +34,17 @@ @RunWith(MockitoJUnitRunner.class) public class SentryAppenderTest { - @Mock + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Raven mockRaven; private SentryAppender sentryAppender; @Before public void setUp() throws Exception { sentryAppender = new SentryAppender(mockRaven); + setMockContextOnAppender(sentryAppender); + } + + private void setMockContextOnAppender(SentryAppender sentryAppender) { Context mockContext = mock(Context.class); sentryAppender.setContext(mockContext); BasicStatusManager statusManager = new BasicStatusManager(); @@ -156,6 +162,62 @@ public void testMdcAddedToExtra() throws Exception { assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); } + @Test + public void testSourceUsedAsStacktrace() throws Exception { + StackTraceElement[] location = {new StackTraceElement(UUID.randomUUID().toString(), + UUID.randomUUID().toString(), + UUID.randomUUID().toString(), 42)}; + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null, + null, null, location, 0)); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + StackTraceInterface stackTraceInterface = (StackTraceInterface) eventCaptor.getValue().getSentryInterfaces() + .get(StackTraceInterface.STACKTRACE_INTERFACE); + assertThat(stackTraceInterface.getStackTrace(), is(location)); + assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); + } + + @Test + public void testCulpritWithSource() throws Exception { + StackTraceElement[] location = {new StackTraceElement("a", "b", "c", 42), + new StackTraceElement("d", "e", "f", 69)}; + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null, + null, null, location, 0)); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getCulprit(), is("a.b(c:42)")); + assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); + } + + @Test + public void testCulpritWithoutSource() throws Exception { + String loggerName = UUID.randomUUID().toString(); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + sentryAppender.append(newLoggingEvent(loggerName, null, Level.INFO, null, null, null)); + + verify(mockRaven).sendEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getCulprit(), is(loggerName)); + assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); + } + + @Test + public void testClose() throws Exception { + sentryAppender.stop(); + verify(mockRaven.getConnection(), never()).close(); + + sentryAppender = new SentryAppender(mockRaven, true); + setMockContextOnAppender(sentryAppender); + + sentryAppender.stop(); + verify(mockRaven.getConnection()).close(); + assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); + } + private ILoggingEvent newLoggingEvent(String loggerName, Marker marker, Level level, String message, Object[] argumentArray, Throwable t) { return newLoggingEvent(loggerName, marker, level, message, argumentArray, t, From 27fc1db5185999dc53fcedaad532f95f55d41386 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 11:39:54 +0100 Subject: [PATCH 0748/2152] Test fail cases with logback appender --- .../raven/logback/SentryAppenderTest.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java index d92d56d4238..afb2e329e9f 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -13,6 +13,7 @@ import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.hamcrest.Matchers; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,6 +45,11 @@ public void setUp() throws Exception { setMockContextOnAppender(sentryAppender); } + @After + public void tearDown() throws Exception { + Raven.RAVEN_THREAD.remove(); + } + private void setMockContextOnAppender(SentryAppender sentryAppender) { Context mockContext = mock(Context.class); sentryAppender.setContext(mockContext); @@ -218,6 +224,25 @@ public void testClose() throws Exception { assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); } + @Test + public void testAppendFailIfCurrentThreadSpawnedByRaven(){ + Raven.RAVEN_THREAD.set(true); + + sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null)); + + verify(mockRaven, never()).sendEvent(any(Event.class)); + assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); + } + + @Test + public void testRavenFailureDoesNotPropagate(){ + doThrow(new UnsupportedOperationException()).when(mockRaven).sendEvent(any(Event.class)); + + sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null)); + + assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(1)); + } + private ILoggingEvent newLoggingEvent(String loggerName, Marker marker, Level level, String message, Object[] argumentArray, Throwable t) { return newLoggingEvent(loggerName, marker, level, message, argumentArray, t, From 8c36e194c765db1e5e007bac32709f6450209dd7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 12:01:44 +0100 Subject: [PATCH 0749/2152] Add fail cases to log4j2 unit tests --- .../raven/log4j2/SentryAppenderTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index bd00ae7e602..b81bbc02ae8 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -18,6 +18,7 @@ import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.spi.DefaultThreadContextStack; import org.hamcrest.Matchers; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,6 +50,11 @@ public void setUp() throws Exception { setMockErrorHandlerOnAppender(sentryAppender); } + @After + public void tearDown() throws Exception { + Raven.RAVEN_THREAD.remove(); + } + private void setMockErrorHandlerOnAppender(final SentryAppender sentryAppender) { sentryAppender.setHandler(mockErrorHandler); @@ -249,6 +255,27 @@ public void testClose() throws Exception { assertNoErrors(); } + @Test + public void testAppendFailIfCurrentThreadSpawnedByRaven() { + Raven.RAVEN_THREAD.set(true); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); + + verify(mockRaven, never()).sendEvent(any(Event.class)); + assertNoErrors(); + } + + @Test + public void testRavenFailureDoesNotPropagate() { + doThrow(new UnsupportedOperationException()).when(mockRaven).sendEvent(any(Event.class)); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); + + verify(mockErrorHandler, never()).error(anyString()); + verify(mockErrorHandler, never()).error(anyString(), any(Throwable.class)); + verify(mockErrorHandler).error(anyString(), any(LogEvent.class), any(Throwable.class)); + } + @Test public void testLazyInitialisation() throws Exception { sentryAppender = new SentryAppender(); From 12bf043061ce0bb804bc5a49e8022a6804e13e3b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 15:44:51 +0100 Subject: [PATCH 0750/2152] Organise manually registered factories in a Map To avoid conflicts when two factories of the same class are registered only take the last factory of that type into account (replacing previously registered factories with the same name) --- .../net/kencochrane/raven/RavenFactory.java | 53 +++++++++++++------ 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index d0ff2af5f0b..e1925b62c9a 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -4,8 +4,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Collection; -import java.util.LinkedList; +import java.util.HashMap; +import java.util.Map; import java.util.ServiceLoader; /** @@ -16,11 +16,11 @@ */ public abstract class RavenFactory { private static final ServiceLoader AUTO_REGISTERED_FACTORIES = ServiceLoader.load(RavenFactory.class); - private static final Collection MANUALLY_REGISTERED_FACTORIES = new LinkedList(); + private static final Map MANUALLY_REGISTERED_FACTORIES = new HashMap(); private static final Logger logger = LoggerFactory.getLogger(RavenFactory.class); public static void registerFactory(RavenFactory ravenFactory) { - MANUALLY_REGISTERED_FACTORIES.add(ravenFactory); + MANUALLY_REGISTERED_FACTORIES.put(ravenFactory.getClass().getName(), ravenFactory); } public static Raven ravenInstance() { @@ -36,34 +36,55 @@ public static Raven ravenInstance(Dsn dsn) { } public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { - for (RavenFactory ravenFactory : MANUALLY_REGISTERED_FACTORIES) { - if (ravenFactoryName != null && !ravenFactoryName.equals(ravenFactory.getClass().getName())) - continue; + Raven raven = ravenInstanceFromManualFactories(dsn, ravenFactoryName); + + if (raven == null) + raven = ravenInstanceFromAutoFactories(dsn, ravenFactoryName); + + if (raven != null) + return raven; + else + throw new IllegalStateException("Couldn't create a raven instance of '" + ravenFactoryName + + "' for '" + dsn + "'"); + } + private static Raven ravenInstanceFromManualFactories(Dsn dsn, String ravenFactoryName) { + Raven raven = null; + if (ravenFactoryName != null && MANUALLY_REGISTERED_FACTORIES.containsKey(ravenFactoryName)) { + RavenFactory ravenFactory = MANUALLY_REGISTERED_FACTORIES.get(ravenFactoryName); logger.info("Found an appropriate Raven factory for '{}': '{}'", ravenFactoryName, ravenFactory); - Raven raven = ravenFactory.createRavenInstance(dsn); - if (raven != null) { - return raven; - } else { + raven = ravenFactory.createRavenInstance(dsn); + if (raven == null) logger.warn("The raven factory '{}' couldn't create an instance of Raven", ravenFactory); + } else if (ravenFactoryName == null) { + for (RavenFactory ravenFactory : MANUALLY_REGISTERED_FACTORIES.values()) { + logger.info("Found an appropriate Raven factory for '{}': '{}'", ravenFactoryName, ravenFactory); + raven = ravenFactory.createRavenInstance(dsn); + if (raven != null) { + break; + } else { + logger.warn("The raven factory '{}' couldn't create an instance of Raven", ravenFactory); + } } } + return raven; + } + private static Raven ravenInstanceFromAutoFactories(Dsn dsn, String ravenFactoryName) { + Raven raven = null; for (RavenFactory ravenFactory : AUTO_REGISTERED_FACTORIES) { if (ravenFactoryName != null && !ravenFactoryName.equals(ravenFactory.getClass().getName())) continue; logger.info("Found an appropriate Raven factory for '{}': '{}'", ravenFactoryName, ravenFactory); - Raven raven = ravenFactory.createRavenInstance(dsn); + raven = ravenFactory.createRavenInstance(dsn); if (raven != null) { - return raven; + break; } else { logger.warn("The raven factory '{}' couldn't create an instance of Raven", ravenFactory); } } - - throw new IllegalStateException("Couldn't create a raven instance of '" + ravenFactoryName - + "' for '" + dsn + "'"); + return raven; } public abstract Raven createRavenInstance(Dsn dsn); From 5b447578b2f5049780f0517c8c268d2e51de03ff Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 15:49:16 +0100 Subject: [PATCH 0751/2152] Automatically remove the RAVEN_THREAD flag --- .../raven/logback/SentryAppenderTest.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java index afb2e329e9f..cd0351525c1 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -13,7 +13,6 @@ import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.hamcrest.Matchers; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,11 +42,7 @@ public class SentryAppenderTest { public void setUp() throws Exception { sentryAppender = new SentryAppender(mockRaven); setMockContextOnAppender(sentryAppender); - } - @After - public void tearDown() throws Exception { - Raven.RAVEN_THREAD.remove(); } private void setMockContextOnAppender(SentryAppender sentryAppender) { @@ -226,12 +221,16 @@ public void testClose() throws Exception { @Test public void testAppendFailIfCurrentThreadSpawnedByRaven(){ - Raven.RAVEN_THREAD.set(true); + try { + Raven.RAVEN_THREAD.set(true); - sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null)); + sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null)); - verify(mockRaven, never()).sendEvent(any(Event.class)); - assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); + verify(mockRaven, never()).sendEvent(any(Event.class)); + assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); + } finally { + Raven.RAVEN_THREAD.remove(); + } } @Test From 3c75e032614bc1059ec94f1c19ca49f967734f33 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 15:49:57 +0100 Subject: [PATCH 0752/2152] Fix style issues --- .../net/kencochrane/raven/logback/SentryAppenderTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java index cd0351525c1..4facdbc1ac7 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -183,7 +183,7 @@ public void testSourceUsedAsStacktrace() throws Exception { @Test public void testCulpritWithSource() throws Exception { StackTraceElement[] location = {new StackTraceElement("a", "b", "c", 42), - new StackTraceElement("d", "e", "f", 69)}; + new StackTraceElement("d", "e", "f", 69)}; ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null, @@ -220,7 +220,7 @@ public void testClose() throws Exception { } @Test - public void testAppendFailIfCurrentThreadSpawnedByRaven(){ + public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { try { Raven.RAVEN_THREAD.set(true); @@ -234,7 +234,7 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven(){ } @Test - public void testRavenFailureDoesNotPropagate(){ + public void testRavenFailureDoesNotPropagate() throws Exception { doThrow(new UnsupportedOperationException()).when(mockRaven).sendEvent(any(Event.class)); sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null)); From 0d6f7d861c21ad7b4552177ef138d6598af0fef5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 15:50:23 +0100 Subject: [PATCH 0753/2152] Remove the RAVEN_THREAD flag automatically --- .../raven/log4j2/SentryAppenderTest.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index b81bbc02ae8..f9df552a6bd 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -18,7 +18,6 @@ import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.spi.DefaultThreadContextStack; import org.hamcrest.Matchers; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -50,11 +49,6 @@ public void setUp() throws Exception { setMockErrorHandlerOnAppender(sentryAppender); } - @After - public void tearDown() throws Exception { - Raven.RAVEN_THREAD.remove(); - } - private void setMockErrorHandlerOnAppender(final SentryAppender sentryAppender) { sentryAppender.setHandler(mockErrorHandler); @@ -257,12 +251,16 @@ public void testClose() throws Exception { @Test public void testAppendFailIfCurrentThreadSpawnedByRaven() { - Raven.RAVEN_THREAD.set(true); + try { + Raven.RAVEN_THREAD.set(true); - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); - verify(mockRaven, never()).sendEvent(any(Event.class)); - assertNoErrors(); + verify(mockRaven, never()).sendEvent(any(Event.class)); + assertNoErrors(); + } finally { + Raven.RAVEN_THREAD.remove(); + } } @Test From e2a19b7978ba7f03abe404a4098040f30940c349 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 15:57:46 +0100 Subject: [PATCH 0754/2152] Use different logback.xml files between integration and unit tests --- raven-logback/pom.xml | 7 +++++++ .../{logback.xml => logback-integration.xml} | 0 raven-logback/src/test/resources/logback-test.xml | 11 +++++++++++ 3 files changed, 18 insertions(+) rename raven-logback/src/test/resources/{logback.xml => logback-integration.xml} (100%) create mode 100644 raven-logback/src/test/resources/logback-test.xml diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 54ee9ad14ad..e46abc7afd8 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -67,6 +67,13 @@ org.apache.maven.plugins maven-failsafe-plugin + + + + ${project.basedir}/src/test/resources/logback-integration.xml + + + org.apache.maven.plugins diff --git a/raven-logback/src/test/resources/logback.xml b/raven-logback/src/test/resources/logback-integration.xml similarity index 100% rename from raven-logback/src/test/resources/logback.xml rename to raven-logback/src/test/resources/logback-integration.xml diff --git a/raven-logback/src/test/resources/logback-test.xml b/raven-logback/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..dcffc76772c --- /dev/null +++ b/raven-logback/src/test/resources/logback-test.xml @@ -0,0 +1,11 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + From 8cd02694cfdc4053b3008790f8a10e534a0c2458 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 16:01:18 +0100 Subject: [PATCH 0755/2152] Add tests for DSN auto detect and raven initialisation --- .../raven/logback/SentryAppenderTest.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java index 4facdbc1ac7..df2163494df 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -7,6 +7,8 @@ import ch.qos.logback.core.Context; import ch.qos.logback.core.status.OnConsoleStatusListener; import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; @@ -36,6 +38,8 @@ public class SentryAppenderTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Raven mockRaven; + @Mock + private RavenFactory mockRavenFactory; private SentryAppender sentryAppender; @Before @@ -43,6 +47,8 @@ public void setUp() throws Exception { sentryAppender = new SentryAppender(mockRaven); setMockContextOnAppender(sentryAppender); + when(mockRavenFactory.createRavenInstance(any(Dsn.class))).thenReturn(mockRaven); + RavenFactory.registerFactory(mockRavenFactory); } private void setMockContextOnAppender(SentryAppender sentryAppender) { @@ -242,6 +248,41 @@ public void testRavenFailureDoesNotPropagate() throws Exception { assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(1)); } + @Test + public void testLazyInitialisation() throws Exception { + String dsnUri = "proto://private:public@host/1"; + sentryAppender = new SentryAppender(); + setMockContextOnAppender(sentryAppender); + sentryAppender.setDsn(dsnUri); + sentryAppender.setRavenFactory(mockRavenFactory.getClass().getName()); + + sentryAppender.start(); + verify(mockRavenFactory, never()).createRavenInstance(any(Dsn.class)); + + sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null)); + verify(mockRavenFactory).createRavenInstance(eq(new Dsn(dsnUri))); + assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); + } + + @Test + public void testDsnAutoDetection() throws Exception { + try { + String dsnUri = "proto://private:public@host/1"; + System.setProperty(Dsn.DSN_VARIABLE, dsnUri); + sentryAppender = new SentryAppender(); + setMockContextOnAppender(sentryAppender); + sentryAppender.setRavenFactory(mockRavenFactory.getClass().getName()); + + sentryAppender.start(); + sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null)); + + verify(mockRavenFactory).createRavenInstance(eq(new Dsn(dsnUri))); + assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); + } finally { + System.clearProperty(Dsn.DSN_VARIABLE); + } + } + private ILoggingEvent newLoggingEvent(String loggerName, Marker marker, Level level, String message, Object[] argumentArray, Throwable t) { return newLoggingEvent(loggerName, marker, level, message, argumentArray, t, From 6d05b22f84e201c8c624b365f90eff024bc9f14c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 16:03:23 +0100 Subject: [PATCH 0756/2152] Throw exceptions from unit tests --- .../java/net/kencochrane/raven/log4j2/SentryAppenderTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index f9df552a6bd..1cfdc3e0a64 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -250,7 +250,7 @@ public void testClose() throws Exception { } @Test - public void testAppendFailIfCurrentThreadSpawnedByRaven() { + public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { try { Raven.RAVEN_THREAD.set(true); @@ -264,7 +264,7 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() { } @Test - public void testRavenFailureDoesNotPropagate() { + public void testRavenFailureDoesNotPropagate() throws Exception { doThrow(new UnsupportedOperationException()).when(mockRaven).sendEvent(any(Event.class)); sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); From 7dd24b7119f5a6848291a7fcca0b6e09e00083b2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 16:03:39 +0100 Subject: [PATCH 0757/2152] Ensure the DSN during the lazy init is the right one --- .../net/kencochrane/raven/log4j2/SentryAppenderTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 1cfdc3e0a64..d53b70c29e2 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -276,9 +276,10 @@ public void testRavenFailureDoesNotPropagate() throws Exception { @Test public void testLazyInitialisation() throws Exception { + String dsnUri = "proto://private:public@host/1"; sentryAppender = new SentryAppender(); setMockErrorHandlerOnAppender(sentryAppender); - sentryAppender.setDsn("proto://private:public@host/1"); + sentryAppender.setDsn(dsnUri); RavenFactory ravenFactory = mock(RavenFactory.class); when(ravenFactory.createRavenInstance(any(Dsn.class))).thenReturn(mockRaven); @@ -289,7 +290,7 @@ public void testLazyInitialisation() throws Exception { verify(ravenFactory, never()).createRavenInstance(any(Dsn.class)); sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); - verify(ravenFactory).createRavenInstance(any(Dsn.class)); + verify(ravenFactory).createRavenInstance(new Dsn(dsnUri)); assertNoErrors(); } From 5b2cd75664556093f00bf10d823dcc4f7237c1bf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 16:14:48 +0100 Subject: [PATCH 0758/2152] Fix style and indentation --- .../raven/log4j2/SentryAppenderTest.java | 22 +++++++++---------- .../raven/logback/SentryAppenderTest.java | 8 ++----- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index d53b70c29e2..0eb304afb10 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -68,20 +68,20 @@ public Void answer(InvocationOnMock invocation) throws Throwable { @Test public void testSimpleMessageLogging() throws Exception { - String messageContent = UUID.randomUUID().toString(); + String message = UUID.randomUUID().toString(); String loggerName = UUID.randomUUID().toString(); String threadName = UUID.randomUUID().toString(); Date date = new Date(1373883196416L); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); Event event; - sentryAppender.append(new Log4jLogEvent(loggerName, null, null, Level.INFO, new SimpleMessage(messageContent), + sentryAppender.append(new Log4jLogEvent(loggerName, null, null, Level.INFO, new SimpleMessage(message), null, null, null, threadName, null, date.getTime())); verify(mockRaven).runBuilderHelpers(any(EventBuilder.class)); verify(mockRaven).sendEvent(eventCaptor.capture()); event = eventCaptor.getValue(); - assertThat(event.getMessage(), is(messageContent)); + assertThat(event.getMessage(), is(message)); assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); @@ -152,8 +152,8 @@ public void testMarkerAddedToTag() throws Exception { String markerName = UUID.randomUUID().toString(); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - sentryAppender.append(new Log4jLogEvent(null, MarkerManager.getMarker(markerName), null, - Level.INFO, new SimpleMessage(""), null)); + sentryAppender.append(new Log4jLogEvent(null, MarkerManager.getMarker(markerName), null, Level.INFO, + new SimpleMessage(""), null)); verify(mockRaven).sendEvent(eventCaptor.capture()); assertThat(eventCaptor.getValue().getTags(), @@ -184,8 +184,8 @@ public void testNdcAddedToExtra() throws Exception { contextStack.push(UUID.randomUUID().toString()); contextStack.push(UUID.randomUUID().toString()); contextStack.push(UUID.randomUUID().toString()); - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, - null, contextStack, null, null, 0)); + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, + contextStack, null, null, 0)); verify(mockRaven).sendEvent(eventCaptor.capture()); assertThat((List) eventCaptor.getValue().getExtra().get(SentryAppender.LOG4J_NDC), @@ -200,8 +200,8 @@ public void testSourceUsedAsStacktrace() throws Exception { UUID.randomUUID().toString(), 42); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, - null, null, null, location, 0)); + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, null, + null, location, 0)); verify(mockRaven).sendEvent(eventCaptor.capture()); StackTraceInterface stackTraceInterface = (StackTraceInterface) eventCaptor.getValue().getSentryInterfaces() @@ -216,8 +216,8 @@ public void testCulpritWithSource() throws Exception { StackTraceElement location = new StackTraceElement("a", "b", "c", 42); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, - null, null, null, location, 0)); + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, null, + null, location, 0)); verify(mockRaven).sendEvent(eventCaptor.capture()); assertThat(eventCaptor.getValue().getCulprit(), is("a.b(c:42)")); diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java index df2163494df..4a61383b3cf 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -22,7 +22,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import org.slf4j.MDC; import org.slf4j.Marker; import org.slf4j.MarkerFactory; import org.slf4j.helpers.MessageFormatter; @@ -160,7 +159,6 @@ public void testMdcAddedToExtra() throws Exception { String extraValue = UUID.randomUUID().toString(); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - MDC.put(extraKey, extraValue); sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null, Collections.singletonMap(extraKey, extraValue), null, null, 0)); @@ -176,8 +174,7 @@ public void testSourceUsedAsStacktrace() throws Exception { UUID.randomUUID().toString(), 42)}; ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null, - null, null, location, 0)); + sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null, null, null, location, 0)); verify(mockRaven).sendEvent(eventCaptor.capture()); StackTraceInterface stackTraceInterface = (StackTraceInterface) eventCaptor.getValue().getSentryInterfaces() @@ -192,8 +189,7 @@ public void testCulpritWithSource() throws Exception { new StackTraceElement("d", "e", "f", 69)}; ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null, - null, null, location, 0)); + sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null, null, null, location, 0)); verify(mockRaven).sendEvent(eventCaptor.capture()); assertThat(eventCaptor.getValue().getCulprit(), is("a.b(c:42)")); From 5ec7491bbeea502d09ade18903b5dab2101b548b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 16:15:07 +0100 Subject: [PATCH 0759/2152] Use a mock of the ravenFactory --- .../raven/log4j2/SentryAppenderTest.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 0eb304afb10..dad50353901 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -40,6 +40,8 @@ public class SentryAppenderTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Raven mockRaven; @Mock + private RavenFactory mockRavenFactory; + @Mock private DefaultErrorHandler mockErrorHandler; private SentryAppender sentryAppender; @@ -47,6 +49,9 @@ public class SentryAppenderTest { public void setUp() throws Exception { sentryAppender = new SentryAppender(mockRaven); setMockErrorHandlerOnAppender(sentryAppender); + + when(mockRavenFactory.createRavenInstance(any(Dsn.class))).thenReturn(mockRaven); + RavenFactory.registerFactory(mockRavenFactory); } private void setMockErrorHandlerOnAppender(final SentryAppender sentryAppender) { @@ -280,17 +285,13 @@ public void testLazyInitialisation() throws Exception { sentryAppender = new SentryAppender(); setMockErrorHandlerOnAppender(sentryAppender); sentryAppender.setDsn(dsnUri); - - RavenFactory ravenFactory = mock(RavenFactory.class); - when(ravenFactory.createRavenInstance(any(Dsn.class))).thenReturn(mockRaven); - RavenFactory.registerFactory(ravenFactory); - sentryAppender.setRavenFactory(ravenFactory.getClass().getName()); + sentryAppender.setRavenFactory(mockRavenFactory.getClass().getName()); sentryAppender.start(); - verify(ravenFactory, never()).createRavenInstance(any(Dsn.class)); + verify(mockRavenFactory, never()).createRavenInstance(any(Dsn.class)); sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); - verify(ravenFactory).createRavenInstance(new Dsn(dsnUri)); + verify(mockRavenFactory).createRavenInstance(eq(new Dsn(dsnUri))); assertNoErrors(); } From 8e4decf89c40075c219e9d9d4085867468c8255e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 16:15:21 +0100 Subject: [PATCH 0760/2152] Add a test for Dsn auto detection --- .../raven/log4j2/SentryAppenderTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index dad50353901..05a6b9c6db4 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -295,6 +295,25 @@ public void testLazyInitialisation() throws Exception { assertNoErrors(); } + @Test + public void testDsnAutoDetection() throws Exception { + try { + String dsnUri = "proto://private:public@host/1"; + System.setProperty(Dsn.DSN_VARIABLE, dsnUri); + sentryAppender = new SentryAppender(); + setMockErrorHandlerOnAppender(sentryAppender); + sentryAppender.setRavenFactory(mockRavenFactory.getClass().getName()); + + sentryAppender.start(); + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); + + verify(mockRavenFactory).createRavenInstance(eq(new Dsn(dsnUri))); + assertNoErrors(); + } finally { + System.clearProperty(Dsn.DSN_VARIABLE); + } + } + private void assertNoErrors() { verify(mockErrorHandler, never()).error(anyString()); verify(mockErrorHandler, never()).error(anyString(), any(Throwable.class)); From c2b7bd9eb739475eac2c4ca36f8b2f535b1440cb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 16:29:21 +0100 Subject: [PATCH 0761/2152] Do not log the exception if initRaven fails If the initialisation fail, the append call should fail entirely and shouldn't rely on a null Raven --- .../net/kencochrane/raven/log4j2/SentryAppender.java | 10 +++------- .../net/kencochrane/raven/logback/SentryAppender.java | 10 +++------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index dd4617b8b70..757ad263b36 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -182,14 +182,10 @@ public void append(LogEvent logEvent) { * Initialises the Raven instance. */ protected void initRaven() { - try { - if (dsn == null) - dsn = Dsn.dsnLookup(); + if (dsn == null) + dsn = Dsn.dsnLookup(); - raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); - } catch (Exception e) { - error("An exception occurred during the creation of a Raven instance", e); - } + raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); } /** diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 57c371eb20f..c3ab4a59753 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -130,14 +130,10 @@ protected void append(ILoggingEvent iLoggingEvent) { * Initialises the Raven instance. */ protected void initRaven() { - try { - if (dsn == null) - dsn = Dsn.dsnLookup(); + if (dsn == null) + dsn = Dsn.dsnLookup(); - raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); - } catch (Exception e) { - addError("An exception occurred during the creation of a Raven instance", e); - } + raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); } /** From 628fdc408b9e3472032d511a3cbaea4e76eefcd6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 16:30:13 +0100 Subject: [PATCH 0762/2152] Test behaviour when the initialisation of Raven fails in logback --- .../raven/logback/SentryAppenderTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java index 4a61383b3cf..63fc8206519 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -279,6 +279,19 @@ public void testDsnAutoDetection() throws Exception { } } + @Test + public void testFailedInitialisation() throws Exception { + String dsnUri = "proto://private:public@host/1"; + sentryAppender = new SentryAppender(); + setMockContextOnAppender(sentryAppender); + sentryAppender.setDsn(dsnUri); + sentryAppender.setRavenFactory("invalid factory"); + + sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null)); + + assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(1)); + } + private ILoggingEvent newLoggingEvent(String loggerName, Marker marker, Level level, String message, Object[] argumentArray, Throwable t) { return newLoggingEvent(loggerName, marker, level, message, argumentArray, t, From b96b09714f29f93db7c9d839391653e2dc35d08a Mon Sep 17 00:00:00 2001 From: Matthew Buckett Date: Wed, 7 Aug 2013 16:12:22 +0100 Subject: [PATCH 0763/2152] Fix so that raven-sentry compiles on JDK-6. --- .travis.yml | 3 +++ .../raven/connection/HttpConnectionTest.java | 16 +++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 969c98a519a..efe8227d345 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,2 +1,5 @@ language: java script: mvn verify +jdk: + - oraclejdk7 + - openjdk6 diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index 2ba8fedf777..959543a16f2 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -6,6 +6,7 @@ import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.marshaller.Marshaller; import org.hamcrest.CustomTypeSafeMatcher; +import org.hamcrest.Matcher; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -119,13 +120,14 @@ public void testHttpErrorLogged() throws Exception { httpConnection.send(new EventBuilder().build()); verify(handler).publish(logRecordCaptor.capture()); - assertThat(logRecordCaptor.getAllValues(), - hasItem(new CustomTypeSafeMatcher("Looks for message '" + httpErrorMessage + "'") { - @Override - protected boolean matchesSafely(LogRecord logRecord) { - return httpErrorMessage.equals(logRecord.getMessage()); - } - })); + Matcher> matcher = hasItem( + new CustomTypeSafeMatcher("Looks for message '" + httpErrorMessage + "'") { + @Override + protected boolean matchesSafely(LogRecord logRecord) { + return httpErrorMessage.equals(logRecord.getMessage()); + } + }); + assertThat(logRecordCaptor.getAllValues(), matcher); Logger.getLogger(HttpConnection.class.getCanonicalName()).removeHandler(handler); } From 44dabba04a058ffc5d9e07ad00fab53d6f85ec0d Mon Sep 17 00:00:00 2001 From: Matthew Buckett Date: Wed, 7 Aug 2013 16:47:35 +0100 Subject: [PATCH 0764/2152] Don't NPE when closing a badly setup appender. Log4J can have its configuration reloaded, if the appender wasn't setup correctly then log4j will attempt to close the appender and this NPEs. Same thing goes with JUL --- .../kencochrane/raven/log4j/SentryAppender.java | 2 +- .../raven/log4j/SentryAppenderBrokenTest.java | 14 ++++++++++++++ .../net/kencochrane/raven/jul/SentryHandler.java | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderBrokenTest.java diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 38255c36e9f..9ee71c2edb3 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -197,7 +197,7 @@ public void setRavenFactory(String ravenFactory) { @Override public void close() { try { - if (propagateClose) + if (propagateClose && raven != null) raven.getConnection().close(); } catch (IOException e) { getErrorHandler().error("An exception occurred while closing the Raven connection", e, diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderBrokenTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderBrokenTest.java new file mode 100644 index 00000000000..904adbe7880 --- /dev/null +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderBrokenTest.java @@ -0,0 +1,14 @@ +package net.kencochrane.raven.log4j; + +import org.junit.Test; + +public class SentryAppenderBrokenTest { + + @Test + public void testFailedOpenClose() { + // This checks that even if sentry wasn't setup correctly its appender can still be closed. + SentryAppender appender = new SentryAppender(); + appender.activateOptions(); + appender.close(); + } +} diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index d44492db4a7..bffe28dd484 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -178,7 +178,7 @@ public void flush() { @Override public void close() throws SecurityException { try { - if (propagateClose) + if (propagateClose && raven != null) raven.getConnection().close(); } catch (IOException e) { reportError("An exception occurred while closing the Raven connection", e, ErrorManager.CLOSE_FAILURE); From 23d0c8e981bdb358de948a08231240abd3113cdc Mon Sep 17 00:00:00 2001 From: Matthew Buckett Date: Wed, 7 Aug 2013 16:59:37 +0100 Subject: [PATCH 0765/2152] Check that we don't close multiple times. --- .../raven/log4j/SentryAppender.java | 4 +++ .../raven/log4j/SentryAppenderBrokenTest.java | 14 -------- .../raven/log4j/SentryAppenderCloseTest.java | 33 +++++++++++++++++++ 3 files changed, 37 insertions(+), 14 deletions(-) delete mode 100644 raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderBrokenTest.java create mode 100644 raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 9ee71c2edb3..c01cf5bd0f4 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -196,6 +196,10 @@ public void setRavenFactory(String ravenFactory) { @Override public void close() { + if (this.closed) + return; + this.closed = true; + try { if (propagateClose && raven != null) raven.getConnection().close(); diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderBrokenTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderBrokenTest.java deleted file mode 100644 index 904adbe7880..00000000000 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderBrokenTest.java +++ /dev/null @@ -1,14 +0,0 @@ -package net.kencochrane.raven.log4j; - -import org.junit.Test; - -public class SentryAppenderBrokenTest { - - @Test - public void testFailedOpenClose() { - // This checks that even if sentry wasn't setup correctly its appender can still be closed. - SentryAppender appender = new SentryAppender(); - appender.activateOptions(); - appender.close(); - } -} diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java new file mode 100644 index 00000000000..0024684d429 --- /dev/null +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java @@ -0,0 +1,33 @@ +package net.kencochrane.raven.log4j; + +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.connection.Connection; +import org.junit.Test; + +import java.io.IOException; + +import static org.mockito.Mockito.*; + +public class SentryAppenderCloseTest { + + @Test + public void testFailedOpenClose() { + // This checks that even if sentry wasn't setup correctly its appender can still be closed. + SentryAppender appender = new SentryAppender(); + appender.activateOptions(); + appender.close(); + } + + @Test + public void testMultipleClose() throws IOException { + Raven raven = mock(Raven.class); + Connection connection = mock(Connection.class); + when(raven.getConnection()).thenReturn(connection); + + SentryAppender appender = new SentryAppender(raven, true); + appender.close(); + + appender.close(); + verify(connection, times(1)).close(); + } +} From c82ddf1cd014f12fc2006e7e811adc3a71a58348 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 17:06:11 +0100 Subject: [PATCH 0766/2152] Use a different log setting for unit or integration tests --- raven-log4j2/pom.xml | 7 +++++++ .../resources/{log4j2.xml => log4j2-integration.xml} | 0 raven-log4j2/src/test/resources/log4j2-test.xml | 12 ++++++++++++ 3 files changed, 19 insertions(+) rename raven-log4j2/src/test/resources/{log4j2.xml => log4j2-integration.xml} (100%) create mode 100644 raven-log4j2/src/test/resources/log4j2-test.xml diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 29faa81b778..0dd2d723a00 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -72,6 +72,13 @@ org.apache.maven.plugins maven-failsafe-plugin + + + + ${project.basedir}/src/test/resources/log4j2-integration.xml + + + org.apache.maven.plugins diff --git a/raven-log4j2/src/test/resources/log4j2.xml b/raven-log4j2/src/test/resources/log4j2-integration.xml similarity index 100% rename from raven-log4j2/src/test/resources/log4j2.xml rename to raven-log4j2/src/test/resources/log4j2-integration.xml diff --git a/raven-log4j2/src/test/resources/log4j2-test.xml b/raven-log4j2/src/test/resources/log4j2-test.xml new file mode 100644 index 00000000000..bfb6e520d05 --- /dev/null +++ b/raven-log4j2/src/test/resources/log4j2-test.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + From 9480f91578509c26a7c655e1aea7cf6024df4065 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 11 Jul 2013 09:28:46 +0100 Subject: [PATCH 0767/2152] Update libraries and dependencies --- pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index f36eeddab4e..72703bca069 100644 --- a/pom.xml +++ b/pom.xml @@ -108,7 +108,7 @@ 1.7.5 14.0.1 2.2.2 - 3.0.1 + 3.1.0 1.0.13 1.2.17 2.0-beta8 @@ -292,12 +292,12 @@ org.eclipse.jetty jetty-maven-plugin - 9.0.3.v20130506 + 9.0.4.v20130625 org.apache.maven.plugins maven-failsafe-plugin - 2.14.1 + 2.15 org.apache.maven.plugins @@ -333,12 +333,12 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.9 + 2.9.1 org.apache.maven.plugins maven-surefire-report-plugin - 2.14.1 + 2.15 org.apache.maven.plugins @@ -469,7 +469,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.9 + 2.9.1 attach-javadocs From 299bdda9829fb1aca84a38d19138e928d0f214bb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Jul 2013 17:30:24 +0100 Subject: [PATCH 0768/2152] Add test for simple messages in log4j --- .../raven/log4j/SentryAppenderTest.java | 76 ++++++++++++++++++- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 8df238968bb..d032f71440f 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -1,35 +1,97 @@ package net.kencochrane.raven.log4j; import net.kencochrane.raven.AbstractLoggerTest; +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.apache.log4j.MDC; -import org.apache.log4j.NDC; +import net.kencochrane.raven.event.EventBuilder; +import org.apache.log4j.*; +import org.apache.log4j.helpers.OnlyOnceErrorHandler; +import org.apache.log4j.spi.ErrorHandler; +import org.apache.log4j.spi.LoggerFactory; +import org.apache.log4j.spi.LoggingEvent; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; +import org.mockito.Answers; import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import java.util.Date; import java.util.List; import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.*; public class SentryAppenderTest extends AbstractLoggerTest { + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Raven mockRaven; + @Mock + private RavenFactory mockRavenFactory; + private SentryAppender sentryAppender; private Logger logger; @Before public void setUp() throws Exception { + sentryAppender = new SentryAppender(mockRaven); + setMockErrorHandlerOnAppender(sentryAppender); + + when(mockRavenFactory.createRavenInstance(any(Dsn.class))).thenReturn(mockRaven); + RavenFactory.registerFactory(mockRavenFactory); + logger = Logger.getLogger(SentryAppenderTest.class); logger.setLevel(Level.ALL); logger.setAdditivity(false); logger.addAppender(new SentryAppender(getMockRaven())); } + private void setMockErrorHandlerOnAppender(SentryAppender sentryAppender) { + ErrorHandler mockErrorHandler = mock(ErrorHandler.class); + sentryAppender.setErrorHandler(mockErrorHandler); + Answer answer = new Answer() { + private final ErrorHandler actualErrorHandler = new OnlyOnceErrorHandler(); + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + invocation.getMethod().invoke(actualErrorHandler, invocation.getArguments()); + return null; + } + }; + doAnswer(answer).when(mockErrorHandler).error(anyString()); + doAnswer(answer).when(mockErrorHandler).error(anyString(), any(Exception.class), anyInt()); + doAnswer(answer).when(mockErrorHandler).error(anyString(), any(Exception.class), anyInt(), any(LoggingEvent.class)); + } + + @Test + public void testSimpleMessageLogging() throws Exception { + String message = UUID.randomUUID().toString(); + String loggerName = UUID.randomUUID().toString(); + String threadName = UUID.randomUUID().toString(); + Date date = new Date(1373883196416L); + Logger logger = Logger.getLogger(loggerName); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + Event event; + + sentryAppender.append(new LoggingEvent(null, logger, date.getTime(), Level.INFO, message, threadName, + null, null, null, null)); + + verify(mockRaven).runBuilderHelpers(any(EventBuilder.class)); + verify(mockRaven).sendEvent(eventCaptor.capture()); + event = eventCaptor.getValue(); + assertThat(event.getMessage(), is(message)); + assertThat(event.getLogger(), is(loggerName)); + assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); + assertThat(event.getTimestamp(), is(date)); + + assertNoErrors(sentryAppender); + } + @Override public void logAnyLevel(String message) { logger.info(message); @@ -124,4 +186,10 @@ public void testNdcAddedToExtra() { assertThat(eventCaptor.getValue().getExtra().get(SentryAppender.LOG4J_NDC), Matchers.is(extra + " " + extra2)); } + + private void assertNoErrors(SentryAppender sentryAppender) { + verify(sentryAppender.getErrorHandler(), never()).error(anyString()); + verify(sentryAppender.getErrorHandler(), never()).error(anyString(), any(Exception.class), anyInt()); + verify(sentryAppender.getErrorHandler(), never()).error(anyString(), any(Exception.class), anyInt(), any(LoggingEvent.class)); + } } From 2c339546c50895745af1acabb6f9ea2122129bde Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Jul 2013 23:21:57 +0100 Subject: [PATCH 0769/2152] Move logging.properties to logging-integration.properties Every other logging system uses a *-integration file for integration test, follow the same convention with JUL --- raven/pom.xml | 2 +- .../{logging.properties => logging-integration.properties} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename raven/src/test/resources/{logging.properties => logging-integration.properties} (100%) diff --git a/raven/pom.xml b/raven/pom.xml index 16034b70e1b..86860b8f7a7 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -85,7 +85,7 @@ - ${project.basedir}/src/test/resources/logging.properties + ${project.basedir}/src/test/resources/logging-integration.properties diff --git a/raven/src/test/resources/logging.properties b/raven/src/test/resources/logging-integration.properties similarity index 100% rename from raven/src/test/resources/logging.properties rename to raven/src/test/resources/logging-integration.properties From eed8ddd8f4114dbda3db844874fc61d9f61be186 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 18 Jul 2013 15:46:52 +0100 Subject: [PATCH 0770/2152] Use a different log4j.properties file for integration and unit tests --- raven-log4j/pom.xml | 7 +++++++ .../{log4j.properties => log4j-integration.properties} | 2 +- raven-log4j/src/test/resources/log4j-test.properties | 5 +++++ 3 files changed, 13 insertions(+), 1 deletion(-) rename raven-log4j/src/test/resources/{log4j.properties => log4j-integration.properties} (86%) create mode 100644 raven-log4j/src/test/resources/log4j-test.properties diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index c1dc93bb28d..b3a4b268fd6 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -67,6 +67,13 @@ org.apache.maven.plugins maven-failsafe-plugin + + + + ${project.basedir}/src/test/resources/log4j-integration.xml + + + org.apache.maven.plugins diff --git a/raven-log4j/src/test/resources/log4j.properties b/raven-log4j/src/test/resources/log4j-integration.properties similarity index 86% rename from raven-log4j/src/test/resources/log4j.properties rename to raven-log4j/src/test/resources/log4j-integration.properties index c925dfeff3a..f3b728893ea 100644 --- a/raven-log4j/src/test/resources/log4j.properties +++ b/raven-log4j/src/test/resources/log4j-integration.properties @@ -1,4 +1,4 @@ -log4j.rootLogger=ALL, ConsoleAppender, SentryAppender +og4j.rootLogger=ALL, ConsoleAppender, SentryAppender log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender diff --git a/raven-log4j/src/test/resources/log4j-test.properties b/raven-log4j/src/test/resources/log4j-test.properties new file mode 100644 index 00000000000..b197dd95f92 --- /dev/null +++ b/raven-log4j/src/test/resources/log4j-test.properties @@ -0,0 +1,5 @@ +log4j.rootLogger=ALL, ConsoleAppender, SentryAppender + +log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender + +log4j.appender.ConsoleAppender.layout=org.apache.log4j.SimpleLayout From b8e1dd2a74fa6b3773aa1f6a3bf8ff0a7b8e15d7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Jul 2013 23:17:41 +0100 Subject: [PATCH 0771/2152] Introduce testng and JMockit as dependencies --- pom.xml | 19 ++++++++++++++++++- raven-log4j/pom.xml | 10 ++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 72703bca069..1205df3651b 100644 --- a/pom.xml +++ b/pom.xml @@ -112,8 +112,10 @@ 1.0.13 1.2.17 2.0-beta8 - 4.11 1.9.5 + 1.3 + 4.11 + 6.8.5 1.3 @@ -201,11 +203,26 @@ mockito-core ${mockito.version} + + com.googlecode.jmockit + jmockit + ${jmockit.version} + junit junit ${junit.version} + + org.testng + testng + ${testng.version} + + + testng + junit + ${junit.version} + org.hamcrest hamcrest-core diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index b3a4b268fd6..48f2aa3d8c3 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -45,11 +45,21 @@ mockito-core test + + com.googlecode.jmockit + jmockit + test + junit junit test + + org.testng + testng + test + org.hamcrest hamcrest-core From d6065330396ee9f7ceeabdabfbf508db0ddd0ded Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 17 Jul 2013 23:19:11 +0100 Subject: [PATCH 0772/2152] Add new tests using testNG in raven-log4j --- .../raven/log4j/SentryAppenderNGTest.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java new file mode 100644 index 00000000000..933035ef72d --- /dev/null +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java @@ -0,0 +1,57 @@ +package net.kencochrane.raven.log4j; + +import mockit.Expectations; +import mockit.Injectable; +import mockit.Mocked; +import mockit.Verifications; +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.EventBuilder; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.spi.LoggingEvent; +import org.hamcrest.Matchers; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.Date; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SentryAppenderNGTest { + private SentryAppender sentryAppender; + @Mocked + private Raven mockRaven = null; + + @BeforeMethod + public void setUp() { + sentryAppender = new SentryAppender(mockRaven); + } + + @Test + public void testSimpleMesageLogging(@Injectable final Logger logger) throws Exception { + final String loggerName = UUID.randomUUID().toString(); + final String message = UUID.randomUUID().toString(); + final String threadName = UUID.randomUUID().toString(); + final Date date = new Date(1373883196416L); + new Expectations() {{ + logger.getName(); + result = loggerName; + }}; + + sentryAppender.append(new LoggingEvent(null, logger, date.getTime(), Level.INFO, message, threadName, + null, null, null, null)); + + new Verifications() {{ + Event event; + mockRaven.runBuilderHelpers(withAny(new EventBuilder())); + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getMessage(), is(message)); + assertThat(event.getLogger(), is(loggerName)); + assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); + assertThat(event.getTimestamp(), is(date)); + }}; + } +} From 4fec4957d0497e0a36d8597f879394292cc10f86 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 18 Jul 2013 17:23:00 +0100 Subject: [PATCH 0773/2152] Nake the injectable mockLogger accessible through the entire test --- .../raven/log4j/SentryAppenderNGTest.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java index 933035ef72d..46235ee8dcf 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java @@ -24,6 +24,8 @@ public class SentryAppenderNGTest { private SentryAppender sentryAppender; @Mocked private Raven mockRaven = null; + @Injectable + private Logger mockLogger = null; @BeforeMethod public void setUp() { @@ -31,17 +33,17 @@ public void setUp() { } @Test - public void testSimpleMesageLogging(@Injectable final Logger logger) throws Exception { + public void testSimpleMesageLogging() throws Exception { final String loggerName = UUID.randomUUID().toString(); final String message = UUID.randomUUID().toString(); final String threadName = UUID.randomUUID().toString(); final Date date = new Date(1373883196416L); new Expectations() {{ - logger.getName(); - result = loggerName; - }}; + onInstance(mockLogger).getName(); + result = loggerName; + }}; - sentryAppender.append(new LoggingEvent(null, logger, date.getTime(), Level.INFO, message, threadName, + sentryAppender.append(new LoggingEvent(null, mockLogger, date.getTime(), Level.INFO, message, threadName, null, null, null, null)); new Verifications() {{ From 088d4eca2d7baf3b54b40b8580c6ec949ac6b52c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 18 Jul 2013 17:23:25 +0100 Subject: [PATCH 0774/2152] Add unit test to ensure log levels are indeed converted --- .../raven/log4j/SentryAppenderNGTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java index 46235ee8dcf..261060d7604 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java @@ -56,4 +56,24 @@ public void testSimpleMesageLogging() throws Exception { assertThat(event.getTimestamp(), is(date)); }}; } + + @Test + public void testLevelConversion() throws Exception { + assertLevelConverted(Event.Level.DEBUG, Level.TRACE); + assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); + assertLevelConverted(Event.Level.INFO, Level.INFO); + assertLevelConverted(Event.Level.WARNING, Level.WARN); + assertLevelConverted(Event.Level.ERROR, Level.ERROR); + assertLevelConverted(Event.Level.FATAL, Level.FATAL); + } + + private void assertLevelConverted(final Event.Level expectedLevel, Level level) throws Exception { + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, level, null, null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getLevel(), is(expectedLevel)); + }}; + } } From 04e71f7c5bb8d7dfe5573dea331d1657df6cb37c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 18 Jul 2013 17:24:10 +0100 Subject: [PATCH 0775/2152] Add test to ensure that Exceptions are properly logged --- .../raven/log4j/SentryAppenderNGTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java index 261060d7604..05a4e9d5246 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java @@ -7,6 +7,7 @@ import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.spi.LoggingEvent; @@ -76,4 +77,22 @@ private void assertLevelConverted(final Event.Level expectedLevel, Level level) assertThat(event.getLevel(), is(expectedLevel)); }}; } + + @Test + public void testExceptionLogging() throws Exception { + final Exception exception = new Exception(UUID.randomUUID().toString()); + + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, exception)); + + new Verifications() {{ + Event event; + Throwable throwable; + mockRaven.sendEvent(event = withCapture()); + ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() + .get(ExceptionInterface.EXCEPTION_INTERFACE); + throwable = exceptionInterface.getThrowable(); + assertThat(throwable.getMessage(), is(exception.getMessage())); + assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); + }}; + } } From b25ad28477331b749c4d30c1bfb47854012c9383 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 18 Jul 2013 17:24:57 +0100 Subject: [PATCH 0776/2152] Add test to ensure that MDC details from log4j are added as extra --- .../raven/log4j/SentryAppenderNGTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java index 05a4e9d5246..757597bc7e0 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java @@ -15,6 +15,7 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import java.util.Collections; import java.util.Date; import java.util.UUID; @@ -95,4 +96,19 @@ public void testExceptionLogging() throws Exception { assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); }}; } + + @Test + public void testMdcAddedToExtra() throws Exception { + final String extraKey = UUID.randomUUID().toString(); + final String extraValue = UUID.randomUUID().toString(); + + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, + null, null, null, Collections.singletonMap(extraKey, extraValue))); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); + }}; + } } From 0f0289ac837aab7f72043a312c5cb4fef9ef8faa Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 18 Jul 2013 17:34:29 +0100 Subject: [PATCH 0777/2152] Add test to ensure that the NDC values are sent as extra --- .../raven/log4j/SentryAppenderNGTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java index 757597bc7e0..400212d389e 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java @@ -1,5 +1,6 @@ package net.kencochrane.raven.log4j; +import com.google.common.base.Joiner; import mockit.Expectations; import mockit.Injectable; import mockit.Mocked; @@ -111,4 +112,18 @@ public void testMdcAddedToExtra() throws Exception { assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); }}; } + + @Test + public void testNdcAddedToExtra() throws Exception { + final String ndcEntries = Joiner.on(' ').join(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()); + + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, + null, ndcEntries, null, null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.LOG4J_NDC, ndcEntries)); + }}; + } } From 873fce281c0303542defae812d2f21e628e59985 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 18 Jul 2013 17:42:47 +0100 Subject: [PATCH 0778/2152] Fix typo --- .../java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java index 400212d389e..fd0121c12ce 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java @@ -36,7 +36,7 @@ public void setUp() { } @Test - public void testSimpleMesageLogging() throws Exception { + public void testSimpleMessageLogging() throws Exception { final String loggerName = UUID.randomUUID().toString(); final String message = UUID.randomUUID().toString(); final String threadName = UUID.randomUUID().toString(); From 566323c22ab1f6610ca61a7949bd5a443640c526 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 18 Jul 2013 21:32:20 +0100 Subject: [PATCH 0779/2152] Add test to ensure the source is used as Stacktrace if available --- .../raven/log4j/SentryAppenderNGTest.java | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java index fd0121c12ce..c701634cb56 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java @@ -1,16 +1,15 @@ package net.kencochrane.raven.log4j; import com.google.common.base.Joiner; -import mockit.Expectations; -import mockit.Injectable; -import mockit.Mocked; -import mockit.Verifications; +import mockit.*; import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.log4j.Level; import org.apache.log4j.Logger; +import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; import org.hamcrest.Matchers; import org.testng.annotations.BeforeMethod; @@ -21,6 +20,7 @@ import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.is; public class SentryAppenderNGTest { @@ -126,4 +126,36 @@ public void testNdcAddedToExtra() throws Exception { assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.LOG4J_NDC, ndcEntries)); }}; } + + @Test + public void testSourceUsedAsStacktrace(@Injectable @NonStrict final LocationInfo locationInfo) throws Exception { + final String className = UUID.randomUUID().toString(); + final String methodName = UUID.randomUUID().toString(); + final String fileName = UUID.randomUUID().toString(); + final int line = 42; + new Expectations() {{ + locationInfo.getClassName(); + result = className; + locationInfo.getMethodName(); + result = methodName; + locationInfo.getFileName(); + result = fileName; + locationInfo.getLineNumber(); + result = Integer.toString(line); + setField(locationInfo, "fullInfo", ""); + }}; + + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, + null, null, locationInfo, null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() + .get(StackTraceInterface.STACKTRACE_INTERFACE); + assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); + assertThat(stackTraceInterface.getStackTrace()[0], + is(new StackTraceElement(className, methodName, fileName, line))); + }}; + } } From f97b517e2c94eaa86a2441ddaa27802f6bcab9cc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 18 Jul 2013 21:37:15 +0100 Subject: [PATCH 0780/2152] Ensure that the culprit is set to the locationInfo if available --- .../raven/log4j/SentryAppenderNGTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java index c701634cb56..4747b5409f1 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java @@ -158,4 +158,32 @@ public void testSourceUsedAsStacktrace(@Injectable @NonStrict final LocationInfo is(new StackTraceElement(className, methodName, fileName, line))); }}; } + + @Test + public void testCulpritWithSource(@Injectable @NonStrict final LocationInfo locationInfo) throws Exception { + final String className = "a"; + final String methodName = "b"; + final String fileName = "c"; + final int line = 42; + new Expectations() {{ + locationInfo.getClassName(); + result = className; + locationInfo.getMethodName(); + result = methodName; + locationInfo.getFileName(); + result = fileName; + locationInfo.getLineNumber(); + result = Integer.toString(line); + setField(locationInfo, "fullInfo", ""); + }}; + + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, + null, null, locationInfo, null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getCulprit(), is("a.b(c:42)")); + }}; + } } From 3fa5a053cf8ce13ebb2259830f3d19b798361b8d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 18 Jul 2013 21:39:16 +0100 Subject: [PATCH 0781/2152] Ensure that the logger name is used as culprit if there is no locationInfo --- .../raven/log4j/SentryAppenderNGTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java index 4747b5409f1..c3b1b3d2cc5 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java @@ -186,4 +186,21 @@ public void testCulpritWithSource(@Injectable @NonStrict final LocationInfo loca assertThat(event.getCulprit(), is("a.b(c:42)")); }}; } + + @Test + public void testCulpritWithoutSource() throws Exception { + final String loggerName = UUID.randomUUID().toString(); + new Expectations() {{ + onInstance(mockLogger).getName(); + result = loggerName; + }}; + + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getCulprit(), is(loggerName)); + }}; + } } From caf0c2c2da4cf598ad7bc522e86fa0b03b5d5994 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 18 Jul 2013 22:17:21 +0100 Subject: [PATCH 0782/2152] Ensure that close() isn't called on the connection if Raven is provided --- .../raven/log4j/SentryAppenderNGTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java index c3b1b3d2cc5..616b961ac6a 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java @@ -3,6 +3,7 @@ import com.google.common.base.Joiner; import mockit.*; import net.kencochrane.raven.Raven; +import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; @@ -203,4 +204,29 @@ public void testCulpritWithoutSource() throws Exception { assertThat(event.getCulprit(), is(loggerName)); }}; } + + @Test + public void testConnectionNotClosedIfARavenInstanceIsProvided() throws Exception { + final SentryAppender sentryAppender = new SentryAppender(mockRaven); + new NonStrictExpectations() { + @Mocked + private Connection connection; + + { + mockRaven.getConnection(); + result = connection; + } + }; + + sentryAppender.close(); + + new Verifications() { + private Connection connection; + + { + connection.close(); + times = 0; + } + }; + } } From e8b8670fb78135026a4d3a1b34bc3ac58bfd375f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 19 Jul 2013 04:46:11 -0400 Subject: [PATCH 0783/2152] Inject the Connection mock instead of making it on the fly --- .../raven/log4j/SentryAppenderNGTest.java | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java index 616b961ac6a..e42e33bfdab 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java @@ -3,6 +3,7 @@ import com.google.common.base.Joiner; import mockit.*; import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; @@ -206,27 +207,19 @@ public void testCulpritWithoutSource() throws Exception { } @Test - public void testConnectionNotClosedIfARavenInstanceIsProvided() throws Exception { + public void testConnectionNotClosedIfRavenInstanceIsProvided(@Mocked final Connection connection) throws Exception { final SentryAppender sentryAppender = new SentryAppender(mockRaven); - new NonStrictExpectations() { - @Mocked - private Connection connection; - - { - mockRaven.getConnection(); - result = connection; - } - }; + new NonStrictExpectations() {{ + mockRaven.getConnection(); + result = connection; + }}; + sentryAppender.activateOptions(); sentryAppender.close(); - new Verifications() { - private Connection connection; - - { - connection.close(); - times = 0; - } - }; + new Verifications() {{ + connection.close(); + times = 0; + }}; } } From 40b0e89515a8581b50551d862e23e67725598054 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 19 Jul 2013 04:47:21 -0400 Subject: [PATCH 0784/2152] Add test to ensure that the raven connection is closed when forced --- .../raven/log4j/SentryAppenderNGTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java index e42e33bfdab..0003e2065f8 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java @@ -222,4 +222,21 @@ public void testConnectionNotClosedIfRavenInstanceIsProvided(@Mocked final Conne times = 0; }}; } + + @Test + public void testConnectionClosedIfRavenInstanceProvidedAndForceClose(@Mocked final Connection connection) + throws Exception { + final SentryAppender sentryAppender = new SentryAppender(mockRaven, true); + new NonStrictExpectations() {{ + mockRaven.getConnection(); + result = connection; + }}; + + sentryAppender.activateOptions(); + sentryAppender.close(); + + new Verifications() {{ + connection.close(); + }}; + } } From bc26e30c04779fc36c0f89d8d13365c11f64b1ce Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 19 Jul 2013 04:47:21 -0400 Subject: [PATCH 0785/2152] Add test to ensure that the raven connection is not closed when not forced --- .../raven/log4j/SentryAppenderNGTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java index 0003e2065f8..4d7932d55c2 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java @@ -239,4 +239,22 @@ public void testConnectionClosedIfRavenInstanceProvidedAndForceClose(@Mocked fin connection.close(); }}; } + + @Test + public void testConnectionNotClosedIfRavenInstanceProvidedAndNotForceClose(@Mocked final Connection connection) + throws Exception { + final SentryAppender sentryAppender = new SentryAppender(mockRaven, false); + new NonStrictExpectations() {{ + mockRaven.getConnection(); + result = connection; + }}; + + sentryAppender.activateOptions(); + sentryAppender.close(); + + new Verifications() {{ + connection.close(); + times = 0; + }}; + } } From 6c891644d314d8035b7a161ac03ffeb573b78113 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 19 Jul 2013 04:47:21 -0400 Subject: [PATCH 0786/2152] Add test to ensure that the raven connection is closed when raven is generated --- .../raven/log4j/SentryAppenderNGTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java index 4d7932d55c2..cd729eb5d98 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java @@ -5,6 +5,7 @@ import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.connection.Connection; +import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; @@ -257,4 +258,29 @@ public void testConnectionNotClosedIfRavenInstanceProvidedAndNotForceClose(@Mock times = 0; }}; } + + @Test + public void testConnectionClosedIfRavenInstanceNotProvided(@Mocked final Connection connection) throws Exception { + final SentryAppender sentryAppender = new SentryAppender(); + new NonStrictExpectations() { + @Mocked + private final Dsn dsn = null; + @Mocked + private RavenFactory ravenFactory = null; + + { + mockRaven.getConnection(); + result = connection; + RavenFactory.ravenInstance(dsn, anyString); + result = mockRaven; + } + }; + + sentryAppender.activateOptions(); + sentryAppender.close(); + + new Verifications() {{ + connection.close(); + }}; + } } From cf5e3e66a19ce002bd09d233d10e924ac63fdb2a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 19 Jul 2013 04:51:23 -0400 Subject: [PATCH 0787/2152] Test whether the Raven instance generated is the one being used --- .../raven/log4j/SentryAppenderNGTest.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java index cd729eb5d98..8aef451bcb0 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java @@ -283,4 +283,28 @@ public void testConnectionClosedIfRavenInstanceNotProvided(@Mocked final Connect connection.close(); }}; } + + @Test + public void testCorrectRavenInstanceUsedIfNotProvided() throws Exception { + final SentryAppender sentryAppender = new SentryAppender(); + + new NonStrictExpectations() { + @Mocked + private final Dsn dsn = null; + @Mocked + private RavenFactory ravenFactory = null; + + { + RavenFactory.ravenInstance(dsn, anyString); + returns(mockRaven); + } + }; + + sentryAppender.activateOptions(); + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); + + new Verifications() {{ + onInstance(mockRaven).sendEvent((Event) any); + }}; + } } From 677e824ae90707ad87df5721d2fcb8e6a2e912b2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 19 Jul 2013 05:19:35 -0400 Subject: [PATCH 0788/2152] Ensure that no errors are generated during the tests --- .../raven/log4j/SentryAppenderNGTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java index 8aef451bcb0..a72a56ff979 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java @@ -12,6 +12,7 @@ import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.log4j.Level; import org.apache.log4j.Logger; +import org.apache.log4j.spi.ErrorHandler; import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; import org.hamcrest.Matchers; @@ -32,10 +33,13 @@ public class SentryAppenderNGTest { private Raven mockRaven = null; @Injectable private Logger mockLogger = null; + @Injectable + private ErrorHandler mockErrorHandler = null; @BeforeMethod public void setUp() { sentryAppender = new SentryAppender(mockRaven); + sentryAppender.setErrorHandler(mockErrorHandler); } @Test @@ -60,6 +64,7 @@ public void testSimpleMessageLogging() throws Exception { assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); + assertDoNotGenerateErrors(); }}; } @@ -80,6 +85,7 @@ private void assertLevelConverted(final Event.Level expectedLevel, Level level) Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getLevel(), is(expectedLevel)); + assertDoNotGenerateErrors(); }}; } @@ -98,6 +104,7 @@ public void testExceptionLogging() throws Exception { throwable = exceptionInterface.getThrowable(); assertThat(throwable.getMessage(), is(exception.getMessage())); assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); + assertDoNotGenerateErrors(); }}; } @@ -113,6 +120,7 @@ public void testMdcAddedToExtra() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); + assertDoNotGenerateErrors(); }}; } @@ -127,6 +135,7 @@ public void testNdcAddedToExtra() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.LOG4J_NDC, ndcEntries)); + assertDoNotGenerateErrors(); }}; } @@ -159,6 +168,7 @@ public void testSourceUsedAsStacktrace(@Injectable @NonStrict final LocationInfo assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); assertThat(stackTraceInterface.getStackTrace()[0], is(new StackTraceElement(className, methodName, fileName, line))); + assertDoNotGenerateErrors(); }}; } @@ -187,6 +197,7 @@ public void testCulpritWithSource(@Injectable @NonStrict final LocationInfo loca Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is("a.b(c:42)")); + assertDoNotGenerateErrors(); }}; } @@ -204,6 +215,7 @@ public void testCulpritWithoutSource() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is(loggerName)); + assertDoNotGenerateErrors(); }}; } @@ -221,6 +233,7 @@ public void testConnectionNotClosedIfRavenInstanceIsProvided(@Mocked final Conne new Verifications() {{ connection.close(); times = 0; + assertDoNotGenerateErrors(); }}; } @@ -238,6 +251,7 @@ public void testConnectionClosedIfRavenInstanceProvidedAndForceClose(@Mocked fin new Verifications() {{ connection.close(); + assertDoNotGenerateErrors(); }}; } @@ -256,6 +270,7 @@ public void testConnectionNotClosedIfRavenInstanceProvidedAndNotForceClose(@Mock new Verifications() {{ connection.close(); times = 0; + assertDoNotGenerateErrors(); }}; } @@ -281,6 +296,7 @@ public void testConnectionClosedIfRavenInstanceNotProvided(@Mocked final Connect new Verifications() {{ connection.close(); + assertDoNotGenerateErrors(); }}; } @@ -305,6 +321,18 @@ public void testCorrectRavenInstanceUsedIfNotProvided() throws Exception { new Verifications() {{ onInstance(mockRaven).sendEvent((Event) any); + assertDoNotGenerateErrors(); + }}; + } + + private void assertDoNotGenerateErrors() throws Exception{ + new Verifications() {{ + mockErrorHandler.error(anyString); + times = 0; + mockErrorHandler.error(anyString, (Exception) any, anyInt); + times = 0; + mockErrorHandler.error(anyString, (Exception) any, anyInt, (LoggingEvent) any); + times = 0; }}; } } From da2c8d6239071c682fc5666e882dfa9f4875b54f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 19 Jul 2013 05:20:23 -0400 Subject: [PATCH 0789/2152] Replace Junit/mockit tests with mockit/testng --- raven-log4j/pom.xml | 5 - .../raven/log4j/SentryAppenderNGTest.java | 338 --------------- .../raven/log4j/SentryAppenderTest.java | 409 ++++++++++++------ 3 files changed, 276 insertions(+), 476 deletions(-) delete mode 100644 raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 48f2aa3d8c3..0459b957dff 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -40,11 +40,6 @@ jackson-databind test - - org.mockito - mockito-core - test - com.googlecode.jmockit jmockit diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java deleted file mode 100644 index a72a56ff979..00000000000 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderNGTest.java +++ /dev/null @@ -1,338 +0,0 @@ -package net.kencochrane.raven.log4j; - -import com.google.common.base.Joiner; -import mockit.*; -import net.kencochrane.raven.Raven; -import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.connection.Connection; -import net.kencochrane.raven.dsn.Dsn; -import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.EventBuilder; -import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.apache.log4j.spi.ErrorHandler; -import org.apache.log4j.spi.LocationInfo; -import org.apache.log4j.spi.LoggingEvent; -import org.hamcrest.Matchers; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.util.Collections; -import java.util.Date; -import java.util.UUID; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.arrayWithSize; -import static org.hamcrest.Matchers.is; - -public class SentryAppenderNGTest { - private SentryAppender sentryAppender; - @Mocked - private Raven mockRaven = null; - @Injectable - private Logger mockLogger = null; - @Injectable - private ErrorHandler mockErrorHandler = null; - - @BeforeMethod - public void setUp() { - sentryAppender = new SentryAppender(mockRaven); - sentryAppender.setErrorHandler(mockErrorHandler); - } - - @Test - public void testSimpleMessageLogging() throws Exception { - final String loggerName = UUID.randomUUID().toString(); - final String message = UUID.randomUUID().toString(); - final String threadName = UUID.randomUUID().toString(); - final Date date = new Date(1373883196416L); - new Expectations() {{ - onInstance(mockLogger).getName(); - result = loggerName; - }}; - - sentryAppender.append(new LoggingEvent(null, mockLogger, date.getTime(), Level.INFO, message, threadName, - null, null, null, null)); - - new Verifications() {{ - Event event; - mockRaven.runBuilderHelpers(withAny(new EventBuilder())); - mockRaven.sendEvent(event = withCapture()); - assertThat(event.getMessage(), is(message)); - assertThat(event.getLogger(), is(loggerName)); - assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); - assertThat(event.getTimestamp(), is(date)); - assertDoNotGenerateErrors(); - }}; - } - - @Test - public void testLevelConversion() throws Exception { - assertLevelConverted(Event.Level.DEBUG, Level.TRACE); - assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); - assertLevelConverted(Event.Level.INFO, Level.INFO); - assertLevelConverted(Event.Level.WARNING, Level.WARN); - assertLevelConverted(Event.Level.ERROR, Level.ERROR); - assertLevelConverted(Event.Level.FATAL, Level.FATAL); - } - - private void assertLevelConverted(final Event.Level expectedLevel, Level level) throws Exception { - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, level, null, null)); - - new Verifications() {{ - Event event; - mockRaven.sendEvent(event = withCapture()); - assertThat(event.getLevel(), is(expectedLevel)); - assertDoNotGenerateErrors(); - }}; - } - - @Test - public void testExceptionLogging() throws Exception { - final Exception exception = new Exception(UUID.randomUUID().toString()); - - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, exception)); - - new Verifications() {{ - Event event; - Throwable throwable; - mockRaven.sendEvent(event = withCapture()); - ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() - .get(ExceptionInterface.EXCEPTION_INTERFACE); - throwable = exceptionInterface.getThrowable(); - assertThat(throwable.getMessage(), is(exception.getMessage())); - assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); - assertDoNotGenerateErrors(); - }}; - } - - @Test - public void testMdcAddedToExtra() throws Exception { - final String extraKey = UUID.randomUUID().toString(); - final String extraValue = UUID.randomUUID().toString(); - - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, - null, null, null, Collections.singletonMap(extraKey, extraValue))); - - new Verifications() {{ - Event event; - mockRaven.sendEvent(event = withCapture()); - assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); - assertDoNotGenerateErrors(); - }}; - } - - @Test - public void testNdcAddedToExtra() throws Exception { - final String ndcEntries = Joiner.on(' ').join(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()); - - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, - null, ndcEntries, null, null)); - - new Verifications() {{ - Event event; - mockRaven.sendEvent(event = withCapture()); - assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.LOG4J_NDC, ndcEntries)); - assertDoNotGenerateErrors(); - }}; - } - - @Test - public void testSourceUsedAsStacktrace(@Injectable @NonStrict final LocationInfo locationInfo) throws Exception { - final String className = UUID.randomUUID().toString(); - final String methodName = UUID.randomUUID().toString(); - final String fileName = UUID.randomUUID().toString(); - final int line = 42; - new Expectations() {{ - locationInfo.getClassName(); - result = className; - locationInfo.getMethodName(); - result = methodName; - locationInfo.getFileName(); - result = fileName; - locationInfo.getLineNumber(); - result = Integer.toString(line); - setField(locationInfo, "fullInfo", ""); - }}; - - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, - null, null, locationInfo, null)); - - new Verifications() {{ - Event event; - mockRaven.sendEvent(event = withCapture()); - StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() - .get(StackTraceInterface.STACKTRACE_INTERFACE); - assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); - assertThat(stackTraceInterface.getStackTrace()[0], - is(new StackTraceElement(className, methodName, fileName, line))); - assertDoNotGenerateErrors(); - }}; - } - - @Test - public void testCulpritWithSource(@Injectable @NonStrict final LocationInfo locationInfo) throws Exception { - final String className = "a"; - final String methodName = "b"; - final String fileName = "c"; - final int line = 42; - new Expectations() {{ - locationInfo.getClassName(); - result = className; - locationInfo.getMethodName(); - result = methodName; - locationInfo.getFileName(); - result = fileName; - locationInfo.getLineNumber(); - result = Integer.toString(line); - setField(locationInfo, "fullInfo", ""); - }}; - - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, - null, null, locationInfo, null)); - - new Verifications() {{ - Event event; - mockRaven.sendEvent(event = withCapture()); - assertThat(event.getCulprit(), is("a.b(c:42)")); - assertDoNotGenerateErrors(); - }}; - } - - @Test - public void testCulpritWithoutSource() throws Exception { - final String loggerName = UUID.randomUUID().toString(); - new Expectations() {{ - onInstance(mockLogger).getName(); - result = loggerName; - }}; - - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); - - new Verifications() {{ - Event event; - mockRaven.sendEvent(event = withCapture()); - assertThat(event.getCulprit(), is(loggerName)); - assertDoNotGenerateErrors(); - }}; - } - - @Test - public void testConnectionNotClosedIfRavenInstanceIsProvided(@Mocked final Connection connection) throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven); - new NonStrictExpectations() {{ - mockRaven.getConnection(); - result = connection; - }}; - - sentryAppender.activateOptions(); - sentryAppender.close(); - - new Verifications() {{ - connection.close(); - times = 0; - assertDoNotGenerateErrors(); - }}; - } - - @Test - public void testConnectionClosedIfRavenInstanceProvidedAndForceClose(@Mocked final Connection connection) - throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven, true); - new NonStrictExpectations() {{ - mockRaven.getConnection(); - result = connection; - }}; - - sentryAppender.activateOptions(); - sentryAppender.close(); - - new Verifications() {{ - connection.close(); - assertDoNotGenerateErrors(); - }}; - } - - @Test - public void testConnectionNotClosedIfRavenInstanceProvidedAndNotForceClose(@Mocked final Connection connection) - throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven, false); - new NonStrictExpectations() {{ - mockRaven.getConnection(); - result = connection; - }}; - - sentryAppender.activateOptions(); - sentryAppender.close(); - - new Verifications() {{ - connection.close(); - times = 0; - assertDoNotGenerateErrors(); - }}; - } - - @Test - public void testConnectionClosedIfRavenInstanceNotProvided(@Mocked final Connection connection) throws Exception { - final SentryAppender sentryAppender = new SentryAppender(); - new NonStrictExpectations() { - @Mocked - private final Dsn dsn = null; - @Mocked - private RavenFactory ravenFactory = null; - - { - mockRaven.getConnection(); - result = connection; - RavenFactory.ravenInstance(dsn, anyString); - result = mockRaven; - } - }; - - sentryAppender.activateOptions(); - sentryAppender.close(); - - new Verifications() {{ - connection.close(); - assertDoNotGenerateErrors(); - }}; - } - - @Test - public void testCorrectRavenInstanceUsedIfNotProvided() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(); - - new NonStrictExpectations() { - @Mocked - private final Dsn dsn = null; - @Mocked - private RavenFactory ravenFactory = null; - - { - RavenFactory.ravenInstance(dsn, anyString); - returns(mockRaven); - } - }; - - sentryAppender.activateOptions(); - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); - - new Verifications() {{ - onInstance(mockRaven).sendEvent((Event) any); - assertDoNotGenerateErrors(); - }}; - } - - private void assertDoNotGenerateErrors() throws Exception{ - new Verifications() {{ - mockErrorHandler.error(anyString); - times = 0; - mockErrorHandler.error(anyString, (Exception) any, anyInt); - times = 0; - mockErrorHandler.error(anyString, (Exception) any, anyInt, (LoggingEvent) any); - times = 0; - }}; - } -} diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index d032f71440f..71dadfdb220 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -1,195 +1,338 @@ package net.kencochrane.raven.log4j; -import net.kencochrane.raven.AbstractLoggerTest; +import com.google.common.base.Joiner; +import mockit.*; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; -import org.apache.log4j.*; -import org.apache.log4j.helpers.OnlyOnceErrorHandler; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; import org.apache.log4j.spi.ErrorHandler; -import org.apache.log4j.spi.LoggerFactory; +import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; import org.hamcrest.Matchers; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Answers; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import java.util.Collections; import java.util.Date; -import java.util.List; import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.*; -public class SentryAppenderTest extends AbstractLoggerTest { - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private Raven mockRaven; - @Mock - private RavenFactory mockRavenFactory; +public class SentryAppenderTest { private SentryAppender sentryAppender; - private Logger logger; - - @Before - public void setUp() throws Exception { + @Mocked + private Raven mockRaven = null; + @Injectable + private Logger mockLogger = null; + @Injectable + private ErrorHandler mockErrorHandler = null; + + @BeforeMethod + public void setUp() { sentryAppender = new SentryAppender(mockRaven); - setMockErrorHandlerOnAppender(sentryAppender); - - when(mockRavenFactory.createRavenInstance(any(Dsn.class))).thenReturn(mockRaven); - RavenFactory.registerFactory(mockRavenFactory); - - logger = Logger.getLogger(SentryAppenderTest.class); - logger.setLevel(Level.ALL); - logger.setAdditivity(false); - logger.addAppender(new SentryAppender(getMockRaven())); - } - - private void setMockErrorHandlerOnAppender(SentryAppender sentryAppender) { - ErrorHandler mockErrorHandler = mock(ErrorHandler.class); sentryAppender.setErrorHandler(mockErrorHandler); - Answer answer = new Answer() { - private final ErrorHandler actualErrorHandler = new OnlyOnceErrorHandler(); - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - invocation.getMethod().invoke(actualErrorHandler, invocation.getArguments()); - return null; - } - }; - doAnswer(answer).when(mockErrorHandler).error(anyString()); - doAnswer(answer).when(mockErrorHandler).error(anyString(), any(Exception.class), anyInt()); - doAnswer(answer).when(mockErrorHandler).error(anyString(), any(Exception.class), anyInt(), any(LoggingEvent.class)); } @Test public void testSimpleMessageLogging() throws Exception { - String message = UUID.randomUUID().toString(); - String loggerName = UUID.randomUUID().toString(); - String threadName = UUID.randomUUID().toString(); - Date date = new Date(1373883196416L); - Logger logger = Logger.getLogger(loggerName); - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - Event event; - - sentryAppender.append(new LoggingEvent(null, logger, date.getTime(), Level.INFO, message, threadName, + final String loggerName = UUID.randomUUID().toString(); + final String message = UUID.randomUUID().toString(); + final String threadName = UUID.randomUUID().toString(); + final Date date = new Date(1373883196416L); + new Expectations() {{ + onInstance(mockLogger).getName(); + result = loggerName; + }}; + + sentryAppender.append(new LoggingEvent(null, mockLogger, date.getTime(), Level.INFO, message, threadName, null, null, null, null)); - verify(mockRaven).runBuilderHelpers(any(EventBuilder.class)); - verify(mockRaven).sendEvent(eventCaptor.capture()); - event = eventCaptor.getValue(); - assertThat(event.getMessage(), is(message)); - assertThat(event.getLogger(), is(loggerName)); - assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); - assertThat(event.getTimestamp(), is(date)); - - assertNoErrors(sentryAppender); + new Verifications() {{ + Event event; + mockRaven.runBuilderHelpers(withAny(new EventBuilder())); + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getMessage(), is(message)); + assertThat(event.getLogger(), is(loggerName)); + assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); + assertThat(event.getTimestamp(), is(date)); + assertDoNotGenerateErrors(); + }}; } - @Override - public void logAnyLevel(String message) { - logger.info(message); + @Test + public void testLevelConversion() throws Exception { + assertLevelConverted(Event.Level.DEBUG, Level.TRACE); + assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); + assertLevelConverted(Event.Level.INFO, Level.INFO); + assertLevelConverted(Event.Level.WARNING, Level.WARN); + assertLevelConverted(Event.Level.ERROR, Level.ERROR); + assertLevelConverted(Event.Level.FATAL, Level.FATAL); } - @Override - public void logAnyLevel(String message, Throwable exception) { - logger.error(message, exception); - } + private void assertLevelConverted(final Event.Level expectedLevel, Level level) throws Exception { + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, level, null, null)); - @Override - public void logAnyLevel(String message, List parameters) { - throw new UnsupportedOperationException(); + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getLevel(), is(expectedLevel)); + assertDoNotGenerateErrors(); + }}; } - @Override - public String getCurrentLoggerName() { - return logger.getName(); + @Test + public void testExceptionLogging() throws Exception { + final Exception exception = new Exception(UUID.randomUUID().toString()); + + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, exception)); + + new Verifications() {{ + Event event; + Throwable throwable; + mockRaven.sendEvent(event = withCapture()); + ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() + .get(ExceptionInterface.EXCEPTION_INTERFACE); + throwable = exceptionInterface.getThrowable(); + assertThat(throwable.getMessage(), is(exception.getMessage())); + assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); + assertDoNotGenerateErrors(); + }}; } - @Override - public String getUnformattedMessage() { - throw new UnsupportedOperationException(); + @Test + public void testMdcAddedToExtra() throws Exception { + final String extraKey = UUID.randomUUID().toString(); + final String extraValue = UUID.randomUUID().toString(); + + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, + null, null, null, Collections.singletonMap(extraKey, extraValue))); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); + assertDoNotGenerateErrors(); + }}; } - @Override @Test - public void testLogLevelConversions() throws Exception { - assertLevelConverted(Event.Level.DEBUG, Level.TRACE); - assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); - assertLevelConverted(Event.Level.INFO, Level.INFO); - assertLevelConverted(Event.Level.WARNING, Level.WARN); - assertLevelConverted(Event.Level.ERROR, Level.ERROR); - assertLevelConverted(Event.Level.FATAL, Level.FATAL); + public void testNdcAddedToExtra() throws Exception { + final String ndcEntries = Joiner.on(' ').join(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()); + + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, + null, ndcEntries, null, null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.LOG4J_NDC, ndcEntries)); + assertDoNotGenerateErrors(); + }}; } - private void assertLevelConverted(Event.Level expectedLevel, Level level) { - logger.log(level, null); - assertLogLevel(expectedLevel); + @Test + public void testSourceUsedAsStacktrace(@Injectable @NonStrict final LocationInfo locationInfo) throws Exception { + final String className = UUID.randomUUID().toString(); + final String methodName = UUID.randomUUID().toString(); + final String fileName = UUID.randomUUID().toString(); + final int line = 42; + new Expectations() {{ + locationInfo.getClassName(); + result = className; + locationInfo.getMethodName(); + result = methodName; + locationInfo.getFileName(); + result = fileName; + locationInfo.getLineNumber(); + result = Integer.toString(line); + setField(locationInfo, "fullInfo", ""); + }}; + + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, + null, null, locationInfo, null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() + .get(StackTraceInterface.STACKTRACE_INTERFACE); + assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); + assertThat(stackTraceInterface.getStackTrace()[0], + is(new StackTraceElement(className, methodName, fileName, line))); + assertDoNotGenerateErrors(); + }}; } - @Override - public void testLogParametrisedMessage() throws Exception { - // Parametrised messages aren't supported + @Test + public void testCulpritWithSource(@Injectable @NonStrict final LocationInfo locationInfo) throws Exception { + final String className = "a"; + final String methodName = "b"; + final String fileName = "c"; + final int line = 42; + new Expectations() {{ + locationInfo.getClassName(); + result = className; + locationInfo.getMethodName(); + result = methodName; + locationInfo.getFileName(); + result = fileName; + locationInfo.getLineNumber(); + result = Integer.toString(line); + setField(locationInfo, "fullInfo", ""); + }}; + + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, + null, null, locationInfo, null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getCulprit(), is("a.b(c:42)")); + assertDoNotGenerateErrors(); + }}; } @Test - public void testThreadNameAddedToExtra() { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + public void testCulpritWithoutSource() throws Exception { + final String loggerName = UUID.randomUUID().toString(); + new Expectations() {{ + onInstance(mockLogger).getName(); + result = loggerName; + }}; + + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getCulprit(), is(loggerName)); + assertDoNotGenerateErrors(); + }}; + } - logger.info("testMessage"); + @Test + public void testConnectionNotClosedIfRavenInstanceIsProvided(@Mocked final Connection connection) throws Exception { + final SentryAppender sentryAppender = new SentryAppender(mockRaven); + new NonStrictExpectations() {{ + mockRaven.getConnection(); + result = connection; + }}; + + sentryAppender.activateOptions(); + sentryAppender.close(); + + new Verifications() {{ + connection.close(); + times = 0; + assertDoNotGenerateErrors(); + }}; + } - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getExtra(), - Matchers.hasEntry(SentryAppender.THREAD_NAME, Thread.currentThread().getName())); + @Test + public void testConnectionClosedIfRavenInstanceProvidedAndForceClose(@Mocked final Connection connection) + throws Exception { + final SentryAppender sentryAppender = new SentryAppender(mockRaven, true); + new NonStrictExpectations() {{ + mockRaven.getConnection(); + result = connection; + }}; + + sentryAppender.activateOptions(); + sentryAppender.close(); + + new Verifications() {{ + connection.close(); + assertDoNotGenerateErrors(); + }}; } @Test - public void testMdcAddedToExtra() { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - String extraKey = UUID.randomUUID().toString(); - Object extraValue = mock(Object.class); + public void testConnectionNotClosedIfRavenInstanceProvidedAndNotForceClose(@Mocked final Connection connection) + throws Exception { + final SentryAppender sentryAppender = new SentryAppender(mockRaven, false); + new NonStrictExpectations() {{ + mockRaven.getConnection(); + result = connection; + }}; + + sentryAppender.activateOptions(); + sentryAppender.close(); + + new Verifications() {{ + connection.close(); + times = 0; + assertDoNotGenerateErrors(); + }}; + } - MDC.put(extraKey, extraValue); + @Test + public void testConnectionClosedIfRavenInstanceNotProvided(@Mocked final Connection connection) throws Exception { + final SentryAppender sentryAppender = new SentryAppender(); + new NonStrictExpectations() { + @Mocked + private final Dsn dsn = null; + @Mocked + private RavenFactory ravenFactory = null; + + { + mockRaven.getConnection(); + result = connection; + RavenFactory.ravenInstance(dsn, anyString); + result = mockRaven; + } + }; - logger.info("testMessage"); + sentryAppender.activateOptions(); + sentryAppender.close(); - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getExtra(), hasEntry(extraKey, extraValue)); + new Verifications() {{ + connection.close(); + assertDoNotGenerateErrors(); + }}; } @Test - @SuppressWarnings("unchecked") - public void testNdcAddedToExtra() { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - String extra = UUID.randomUUID().toString(); - String extra2 = UUID.randomUUID().toString(); - - NDC.push(extra); - logger.info("testMessage"); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getExtra(), hasKey(SentryAppender.LOG4J_NDC)); - assertThat(eventCaptor.getValue().getExtra().get(SentryAppender.LOG4J_NDC), Matchers.is(extra)); + public void testCorrectRavenInstanceUsedIfNotProvided() throws Exception { + final SentryAppender sentryAppender = new SentryAppender(); + + new NonStrictExpectations() { + @Mocked + private final Dsn dsn = null; + @Mocked + private RavenFactory ravenFactory = null; + + { + RavenFactory.ravenInstance(dsn, anyString); + returns(mockRaven); + } + }; - reset(mockRaven); - NDC.push(extra2); - logger.info("testMessage"); + sentryAppender.activateOptions(); + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getExtra(), hasKey(SentryAppender.LOG4J_NDC)); - assertThat(eventCaptor.getValue().getExtra().get(SentryAppender.LOG4J_NDC), - Matchers.is(extra + " " + extra2)); + new Verifications() {{ + onInstance(mockRaven).sendEvent((Event) any); + assertDoNotGenerateErrors(); + }}; } - private void assertNoErrors(SentryAppender sentryAppender) { - verify(sentryAppender.getErrorHandler(), never()).error(anyString()); - verify(sentryAppender.getErrorHandler(), never()).error(anyString(), any(Exception.class), anyInt()); - verify(sentryAppender.getErrorHandler(), never()).error(anyString(), any(Exception.class), anyInt(), any(LoggingEvent.class)); + private void assertDoNotGenerateErrors() throws Exception{ + new Verifications() {{ + mockErrorHandler.error(anyString); + times = 0; + mockErrorHandler.error(anyString, (Exception) any, anyInt); + times = 0; + mockErrorHandler.error(anyString, (Exception) any, anyInt, (LoggingEvent) any); + times = 0; + }}; } } From 16992e0180e4515454bdb9c3585829f84c8e2070 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 19 Jul 2013 13:22:24 -0400 Subject: [PATCH 0790/2152] Move the sentry stub in a dedicated package --- .../java/net/kencochrane/raven/log4j/SentryAppenderIT.java | 2 +- .../java/net/kencochrane/raven/log4j2/SentryAppenderIT.java | 2 +- .../java/net/kencochrane/raven/logback/SentryAppenderIT.java | 2 +- .../test/java/net/kencochrane/raven/jul/SentryHandlerIT.java | 2 +- .../java/net/kencochrane/raven/{ => stub}/SentryStub.java | 2 +- raven/src/test/resources/logging-integration.properties | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) rename raven/src/test/java/net/kencochrane/raven/{ => stub}/SentryStub.java (98%) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java index c444a17c5d2..c2c873c9c26 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java @@ -1,6 +1,6 @@ package net.kencochrane.raven.log4j; -import net.kencochrane.raven.SentryStub; +import net.kencochrane.raven.stub.SentryStub; import org.apache.log4j.Logger; import org.junit.After; import org.junit.Before; diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java index ed1539e29be..c346cdb84c5 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java @@ -1,6 +1,6 @@ package net.kencochrane.raven.log4j2; -import net.kencochrane.raven.SentryStub; +import net.kencochrane.raven.stub.SentryStub; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.After; diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java index 021e0478f85..52f3ed2b34d 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java @@ -1,6 +1,6 @@ package net.kencochrane.raven.logback; -import net.kencochrane.raven.SentryStub; +import net.kencochrane.raven.stub.SentryStub; import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java index 7c757952dcc..61c5279fb05 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java @@ -1,6 +1,6 @@ package net.kencochrane.raven.jul; -import net.kencochrane.raven.SentryStub; +import net.kencochrane.raven.stub.SentryStub; import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/raven/src/test/java/net/kencochrane/raven/SentryStub.java b/raven/src/test/java/net/kencochrane/raven/stub/SentryStub.java similarity index 98% rename from raven/src/test/java/net/kencochrane/raven/SentryStub.java rename to raven/src/test/java/net/kencochrane/raven/stub/SentryStub.java index d3964ba437c..08fbcd7c6ac 100644 --- a/raven/src/test/java/net/kencochrane/raven/SentryStub.java +++ b/raven/src/test/java/net/kencochrane/raven/stub/SentryStub.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven; +package net.kencochrane.raven.stub; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; diff --git a/raven/src/test/resources/logging-integration.properties b/raven/src/test/resources/logging-integration.properties index e500a42eb17..b53686462be 100644 --- a/raven/src/test/resources/logging-integration.properties +++ b/raven/src/test/resources/logging-integration.properties @@ -4,7 +4,7 @@ level=ALL net.kencochrane.raven.handlers=java.util.logging.ConsoleHandler, net.kencochrane.raven.jul.SentryHandler net.kencochrane.raven.level=ALL -net.kencochrane.raven.SentryStub.handlers=java.util.logging.ConsoleHandler -net.kencochrane.raven.SentryStub.level=INFO +net.kencochrane.raven.stub.handlers=java.util.logging.ConsoleHandler +net.kencochrane.raven.stub.level=ALL net.kencochrane.raven.jul.SentryHandler.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false From 4c97956bd26e055e8902c5a775ab30a91671c697 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 19 Jul 2013 13:22:56 -0400 Subject: [PATCH 0791/2152] Fix path to log4j-integration.properties --- raven-log4j/pom.xml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 0459b957dff..9eb0377fe80 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -74,9 +74,7 @@ maven-failsafe-plugin - - ${project.basedir}/src/test/resources/log4j-integration.xml - + log4j-integration.properties From 3d4aaabcf6fd8dcfc2f64fca9ac0679471dab138 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 19 Jul 2013 13:23:20 -0400 Subject: [PATCH 0792/2152] Fix log4j-integration configuration to ignore the stub --- raven-log4j/src/test/resources/log4j-integration.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven-log4j/src/test/resources/log4j-integration.properties b/raven-log4j/src/test/resources/log4j-integration.properties index f3b728893ea..69678e8c7e3 100644 --- a/raven-log4j/src/test/resources/log4j-integration.properties +++ b/raven-log4j/src/test/resources/log4j-integration.properties @@ -1,4 +1,5 @@ -og4j.rootLogger=ALL, ConsoleAppender, SentryAppender +log4j.rootLogger=ALL, ConsoleAppender, SentryAppender +log4j.net.kencochrane.raven.stub=ALL, ConsoleAppender log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender From adaf4fe6e0db2d1f43d08ee7f5af46237037b8d4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 19 Jul 2013 13:24:10 -0400 Subject: [PATCH 0793/2152] Move from junit to testng for integration tests --- .../kencochrane/raven/log4j/SentryAppenderIT.java | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java index c2c873c9c26..8ce41929745 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java @@ -2,24 +2,17 @@ import net.kencochrane.raven.stub.SentryStub; import org.apache.log4j.Logger; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; public class SentryAppenderIT { private static final Logger logger = Logger.getLogger(SentryAppenderIT.class); - private SentryStub sentryStub; + private SentryStub sentryStub = new SentryStub(); - @Before - public void setUp() { - sentryStub = new SentryStub(); - sentryStub.removeEvents(); - } - - @After + @AfterMethod public void tearDown() { sentryStub.removeEvents(); } From dcf0ae111359138a69300e89ee0c55241f5551ac Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 19 Jul 2013 13:25:19 -0400 Subject: [PATCH 0794/2152] Remove dependency on junit --- raven-log4j/pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 9eb0377fe80..3b13e36dddf 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -45,11 +45,6 @@ jmockit test - - junit - junit - test - org.testng testng From e4d8ad60cad74510254e5dd15550f643fc8d93a1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 10 Aug 2013 08:36:18 +0200 Subject: [PATCH 0795/2152] Combine tests on the connection closing --- .../raven/log4j/SentryAppenderCloseTest.java | 33 ---- .../SentryAppenderClosingConnectionTest.java | 143 ++++++++++++++++++ .../raven/log4j/SentryAppenderTest.java | 82 ---------- 3 files changed, 143 insertions(+), 115 deletions(-) delete mode 100644 raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java create mode 100644 raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java deleted file mode 100644 index 0024684d429..00000000000 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.kencochrane.raven.log4j; - -import net.kencochrane.raven.Raven; -import net.kencochrane.raven.connection.Connection; -import org.junit.Test; - -import java.io.IOException; - -import static org.mockito.Mockito.*; - -public class SentryAppenderCloseTest { - - @Test - public void testFailedOpenClose() { - // This checks that even if sentry wasn't setup correctly its appender can still be closed. - SentryAppender appender = new SentryAppender(); - appender.activateOptions(); - appender.close(); - } - - @Test - public void testMultipleClose() throws IOException { - Raven raven = mock(Raven.class); - Connection connection = mock(Connection.class); - when(raven.getConnection()).thenReturn(connection); - - SentryAppender appender = new SentryAppender(raven, true); - appender.close(); - - appender.close(); - verify(connection, times(1)).close(); - } -} diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java new file mode 100644 index 00000000000..ec6b4a2e811 --- /dev/null +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java @@ -0,0 +1,143 @@ +package net.kencochrane.raven.log4j; + +import mockit.*; +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.connection.Connection; +import net.kencochrane.raven.dsn.Dsn; +import org.apache.log4j.spi.ErrorHandler; +import org.apache.log4j.spi.LoggingEvent; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class SentryAppenderClosingConnectionTest { + @Mocked + private Raven mockRaven = null; + @Mocked + private Connection mockConnection = null; + @Injectable + private ErrorHandler mockErrorHandler = null; + + @BeforeMethod + public void setUp() { + new NonStrictExpectations() {{ + mockRaven.getConnection(); + result = mockConnection; + }}; + } + + @Test + public void testNotClosedIfRavenInstanceIsProvided() throws Exception { + final SentryAppender sentryAppender = new SentryAppender(mockRaven); + sentryAppender.setErrorHandler(mockErrorHandler); + + sentryAppender.activateOptions(); + sentryAppender.close(); + + new Verifications() {{ + mockConnection.close(); + times = 0; + }}; + assertDoNotGenerateErrors(); + } + + @Test + public void testClosedIfRavenInstanceProvidedAndForceClose() throws Exception { + final SentryAppender sentryAppender = new SentryAppender(mockRaven, true); + sentryAppender.setErrorHandler(mockErrorHandler); + + sentryAppender.activateOptions(); + sentryAppender.close(); + + new Verifications() {{ + mockConnection.close(); + }}; + assertDoNotGenerateErrors(); + } + + @Test + public void testNotClosedIfRavenInstanceProvidedAndNotForceClose() + throws Exception { + final SentryAppender sentryAppender = new SentryAppender(mockRaven, false); + sentryAppender.setErrorHandler(mockErrorHandler); + + sentryAppender.activateOptions(); + sentryAppender.close(); + + new Verifications() {{ + mockConnection.close(); + times = 0; + }}; + assertDoNotGenerateErrors(); + } + + @Test + public void testClosedIfRavenInstanceNotProvided() throws Exception { + final SentryAppender sentryAppender = new SentryAppender(); + sentryAppender.setErrorHandler(mockErrorHandler); + new Expectations() { + @Mocked + private final Dsn dsn = null; + @Mocked("ravenInstance") + private RavenFactory ravenFactory; + + { + RavenFactory.ravenInstance(withAny(dsn), anyString); + result = mockRaven; + } + }; + + sentryAppender.activateOptions(); + sentryAppender.close(); + + new Verifications() {{ + mockConnection.close(); + }}; + assertDoNotGenerateErrors(); + } + + @Test + public void testCloseDoNotFailIfInitFailed() throws Exception { + // This checks that even if sentry wasn't setup correctly its appender can still be closed. + SentryAppender sentryAppender = new SentryAppender(); + sentryAppender.setErrorHandler(mockErrorHandler); + + sentryAppender.activateOptions(); + sentryAppender.close(); + + new Verifications() {{ + mockErrorHandler.error(anyString); + times = 0; + mockErrorHandler.error(anyString, (Exception) any, anyInt); + times = 1; + mockErrorHandler.error(anyString, (Exception) any, anyInt, (LoggingEvent) any); + times = 0; + }}; + } + + @Test + public void testCloseDoNotFailWhenMultipleCalls() throws Exception { + final SentryAppender sentryAppender = new SentryAppender(mockRaven); + sentryAppender.setErrorHandler(mockErrorHandler); + + sentryAppender.close(); + sentryAppender.close(); + + new Verifications() {{ + onInstance(mockConnection).close(); + times = 0; + }}; + assertDoNotGenerateErrors(); + } + + private void assertDoNotGenerateErrors() throws Exception { + new Verifications() {{ + mockErrorHandler.error(anyString); + times = 0; + mockErrorHandler.error(anyString, (Exception) any, anyInt); + times = 0; + mockErrorHandler.error(anyString, (Exception) any, anyInt, (LoggingEvent) any); + times = 0; + }}; + } +} diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 71dadfdb220..a441f08f886 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -4,7 +4,6 @@ import mockit.*; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; @@ -219,87 +218,6 @@ public void testCulpritWithoutSource() throws Exception { }}; } - @Test - public void testConnectionNotClosedIfRavenInstanceIsProvided(@Mocked final Connection connection) throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven); - new NonStrictExpectations() {{ - mockRaven.getConnection(); - result = connection; - }}; - - sentryAppender.activateOptions(); - sentryAppender.close(); - - new Verifications() {{ - connection.close(); - times = 0; - assertDoNotGenerateErrors(); - }}; - } - - @Test - public void testConnectionClosedIfRavenInstanceProvidedAndForceClose(@Mocked final Connection connection) - throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven, true); - new NonStrictExpectations() {{ - mockRaven.getConnection(); - result = connection; - }}; - - sentryAppender.activateOptions(); - sentryAppender.close(); - - new Verifications() {{ - connection.close(); - assertDoNotGenerateErrors(); - }}; - } - - @Test - public void testConnectionNotClosedIfRavenInstanceProvidedAndNotForceClose(@Mocked final Connection connection) - throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven, false); - new NonStrictExpectations() {{ - mockRaven.getConnection(); - result = connection; - }}; - - sentryAppender.activateOptions(); - sentryAppender.close(); - - new Verifications() {{ - connection.close(); - times = 0; - assertDoNotGenerateErrors(); - }}; - } - - @Test - public void testConnectionClosedIfRavenInstanceNotProvided(@Mocked final Connection connection) throws Exception { - final SentryAppender sentryAppender = new SentryAppender(); - new NonStrictExpectations() { - @Mocked - private final Dsn dsn = null; - @Mocked - private RavenFactory ravenFactory = null; - - { - mockRaven.getConnection(); - result = connection; - RavenFactory.ravenInstance(dsn, anyString); - result = mockRaven; - } - }; - - sentryAppender.activateOptions(); - sentryAppender.close(); - - new Verifications() {{ - connection.close(); - assertDoNotGenerateErrors(); - }}; - } - @Test public void testCorrectRavenInstanceUsedIfNotProvided() throws Exception { final SentryAppender sentryAppender = new SentryAppender(); From 1c5dc1827cdbe14b32c31f7b63720d287165caf7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 10 Aug 2013 08:38:05 +0200 Subject: [PATCH 0796/2152] Do not nest verifications --- .../raven/log4j/SentryAppenderTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index a441f08f886..7176b7fede4 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -63,8 +63,8 @@ public void testSimpleMessageLogging() throws Exception { assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); - assertDoNotGenerateErrors(); }}; + assertDoNotGenerateErrors(); } @Test @@ -84,8 +84,8 @@ private void assertLevelConverted(final Event.Level expectedLevel, Level level) Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getLevel(), is(expectedLevel)); - assertDoNotGenerateErrors(); }}; + assertDoNotGenerateErrors(); } @Test @@ -103,8 +103,8 @@ public void testExceptionLogging() throws Exception { throwable = exceptionInterface.getThrowable(); assertThat(throwable.getMessage(), is(exception.getMessage())); assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); - assertDoNotGenerateErrors(); }}; + assertDoNotGenerateErrors(); } @Test @@ -119,8 +119,8 @@ public void testMdcAddedToExtra() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); - assertDoNotGenerateErrors(); }}; + assertDoNotGenerateErrors(); } @Test @@ -134,8 +134,8 @@ public void testNdcAddedToExtra() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.LOG4J_NDC, ndcEntries)); - assertDoNotGenerateErrors(); }}; + assertDoNotGenerateErrors(); } @Test @@ -167,8 +167,8 @@ public void testSourceUsedAsStacktrace(@Injectable @NonStrict final LocationInfo assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); assertThat(stackTraceInterface.getStackTrace()[0], is(new StackTraceElement(className, methodName, fileName, line))); - assertDoNotGenerateErrors(); }}; + assertDoNotGenerateErrors(); } @Test @@ -196,8 +196,8 @@ public void testCulpritWithSource(@Injectable @NonStrict final LocationInfo loca Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is("a.b(c:42)")); - assertDoNotGenerateErrors(); }}; + assertDoNotGenerateErrors(); } @Test @@ -214,8 +214,8 @@ public void testCulpritWithoutSource() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is(loggerName)); - assertDoNotGenerateErrors(); }}; + assertDoNotGenerateErrors(); } @Test From f634686886a116473ed79272d96a31cf2d448043 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 10 Aug 2013 08:50:50 +0200 Subject: [PATCH 0797/2152] Specify which DSN to use in mocks --- .../raven/log4j/SentryAppenderClosingConnectionTest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java index ec6b4a2e811..0bcdbe28037 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java @@ -76,13 +76,16 @@ public void testClosedIfRavenInstanceNotProvided() throws Exception { final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setErrorHandler(mockErrorHandler); new Expectations() { - @Mocked - private final Dsn dsn = null; + private final String dsnUri = "protocol://public:private@host/1"; + @Mocked("dsnLookup") + private Dsn dsn; @Mocked("ravenInstance") private RavenFactory ravenFactory; { - RavenFactory.ravenInstance(withAny(dsn), anyString); + Dsn.dsnLookup(); + result = dsnUri; + RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); result = mockRaven; } }; From 66f30e8158718bbaf39ced56c861827d78675200 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 10 Aug 2013 08:58:02 +0200 Subject: [PATCH 0798/2152] Ensure that the URI generated by the lookup is used as the DSN --- .../raven/log4j/SentryAppenderTest.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 7176b7fede4..7298dd7a40a 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -222,25 +222,28 @@ public void testCulpritWithoutSource() throws Exception { public void testCorrectRavenInstanceUsedIfNotProvided() throws Exception { final SentryAppender sentryAppender = new SentryAppender(); - new NonStrictExpectations() { - @Mocked - private final Dsn dsn = null; - @Mocked - private RavenFactory ravenFactory = null; + new Expectations() { + private final String dsnUri = "protocol://public:private@host/1"; + @Mocked("dsnLookup") + private Dsn dsn; + @Mocked("ravenInstance") + private RavenFactory ravenFactory; { - RavenFactory.ravenInstance(dsn, anyString); - returns(mockRaven); + Dsn.dsnLookup(); + result = dsnUri; + RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); + result = mockRaven; } }; sentryAppender.activateOptions(); - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); new Verifications() {{ onInstance(mockRaven).sendEvent((Event) any); - assertDoNotGenerateErrors(); }}; + assertDoNotGenerateErrors(); } private void assertDoNotGenerateErrors() throws Exception{ From 60414d1c5820212e68256da5c200f3fd65b74a7f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 10 Aug 2013 11:08:30 +0200 Subject: [PATCH 0799/2152] Prefer injectable to abuse of onInstace --- .../raven/log4j/SentryAppenderClosingConnectionTest.java | 6 +++--- .../net/kencochrane/raven/log4j/SentryAppenderTest.java | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java index 0bcdbe28037..5bff862db7d 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java @@ -11,9 +11,9 @@ import org.testng.annotations.Test; public class SentryAppenderClosingConnectionTest { - @Mocked + @Injectable private Raven mockRaven = null; - @Mocked + @Injectable private Connection mockConnection = null; @Injectable private ErrorHandler mockErrorHandler = null; @@ -127,7 +127,7 @@ public void testCloseDoNotFailWhenMultipleCalls() throws Exception { sentryAppender.close(); new Verifications() {{ - onInstance(mockConnection).close(); + mockConnection.close(); times = 0; }}; assertDoNotGenerateErrors(); diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 7298dd7a40a..5457d49aee8 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -28,7 +28,7 @@ public class SentryAppenderTest { private SentryAppender sentryAppender; - @Mocked + @Injectable private Raven mockRaven = null; @Injectable private Logger mockLogger = null; @@ -48,7 +48,7 @@ public void testSimpleMessageLogging() throws Exception { final String threadName = UUID.randomUUID().toString(); final Date date = new Date(1373883196416L); new Expectations() {{ - onInstance(mockLogger).getName(); + mockLogger.getName(); result = loggerName; }}; @@ -204,7 +204,7 @@ public void testCulpritWithSource(@Injectable @NonStrict final LocationInfo loca public void testCulpritWithoutSource() throws Exception { final String loggerName = UUID.randomUUID().toString(); new Expectations() {{ - onInstance(mockLogger).getName(); + mockLogger.getName(); result = loggerName; }}; @@ -241,7 +241,7 @@ public void testCorrectRavenInstanceUsedIfNotProvided() throws Exception { sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); new Verifications() {{ - onInstance(mockRaven).sendEvent((Event) any); + mockRaven.sendEvent((Event) any); }}; assertDoNotGenerateErrors(); } From 87b4bf912d29c89becee91d1b72773fc016fdfcd Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 10 Aug 2013 11:09:57 +0200 Subject: [PATCH 0800/2152] Add test to check that RavenThread can't append more logs --- .../raven/log4j/SentryAppenderTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 5457d49aee8..6e43b7b6aaf 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -246,6 +246,22 @@ public void testCorrectRavenInstanceUsedIfNotProvided() throws Exception { assertDoNotGenerateErrors(); } + @Test + public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { + try { + Raven.RAVEN_THREAD.set(true); + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); + + new Verifications() {{ + mockRaven.sendEvent((Event) any); + times = 0; + }}; + assertDoNotGenerateErrors(); + } finally { + Raven.RAVEN_THREAD.remove(); + } + } + private void assertDoNotGenerateErrors() throws Exception{ new Verifications() {{ mockErrorHandler.error(anyString); From 9c663566b7f0c8dc2fcff99b4003b501e60fe859 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 10 Aug 2013 12:01:56 +0200 Subject: [PATCH 0801/2152] Remove warnings due to double cast of 'any' --- .../raven/log4j/SentryAppenderClosingConnectionTest.java | 2 +- .../java/net/kencochrane/raven/log4j/SentryAppenderTest.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java index 5bff862db7d..149ca2cee95 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java @@ -139,7 +139,7 @@ private void assertDoNotGenerateErrors() throws Exception { times = 0; mockErrorHandler.error(anyString, (Exception) any, anyInt); times = 0; - mockErrorHandler.error(anyString, (Exception) any, anyInt, (LoggingEvent) any); + mockErrorHandler.error(anyString, (Exception) any, anyInt, withInstanceOf(LoggingEvent.class)); times = 0; }}; } diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 6e43b7b6aaf..b229757b7d2 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -57,7 +57,7 @@ public void testSimpleMessageLogging() throws Exception { new Verifications() {{ Event event; - mockRaven.runBuilderHelpers(withAny(new EventBuilder())); + mockRaven.runBuilderHelpers((EventBuilder) any); mockRaven.sendEvent(event = withCapture()); assertThat(event.getMessage(), is(message)); assertThat(event.getLogger(), is(loggerName)); @@ -268,7 +268,7 @@ private void assertDoNotGenerateErrors() throws Exception{ times = 0; mockErrorHandler.error(anyString, (Exception) any, anyInt); times = 0; - mockErrorHandler.error(anyString, (Exception) any, anyInt, (LoggingEvent) any); + mockErrorHandler.error(anyString, (Exception) any, anyInt, withInstanceOf(LoggingEvent.class)); times = 0; }}; } From 09eed8251922fc77a85998689d24eb53b433b11b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 10 Aug 2013 12:03:53 +0200 Subject: [PATCH 0802/2152] Rewrite the error count check system --- .../raven/log4j/MockUpErrorHandler.java | 29 +++++++++++ .../SentryAppenderClosingConnectionTest.java | 51 +++++++------------ .../raven/log4j/SentryAppenderTest.java | 38 +++++--------- 3 files changed, 59 insertions(+), 59 deletions(-) create mode 100644 raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java new file mode 100644 index 00000000000..c504503d732 --- /dev/null +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java @@ -0,0 +1,29 @@ +package net.kencochrane.raven.log4j; + +import mockit.Mock; +import mockit.MockUp; +import org.apache.log4j.spi.ErrorHandler; +import org.apache.log4j.spi.LoggingEvent; + +public class MockUpErrorHandler extends MockUp { + private int errorCount = 0; + + @Mock + public void error(String message, Exception e, int errorCode) { + errorCount++; + } + + @Mock + public void error(String message) { + errorCount++; + } + + @Mock + public void error(String message, Exception e, int errorCode, LoggingEvent event) { + errorCount++; + } + + public int getErrorCount() { + return errorCount; + } +} diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java index 149ca2cee95..2e6d02f49a1 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java @@ -5,21 +5,22 @@ import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.dsn.Dsn; -import org.apache.log4j.spi.ErrorHandler; -import org.apache.log4j.spi.LoggingEvent; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + public class SentryAppenderClosingConnectionTest { + private MockUpErrorHandler mockUpErrorHandler; @Injectable private Raven mockRaven = null; @Injectable private Connection mockConnection = null; - @Injectable - private ErrorHandler mockErrorHandler = null; @BeforeMethod public void setUp() { + mockUpErrorHandler = new MockUpErrorHandler(); new NonStrictExpectations() {{ mockRaven.getConnection(); result = mockConnection; @@ -29,7 +30,7 @@ public void setUp() { @Test public void testNotClosedIfRavenInstanceIsProvided() throws Exception { final SentryAppender sentryAppender = new SentryAppender(mockRaven); - sentryAppender.setErrorHandler(mockErrorHandler); + sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.activateOptions(); sentryAppender.close(); @@ -38,13 +39,13 @@ public void testNotClosedIfRavenInstanceIsProvided() throws Exception { mockConnection.close(); times = 0; }}; - assertDoNotGenerateErrors(); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test public void testClosedIfRavenInstanceProvidedAndForceClose() throws Exception { final SentryAppender sentryAppender = new SentryAppender(mockRaven, true); - sentryAppender.setErrorHandler(mockErrorHandler); + sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.activateOptions(); sentryAppender.close(); @@ -52,14 +53,14 @@ public void testClosedIfRavenInstanceProvidedAndForceClose() throws Exception { new Verifications() {{ mockConnection.close(); }}; - assertDoNotGenerateErrors(); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test public void testNotClosedIfRavenInstanceProvidedAndNotForceClose() throws Exception { final SentryAppender sentryAppender = new SentryAppender(mockRaven, false); - sentryAppender.setErrorHandler(mockErrorHandler); + sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.activateOptions(); sentryAppender.close(); @@ -68,13 +69,13 @@ public void testNotClosedIfRavenInstanceProvidedAndNotForceClose() mockConnection.close(); times = 0; }}; - assertDoNotGenerateErrors(); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test public void testClosedIfRavenInstanceNotProvided() throws Exception { final SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setErrorHandler(mockErrorHandler); + sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); new Expectations() { private final String dsnUri = "protocol://public:private@host/1"; @Mocked("dsnLookup") @@ -96,32 +97,25 @@ public void testClosedIfRavenInstanceNotProvided() throws Exception { new Verifications() {{ mockConnection.close(); }}; - assertDoNotGenerateErrors(); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test public void testCloseDoNotFailIfInitFailed() throws Exception { // This checks that even if sentry wasn't setup correctly its appender can still be closed. SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setErrorHandler(mockErrorHandler); + sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.activateOptions(); sentryAppender.close(); - new Verifications() {{ - mockErrorHandler.error(anyString); - times = 0; - mockErrorHandler.error(anyString, (Exception) any, anyInt); - times = 1; - mockErrorHandler.error(anyString, (Exception) any, anyInt, (LoggingEvent) any); - times = 0; - }}; + assertThat(mockUpErrorHandler.getErrorCount(), is(1)); } @Test public void testCloseDoNotFailWhenMultipleCalls() throws Exception { final SentryAppender sentryAppender = new SentryAppender(mockRaven); - sentryAppender.setErrorHandler(mockErrorHandler); + sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.close(); sentryAppender.close(); @@ -130,17 +124,6 @@ public void testCloseDoNotFailWhenMultipleCalls() throws Exception { mockConnection.close(); times = 0; }}; - assertDoNotGenerateErrors(); - } - - private void assertDoNotGenerateErrors() throws Exception { - new Verifications() {{ - mockErrorHandler.error(anyString); - times = 0; - mockErrorHandler.error(anyString, (Exception) any, anyInt); - times = 0; - mockErrorHandler.error(anyString, (Exception) any, anyInt, withInstanceOf(LoggingEvent.class)); - times = 0; - }}; + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } } diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index b229757b7d2..85b5e659bfd 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -11,7 +11,6 @@ import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.log4j.Level; import org.apache.log4j.Logger; -import org.apache.log4j.spi.ErrorHandler; import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; import org.hamcrest.Matchers; @@ -28,17 +27,17 @@ public class SentryAppenderTest { private SentryAppender sentryAppender; + private MockUpErrorHandler mockUpErrorHandler; @Injectable private Raven mockRaven = null; @Injectable private Logger mockLogger = null; - @Injectable - private ErrorHandler mockErrorHandler = null; @BeforeMethod public void setUp() { sentryAppender = new SentryAppender(mockRaven); - sentryAppender.setErrorHandler(mockErrorHandler); + mockUpErrorHandler = new MockUpErrorHandler(); + sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); } @Test @@ -64,7 +63,7 @@ public void testSimpleMessageLogging() throws Exception { assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); }}; - assertDoNotGenerateErrors(); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -85,7 +84,7 @@ private void assertLevelConverted(final Event.Level expectedLevel, Level level) mockRaven.sendEvent(event = withCapture()); assertThat(event.getLevel(), is(expectedLevel)); }}; - assertDoNotGenerateErrors(); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -104,7 +103,7 @@ public void testExceptionLogging() throws Exception { assertThat(throwable.getMessage(), is(exception.getMessage())); assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); }}; - assertDoNotGenerateErrors(); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -120,7 +119,7 @@ public void testMdcAddedToExtra() throws Exception { mockRaven.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); }}; - assertDoNotGenerateErrors(); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -135,7 +134,7 @@ public void testNdcAddedToExtra() throws Exception { mockRaven.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.LOG4J_NDC, ndcEntries)); }}; - assertDoNotGenerateErrors(); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -168,7 +167,7 @@ public void testSourceUsedAsStacktrace(@Injectable @NonStrict final LocationInfo assertThat(stackTraceInterface.getStackTrace()[0], is(new StackTraceElement(className, methodName, fileName, line))); }}; - assertDoNotGenerateErrors(); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -197,7 +196,7 @@ public void testCulpritWithSource(@Injectable @NonStrict final LocationInfo loca mockRaven.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is("a.b(c:42)")); }}; - assertDoNotGenerateErrors(); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -215,7 +214,7 @@ public void testCulpritWithoutSource() throws Exception { mockRaven.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is(loggerName)); }}; - assertDoNotGenerateErrors(); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -243,7 +242,7 @@ public void testCorrectRavenInstanceUsedIfNotProvided() throws Exception { new Verifications() {{ mockRaven.sendEvent((Event) any); }}; - assertDoNotGenerateErrors(); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -256,20 +255,9 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { mockRaven.sendEvent((Event) any); times = 0; }}; - assertDoNotGenerateErrors(); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } finally { Raven.RAVEN_THREAD.remove(); } } - - private void assertDoNotGenerateErrors() throws Exception{ - new Verifications() {{ - mockErrorHandler.error(anyString); - times = 0; - mockErrorHandler.error(anyString, (Exception) any, anyInt); - times = 0; - mockErrorHandler.error(anyString, (Exception) any, anyInt, withInstanceOf(LoggingEvent.class)); - times = 0; - }}; - } } From 4dfcab767ec60dc697cd280371cc2846ae404a60 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 10 Aug 2013 12:06:55 +0200 Subject: [PATCH 0803/2152] Add test to ensure that errors are logged when raven fails --- .../raven/log4j/SentryAppenderTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 85b5e659bfd..c52569c2d51 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -260,4 +260,19 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { Raven.RAVEN_THREAD.remove(); } } + + @Test + public void testRavenFailureDoesNotPropagate() throws Exception { + new NonStrictExpectations() {{ + mockRaven.sendEvent((Event) any); + result = new UnsupportedOperationException(); + }}; + + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); + + new Verifications() {{ + mockRaven.sendEvent((Event) any); + }}; + assertThat(mockUpErrorHandler.getErrorCount(), is(1)); + } } From 5dcfbf236d9949b56b4369ad8ae4898930e73103 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 10 Aug 2013 12:16:06 +0200 Subject: [PATCH 0804/2152] Add test to ensure that ravenFactory exceptions do not propagate --- .../raven/log4j/SentryAppenderTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index c52569c2d51..0857a39eaa2 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -275,4 +275,23 @@ public void testRavenFailureDoesNotPropagate() throws Exception { }}; assertThat(mockUpErrorHandler.getErrorCount(), is(1)); } + + @Test + public void testRavenFactoryFailureDoesNotPropagate() throws Exception { + new Expectations() { + @Mocked("ravenInstance") + private RavenFactory ravenFactory; + { + RavenFactory.ravenInstance((Dsn) any, anyString); + result = new UnsupportedOperationException(); + } + }; + SentryAppender sentryAppender = new SentryAppender(); + sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.setDsn("protocol://public:private@host/1"); + + sentryAppender.activateOptions(); + + assertThat(mockUpErrorHandler.getErrorCount(), is(1)); + } } From d82d708ded7c15cbecc27f73e8d6eb7f445b4dde Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 10 Aug 2013 13:35:45 +0200 Subject: [PATCH 0805/2152] Move back to servlet 3.0.1 (JEE6) as 3.1.0 requires JDK7 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1205df3651b..265fa500567 100644 --- a/pom.xml +++ b/pom.xml @@ -108,7 +108,7 @@ 1.7.5 14.0.1 2.2.2 - 3.1.0 + 3.0.1 1.0.13 1.2.17 2.0-beta8 From edf1c0f77b0c712eb8573e8babd06df9d4685fe3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 10 Aug 2013 13:43:46 +0200 Subject: [PATCH 0806/2152] Add testng and jmockit to raven-log4j2 --- raven-log4j2/pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 0dd2d723a00..801ea7c4f8f 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -50,11 +50,21 @@ mockito-core test + + com.googlecode.jmockit + jmockit + test + junit junit test + + org.testng + testng + test + org.hamcrest hamcrest-core From c42e362863a5c453692bfe2411135ed9ba9c0acf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 10 Aug 2013 13:44:44 +0200 Subject: [PATCH 0807/2152] Convert integration tests to testng --- .../net/kencochrane/raven/log4j2/SentryAppenderIT.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java index c346cdb84c5..9166757ecdf 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java @@ -3,9 +3,9 @@ import net.kencochrane.raven.stub.SentryStub; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -14,13 +14,13 @@ public class SentryAppenderIT { private static final Logger logger = LogManager.getLogger(SentryAppenderIT.class); private SentryStub sentryStub; - @Before + @BeforeMethod public void setUp() { sentryStub = new SentryStub(); sentryStub.removeEvents(); } - @After + @AfterMethod public void tearDown() { sentryStub.removeEvents(); } From aa2b77580f52fa2243a1ee2f7cfc79a0480a207c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 10 Aug 2013 14:49:06 +0200 Subject: [PATCH 0808/2152] Rewrite first tests in testng+jmockit --- .../raven/log4j2/MockUpErrorHandler.java | 29 +++++++ .../raven/log4j2/SentryAppenderNewTest.java | 78 +++++++++++++++++++ .../raven/log4j2/SentryAppenderTest.java | 44 ----------- 3 files changed, 107 insertions(+), 44 deletions(-) create mode 100644 raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java create mode 100644 raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java new file mode 100644 index 00000000000..be759c8e256 --- /dev/null +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java @@ -0,0 +1,29 @@ +package net.kencochrane.raven.log4j2; + +import mockit.Mock; +import mockit.MockUp; +import org.apache.logging.log4j.core.ErrorHandler; +import org.apache.logging.log4j.core.LogEvent; + +public class MockUpErrorHandler extends MockUp { + private int errorCount = 0; + + @Mock + public void error(String msg) { + errorCount++; + } + + @Mock + public void error(String msg, Throwable t) { + errorCount++; + } + + @Mock + public void error(String msg, LogEvent event, Throwable t) { + errorCount++; + } + + public int getErrorCount() { + return errorCount; + } +} diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java new file mode 100644 index 00000000000..439731754d3 --- /dev/null +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java @@ -0,0 +1,78 @@ +package net.kencochrane.raven.log4j2; + +import mockit.Expectations; +import mockit.Injectable; +import mockit.Verifications; +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.EventBuilder; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.SimpleMessage; +import org.hamcrest.Matchers; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.Date; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SentryAppenderNewTest { + private SentryAppender sentryAppender; + private MockUpErrorHandler mockUpErrorHandler; + @Injectable + private Raven mockRaven = null; + + + @BeforeMethod + public void setUp() { + sentryAppender = new SentryAppender(mockRaven); + mockUpErrorHandler = new MockUpErrorHandler(); + sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + } + + @Test + public void testSimpleMessageLogging() throws Exception { + final String loggerName = UUID.randomUUID().toString(); + final String message = UUID.randomUUID().toString(); + final String threadName = UUID.randomUUID().toString(); + final Date date = new Date(1373883196416L); + + sentryAppender.append(new Log4jLogEvent(loggerName, null, null, Level.INFO, new SimpleMessage(message), + null, null, null, threadName, null, date.getTime())); + + new Verifications() {{ + Event event; + mockRaven.runBuilderHelpers((EventBuilder) any); + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getMessage(), is(message)); + assertThat(event.getLogger(), is(loggerName)); + assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); + assertThat(event.getTimestamp(), is(date)); + }}; + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + } + + @Test + public void testLevelConversion() throws Exception { + assertLevelConverted(Event.Level.DEBUG, Level.TRACE); + assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); + assertLevelConverted(Event.Level.INFO, Level.INFO); + assertLevelConverted(Event.Level.WARNING, Level.WARN); + assertLevelConverted(Event.Level.ERROR, Level.ERROR); + assertLevelConverted(Event.Level.FATAL, Level.FATAL); + } + + private void assertLevelConverted(final Event.Level expectedLevel, Level level) throws Exception { + sentryAppender.append(new Log4jLogEvent(null, null, null, level, new SimpleMessage(""), null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getLevel(), is(expectedLevel)); + }}; + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + } +} diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 05a6b9c6db4..7b44009782c 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -71,50 +71,6 @@ public Void answer(InvocationOnMock invocation) throws Throwable { doAnswer(answer).when(mockErrorHandler).error(anyString(), any(LogEvent.class), any(Throwable.class)); } - @Test - public void testSimpleMessageLogging() throws Exception { - String message = UUID.randomUUID().toString(); - String loggerName = UUID.randomUUID().toString(); - String threadName = UUID.randomUUID().toString(); - Date date = new Date(1373883196416L); - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - Event event; - - sentryAppender.append(new Log4jLogEvent(loggerName, null, null, Level.INFO, new SimpleMessage(message), - null, null, null, threadName, null, date.getTime())); - - verify(mockRaven).runBuilderHelpers(any(EventBuilder.class)); - verify(mockRaven).sendEvent(eventCaptor.capture()); - event = eventCaptor.getValue(); - assertThat(event.getMessage(), is(message)); - assertThat(event.getLogger(), is(loggerName)); - assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); - assertThat(event.getTimestamp(), is(date)); - - assertNoErrors(); - } - - @Test - public void testLogLevelConversions() throws Exception { - assertLevelConverted(Event.Level.DEBUG, Level.TRACE); - assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); - assertLevelConverted(Event.Level.INFO, Level.INFO); - assertLevelConverted(Event.Level.WARNING, Level.WARN); - assertLevelConverted(Event.Level.ERROR, Level.ERROR); - assertLevelConverted(Event.Level.FATAL, Level.FATAL); - } - - private void assertLevelConverted(Event.Level expectedLevel, Level level) { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - sentryAppender.append(new Log4jLogEvent(null, null, null, level, new SimpleMessage(""), null)); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getLevel(), is(expectedLevel)); - assertNoErrors(); - reset(mockRaven); - } - @Test public void testExceptionLogging() throws Exception { Exception exception = new Exception(); From 5e87d766f023baba28c850058fa4e85f686819f0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 10 Aug 2013 14:50:55 +0200 Subject: [PATCH 0809/2152] move testExceptionLogging --- .../raven/log4j2/SentryAppenderNewTest.java | 22 +++++++++++++++-- .../raven/log4j2/SentryAppenderTest.java | 24 ++++--------------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java index 439731754d3..eaf9f406ce5 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java @@ -1,11 +1,11 @@ package net.kencochrane.raven.log4j2; -import mockit.Expectations; import mockit.Injectable; import mockit.Verifications; import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.SimpleMessage; @@ -25,7 +25,6 @@ public class SentryAppenderNewTest { @Injectable private Raven mockRaven = null; - @BeforeMethod public void setUp() { sentryAppender = new SentryAppender(mockRaven); @@ -75,4 +74,23 @@ private void assertLevelConverted(final Event.Level expectedLevel, Level level) }}; assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } + + @Test + public void testExceptionLogging() throws Exception { + final Exception exception = new Exception(UUID.randomUUID().toString()); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.ERROR, new SimpleMessage(""), exception)); + + new Verifications() {{ + Event event; + Throwable throwable; + mockRaven.sendEvent(event = withCapture()); + ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() + .get(ExceptionInterface.EXCEPTION_INTERFACE); + throwable = exceptionInterface.getThrowable(); + assertThat(throwable.getMessage(), is(exception.getMessage())); + assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); + }}; + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + } } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 7b44009782c..891f501fc51 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -4,8 +4,6 @@ import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.EventBuilder; -import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.logging.log4j.Level; @@ -28,7 +26,10 @@ import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -71,23 +72,6 @@ public Void answer(InvocationOnMock invocation) throws Throwable { doAnswer(answer).when(mockErrorHandler).error(anyString(), any(LogEvent.class), any(Throwable.class)); } - @Test - public void testExceptionLogging() throws Exception { - Exception exception = new Exception(); - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.ERROR, new SimpleMessage(""), exception)); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - ExceptionInterface exceptionInterface = (ExceptionInterface) eventCaptor.getValue().getSentryInterfaces() - .get(ExceptionInterface.EXCEPTION_INTERFACE); - Throwable capturedException = exceptionInterface.getThrowable(); - - assertThat(capturedException.getMessage(), is(exception.getMessage())); - assertThat(capturedException.getStackTrace(), is(exception.getStackTrace())); - assertNoErrors(); - } - @Test public void testLogParametrisedMessage() throws Exception { String messagePattern = "Formatted message {} {} {}"; From 346cce7dc6432a10e3ca514a1e4e132b4cee5e89 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 10 Aug 2013 20:40:23 +0200 Subject: [PATCH 0810/2152] Move assertions in verification --- .../net/kencochrane/raven/log4j2/SentryAppenderNewTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java index eaf9f406ce5..66c7bad780e 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java @@ -50,8 +50,8 @@ public void testSimpleMessageLogging() throws Exception { assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -71,8 +71,8 @@ private void assertLevelConverted(final Event.Level expectedLevel, Level level) Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getLevel(), is(expectedLevel)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -90,7 +90,7 @@ public void testExceptionLogging() throws Exception { throwable = exceptionInterface.getThrowable(); assertThat(throwable.getMessage(), is(exception.getMessage())); assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } } From 485c540a480b285b17a809e026180d51ead09c50 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 10 Aug 2013 20:40:52 +0200 Subject: [PATCH 0811/2152] Convert testLogParametrisedMessage to testng --- .../raven/log4j2/SentryAppenderNewTest.java | 25 +++++++++++++++++++ .../raven/log4j2/SentryAppenderTest.java | 20 --------------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java index 66c7bad780e..f0f1f20e740 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java @@ -6,13 +6,16 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.MessageInterface; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.FormattedMessage; import org.apache.logging.log4j.message.SimpleMessage; import org.hamcrest.Matchers; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import java.util.Arrays; import java.util.Date; import java.util.UUID; @@ -93,4 +96,26 @@ public void testExceptionLogging() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } + + @Test + public void testLogParametrisedMessage() throws Exception { + final String messagePattern = "Formatted message {} {} {}"; + final Object[] parameters = {"first parameter", new Object[0], null}; + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, + new FormattedMessage(messagePattern, parameters), null)); + + new Verifications(){{ + Event event; + mockRaven.sendEvent(event = withCapture()); + + MessageInterface messageInterface = (MessageInterface) event.getSentryInterfaces() + .get(MessageInterface.MESSAGE_INTERFACE); + assertThat(event.getMessage(), is("Formatted message first parameter [] null")); + assertThat(messageInterface.getMessage(), is(messagePattern)); + assertThat(messageInterface.getParameters(), + is(Arrays.asList(parameters[0].toString(), parameters[1].toString(), null))); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; + } } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 891f501fc51..3220317c35e 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -72,26 +72,6 @@ public Void answer(InvocationOnMock invocation) throws Throwable { doAnswer(answer).when(mockErrorHandler).error(anyString(), any(LogEvent.class), any(Throwable.class)); } - @Test - public void testLogParametrisedMessage() throws Exception { - String messagePattern = "Formatted message {} {} {}"; - Object[] parameters = {"first parameter", new Object[0], null}; - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, - new FormattedMessage(messagePattern, parameters), null)); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - MessageInterface messageInterface = (MessageInterface) eventCaptor.getValue().getSentryInterfaces() - .get(MessageInterface.MESSAGE_INTERFACE); - - assertThat(eventCaptor.getValue().getMessage(), is("Formatted message first parameter [] null")); - assertThat(messageInterface.getMessage(), is(messagePattern)); - assertThat(messageInterface.getParameters(), - is(Arrays.asList(parameters[0].toString(), parameters[1].toString(), null))); - assertNoErrors(); - } - @Test public void testMarkerAddedToTag() throws Exception { String markerName = UUID.randomUUID().toString(); From 6b69d888ff632a200936e76ba6435d6c16c65fed Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 10 Aug 2013 20:42:59 +0200 Subject: [PATCH 0812/2152] Move assertions in Verifications blocs --- .../raven/log4j/SentryAppenderTest.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 0857a39eaa2..40ffbfbaef2 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -62,8 +62,8 @@ public void testSimpleMessageLogging() throws Exception { assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -83,8 +83,8 @@ private void assertLevelConverted(final Event.Level expectedLevel, Level level) Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getLevel(), is(expectedLevel)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -102,8 +102,8 @@ public void testExceptionLogging() throws Exception { throwable = exceptionInterface.getThrowable(); assertThat(throwable.getMessage(), is(exception.getMessage())); assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -118,8 +118,8 @@ public void testMdcAddedToExtra() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -133,8 +133,8 @@ public void testNdcAddedToExtra() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.LOG4J_NDC, ndcEntries)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -166,8 +166,8 @@ public void testSourceUsedAsStacktrace(@Injectable @NonStrict final LocationInfo assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); assertThat(stackTraceInterface.getStackTrace()[0], is(new StackTraceElement(className, methodName, fileName, line))); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -195,8 +195,8 @@ public void testCulpritWithSource(@Injectable @NonStrict final LocationInfo loca Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is("a.b(c:42)")); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -213,8 +213,8 @@ public void testCulpritWithoutSource() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is(loggerName)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -241,8 +241,8 @@ public void testCorrectRavenInstanceUsedIfNotProvided() throws Exception { new Verifications() {{ mockRaven.sendEvent((Event) any); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -254,8 +254,8 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { new Verifications() {{ mockRaven.sendEvent((Event) any); times = 0; + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } finally { Raven.RAVEN_THREAD.remove(); } @@ -272,8 +272,8 @@ public void testRavenFailureDoesNotPropagate() throws Exception { new Verifications() {{ mockRaven.sendEvent((Event) any); + assertThat(mockUpErrorHandler.getErrorCount(), is(1)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(1)); } @Test @@ -281,6 +281,7 @@ public void testRavenFactoryFailureDoesNotPropagate() throws Exception { new Expectations() { @Mocked("ravenInstance") private RavenFactory ravenFactory; + { RavenFactory.ravenInstance((Dsn) any, anyString); result = new UnsupportedOperationException(); From ba497133356bd0628f04aaf3d45fa27d35994d28 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 12 Aug 2013 22:36:47 +0200 Subject: [PATCH 0813/2152] Move mockito mocks to jmockit mocks --- .../raven/log4j2/SentryAppenderNewTest.java | 111 +++++++++++++++++- .../raven/log4j2/SentryAppenderTest.java | 104 ---------------- 2 files changed, 107 insertions(+), 108 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java index f0f1f20e740..f256b0b9f44 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java @@ -7,20 +7,22 @@ import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.FormattedMessage; import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.spi.DefaultThreadContextStack; import org.hamcrest.Matchers; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import java.util.Arrays; -import java.util.Date; -import java.util.UUID; +import java.util.*; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; public class SentryAppenderNewTest { private SentryAppender sentryAppender; @@ -118,4 +120,105 @@ public void testLogParametrisedMessage() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } + + @Test + public void testMarkerAddedToTag() throws Exception { + final String markerName = UUID.randomUUID().toString(); + + sentryAppender.append(new Log4jLogEvent(null, MarkerManager.getMarker(markerName), null, Level.INFO, + new SimpleMessage(""), null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getTags(), Matchers.hasEntry(SentryAppender.LOG4J_MARKER, markerName)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; + } + + @Test + public void testMdcAddedToExtra() throws Exception { + final String extraKey = UUID.randomUUID().toString(); + final String extraValue = UUID.randomUUID().toString(); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, + Collections.singletonMap(extraKey, extraValue), null, null, null, 0)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; + } + + @Test + @SuppressWarnings("unchecked") + public void testNdcAddedToExtra() throws Exception { + final ThreadContext.ContextStack contextStack = new DefaultThreadContextStack(true); + contextStack.push(UUID.randomUUID().toString()); + contextStack.push(UUID.randomUUID().toString()); + contextStack.push(UUID.randomUUID().toString()); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, + contextStack, null, null, 0)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat((List) event.getExtra().get(SentryAppender.LOG4J_NDC), equalTo(contextStack.asList())); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; + } + + @Test + public void testSourceUsedAsStacktrace() throws Exception { + final StackTraceElement location = new StackTraceElement(UUID.randomUUID().toString(), + UUID.randomUUID().toString(), UUID.randomUUID().toString(), 42); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, null, + null, location, 0)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() + .get(StackTraceInterface.STACKTRACE_INTERFACE); + assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); + assertThat(stackTraceInterface.getStackTrace()[0], is(location)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + + }}; + } + + @Test + public void testCulpritWithSource() throws Exception { + final StackTraceElement location = new StackTraceElement("a", "b", "c", 42); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, null, + null, location, 0)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getCulprit(), is("a.b(c:42)")); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + + }}; + } + + @Test + public void testCulpritWithoutSource() throws Exception { + final String loggerName = UUID.randomUUID().toString(); + + sentryAppender.append(new Log4jLogEvent(loggerName, null, null, Level.INFO, new SimpleMessage(""), null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getCulprit(), is(loggerName)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + + }}; + } } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 3220317c35e..12ed227b01e 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -4,35 +4,20 @@ import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.interfaces.MessageInterface; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.MarkerManager; -import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.DefaultErrorHandler; import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.message.FormattedMessage; import org.apache.logging.log4j.message.SimpleMessage; -import org.apache.logging.log4j.spi.DefaultThreadContextStack; -import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.UUID; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; @@ -72,95 +57,6 @@ public Void answer(InvocationOnMock invocation) throws Throwable { doAnswer(answer).when(mockErrorHandler).error(anyString(), any(LogEvent.class), any(Throwable.class)); } - @Test - public void testMarkerAddedToTag() throws Exception { - String markerName = UUID.randomUUID().toString(); - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - sentryAppender.append(new Log4jLogEvent(null, MarkerManager.getMarker(markerName), null, Level.INFO, - new SimpleMessage(""), null)); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getTags(), - Matchers.hasEntry(SentryAppender.LOG4J_MARKER, markerName)); - assertNoErrors(); - } - - @Test - public void testMdcAddedToExtra() throws Exception { - String extraKey = UUID.randomUUID().toString(); - String extraValue = UUID.randomUUID().toString(); - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, - Collections.singletonMap(extraKey, extraValue), null, null, null, 0)); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getExtra(), Matchers.hasEntry(extraKey, extraValue)); - assertNoErrors(); - } - - @Test - @SuppressWarnings("unchecked") - public void testNdcAddedToExtra() throws Exception { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - ThreadContext.ContextStack contextStack = new DefaultThreadContextStack(true); - - contextStack.push(UUID.randomUUID().toString()); - contextStack.push(UUID.randomUUID().toString()); - contextStack.push(UUID.randomUUID().toString()); - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, - contextStack, null, null, 0)); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat((List) eventCaptor.getValue().getExtra().get(SentryAppender.LOG4J_NDC), - equalTo(contextStack.asList())); - assertNoErrors(); - } - - @Test - public void testSourceUsedAsStacktrace() throws Exception { - StackTraceElement location = new StackTraceElement(UUID.randomUUID().toString(), - UUID.randomUUID().toString(), - UUID.randomUUID().toString(), 42); - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, null, - null, location, 0)); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - StackTraceInterface stackTraceInterface = (StackTraceInterface) eventCaptor.getValue().getSentryInterfaces() - .get(StackTraceInterface.STACKTRACE_INTERFACE); - assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); - assertThat(stackTraceInterface.getStackTrace()[0], is(location)); - assertNoErrors(); - } - - @Test - public void testCulpritWithSource() throws Exception { - StackTraceElement location = new StackTraceElement("a", "b", "c", 42); - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, null, - null, location, 0)); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getCulprit(), is("a.b(c:42)")); - assertNoErrors(); - } - - @Test - public void testCulpritWithoutSource() throws Exception { - String loggerName = UUID.randomUUID().toString(); - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - sentryAppender.append(new Log4jLogEvent(loggerName, null, null, Level.INFO, new SimpleMessage(""), null)); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getCulprit(), is(loggerName)); - assertNoErrors(); - } - @Test public void testClose() throws Exception { sentryAppender.stop(); From 8d02dd053eb334ac43434af2276cdede9233ce18 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 13 Aug 2013 09:11:19 +0200 Subject: [PATCH 0814/2152] Replace JUnit with testNg --- pom.xml | 11 -------- raven-log4j2/pom.xml | 5 ---- .../raven/log4j2/SentryAppenderTest.java | 11 ++++---- raven-logback/pom.xml | 4 +-- .../raven/logback/SentryAppenderIT.java | 12 +++------ .../raven/logback/SentryAppenderTest.java | 11 ++++---- raven/pom.xml | 4 +-- .../kencochrane/raven/AbstractLoggerTest.java | 12 ++++++--- .../java/net/kencochrane/raven/RavenTest.java | 13 +++++----- .../raven/connection/AsyncConnectionTest.java | 11 ++++---- .../raven/connection/HttpConnectionTest.java | 12 ++++----- .../raven/connection/UdpConnectionTest.java | 11 ++++---- .../net/kencochrane/raven/dsn/DsnTest.java | 25 +++++++++---------- .../raven/event/EventBuilderTest.java | 16 ++++++------ .../helper/HttpEventBuilderHelperTest.java | 11 ++++---- .../interfaces/StackTraceInterfaceTest.java | 2 +- .../raven/jul/SentryHandlerIT.java | 10 ++++---- .../raven/jul/SentryHandlerTest.java | 7 +++--- .../json/AbstractInterfaceBindingTest.java | 4 +-- .../json/ExceptionInterfaceBindingTest.java | 11 ++++---- .../json/MessageInterfaceBindingTest.java | 11 ++++---- .../json/StackTraceInterfaceBindingTest.java | 11 ++++---- 22 files changed, 98 insertions(+), 127 deletions(-) diff --git a/pom.xml b/pom.xml index 265fa500567..0d01651a1f6 100644 --- a/pom.xml +++ b/pom.xml @@ -114,7 +114,6 @@ 2.0-beta8 1.9.5 1.3 - 4.11 6.8.5 1.3 @@ -208,21 +207,11 @@ jmockit ${jmockit.version} - - junit - junit - ${junit.version} - org.testng testng ${testng.version} - - testng - junit - ${junit.version} - org.hamcrest hamcrest-core diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 801ea7c4f8f..2dea9cf4d11 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -55,11 +55,6 @@ jmockit test - - junit - junit - test - org.testng testng diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 12ed227b01e..7de371a6466 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -9,19 +9,17 @@ import org.apache.logging.log4j.core.appender.DefaultErrorHandler; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.SimpleMessage; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; -import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; -@RunWith(MockitoJUnitRunner.class) public class SentryAppenderTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Raven mockRaven; @@ -31,8 +29,9 @@ public class SentryAppenderTest { private DefaultErrorHandler mockErrorHandler; private SentryAppender sentryAppender; - @Before + @BeforeMethod public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); sentryAppender = new SentryAppender(mockRaven); setMockErrorHandlerOnAppender(sentryAppender); diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index e46abc7afd8..c86d27d4e60 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -46,8 +46,8 @@ test - junit - junit + org.testng + testng test diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java index 52f3ed2b34d..037a24754b7 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java @@ -1,11 +1,10 @@ package net.kencochrane.raven.logback; import net.kencochrane.raven.stub.SentryStub; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -14,17 +13,12 @@ public class SentryAppenderIT { private static final Logger logger = LoggerFactory.getLogger(SentryAppenderIT.class); private SentryStub sentryStub; - @Before + @BeforeMethod public void setUp() { sentryStub = new SentryStub(); sentryStub.removeEvents(); } - @After - public void tearDown() { - sentryStub.removeEvents(); - } - @Test public void testInfoLog() { assertThat(sentryStub.getEventCount(), is(0)); diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java index 63fc8206519..95a718cd918 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -15,16 +15,15 @@ import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.hamcrest.Matchers; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.MockitoAnnotations; import org.slf4j.Marker; import org.slf4j.MarkerFactory; import org.slf4j.helpers.MessageFormatter; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import java.util.*; @@ -33,7 +32,6 @@ import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; -@RunWith(MockitoJUnitRunner.class) public class SentryAppenderTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Raven mockRaven; @@ -41,8 +39,9 @@ public class SentryAppenderTest { private RavenFactory mockRavenFactory; private SentryAppender sentryAppender; - @Before + @BeforeMethod public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); sentryAppender = new SentryAppender(mockRaven); setMockContextOnAppender(sentryAppender); diff --git a/raven/pom.xml b/raven/pom.xml index 86860b8f7a7..4645b5d35a2 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -49,8 +49,8 @@ test - junit - junit + org.testng + testng test diff --git a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java index 62dda00783d..58e09304135 100644 --- a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java @@ -4,11 +4,11 @@ import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.SentryInterface; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import java.util.Arrays; import java.util.List; @@ -20,11 +20,15 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; -@RunWith(MockitoJUnitRunner.class) public abstract class AbstractLoggerTest { @Mock protected Raven mockRaven; + @BeforeMethod + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + @Test public void testSimpleMessageLogging() throws Exception { ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index 0acd6f90fc1..ec0fe62d548 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -5,12 +5,11 @@ import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.helper.EventBuilderHelper; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import java.util.UUID; @@ -18,7 +17,6 @@ import static org.hamcrest.Matchers.*; import static org.mockito.Mockito.*; -@RunWith(MockitoJUnitRunner.class) public class RavenTest { private Raven raven; @Mock @@ -28,8 +26,9 @@ public class RavenTest { @Mock private Event mockEvent; - @Before + @BeforeMethod public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); raven = new Raven(); raven.setConnection(mockConnection); } @@ -86,7 +85,7 @@ public void testAddRemoveBuilderHelpers() throws Exception { assertThat(raven.getBuilderHelpers(), not(contains(mockBuilderHelper))); } - @Test(expected = UnsupportedOperationException.class) + @Test(expectedExceptions = UnsupportedOperationException.class) public void testCantModifyBuilderHelpersDirectly() throws Exception { raven.getBuilderHelpers().add(mockBuilderHelper); } diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index 155606ea8de..32b6e14e4ff 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -2,13 +2,12 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; -import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -17,7 +16,6 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; -@RunWith(MockitoJUnitRunner.class) public class AsyncConnectionTest { private AsyncConnection asyncConnection; @Mock @@ -25,8 +23,9 @@ public class AsyncConnectionTest { @Mock private ExecutorService mockExecutorService; - @Before + @BeforeMethod public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); asyncConnection = new AsyncConnection(mockConnection); asyncConnection.setExecutorService(mockExecutorService); } diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index 959543a16f2..53eb36c2f5f 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -7,14 +7,12 @@ import net.kencochrane.raven.marshaller.Marshaller; import org.hamcrest.CustomTypeSafeMatcher; import org.hamcrest.Matcher; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; @@ -36,7 +34,6 @@ import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; -@RunWith(MockitoJUnitRunner.class) public class HttpConnectionTest { private HttpConnection httpConnection; private String publicKey = UUID.randomUUID().toString(); @@ -46,8 +43,9 @@ public class HttpConnectionTest { @Mock private Marshaller mockMarshaller; - @Before + @BeforeMethod public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); URLStreamHandler stubUrlHandler = new URLStreamHandler() { @Override protected URLConnection openConnection(URL u) throws IOException { diff --git a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java index f5b9496964b..b3a6d47c722 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java @@ -3,11 +3,10 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.marshaller.Marshaller; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import java.io.OutputStream; @@ -15,14 +14,14 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; -@RunWith(MockitoJUnitRunner.class) public class UdpConnectionTest { private UdpConnection udpConnection; @Mock private Marshaller mockMarshaller; - @Before + @BeforeMethod public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); udpConnection = new UdpConnection("", "", ""); udpConnection.setMarshaller(mockMarshaller); } diff --git a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java index f8108b4a656..663557c0cff 100644 --- a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java @@ -1,10 +1,9 @@ package net.kencochrane.raven.dsn; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import javax.naming.Context; import java.lang.reflect.Field; @@ -16,18 +15,18 @@ import static org.hamcrest.Matchers.*; import static org.mockito.Mockito.when; -@RunWith(MockitoJUnitRunner.class) public class DsnTest { @Mock private Context mockContext; - @Before + @BeforeMethod public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); System.setProperty("java.naming.factory.initial", InitialContextMockFactory.class.getName()); InitialContextMockFactory.context = mockContext; } - @Test(expected = InvalidDsnException.class) + @Test(expectedExceptions = InvalidDsnException.class) public void testEmptyDsnInvalid() throws Exception { new Dsn(""); } @@ -126,22 +125,22 @@ private void removeEnv(String key) throws Exception { } } - @Test(expected = InvalidDsnException.class) + @Test(expectedExceptions = InvalidDsnException.class) public void testMissingSecretKeyInvalid() throws Exception { new Dsn("http://publicKey:@host/9"); } - @Test(expected = InvalidDsnException.class) + @Test(expectedExceptions = InvalidDsnException.class) public void testMissingHostInvalid() throws Exception { new Dsn("http://publicKey:secretKey@/9"); } - @Test(expected = InvalidDsnException.class) + @Test(expectedExceptions = InvalidDsnException.class) public void testMissingPathInvalid() throws Exception { new Dsn("http://publicKey:secretKey@host"); } - @Test(expected = InvalidDsnException.class) + @Test(expectedExceptions = InvalidDsnException.class) public void testMissingProjectIdInvalid() throws Exception { new Dsn("http://publicKey:secretKey@host/"); } @@ -164,14 +163,14 @@ public void testAdvancedDsnValid() throws Exception { assertThat(dsn.getOptions().get("option2"), is("valueOption2")); } - @Test(expected = UnsupportedOperationException.class) + @Test(expectedExceptions = UnsupportedOperationException.class) public void testOptionsImmutable() throws Exception { Dsn dsn = new Dsn("http://publicKey:secretKey@host/9"); dsn.getOptions().put("test", "test"); } - @Test(expected = UnsupportedOperationException.class) + @Test(expectedExceptions = UnsupportedOperationException.class) public void testProtocolSettingsImmutable() throws Exception { Dsn dsn = new Dsn("http://publicKey:secretKey@host/9"); diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index ce95bf8eac0..6774ed94d2e 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -1,8 +1,8 @@ package net.kencochrane.raven.event; import net.kencochrane.raven.event.interfaces.SentryInterface; -import org.junit.Before; -import org.junit.Test; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import java.net.InetAddress; import java.util.Date; @@ -17,7 +17,7 @@ public class EventBuilderTest { private EventBuilder eventBuilder; - @Before + @BeforeMethod public void setUp() throws Exception { eventBuilder = new EventBuilder(); } @@ -103,7 +103,7 @@ public void testChecksumGeneration() throws Exception { assertThat(firstChecksumEvent.getChecksum(), is(secondChecksumEvent.getChecksum())); } - @Test(expected = UnsupportedOperationException.class) + @Test(expectedExceptions = UnsupportedOperationException.class) public void testTagsAreImmutable() throws Exception { String tagKey = UUID.randomUUID().toString(); String tagValue = UUID.randomUUID().toString(); @@ -116,7 +116,7 @@ public void testTagsAreImmutable() throws Exception { tags.put(UUID.randomUUID().toString(), UUID.randomUUID().toString()); } - @Test(expected = UnsupportedOperationException.class) + @Test(expectedExceptions = UnsupportedOperationException.class) public void testExtrasAreImmutable() throws Exception { String extraKey = UUID.randomUUID().toString(); Object extraValue = mock(Object.class); @@ -129,7 +129,7 @@ public void testExtrasAreImmutable() throws Exception { extra.put(UUID.randomUUID().toString(), mock(Object.class)); } - @Test(expected = UnsupportedOperationException.class) + @Test(expectedExceptions = UnsupportedOperationException.class) public void testSentryInterfacesAreImmutable() throws Exception { SentryInterface sentryInterface = mock(SentryInterface.class); when(sentryInterface.getInterfaceName()).thenReturn(UUID.randomUUID().toString()); @@ -145,13 +145,13 @@ public void testSentryInterfacesAreImmutable() throws Exception { sentryInterfaces.put(UUID.randomUUID().toString(), null); } - @Test(expected = IllegalStateException.class) + @Test(expectedExceptions = IllegalStateException.class) public void testBuildCanBeCalledOnlyOnce() throws Exception { eventBuilder.build(); eventBuilder.build(); } - @Test(expected = IllegalArgumentException.class) + @Test(expectedExceptions = IllegalArgumentException.class) public void testNoUuidFails() throws Exception { new EventBuilder(null); } diff --git a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java index 31c4f227613..ef5247390a1 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java @@ -3,12 +3,11 @@ import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.SentryInterface; import net.kencochrane.raven.servlet.RavenServletRequestListener; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import javax.servlet.ServletRequestEvent; import javax.servlet.http.HttpServletRequest; @@ -18,7 +17,6 @@ import static org.hamcrest.Matchers.notNullValue; import static org.mockito.Mockito.*; -@RunWith(MockitoJUnitRunner.class) public class HttpEventBuilderHelperTest { private HttpEventBuilderHelper httpEventBuilderHelper; @Mock @@ -30,8 +28,9 @@ private static void simulateRequest() { new RavenServletRequestListener().requestInitialized(servletRequestEvent); } - @Before + @BeforeMethod public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); httpEventBuilderHelper = new HttpEventBuilderHelper(); } diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/StackTraceInterfaceTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/StackTraceInterfaceTest.java index fc81e456a2b..4bbacb92ce7 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/interfaces/StackTraceInterfaceTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/StackTraceInterfaceTest.java @@ -1,7 +1,7 @@ package net.kencochrane.raven.event.interfaces; -import org.junit.Test; +import org.testng.annotations.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java index 61c5279fb05..9db03a15a8d 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java @@ -1,9 +1,9 @@ package net.kencochrane.raven.jul; import net.kencochrane.raven.stub.SentryStub; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import java.util.logging.Level; import java.util.logging.Logger; @@ -15,13 +15,13 @@ public class SentryHandlerIT { private static final Logger logger = Logger.getLogger(SentryHandlerIT.class.getName()); private SentryStub sentryStub; - @Before + @BeforeMethod public void setUp() { sentryStub = new SentryStub(); sentryStub.removeEvents(); } - @After + @AfterMethod public void tearDown() { sentryStub.removeEvents(); } diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java index 7ce6dee6347..cba99a73ceb 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java @@ -3,8 +3,8 @@ import net.kencochrane.raven.AbstractLoggerTest; import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.event.Event; -import org.junit.Before; -import org.junit.Test; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import java.util.List; import java.util.logging.Handler; @@ -16,8 +16,9 @@ public class SentryHandlerTest extends AbstractLoggerTest { private Logger logger; - @Before + @BeforeMethod public void setUp() throws Exception { + super.setUp(); logger = Logger.getAnonymousLogger(); logger.setUseParentHandlers(false); logger.setLevel(Level.ALL); diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractInterfaceBindingTest.java index 0e1024ac373..36fff587028 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractInterfaceBindingTest.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.Before; +import org.testng.annotations.BeforeMethod; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -14,7 +14,7 @@ public abstract class AbstractInterfaceBindingTest { private ObjectMapper mapper; private ByteArrayOutputStream jsonContentStream; - @Before + @BeforeMethod protected void setUp() throws Exception { jsonFactory = new JsonFactory(); mapper = new ObjectMapper(); diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index d0cccba269d..abac09fa2ef 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -5,13 +5,12 @@ import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.ImmutableThrowable; import net.kencochrane.raven.event.interfaces.StackTraceInterface; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; -import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import java.util.UUID; @@ -21,7 +20,6 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; -@RunWith(MockitoJUnitRunner.class) public class ExceptionInterfaceBindingTest extends AbstractInterfaceBindingTest { private ExceptionInterfaceBinding interfaceBinding; @Mock @@ -29,9 +27,10 @@ public class ExceptionInterfaceBindingTest extends AbstractInterfaceBindingTest @Mock private InterfaceBinding stackTraceInterfaceBinding; - @Before + @BeforeMethod public void setUp() throws Exception { super.setUp(); + MockitoAnnotations.initMocks(this); interfaceBinding = new ExceptionInterfaceBinding(stackTraceInterfaceBinding); doAnswer(new Answer() { diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java index 3e80602aec0..b91222d5a40 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java @@ -4,11 +4,10 @@ import com.fasterxml.jackson.databind.JsonNode; import net.kencochrane.raven.event.interfaces.MessageInterface; import org.hamcrest.Matchers; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import java.util.Arrays; import java.util.List; @@ -18,15 +17,15 @@ import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.when; -@RunWith(MockitoJUnitRunner.class) public class MessageInterfaceBindingTest extends AbstractInterfaceBindingTest { private MessageInterfaceBinding interfaceBinding; @Mock private MessageInterface mockMessageInterface; - @Before + @BeforeMethod public void setUp() throws Exception { super.setUp(); + MockitoAnnotations.initMocks(this); interfaceBinding = new MessageInterfaceBinding(); } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java index e4a1b6b8258..9e7fe695c41 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java @@ -3,11 +3,10 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonNode; import net.kencochrane.raven.event.interfaces.StackTraceInterface; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import java.util.UUID; @@ -15,15 +14,15 @@ import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.when; -@RunWith(MockitoJUnitRunner.class) public class StackTraceInterfaceBindingTest extends AbstractInterfaceBindingTest { private StackTraceInterfaceBinding interfaceBinding; @Mock private StackTraceInterface mockStackTraceInterface; - @Before + @BeforeMethod public void setUp() throws Exception { super.setUp(); + MockitoAnnotations.initMocks(this); interfaceBinding = new StackTraceInterfaceBinding(); } From 45d2606c419aedd23d01c70e6d1fa7dfa07e3186 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 13 Aug 2013 09:25:15 +0200 Subject: [PATCH 0815/2152] Mention the version 6.0 of Sentry in the README file --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f14be5bc4a1..01ada1fd97d 100644 --- a/README.md +++ b/README.md @@ -23,16 +23,16 @@ instance. ## Sentry Protocol and Raven versions Since 2.0, the major version of raven matches the version of the Sentry protocol. -| Raven version | Sentry version | Protocol version | -| ------------- | -------------- | ---------------- | -| Raven 2.x | >= 2.0 | V2 | -| Raven 3.x | >= 5.1 | V3 | -| Raven 4.x(DEV)| >= 5.5(DEV) | V4 | +| Raven version | Protocol version | Sentry version | +| ------------- | ---------------- | -------------- | +| Raven 2.x | V2 | >= 2.0 | +| Raven 3.x | V3 | >= 5.1 | +| Raven 4.x | V4 | >= 6.0 | Each release of Sentry supports the last two version of the protocol -(i.e. Sentry 5.4.5 supports both the protocol V3 and V2), for this reason, only -the two last stable version of Raven are actively maintained. +(i.e. Sentry 6.0.4 supports both the protocol V4 and V3), for this reason, only +the two last stable versions of Raven are actively maintained. ### Snapshot versions While the stable versions of raven are available on the From 479a9fd3309bcfccbdcdc86697468dfe7860c2ab Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 13 Aug 2013 09:28:21 +0200 Subject: [PATCH 0816/2152] Fix style and indentaiton --- .../raven/log4j2/SentryAppenderNewTest.java | 2 +- .../java/net/kencochrane/raven/event/EventBuilder.java | 5 ++++- .../raven/connection/HttpConnectionTest.java | 10 +++++----- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java index f256b0b9f44..fce24ac449c 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java @@ -107,7 +107,7 @@ public void testLogParametrisedMessage() throws Exception { sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new FormattedMessage(messagePattern, parameters), null)); - new Verifications(){{ + new Verifications() {{ Event event; mockRaven.sendEvent(event = withCapture()); diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 26e8b9db11d..528f7d85614 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -8,7 +8,10 @@ import java.util.Collections; import java.util.Date; import java.util.UUID; -import java.util.concurrent.*; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import java.util.zip.CRC32; import java.util.zip.Checksum; diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index 53eb36c2f5f..f8eb3ac0007 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -120,11 +120,11 @@ public void testHttpErrorLogged() throws Exception { verify(handler).publish(logRecordCaptor.capture()); Matcher> matcher = hasItem( new CustomTypeSafeMatcher("Looks for message '" + httpErrorMessage + "'") { - @Override - protected boolean matchesSafely(LogRecord logRecord) { - return httpErrorMessage.equals(logRecord.getMessage()); - } - }); + @Override + protected boolean matchesSafely(LogRecord logRecord) { + return httpErrorMessage.equals(logRecord.getMessage()); + } + }); assertThat(logRecordCaptor.getAllValues(), matcher); Logger.getLogger(HttpConnection.class.getCanonicalName()).removeHandler(handler); From 449b96c451390b95abd9f885512860491a35bd09 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 12 Aug 2013 22:36:47 +0200 Subject: [PATCH 0817/2152] Move mockito mocks to jmockit mocks --- .../raven/log4j2/SentryAppenderNewTest.java | 54 +++++++++++++++++++ .../raven/log4j2/SentryAppenderTest.java | 13 ----- 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java index fce24ac449c..d9249db642e 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java @@ -1,8 +1,11 @@ package net.kencochrane.raven.log4j2; import mockit.Injectable; +import mockit.Mocked; +import mockit.NonStrictExpectations; import mockit.Verifications; import net.kencochrane.raven.Raven; +import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; @@ -221,4 +224,55 @@ public void testCulpritWithoutSource() throws Exception { }}; } + + @Test + public void testCloseNotCalled() throws Exception { + sentryAppender = new SentryAppender(mockRaven, false); + new NonStrictExpectations() { + @Mocked + private Connection connection; + + { + mockRaven.getConnection(); + result = connection; + } + }; + + sentryAppender.stop(); + + new Verifications() { + private Connection connection; + + { + connection.close(); + times = 0; + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + } + }; + } + + @Test + public void testClose() throws Exception { + sentryAppender = new SentryAppender(mockRaven, true); + new NonStrictExpectations() { + @Mocked + private Connection connection; + + { + mockRaven.getConnection(); + result = connection; + } + }; + + sentryAppender.stop(); + + new Verifications() { + private Connection connection; + + { + connection.close(); times =3; + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + } + }; + } } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 7de371a6466..dd7bd71bad0 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -56,19 +56,6 @@ public Void answer(InvocationOnMock invocation) throws Throwable { doAnswer(answer).when(mockErrorHandler).error(anyString(), any(LogEvent.class), any(Throwable.class)); } - @Test - public void testClose() throws Exception { - sentryAppender.stop(); - verify(mockRaven.getConnection(), never()).close(); - - sentryAppender = new SentryAppender(mockRaven, true); - setMockErrorHandlerOnAppender(sentryAppender); - - sentryAppender.stop(); - verify(mockRaven.getConnection()).close(); - assertNoErrors(); - } - @Test public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { try { From 7f6131a9b3ce3284cff18a7437c30cc5d0473fb6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 12 Aug 2013 22:36:47 +0200 Subject: [PATCH 0818/2152] Move mockito mocks to jmockit mocks --- .../raven/log4j2/SentryAppenderNewTest.java | 17 +++++++++++++++++ .../raven/log4j2/SentryAppenderTest.java | 14 -------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java index d9249db642e..6a70179ef89 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java @@ -275,4 +275,21 @@ public void testClose() throws Exception { } }; } + + @Test + public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { + try { + Raven.RAVEN_THREAD.set(true); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); + + new Verifications() {{ + mockRaven.sendEvent((Event) any); + times = 0; + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; + } finally { + Raven.RAVEN_THREAD.remove(); + } + } } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index dd7bd71bad0..9da696d3669 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -56,20 +56,6 @@ public Void answer(InvocationOnMock invocation) throws Throwable { doAnswer(answer).when(mockErrorHandler).error(anyString(), any(LogEvent.class), any(Throwable.class)); } - @Test - public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { - try { - Raven.RAVEN_THREAD.set(true); - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); - - verify(mockRaven, never()).sendEvent(any(Event.class)); - assertNoErrors(); - } finally { - Raven.RAVEN_THREAD.remove(); - } - } - @Test public void testRavenFailureDoesNotPropagate() throws Exception { doThrow(new UnsupportedOperationException()).when(mockRaven).sendEvent(any(Event.class)); From 1aa0701a3f4d0792807828d403ba895204ff22c4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 12 Aug 2013 22:36:47 +0200 Subject: [PATCH 0819/2152] Move mockito mocks to jmockit mocks --- .../raven/log4j2/SentryAppenderNewTest.java | 14 ++++++++++++++ .../raven/log4j2/SentryAppenderTest.java | 11 ----------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java index 6a70179ef89..6e495761b96 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java @@ -292,4 +292,18 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { Raven.RAVEN_THREAD.remove(); } } + + @Test + public void testRavenFailureDoesNotPropagate() throws Exception { + new NonStrictExpectations() {{ + mockRaven.sendEvent((Event) any); + result = new UnsupportedOperationException(); + }}; + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); + + new Verifications() {{ + assertThat(mockUpErrorHandler.getErrorCount(), is(1)); + }}; + } } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 9da696d3669..5d9b59208c2 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -56,17 +56,6 @@ public Void answer(InvocationOnMock invocation) throws Throwable { doAnswer(answer).when(mockErrorHandler).error(anyString(), any(LogEvent.class), any(Throwable.class)); } - @Test - public void testRavenFailureDoesNotPropagate() throws Exception { - doThrow(new UnsupportedOperationException()).when(mockRaven).sendEvent(any(Event.class)); - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); - - verify(mockErrorHandler, never()).error(anyString()); - verify(mockErrorHandler, never()).error(anyString(), any(Throwable.class)); - verify(mockErrorHandler).error(anyString(), any(LogEvent.class), any(Throwable.class)); - } - @Test public void testLazyInitialisation() throws Exception { String dsnUri = "proto://private:public@host/1"; From faec30a404dd6c1e15e5075553fa2e746376808d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 12 Aug 2013 22:36:47 +0200 Subject: [PATCH 0820/2152] Move mockito mocks to jmockit mocks --- .../raven/log4j2/SentryAppenderNewTest.java | 30 ++++++++++++++++--- .../raven/log4j2/SentryAppenderTest.java | 16 ---------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java index 6e495761b96..59710ed0ac6 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java @@ -1,11 +1,10 @@ package net.kencochrane.raven.log4j2; -import mockit.Injectable; -import mockit.Mocked; -import mockit.NonStrictExpectations; -import mockit.Verifications; +import mockit.*; import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.connection.Connection; +import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; @@ -306,4 +305,27 @@ public void testRavenFailureDoesNotPropagate() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(1)); }}; } + + @Test + public void testLazyInitialisation(@Injectable final RavenFactory ravenFactory) throws Exception { + final String dsnUri = "proto://private:public@host/1"; + RavenFactory.registerFactory(ravenFactory); + sentryAppender = new SentryAppender(); + sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.setDsn(dsnUri); + sentryAppender.setRavenFactory(ravenFactory.getClass().getName()); + new Expectations() { + { + ravenFactory.createRavenInstance(withEqual(new Dsn(dsnUri))); + result = mockRaven; + } + }; + + sentryAppender.start(); + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); + + new Verifications() {{ + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; + } } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 5d9b59208c2..9c9bef914e4 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -56,22 +56,6 @@ public Void answer(InvocationOnMock invocation) throws Throwable { doAnswer(answer).when(mockErrorHandler).error(anyString(), any(LogEvent.class), any(Throwable.class)); } - @Test - public void testLazyInitialisation() throws Exception { - String dsnUri = "proto://private:public@host/1"; - sentryAppender = new SentryAppender(); - setMockErrorHandlerOnAppender(sentryAppender); - sentryAppender.setDsn(dsnUri); - sentryAppender.setRavenFactory(mockRavenFactory.getClass().getName()); - - sentryAppender.start(); - verify(mockRavenFactory, never()).createRavenInstance(any(Dsn.class)); - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); - verify(mockRavenFactory).createRavenInstance(eq(new Dsn(dsnUri))); - assertNoErrors(); - } - @Test public void testDsnAutoDetection() throws Exception { try { From 314580549ff6699282512a16f5bdbba7e7dcb456 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 19 Aug 2013 14:18:54 +0200 Subject: [PATCH 0821/2152] Print errors with MockUpErrorHandler --- .../net/kencochrane/raven/log4j2/MockUpErrorHandler.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java index be759c8e256..d999bfc16d5 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java @@ -11,16 +11,22 @@ public class MockUpErrorHandler extends MockUp { @Mock public void error(String msg) { errorCount++; + System.err.println(msg); } @Mock public void error(String msg, Throwable t) { errorCount++; + System.err.println(msg); + t.printStackTrace(System.err); } @Mock public void error(String msg, LogEvent event, Throwable t) { errorCount++; + System.err.println(msg); + t.printStackTrace(System.err); + } public int getErrorCount() { From e54fcbeb01aecd5122755037bc4bcf174c821868 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 19 Aug 2013 14:20:03 +0200 Subject: [PATCH 0822/2152] Prfix mocks with 'mock' --- .../kencochrane/raven/log4j2/SentryAppenderNewTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java index 59710ed0ac6..c41e42c9b3e 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java @@ -307,16 +307,16 @@ public void testRavenFailureDoesNotPropagate() throws Exception { } @Test - public void testLazyInitialisation(@Injectable final RavenFactory ravenFactory) throws Exception { + public void testLazyInitialisation(@Injectable final RavenFactory mockRavenFactory) throws Exception { final String dsnUri = "proto://private:public@host/1"; - RavenFactory.registerFactory(ravenFactory); + RavenFactory.registerFactory(mockRavenFactory); sentryAppender = new SentryAppender(); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.setDsn(dsnUri); - sentryAppender.setRavenFactory(ravenFactory.getClass().getName()); + sentryAppender.setRavenFactory(mockRavenFactory.getClass().getName()); new Expectations() { { - ravenFactory.createRavenInstance(withEqual(new Dsn(dsnUri))); + mockRavenFactory.createRavenInstance(withEqual(new Dsn(dsnUri))); result = mockRaven; } }; From a564a91d842344860414b0b7bf4dd942e092706e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 12 Aug 2013 22:36:47 +0200 Subject: [PATCH 0823/2152] Move mockito mocks to jmockit mocks --- .../raven/log4j2/SentryAppenderNewTest.java | 331 ---------------- .../raven/log4j2/SentryAppenderTest.java | 366 +++++++++++++++--- 2 files changed, 321 insertions(+), 376 deletions(-) delete mode 100644 raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java deleted file mode 100644 index c41e42c9b3e..00000000000 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderNewTest.java +++ /dev/null @@ -1,331 +0,0 @@ -package net.kencochrane.raven.log4j2; - -import mockit.*; -import net.kencochrane.raven.Raven; -import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.connection.Connection; -import net.kencochrane.raven.dsn.Dsn; -import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.EventBuilder; -import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.MessageInterface; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.MarkerManager; -import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.message.FormattedMessage; -import org.apache.logging.log4j.message.SimpleMessage; -import org.apache.logging.log4j.spi.DefaultThreadContextStack; -import org.hamcrest.Matchers; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.util.*; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; - -public class SentryAppenderNewTest { - private SentryAppender sentryAppender; - private MockUpErrorHandler mockUpErrorHandler; - @Injectable - private Raven mockRaven = null; - - @BeforeMethod - public void setUp() { - sentryAppender = new SentryAppender(mockRaven); - mockUpErrorHandler = new MockUpErrorHandler(); - sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); - } - - @Test - public void testSimpleMessageLogging() throws Exception { - final String loggerName = UUID.randomUUID().toString(); - final String message = UUID.randomUUID().toString(); - final String threadName = UUID.randomUUID().toString(); - final Date date = new Date(1373883196416L); - - sentryAppender.append(new Log4jLogEvent(loggerName, null, null, Level.INFO, new SimpleMessage(message), - null, null, null, threadName, null, date.getTime())); - - new Verifications() {{ - Event event; - mockRaven.runBuilderHelpers((EventBuilder) any); - mockRaven.sendEvent(event = withCapture()); - assertThat(event.getMessage(), is(message)); - assertThat(event.getLogger(), is(loggerName)); - assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); - assertThat(event.getTimestamp(), is(date)); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - }}; - } - - @Test - public void testLevelConversion() throws Exception { - assertLevelConverted(Event.Level.DEBUG, Level.TRACE); - assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); - assertLevelConverted(Event.Level.INFO, Level.INFO); - assertLevelConverted(Event.Level.WARNING, Level.WARN); - assertLevelConverted(Event.Level.ERROR, Level.ERROR); - assertLevelConverted(Event.Level.FATAL, Level.FATAL); - } - - private void assertLevelConverted(final Event.Level expectedLevel, Level level) throws Exception { - sentryAppender.append(new Log4jLogEvent(null, null, null, level, new SimpleMessage(""), null)); - - new Verifications() {{ - Event event; - mockRaven.sendEvent(event = withCapture()); - assertThat(event.getLevel(), is(expectedLevel)); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - }}; - } - - @Test - public void testExceptionLogging() throws Exception { - final Exception exception = new Exception(UUID.randomUUID().toString()); - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.ERROR, new SimpleMessage(""), exception)); - - new Verifications() {{ - Event event; - Throwable throwable; - mockRaven.sendEvent(event = withCapture()); - ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() - .get(ExceptionInterface.EXCEPTION_INTERFACE); - throwable = exceptionInterface.getThrowable(); - assertThat(throwable.getMessage(), is(exception.getMessage())); - assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - }}; - } - - @Test - public void testLogParametrisedMessage() throws Exception { - final String messagePattern = "Formatted message {} {} {}"; - final Object[] parameters = {"first parameter", new Object[0], null}; - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, - new FormattedMessage(messagePattern, parameters), null)); - - new Verifications() {{ - Event event; - mockRaven.sendEvent(event = withCapture()); - - MessageInterface messageInterface = (MessageInterface) event.getSentryInterfaces() - .get(MessageInterface.MESSAGE_INTERFACE); - assertThat(event.getMessage(), is("Formatted message first parameter [] null")); - assertThat(messageInterface.getMessage(), is(messagePattern)); - assertThat(messageInterface.getParameters(), - is(Arrays.asList(parameters[0].toString(), parameters[1].toString(), null))); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - }}; - } - - @Test - public void testMarkerAddedToTag() throws Exception { - final String markerName = UUID.randomUUID().toString(); - - sentryAppender.append(new Log4jLogEvent(null, MarkerManager.getMarker(markerName), null, Level.INFO, - new SimpleMessage(""), null)); - - new Verifications() {{ - Event event; - mockRaven.sendEvent(event = withCapture()); - assertThat(event.getTags(), Matchers.hasEntry(SentryAppender.LOG4J_MARKER, markerName)); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - }}; - } - - @Test - public void testMdcAddedToExtra() throws Exception { - final String extraKey = UUID.randomUUID().toString(); - final String extraValue = UUID.randomUUID().toString(); - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, - Collections.singletonMap(extraKey, extraValue), null, null, null, 0)); - - new Verifications() {{ - Event event; - mockRaven.sendEvent(event = withCapture()); - assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - }}; - } - - @Test - @SuppressWarnings("unchecked") - public void testNdcAddedToExtra() throws Exception { - final ThreadContext.ContextStack contextStack = new DefaultThreadContextStack(true); - contextStack.push(UUID.randomUUID().toString()); - contextStack.push(UUID.randomUUID().toString()); - contextStack.push(UUID.randomUUID().toString()); - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, - contextStack, null, null, 0)); - - new Verifications() {{ - Event event; - mockRaven.sendEvent(event = withCapture()); - assertThat((List) event.getExtra().get(SentryAppender.LOG4J_NDC), equalTo(contextStack.asList())); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - }}; - } - - @Test - public void testSourceUsedAsStacktrace() throws Exception { - final StackTraceElement location = new StackTraceElement(UUID.randomUUID().toString(), - UUID.randomUUID().toString(), UUID.randomUUID().toString(), 42); - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, null, - null, location, 0)); - - new Verifications() {{ - Event event; - mockRaven.sendEvent(event = withCapture()); - StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() - .get(StackTraceInterface.STACKTRACE_INTERFACE); - assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); - assertThat(stackTraceInterface.getStackTrace()[0], is(location)); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - - }}; - } - - @Test - public void testCulpritWithSource() throws Exception { - final StackTraceElement location = new StackTraceElement("a", "b", "c", 42); - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, null, - null, location, 0)); - - new Verifications() {{ - Event event; - mockRaven.sendEvent(event = withCapture()); - assertThat(event.getCulprit(), is("a.b(c:42)")); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - - }}; - } - - @Test - public void testCulpritWithoutSource() throws Exception { - final String loggerName = UUID.randomUUID().toString(); - - sentryAppender.append(new Log4jLogEvent(loggerName, null, null, Level.INFO, new SimpleMessage(""), null)); - - new Verifications() {{ - Event event; - mockRaven.sendEvent(event = withCapture()); - assertThat(event.getCulprit(), is(loggerName)); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - - }}; - } - - @Test - public void testCloseNotCalled() throws Exception { - sentryAppender = new SentryAppender(mockRaven, false); - new NonStrictExpectations() { - @Mocked - private Connection connection; - - { - mockRaven.getConnection(); - result = connection; - } - }; - - sentryAppender.stop(); - - new Verifications() { - private Connection connection; - - { - connection.close(); - times = 0; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - } - }; - } - - @Test - public void testClose() throws Exception { - sentryAppender = new SentryAppender(mockRaven, true); - new NonStrictExpectations() { - @Mocked - private Connection connection; - - { - mockRaven.getConnection(); - result = connection; - } - }; - - sentryAppender.stop(); - - new Verifications() { - private Connection connection; - - { - connection.close(); times =3; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - } - }; - } - - @Test - public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { - try { - Raven.RAVEN_THREAD.set(true); - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); - - new Verifications() {{ - mockRaven.sendEvent((Event) any); - times = 0; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - }}; - } finally { - Raven.RAVEN_THREAD.remove(); - } - } - - @Test - public void testRavenFailureDoesNotPropagate() throws Exception { - new NonStrictExpectations() {{ - mockRaven.sendEvent((Event) any); - result = new UnsupportedOperationException(); - }}; - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); - - new Verifications() {{ - assertThat(mockUpErrorHandler.getErrorCount(), is(1)); - }}; - } - - @Test - public void testLazyInitialisation(@Injectable final RavenFactory mockRavenFactory) throws Exception { - final String dsnUri = "proto://private:public@host/1"; - RavenFactory.registerFactory(mockRavenFactory); - sentryAppender = new SentryAppender(); - sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.setDsn(dsnUri); - sentryAppender.setRavenFactory(mockRavenFactory.getClass().getName()); - new Expectations() { - { - mockRavenFactory.createRavenInstance(withEqual(new Dsn(dsnUri))); - result = mockRaven; - } - }; - - sentryAppender.start(); - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); - - new Verifications() {{ - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - }}; - } -} diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 9c9bef914e4..bc36580cad0 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -1,83 +1,359 @@ package net.kencochrane.raven.log4j2; +import mockit.*; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.MessageInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.appender.DefaultErrorHandler; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.FormattedMessage; import org.apache.logging.log4j.message.SimpleMessage; -import org.mockito.Answers; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; +import org.apache.logging.log4j.spi.DefaultThreadContextStack; +import org.hamcrest.Matchers; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.*; +import java.util.*; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; public class SentryAppenderTest { - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private Raven mockRaven; - @Mock - private RavenFactory mockRavenFactory; - @Mock - private DefaultErrorHandler mockErrorHandler; private SentryAppender sentryAppender; + private MockUpErrorHandler mockUpErrorHandler; + @Injectable + private Raven mockRaven = null; @BeforeMethod - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); + public void setUp() { sentryAppender = new SentryAppender(mockRaven); - setMockErrorHandlerOnAppender(sentryAppender); + mockUpErrorHandler = new MockUpErrorHandler(); + sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + } - when(mockRavenFactory.createRavenInstance(any(Dsn.class))).thenReturn(mockRaven); - RavenFactory.registerFactory(mockRavenFactory); + @Test + public void testSimpleMessageLogging() throws Exception { + final String loggerName = UUID.randomUUID().toString(); + final String message = UUID.randomUUID().toString(); + final String threadName = UUID.randomUUID().toString(); + final Date date = new Date(1373883196416L); + + sentryAppender.append(new Log4jLogEvent(loggerName, null, null, Level.INFO, new SimpleMessage(message), + null, null, null, threadName, null, date.getTime())); + + new Verifications() {{ + Event event; + mockRaven.runBuilderHelpers((EventBuilder) any); + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getMessage(), is(message)); + assertThat(event.getLogger(), is(loggerName)); + assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); + assertThat(event.getTimestamp(), is(date)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; + } + + @Test + public void testLevelConversion() throws Exception { + assertLevelConverted(Event.Level.DEBUG, Level.TRACE); + assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); + assertLevelConverted(Event.Level.INFO, Level.INFO); + assertLevelConverted(Event.Level.WARNING, Level.WARN); + assertLevelConverted(Event.Level.ERROR, Level.ERROR); + assertLevelConverted(Event.Level.FATAL, Level.FATAL); + } + + private void assertLevelConverted(final Event.Level expectedLevel, Level level) throws Exception { + sentryAppender.append(new Log4jLogEvent(null, null, null, level, new SimpleMessage(""), null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getLevel(), is(expectedLevel)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; + } + + @Test + public void testExceptionLogging() throws Exception { + final Exception exception = new Exception(UUID.randomUUID().toString()); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.ERROR, new SimpleMessage(""), exception)); + + new Verifications() {{ + Event event; + Throwable throwable; + mockRaven.sendEvent(event = withCapture()); + ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() + .get(ExceptionInterface.EXCEPTION_INTERFACE); + throwable = exceptionInterface.getThrowable(); + assertThat(throwable.getMessage(), is(exception.getMessage())); + assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; + } + + @Test + public void testLogParametrisedMessage() throws Exception { + final String messagePattern = "Formatted message {} {} {}"; + final Object[] parameters = {"first parameter", new Object[0], null}; + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, + new FormattedMessage(messagePattern, parameters), null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + + MessageInterface messageInterface = (MessageInterface) event.getSentryInterfaces() + .get(MessageInterface.MESSAGE_INTERFACE); + assertThat(event.getMessage(), is("Formatted message first parameter [] null")); + assertThat(messageInterface.getMessage(), is(messagePattern)); + assertThat(messageInterface.getParameters(), + is(Arrays.asList(parameters[0].toString(), parameters[1].toString(), null))); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; + } + + @Test + public void testMarkerAddedToTag() throws Exception { + final String markerName = UUID.randomUUID().toString(); + + sentryAppender.append(new Log4jLogEvent(null, MarkerManager.getMarker(markerName), null, Level.INFO, + new SimpleMessage(""), null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getTags(), Matchers.hasEntry(SentryAppender.LOG4J_MARKER, markerName)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; + } + + @Test + public void testMdcAddedToExtra() throws Exception { + final String extraKey = UUID.randomUUID().toString(); + final String extraValue = UUID.randomUUID().toString(); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, + Collections.singletonMap(extraKey, extraValue), null, null, null, 0)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; + } + + @Test + @SuppressWarnings("unchecked") + public void testNdcAddedToExtra() throws Exception { + final ThreadContext.ContextStack contextStack = new DefaultThreadContextStack(true); + contextStack.push(UUID.randomUUID().toString()); + contextStack.push(UUID.randomUUID().toString()); + contextStack.push(UUID.randomUUID().toString()); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, + contextStack, null, null, 0)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat((List) event.getExtra().get(SentryAppender.LOG4J_NDC), equalTo(contextStack.asList())); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; + } + + @Test + public void testSourceUsedAsStacktrace() throws Exception { + final StackTraceElement location = new StackTraceElement(UUID.randomUUID().toString(), + UUID.randomUUID().toString(), UUID.randomUUID().toString(), 42); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, null, + null, location, 0)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() + .get(StackTraceInterface.STACKTRACE_INTERFACE); + assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); + assertThat(stackTraceInterface.getStackTrace()[0], is(location)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + + }}; + } + + @Test + public void testCulpritWithSource() throws Exception { + final StackTraceElement location = new StackTraceElement("a", "b", "c", 42); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, null, + null, location, 0)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getCulprit(), is("a.b(c:42)")); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + + }}; + } + + @Test + public void testCulpritWithoutSource() throws Exception { + final String loggerName = UUID.randomUUID().toString(); + + sentryAppender.append(new Log4jLogEvent(loggerName, null, null, Level.INFO, new SimpleMessage(""), null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getCulprit(), is(loggerName)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + + }}; } - private void setMockErrorHandlerOnAppender(final SentryAppender sentryAppender) { - sentryAppender.setHandler(mockErrorHandler); + @Test + public void testCloseNotCalled() throws Exception { + sentryAppender = new SentryAppender(mockRaven, false); + new NonStrictExpectations() { + @Mocked + private Connection connection; + + { + mockRaven.getConnection(); + result = connection; + } + }; + + sentryAppender.stop(); + + new Verifications() { + private Connection connection; + + { + connection.close(); + times = 0; + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + } + }; + } + + @Test + public void testClose() throws Exception { + sentryAppender = new SentryAppender(mockRaven, true); + new NonStrictExpectations() { + @Mocked + private Connection connection; + + { + mockRaven.getConnection(); + result = connection; + } + }; + + sentryAppender.stop(); - Answer answer = new Answer() { - private final DefaultErrorHandler actualErrorHandler = new DefaultErrorHandler(sentryAppender); + new Verifications() { + private Connection connection; - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - invocation.getMethod().invoke(actualErrorHandler, invocation.getArguments()); - return null; + { + connection.close(); + times = 3; + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } }; - doAnswer(answer).when(mockErrorHandler).error(anyString()); - doAnswer(answer).when(mockErrorHandler).error(anyString(), any(Throwable.class)); - doAnswer(answer).when(mockErrorHandler).error(anyString(), any(LogEvent.class), any(Throwable.class)); } @Test - public void testDsnAutoDetection() throws Exception { + public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { try { - String dsnUri = "proto://private:public@host/1"; - System.setProperty(Dsn.DSN_VARIABLE, dsnUri); - sentryAppender = new SentryAppender(); - setMockErrorHandlerOnAppender(sentryAppender); - sentryAppender.setRavenFactory(mockRavenFactory.getClass().getName()); + Raven.RAVEN_THREAD.set(true); - sentryAppender.start(); sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); - verify(mockRavenFactory).createRavenInstance(eq(new Dsn(dsnUri))); - assertNoErrors(); + new Verifications() {{ + mockRaven.sendEvent((Event) any); + times = 0; + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; } finally { - System.clearProperty(Dsn.DSN_VARIABLE); + Raven.RAVEN_THREAD.remove(); } } - private void assertNoErrors() { - verify(mockErrorHandler, never()).error(anyString()); - verify(mockErrorHandler, never()).error(anyString(), any(Throwable.class)); - verify(mockErrorHandler, never()).error(anyString(), any(LogEvent.class), any(Throwable.class)); + @Test + public void testRavenFailureDoesNotPropagate() throws Exception { + new NonStrictExpectations() {{ + mockRaven.sendEvent((Event) any); + result = new UnsupportedOperationException(); + }}; + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); + + new Verifications() {{ + assertThat(mockUpErrorHandler.getErrorCount(), is(1)); + }}; + } + + @Test + public void testLazyInitialisation(@Injectable final RavenFactory mockRavenFactory) throws Exception { + final String dsnUri = "proto://private:public@host/1"; + RavenFactory.registerFactory(mockRavenFactory); + sentryAppender = new SentryAppender(); + sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.setDsn(dsnUri); + sentryAppender.setRavenFactory(mockRavenFactory.getClass().getName()); + new Expectations() { + { + mockRavenFactory.createRavenInstance(withEqual(new Dsn(dsnUri))); + result = mockRaven; + } + }; + + sentryAppender.start(); + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); + + new Verifications() {{ + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; + } + + @Test + public void testDsnAutoDetection(@Injectable final RavenFactory mockRavenFactory) throws Exception { + final String dsnUri = "proto://private:public@host/1"; + RavenFactory.registerFactory(mockRavenFactory); + sentryAppender = new SentryAppender(); + sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.setRavenFactory(mockRavenFactory.getClass().getName()); + new Expectations() { + @Mocked("dsnLookup") + private Dsn dsn; + + { + Dsn.dsnLookup(); + result = dsnUri; + mockRavenFactory.createRavenInstance(withEqual(new Dsn(dsnUri))); + result = mockRaven; + } + }; + + sentryAppender.start(); + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); + + new Verifications() {{ + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; } } From 58a87cec8e01f61e6926f69d5c02c061cbb52a12 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 19 Aug 2013 14:46:39 +0200 Subject: [PATCH 0824/2152] Add tests for DSN verification --- .../raven/log4j/SentryAppenderDsnTest.java | 78 +++++++++++++++++++ .../raven/log4j/SentryAppenderTest.java | 28 ------- 2 files changed, 78 insertions(+), 28 deletions(-) create mode 100644 raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java new file mode 100644 index 00000000000..4d67648578a --- /dev/null +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java @@ -0,0 +1,78 @@ +package net.kencochrane.raven.log4j; + +import mockit.Expectations; +import mockit.Injectable; +import mockit.Mocked; +import mockit.Verifications; +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.dsn.Dsn; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.spi.LoggingEvent; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SentryAppenderDsnTest { + private SentryAppender sentryAppender; + private MockUpErrorHandler mockUpErrorHandler; + @Injectable + private Raven mockRaven = null; + @Injectable + private Logger mockLogger = null; + @Injectable + private RavenFactory ravenFactory = null; + + @BeforeMethod + public void setUp() { + sentryAppender = new SentryAppender(); + mockUpErrorHandler = new MockUpErrorHandler(); + sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); + RavenFactory.registerFactory(ravenFactory); + sentryAppender.setRavenFactory(ravenFactory.getClass().getName()); + } + + @Test + public void testDsnDetected() throws Exception { + final String dsnUri = "protocol://public:private@host/1"; + sentryAppender.setRavenFactory(ravenFactory.getClass().getName()); + new Expectations() { + @Mocked("dsnLookup") + private Dsn dsn; + + { + Dsn.dsnLookup(); + result = dsnUri; + ravenFactory.createRavenInstance(withEqual(new Dsn(dsnUri))); + result = mockRaven; + } + }; + + sentryAppender.activateOptions(); + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); + + new Verifications() {{ + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; + } + + @Test + public void testDsnProvided() throws Exception { + final String dsnUri = "protocol://public:private@host/2"; + sentryAppender.setDsn(dsnUri); + new Expectations() {{ + ravenFactory.createRavenInstance(withEqual(new Dsn(dsnUri))); + result = mockRaven; + }}; + + sentryAppender.activateOptions(); + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); + + new Verifications() {{ + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; + } +} diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 40ffbfbaef2..9795ac2e40b 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -217,34 +217,6 @@ public void testCulpritWithoutSource() throws Exception { }}; } - @Test - public void testCorrectRavenInstanceUsedIfNotProvided() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(); - - new Expectations() { - private final String dsnUri = "protocol://public:private@host/1"; - @Mocked("dsnLookup") - private Dsn dsn; - @Mocked("ravenInstance") - private RavenFactory ravenFactory; - - { - Dsn.dsnLookup(); - result = dsnUri; - RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockRaven; - } - }; - - sentryAppender.activateOptions(); - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); - - new Verifications() {{ - mockRaven.sendEvent((Event) any); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - }}; - } - @Test public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { try { From 35fe323cfb332b55d2293fbf9c6389a02df3414b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 19 Aug 2013 14:47:17 +0200 Subject: [PATCH 0825/2152] Execute asserts in verifications --- .../log4j/SentryAppenderClosingConnectionTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java index 2e6d02f49a1..f1b50654cf7 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java @@ -38,8 +38,8 @@ public void testNotClosedIfRavenInstanceIsProvided() throws Exception { new Verifications() {{ mockConnection.close(); times = 0; + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -52,8 +52,8 @@ public void testClosedIfRavenInstanceProvidedAndForceClose() throws Exception { new Verifications() {{ mockConnection.close(); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -68,8 +68,8 @@ public void testNotClosedIfRavenInstanceProvidedAndNotForceClose() new Verifications() {{ mockConnection.close(); times = 0; + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -96,8 +96,8 @@ public void testClosedIfRavenInstanceNotProvided() throws Exception { new Verifications() {{ mockConnection.close(); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -123,7 +123,7 @@ public void testCloseDoNotFailWhenMultipleCalls() throws Exception { new Verifications() {{ mockConnection.close(); times = 0; + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } } From 534f2b30d99b3b5799e5e12e6ac3bef4b63d8d8e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 19 Aug 2013 14:47:31 +0200 Subject: [PATCH 0826/2152] Ensure that close do not fail on second call is successful on the first one --- .../raven/log4j/SentryAppenderClosingConnectionTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java index f1b50654cf7..6f4023adca5 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java @@ -114,7 +114,7 @@ public void testCloseDoNotFailIfInitFailed() throws Exception { @Test public void testCloseDoNotFailWhenMultipleCalls() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven); + final SentryAppender sentryAppender = new SentryAppender(mockRaven, true); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.close(); @@ -122,7 +122,7 @@ public void testCloseDoNotFailWhenMultipleCalls() throws Exception { new Verifications() {{ mockConnection.close(); - times = 0; + times = 1; assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } From 0d23dde8cbde36ebc4b19e9d20c07e06272905c6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 19 Aug 2013 15:00:39 +0200 Subject: [PATCH 0827/2152] Use a dataprovider to test level conversions --- .../raven/log4j/SentryAppenderTest.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 9795ac2e40b..740d971dadf 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -15,6 +15,7 @@ import org.apache.log4j.spi.LoggingEvent; import org.hamcrest.Matchers; import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.Collections; @@ -66,17 +67,19 @@ public void testSimpleMessageLogging() throws Exception { }}; } - @Test - public void testLevelConversion() throws Exception { - assertLevelConverted(Event.Level.DEBUG, Level.TRACE); - assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); - assertLevelConverted(Event.Level.INFO, Level.INFO); - assertLevelConverted(Event.Level.WARNING, Level.WARN); - assertLevelConverted(Event.Level.ERROR, Level.ERROR); - assertLevelConverted(Event.Level.FATAL, Level.FATAL); + @DataProvider(name = "levels") + private Object[][] levelConversions() { + return new Object[][]{ + {Event.Level.DEBUG, Level.TRACE}, + {Event.Level.DEBUG, Level.DEBUG}, + {Event.Level.INFO, Level.INFO}, + {Event.Level.WARNING, Level.WARN}, + {Event.Level.ERROR, Level.ERROR}, + {Event.Level.FATAL, Level.FATAL}}; } - private void assertLevelConverted(final Event.Level expectedLevel, Level level) throws Exception { + @Test(dataProvider = "levels") + public void assertLevelConverted(final Event.Level expectedLevel, Level level) throws Exception { sentryAppender.append(new LoggingEvent(null, mockLogger, 0, level, null, null)); new Verifications() {{ From ff64e4e521be379f3e1f07a5ca9cc6f9adadcf8f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 19 Aug 2013 15:03:02 +0200 Subject: [PATCH 0828/2152] Move tests on failure in a dedicated class --- .../log4j/SentryAppenderFailuresTest.java | 66 +++++++++++++++++++ .../raven/log4j/SentryAppenderTest.java | 35 ---------- 2 files changed, 66 insertions(+), 35 deletions(-) create mode 100644 raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java new file mode 100644 index 00000000000..55cfba93181 --- /dev/null +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java @@ -0,0 +1,66 @@ +package net.kencochrane.raven.log4j; + +import mockit.*; +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.dsn.Dsn; +import net.kencochrane.raven.event.Event; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.spi.LoggingEvent; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SentryAppenderFailuresTest { + private SentryAppender sentryAppender; + private MockUpErrorHandler mockUpErrorHandler; + @Injectable + private Raven mockRaven = null; + @Injectable + private Logger mockLogger = null; + + @BeforeMethod + public void setUp() throws Exception{ + sentryAppender = new SentryAppender(mockRaven); + mockUpErrorHandler = new MockUpErrorHandler(); + sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); + } + + @Test + public void testRavenFailureDoesNotPropagate() throws Exception { + new NonStrictExpectations() {{ + mockRaven.sendEvent((Event) any); + result = new UnsupportedOperationException(); + }}; + + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); + + new Verifications() {{ + mockRaven.sendEvent((Event) any); + assertThat(mockUpErrorHandler.getErrorCount(), is(1)); + }}; + } + + @Test + public void testRavenFactoryFailureDoesNotPropagate() throws Exception { + new Expectations() { + @Mocked("ravenInstance") + private RavenFactory ravenFactory; + + { + RavenFactory.ravenInstance((Dsn) any, anyString); + result = new UnsupportedOperationException(); + } + }; + SentryAppender sentryAppender = new SentryAppender(); + sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.setDsn("protocol://public:private@host/1"); + + sentryAppender.activateOptions(); + + assertThat(mockUpErrorHandler.getErrorCount(), is(1)); + } +} diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 740d971dadf..fcb1649e799 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -235,39 +235,4 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { Raven.RAVEN_THREAD.remove(); } } - - @Test - public void testRavenFailureDoesNotPropagate() throws Exception { - new NonStrictExpectations() {{ - mockRaven.sendEvent((Event) any); - result = new UnsupportedOperationException(); - }}; - - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); - - new Verifications() {{ - mockRaven.sendEvent((Event) any); - assertThat(mockUpErrorHandler.getErrorCount(), is(1)); - }}; - } - - @Test - public void testRavenFactoryFailureDoesNotPropagate() throws Exception { - new Expectations() { - @Mocked("ravenInstance") - private RavenFactory ravenFactory; - - { - RavenFactory.ravenInstance((Dsn) any, anyString); - result = new UnsupportedOperationException(); - } - }; - SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.setDsn("protocol://public:private@host/1"); - - sentryAppender.activateOptions(); - - assertThat(mockUpErrorHandler.getErrorCount(), is(1)); - } } From e801f630ab1372021b91137845137009e70ee633 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 19 Aug 2013 15:03:57 +0200 Subject: [PATCH 0829/2152] Test the non generation of error messages in one single place --- .../raven/log4j/SentryAppenderTest.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index fcb1649e799..f0c3e51309a 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -14,6 +14,7 @@ import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; import org.hamcrest.Matchers; +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -41,6 +42,11 @@ public void setUp() { sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); } + @AfterMethod + public void tearDown() { + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + } + @Test public void testSimpleMessageLogging() throws Exception { final String loggerName = UUID.randomUUID().toString(); @@ -63,7 +69,6 @@ public void testSimpleMessageLogging() throws Exception { assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } @@ -86,7 +91,6 @@ public void assertLevelConverted(final Event.Level expectedLevel, Level level) t Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getLevel(), is(expectedLevel)); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } @@ -105,7 +109,6 @@ public void testExceptionLogging() throws Exception { throwable = exceptionInterface.getThrowable(); assertThat(throwable.getMessage(), is(exception.getMessage())); assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } @@ -121,7 +124,6 @@ public void testMdcAddedToExtra() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } @@ -136,7 +138,6 @@ public void testNdcAddedToExtra() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.LOG4J_NDC, ndcEntries)); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } @@ -169,7 +170,6 @@ public void testSourceUsedAsStacktrace(@Injectable @NonStrict final LocationInfo assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); assertThat(stackTraceInterface.getStackTrace()[0], is(new StackTraceElement(className, methodName, fileName, line))); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } @@ -198,7 +198,6 @@ public void testCulpritWithSource(@Injectable @NonStrict final LocationInfo loca Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is("a.b(c:42)")); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } @@ -216,7 +215,6 @@ public void testCulpritWithoutSource() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is(loggerName)); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } @@ -229,7 +227,6 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { new Verifications() {{ mockRaven.sendEvent((Event) any); times = 0; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } finally { Raven.RAVEN_THREAD.remove(); From 0a93ec5df1e6a3ea7da0e150542cb93829a63f0a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 19 Aug 2013 15:04:29 +0200 Subject: [PATCH 0830/2152] Use NonStrictException instead of NonStrict Injectable in Expectations --- .../raven/log4j/SentryAppenderTest.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index f0c3e51309a..0c681a4d750 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -1,10 +1,11 @@ package net.kencochrane.raven.log4j; import com.google.common.base.Joiner; -import mockit.*; +import mockit.Expectations; +import mockit.Injectable; +import mockit.NonStrictExpectations; +import mockit.Verifications; import net.kencochrane.raven.Raven; -import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; @@ -142,12 +143,12 @@ public void testNdcAddedToExtra() throws Exception { } @Test - public void testSourceUsedAsStacktrace(@Injectable @NonStrict final LocationInfo locationInfo) throws Exception { + public void testSourceUsedAsStacktrace(@Injectable final LocationInfo locationInfo) throws Exception { final String className = UUID.randomUUID().toString(); final String methodName = UUID.randomUUID().toString(); final String fileName = UUID.randomUUID().toString(); final int line = 42; - new Expectations() {{ + new NonStrictExpectations() {{ locationInfo.getClassName(); result = className; locationInfo.getMethodName(); @@ -174,12 +175,12 @@ public void testSourceUsedAsStacktrace(@Injectable @NonStrict final LocationInfo } @Test - public void testCulpritWithSource(@Injectable @NonStrict final LocationInfo locationInfo) throws Exception { + public void testCulpritWithSource(@Injectable final LocationInfo locationInfo) throws Exception { final String className = "a"; final String methodName = "b"; final String fileName = "c"; final int line = 42; - new Expectations() {{ + new NonStrictExpectations() {{ locationInfo.getClassName(); result = className; locationInfo.getMethodName(); From 3be4f2960029f59c73e89186a10beb0515c652db Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 19 Aug 2013 16:21:06 +0200 Subject: [PATCH 0831/2152] Fix the testClose test --- .../java/net/kencochrane/raven/log4j2/SentryAppenderTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index bc36580cad0..4fa7bad5e8c 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -270,7 +270,7 @@ public void testClose() throws Exception { { connection.close(); - times = 3; + times = 1; assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } }; From a07502d2dce74678de70e544b82a0e700d0c4a50 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 08:58:12 +0200 Subject: [PATCH 0832/2152] Fix style and indentation --- .../log4j/SentryAppenderClosingConnectionTest.java | 13 ++++++------- .../raven/log4j/SentryAppenderFailuresTest.java | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java index 6f4023adca5..6d9c837bd94 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java @@ -31,8 +31,8 @@ public void setUp() { public void testNotClosedIfRavenInstanceIsProvided() throws Exception { final SentryAppender sentryAppender = new SentryAppender(mockRaven); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.activateOptions(); + sentryAppender.close(); new Verifications() {{ @@ -46,8 +46,8 @@ public void testNotClosedIfRavenInstanceIsProvided() throws Exception { public void testClosedIfRavenInstanceProvidedAndForceClose() throws Exception { final SentryAppender sentryAppender = new SentryAppender(mockRaven, true); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.activateOptions(); + sentryAppender.close(); new Verifications() {{ @@ -57,12 +57,11 @@ public void testClosedIfRavenInstanceProvidedAndForceClose() throws Exception { } @Test - public void testNotClosedIfRavenInstanceProvidedAndNotForceClose() - throws Exception { + public void testNotClosedIfRavenInstanceProvidedAndNotForceClose() throws Exception { final SentryAppender sentryAppender = new SentryAppender(mockRaven, false); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.activateOptions(); + sentryAppender.close(); new Verifications() {{ @@ -90,8 +89,8 @@ public void testClosedIfRavenInstanceNotProvided() throws Exception { result = mockRaven; } }; - sentryAppender.activateOptions(); + sentryAppender.close(); new Verifications() {{ @@ -105,8 +104,8 @@ public void testCloseDoNotFailIfInitFailed() throws Exception { // This checks that even if sentry wasn't setup correctly its appender can still be closed. SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.activateOptions(); + sentryAppender.close(); assertThat(mockUpErrorHandler.getErrorCount(), is(1)); diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java index 55cfba93181..ea300e59127 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java @@ -23,7 +23,7 @@ public class SentryAppenderFailuresTest { private Logger mockLogger = null; @BeforeMethod - public void setUp() throws Exception{ + public void setUp() throws Exception { sentryAppender = new SentryAppender(mockRaven); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); From 17ce86a12a03976dff4d53b4888a6167526b48ee Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 08:59:34 +0200 Subject: [PATCH 0833/2152] Mock static methods in the test class rather than the tests themselves --- .../SentryAppenderClosingConnectionTest.java | 33 ++++++++++--------- .../raven/log4j/SentryAppenderDsnTest.java | 28 +++++++--------- .../log4j/SentryAppenderFailuresTest.java | 15 ++++----- 3 files changed, 35 insertions(+), 41 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java index 6d9c837bd94..ba7c314a475 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java @@ -17,6 +17,10 @@ public class SentryAppenderClosingConnectionTest { private Raven mockRaven = null; @Injectable private Connection mockConnection = null; + @Mocked("ravenInstance") + private RavenFactory mockRavenFactory; + @Mocked("dsnLookup") + private Dsn mockDsn; @BeforeMethod public void setUp() { @@ -73,22 +77,15 @@ public void testNotClosedIfRavenInstanceProvidedAndNotForceClose() throws Except @Test public void testClosedIfRavenInstanceNotProvided() throws Exception { + final String dsnUri = "protocol://public:private@host/1"; final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); - new Expectations() { - private final String dsnUri = "protocol://public:private@host/1"; - @Mocked("dsnLookup") - private Dsn dsn; - @Mocked("ravenInstance") - private RavenFactory ravenFactory; - - { - Dsn.dsnLookup(); - result = dsnUri; - RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockRaven; - } - }; + new Expectations() {{ + Dsn.dsnLookup(); + result = dsnUri; + RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); + result = mockRaven; + }}; sentryAppender.activateOptions(); sentryAppender.close(); @@ -104,11 +101,17 @@ public void testCloseDoNotFailIfInitFailed() throws Exception { // This checks that even if sentry wasn't setup correctly its appender can still be closed. SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); + new NonStrictExpectations() {{ + RavenFactory.ravenInstance((Dsn) any, anyString); + result = new UnsupportedOperationException(); + }}; sentryAppender.activateOptions(); sentryAppender.close(); - assertThat(mockUpErrorHandler.getErrorCount(), is(1)); + new Verifications() {{ + assertThat(mockUpErrorHandler.getErrorCount(), is(1)); + }}; } @Test diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java index 4d67648578a..b5ecd32f951 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java @@ -23,33 +23,27 @@ public class SentryAppenderDsnTest { private Raven mockRaven = null; @Injectable private Logger mockLogger = null; - @Injectable - private RavenFactory ravenFactory = null; + @Mocked("ravenInstance") + private RavenFactory mockRavenFactory; + @Mocked("dsnLookup") + private Dsn mockDsn; @BeforeMethod public void setUp() { sentryAppender = new SentryAppender(); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); - RavenFactory.registerFactory(ravenFactory); - sentryAppender.setRavenFactory(ravenFactory.getClass().getName()); } @Test public void testDsnDetected() throws Exception { final String dsnUri = "protocol://public:private@host/1"; - sentryAppender.setRavenFactory(ravenFactory.getClass().getName()); - new Expectations() { - @Mocked("dsnLookup") - private Dsn dsn; - - { - Dsn.dsnLookup(); - result = dsnUri; - ravenFactory.createRavenInstance(withEqual(new Dsn(dsnUri))); - result = mockRaven; - } - }; + new Expectations() {{ + Dsn.dsnLookup(); + result = dsnUri; + RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); + result = mockRaven; + }}; sentryAppender.activateOptions(); sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); @@ -64,7 +58,7 @@ public void testDsnProvided() throws Exception { final String dsnUri = "protocol://public:private@host/2"; sentryAppender.setDsn(dsnUri); new Expectations() {{ - ravenFactory.createRavenInstance(withEqual(new Dsn(dsnUri))); + RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); result = mockRaven; }}; diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java index ea300e59127..12d8289f0a7 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java @@ -21,6 +21,8 @@ public class SentryAppenderFailuresTest { private Raven mockRaven = null; @Injectable private Logger mockLogger = null; + @Mocked("ravenInstance") + private RavenFactory mockRavenFactory; @BeforeMethod public void setUp() throws Exception { @@ -46,15 +48,10 @@ public void testRavenFailureDoesNotPropagate() throws Exception { @Test public void testRavenFactoryFailureDoesNotPropagate() throws Exception { - new Expectations() { - @Mocked("ravenInstance") - private RavenFactory ravenFactory; - - { - RavenFactory.ravenInstance((Dsn) any, anyString); - result = new UnsupportedOperationException(); - } - }; + new Expectations() {{ + RavenFactory.ravenInstance((Dsn) any, anyString); + result = new UnsupportedOperationException(); + }}; SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.setDsn("protocol://public:private@host/1"); From c070c58ea8e784d934557eda9ad1b64a8625e969 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 09:00:00 +0200 Subject: [PATCH 0834/2152] Initialise the appender in the double close test --- .../raven/log4j/SentryAppenderClosingConnectionTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java index ba7c314a475..46818307336 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java @@ -118,6 +118,7 @@ public void testCloseDoNotFailIfInitFailed() throws Exception { public void testCloseDoNotFailWhenMultipleCalls() throws Exception { final SentryAppender sentryAppender = new SentryAppender(mockRaven, true); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.activateOptions(); sentryAppender.close(); sentryAppender.close(); From e7af321f14cd93a73410d07593740ded899dfd63 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 09:00:33 +0200 Subject: [PATCH 0835/2152] CHeck that close do not fail if the appender hasn't been initalised --- .../log4j/SentryAppenderClosingConnectionTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java index 46818307336..a5a810c5823 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java @@ -114,6 +114,19 @@ public void testCloseDoNotFailIfInitFailed() throws Exception { }}; } + @Test + public void testCloseDoNotFailIfNoInit() + throws Exception { + SentryAppender sentryAppender = new SentryAppender(); + sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); + + sentryAppender.close(); + + new Verifications() {{ + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + }}; + } + @Test public void testCloseDoNotFailWhenMultipleCalls() throws Exception { final SentryAppender sentryAppender = new SentryAppender(mockRaven, true); From 4899f972977527315702bc70a2c796bcbcc3f5ce Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 09:03:31 +0200 Subject: [PATCH 0836/2152] Rename level conversion test method (log4j) --- .../java/net/kencochrane/raven/log4j/SentryAppenderTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java index 0c681a4d750..202a49ca66d 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java @@ -85,7 +85,7 @@ private Object[][] levelConversions() { } @Test(dataProvider = "levels") - public void assertLevelConverted(final Event.Level expectedLevel, Level level) throws Exception { + public void testLevelConversion(final Event.Level expectedLevel, Level level) throws Exception { sentryAppender.append(new LoggingEvent(null, mockLogger, 0, level, null, null)); new Verifications() {{ From b475375bdaf408143231467a6d519da65f2c5e32 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 09:03:59 +0200 Subject: [PATCH 0837/2152] Use DataProvider for level conversions test --- .../raven/log4j2/SentryAppenderTest.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 4fa7bad5e8c..592f0411847 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -19,6 +19,7 @@ import org.apache.logging.log4j.spi.DefaultThreadContextStack; import org.hamcrest.Matchers; import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.*; @@ -61,17 +62,19 @@ public void testSimpleMessageLogging() throws Exception { }}; } - @Test - public void testLevelConversion() throws Exception { - assertLevelConverted(Event.Level.DEBUG, Level.TRACE); - assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); - assertLevelConverted(Event.Level.INFO, Level.INFO); - assertLevelConverted(Event.Level.WARNING, Level.WARN); - assertLevelConverted(Event.Level.ERROR, Level.ERROR); - assertLevelConverted(Event.Level.FATAL, Level.FATAL); + @DataProvider(name = "levels") + private Object[][] levelConversions() { + return new Object[][]{ + {Event.Level.DEBUG, Level.TRACE}, + {Event.Level.DEBUG, Level.DEBUG}, + {Event.Level.INFO, Level.INFO}, + {Event.Level.WARNING, Level.WARN}, + {Event.Level.ERROR, Level.ERROR}, + {Event.Level.FATAL, Level.FATAL}}; } - private void assertLevelConverted(final Event.Level expectedLevel, Level level) throws Exception { + @Test(dataProvider = "levels") + public void testLevelConversion(final Event.Level expectedLevel, Level level) throws Exception { sentryAppender.append(new Log4jLogEvent(null, null, null, level, new SimpleMessage(""), null)); new Verifications() {{ From 15c3007b5441f9403715a335e3375eaf22c57a64 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 09:09:09 +0200 Subject: [PATCH 0838/2152] Move tests about failures in a dedicated class --- .../log4j2/SentryAppenderFailuresTest.java | 43 +++++++++++++++++++ .../raven/log4j2/SentryAppenderTest.java | 14 ------ 2 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java new file mode 100644 index 00000000000..e990f4ed3ec --- /dev/null +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java @@ -0,0 +1,43 @@ +package net.kencochrane.raven.log4j2; + +import mockit.Injectable; +import mockit.NonStrictExpectations; +import mockit.Verifications; +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.event.Event; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.SimpleMessage; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SentryAppenderFailuresTest { + private SentryAppender sentryAppender; + private MockUpErrorHandler mockUpErrorHandler; + @Injectable + private Raven mockRaven = null; + + @BeforeMethod + public void setUp() { + sentryAppender = new SentryAppender(mockRaven); + mockUpErrorHandler = new MockUpErrorHandler(); + sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + } + + @Test + public void testRavenFailureDoesNotPropagate() throws Exception { + new NonStrictExpectations() {{ + mockRaven.sendEvent((Event) any); + result = new UnsupportedOperationException(); + }}; + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); + + new Verifications() {{ + assertThat(mockUpErrorHandler.getErrorCount(), is(1)); + }}; + } +} diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 592f0411847..234f5cd2449 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -296,20 +296,6 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { } } - @Test - public void testRavenFailureDoesNotPropagate() throws Exception { - new NonStrictExpectations() {{ - mockRaven.sendEvent((Event) any); - result = new UnsupportedOperationException(); - }}; - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); - - new Verifications() {{ - assertThat(mockUpErrorHandler.getErrorCount(), is(1)); - }}; - } - @Test public void testLazyInitialisation(@Injectable final RavenFactory mockRavenFactory) throws Exception { final String dsnUri = "proto://private:public@host/1"; From 382e567fbd2e178b84e08f06d2674b14b53a4bd5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 09:09:46 +0200 Subject: [PATCH 0839/2152] Automatically assert that there is no errors --- .../raven/log4j2/SentryAppenderTest.java | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 234f5cd2449..b3bf3e6a220 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -18,6 +18,7 @@ import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.spi.DefaultThreadContextStack; import org.hamcrest.Matchers; +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -40,6 +41,11 @@ public void setUp() { sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); } + @AfterMethod + public void tearDown() { + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + } + @Test public void testSimpleMessageLogging() throws Exception { final String loggerName = UUID.randomUUID().toString(); @@ -58,7 +64,6 @@ public void testSimpleMessageLogging() throws Exception { assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } @@ -81,7 +86,6 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getLevel(), is(expectedLevel)); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } @@ -100,7 +104,6 @@ public void testExceptionLogging() throws Exception { throwable = exceptionInterface.getThrowable(); assertThat(throwable.getMessage(), is(exception.getMessage())); assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } @@ -122,7 +125,6 @@ public void testLogParametrisedMessage() throws Exception { assertThat(messageInterface.getMessage(), is(messagePattern)); assertThat(messageInterface.getParameters(), is(Arrays.asList(parameters[0].toString(), parameters[1].toString(), null))); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } @@ -137,7 +139,6 @@ public void testMarkerAddedToTag() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getTags(), Matchers.hasEntry(SentryAppender.LOG4J_MARKER, markerName)); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } @@ -153,7 +154,6 @@ public void testMdcAddedToExtra() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } @@ -172,7 +172,6 @@ public void testNdcAddedToExtra() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat((List) event.getExtra().get(SentryAppender.LOG4J_NDC), equalTo(contextStack.asList())); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } @@ -191,7 +190,6 @@ public void testSourceUsedAsStacktrace() throws Exception { .get(StackTraceInterface.STACKTRACE_INTERFACE); assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); assertThat(stackTraceInterface.getStackTrace()[0], is(location)); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } @@ -207,7 +205,6 @@ public void testCulpritWithSource() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is("a.b(c:42)")); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } @@ -222,7 +219,6 @@ public void testCulpritWithoutSource() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is(loggerName)); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } @@ -248,7 +244,6 @@ public void testCloseNotCalled() throws Exception { { connection.close(); times = 0; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } }; } @@ -274,7 +269,6 @@ public void testClose() throws Exception { { connection.close(); times = 1; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } }; } @@ -289,7 +283,6 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { new Verifications() {{ mockRaven.sendEvent((Event) any); times = 0; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } finally { Raven.RAVEN_THREAD.remove(); @@ -313,10 +306,6 @@ public void testLazyInitialisation(@Injectable final RavenFactory mockRavenFacto sentryAppender.start(); sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); - - new Verifications() {{ - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - }}; } @Test @@ -342,7 +331,6 @@ public void testDsnAutoDetection(@Injectable final RavenFactory mockRavenFactory sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); new Verifications() {{ - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } } From 0ed51b4a1db8bb36a82011ca77cb0daec7466d9f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 09:25:25 +0200 Subject: [PATCH 0840/2152] Move dsn discovery tests in a dedicated class --- .../raven/log4j2/SentryAppenderDsnTest.java | 62 +++++++++++++++++++ .../raven/log4j2/SentryAppenderTest.java | 52 ++-------------- 2 files changed, 66 insertions(+), 48 deletions(-) create mode 100644 raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java new file mode 100644 index 00000000000..96b7a948356 --- /dev/null +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java @@ -0,0 +1,62 @@ +package net.kencochrane.raven.log4j2; + +import mockit.Expectations; +import mockit.Injectable; +import mockit.Mocked; +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.dsn.Dsn; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.SimpleMessage; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SentryAppenderDsnTest { + private MockUpErrorHandler mockUpErrorHandler = new MockUpErrorHandler(); + @Injectable + private Raven mockRaven = null; + @Mocked("ravenInstance") + private RavenFactory mockRavenFactory; + @Mocked("dsnLookup") + private Dsn dsn; + + @AfterMethod + public void tearDown() { + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + } + + @Test + public void testLazyInitialisation() throws Exception { + final String dsnUri = "proto://private:public@host/1"; + final SentryAppender sentryAppender = new SentryAppender(); + sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.setDsn(dsnUri); + new Expectations() {{ + RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); + result = mockRaven; + }}; + + sentryAppender.start(); + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); + } + + @Test + public void testDsnAutoDetection() throws Exception { + final String dsnUri = "proto://private:public@host/1"; + final SentryAppender sentryAppender = new SentryAppender(); + sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + new Expectations() {{ + Dsn.dsnLookup(); + result = dsnUri; + RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); + result = mockRaven; + }}; + + sentryAppender.start(); + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); + } +} diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index b3bf3e6a220..971b939ce21 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -1,10 +1,11 @@ package net.kencochrane.raven.log4j2; -import mockit.*; +import mockit.Injectable; +import mockit.Mocked; +import mockit.NonStrictExpectations; +import mockit.Verifications; import net.kencochrane.raven.Raven; -import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.connection.Connection; -import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; @@ -288,49 +289,4 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { Raven.RAVEN_THREAD.remove(); } } - - @Test - public void testLazyInitialisation(@Injectable final RavenFactory mockRavenFactory) throws Exception { - final String dsnUri = "proto://private:public@host/1"; - RavenFactory.registerFactory(mockRavenFactory); - sentryAppender = new SentryAppender(); - sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.setDsn(dsnUri); - sentryAppender.setRavenFactory(mockRavenFactory.getClass().getName()); - new Expectations() { - { - mockRavenFactory.createRavenInstance(withEqual(new Dsn(dsnUri))); - result = mockRaven; - } - }; - - sentryAppender.start(); - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); - } - - @Test - public void testDsnAutoDetection(@Injectable final RavenFactory mockRavenFactory) throws Exception { - final String dsnUri = "proto://private:public@host/1"; - RavenFactory.registerFactory(mockRavenFactory); - sentryAppender = new SentryAppender(); - sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.setRavenFactory(mockRavenFactory.getClass().getName()); - new Expectations() { - @Mocked("dsnLookup") - private Dsn dsn; - - { - Dsn.dsnLookup(); - result = dsnUri; - mockRavenFactory.createRavenInstance(withEqual(new Dsn(dsnUri))); - result = mockRaven; - } - }; - - sentryAppender.start(); - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); - - new Verifications() {{ - }}; - } } From 1b7144142c630efa62c53e6151357107ff405ba3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 09:38:01 +0200 Subject: [PATCH 0841/2152] Use NonStrict injectable rather than having NonStrictExpectations --- .../raven/log4j/SentryAppenderFailuresTest.java | 13 +++++++------ .../raven/log4j2/SentryAppenderFailuresTest.java | 11 +++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java index 12d8289f0a7..fbe2dc7fdb6 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java @@ -1,6 +1,9 @@ package net.kencochrane.raven.log4j; -import mockit.*; +import mockit.Expectations; +import mockit.Injectable; +import mockit.Mocked; +import mockit.NonStrict; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; @@ -18,6 +21,7 @@ public class SentryAppenderFailuresTest { private SentryAppender sentryAppender; private MockUpErrorHandler mockUpErrorHandler; @Injectable + @NonStrict private Raven mockRaven = null; @Injectable private Logger mockLogger = null; @@ -33,17 +37,14 @@ public void setUp() throws Exception { @Test public void testRavenFailureDoesNotPropagate() throws Exception { - new NonStrictExpectations() {{ + new Expectations() {{ mockRaven.sendEvent((Event) any); result = new UnsupportedOperationException(); }}; sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); - new Verifications() {{ - mockRaven.sendEvent((Event) any); - assertThat(mockUpErrorHandler.getErrorCount(), is(1)); - }}; + assertThat(mockUpErrorHandler.getErrorCount(), is(1)); } @Test diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java index e990f4ed3ec..5fdeb30d877 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java @@ -1,8 +1,8 @@ package net.kencochrane.raven.log4j2; +import mockit.Expectations; import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Verifications; +import mockit.NonStrict; import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import org.apache.logging.log4j.Level; @@ -18,6 +18,7 @@ public class SentryAppenderFailuresTest { private SentryAppender sentryAppender; private MockUpErrorHandler mockUpErrorHandler; @Injectable + @NonStrict private Raven mockRaven = null; @BeforeMethod @@ -29,15 +30,13 @@ public void setUp() { @Test public void testRavenFailureDoesNotPropagate() throws Exception { - new NonStrictExpectations() {{ + new Expectations() {{ mockRaven.sendEvent((Event) any); result = new UnsupportedOperationException(); }}; sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); - new Verifications() {{ - assertThat(mockUpErrorHandler.getErrorCount(), is(1)); - }}; + assertThat(mockUpErrorHandler.getErrorCount(), is(1)); } } From f848989ec6d262c9c14d777908be283abf4f6db2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 09:42:41 +0200 Subject: [PATCH 0842/2152] Move close test in a dedicated class --- .../raven/log4j2/SentryAppenderCloseTest.java | 60 +++++++++++++++++++ .../raven/log4j2/SentryAppenderTest.java | 53 ---------------- 2 files changed, 60 insertions(+), 53 deletions(-) create mode 100644 raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java new file mode 100644 index 00000000000..7c4db7fb238 --- /dev/null +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java @@ -0,0 +1,60 @@ +package net.kencochrane.raven.log4j2; + +import mockit.Injectable; +import mockit.NonStrictExpectations; +import mockit.Verifications; +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.connection.Connection; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SentryAppenderCloseTest { + private MockUpErrorHandler mockUpErrorHandler; + @Injectable + private Connection mockConnection = null; + @Injectable + private Raven mockRaven = null; + + @BeforeMethod + public void setUp() { + mockUpErrorHandler = new MockUpErrorHandler(); + new NonStrictExpectations() {{ + mockRaven.getConnection(); + result = mockConnection; + }}; + } + + @AfterMethod + public void tearDown() { + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + } + + @Test + public void testCloseNotCalled() throws Exception { + SentryAppender sentryAppender = new SentryAppender(mockRaven, false); + sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + + sentryAppender.stop(); + + new Verifications() {{ + mockConnection.close(); + times = 0; + }}; + } + + @Test + public void testClose() throws Exception { + SentryAppender sentryAppender = new SentryAppender(mockRaven, true); + sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + + sentryAppender.stop(); + + new Verifications() {{ + mockConnection.close(); + }}; + } +} diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java index 971b939ce21..57e39071b22 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java @@ -1,11 +1,8 @@ package net.kencochrane.raven.log4j2; import mockit.Injectable; -import mockit.Mocked; -import mockit.NonStrictExpectations; import mockit.Verifications; import net.kencochrane.raven.Raven; -import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; @@ -224,56 +221,6 @@ public void testCulpritWithoutSource() throws Exception { }}; } - @Test - public void testCloseNotCalled() throws Exception { - sentryAppender = new SentryAppender(mockRaven, false); - new NonStrictExpectations() { - @Mocked - private Connection connection; - - { - mockRaven.getConnection(); - result = connection; - } - }; - - sentryAppender.stop(); - - new Verifications() { - private Connection connection; - - { - connection.close(); - times = 0; - } - }; - } - - @Test - public void testClose() throws Exception { - sentryAppender = new SentryAppender(mockRaven, true); - new NonStrictExpectations() { - @Mocked - private Connection connection; - - { - mockRaven.getConnection(); - result = connection; - } - }; - - sentryAppender.stop(); - - new Verifications() { - private Connection connection; - - { - connection.close(); - times = 1; - } - }; - } - @Test public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { try { From e4d49024179f1a78f5fbe25fa3bc8cb05119d6c1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 09:47:04 +0200 Subject: [PATCH 0843/2152] Do not use a "global" test file for appender tests --- ...a => SentryAppenderEventBuildingTest.java} | 17 +--------------- .../log4j/SentryAppenderFailuresTest.java | 20 +++++++++++++++---- ...a => SentryAppenderEventBuildingTest.java} | 18 +---------------- .../log4j2/SentryAppenderFailuresTest.java | 17 ++++++++++++++++ 4 files changed, 35 insertions(+), 37 deletions(-) rename raven-log4j/src/test/java/net/kencochrane/raven/log4j/{SentryAppenderTest.java => SentryAppenderEventBuildingTest.java} (94%) rename raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/{SentryAppenderTest.java => SentryAppenderEventBuildingTest.java} (94%) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java similarity index 94% rename from raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java rename to raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java index 202a49ca66d..1026baf201b 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java @@ -28,7 +28,7 @@ import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.is; -public class SentryAppenderTest { +public class SentryAppenderEventBuildingTest { private SentryAppender sentryAppender; private MockUpErrorHandler mockUpErrorHandler; @Injectable @@ -218,19 +218,4 @@ public void testCulpritWithoutSource() throws Exception { assertThat(event.getCulprit(), is(loggerName)); }}; } - - @Test - public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { - try { - Raven.RAVEN_THREAD.set(true); - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); - - new Verifications() {{ - mockRaven.sendEvent((Event) any); - times = 0; - }}; - } finally { - Raven.RAVEN_THREAD.remove(); - } - } } diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java index fbe2dc7fdb6..a4a7c80888f 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java @@ -1,9 +1,6 @@ package net.kencochrane.raven.log4j; -import mockit.Expectations; -import mockit.Injectable; -import mockit.Mocked; -import mockit.NonStrict; +import mockit.*; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; @@ -61,4 +58,19 @@ public void testRavenFactoryFailureDoesNotPropagate() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(1)); } + + @Test + public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { + try { + Raven.RAVEN_THREAD.set(true); + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); + + new Verifications() {{ + mockRaven.sendEvent((Event) any); + times = 0; + }}; + } finally { + Raven.RAVEN_THREAD.remove(); + } + } } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java similarity index 94% rename from raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java rename to raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java index 57e39071b22..c72f20f02b7 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java @@ -26,7 +26,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -public class SentryAppenderTest { +public class SentryAppenderEventBuildingTest { private SentryAppender sentryAppender; private MockUpErrorHandler mockUpErrorHandler; @Injectable @@ -220,20 +220,4 @@ public void testCulpritWithoutSource() throws Exception { }}; } - - @Test - public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { - try { - Raven.RAVEN_THREAD.set(true); - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); - - new Verifications() {{ - mockRaven.sendEvent((Event) any); - times = 0; - }}; - } finally { - Raven.RAVEN_THREAD.remove(); - } - } } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java index 5fdeb30d877..493cd1cae1f 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java @@ -3,6 +3,7 @@ import mockit.Expectations; import mockit.Injectable; import mockit.NonStrict; +import mockit.Verifications; import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import org.apache.logging.log4j.Level; @@ -39,4 +40,20 @@ public void testRavenFailureDoesNotPropagate() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(1)); } + + @Test + public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { + try { + Raven.RAVEN_THREAD.set(true); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); + + new Verifications() {{ + mockRaven.sendEvent((Event) any); + times = 0; + }}; + } finally { + Raven.RAVEN_THREAD.remove(); + } + } } From 2eb506e71276b847105f1604c653be9906c14ac6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 09:48:17 +0200 Subject: [PATCH 0844/2152] Rename ClosingConnectionTest in close test --- ...rClosingConnectionTest.java => SentryAppenderCloseTest.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename raven-log4j/src/test/java/net/kencochrane/raven/log4j/{SentryAppenderClosingConnectionTest.java => SentryAppenderCloseTest.java} (98%) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java similarity index 98% rename from raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java rename to raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java index a5a810c5823..d8e0a2a6472 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderClosingConnectionTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java @@ -11,7 +11,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -public class SentryAppenderClosingConnectionTest { +public class SentryAppenderCloseTest { private MockUpErrorHandler mockUpErrorHandler; @Injectable private Raven mockRaven = null; From 32929664f21dd93372042a9705e955473e6fca2b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 09:49:55 +0200 Subject: [PATCH 0845/2152] Print error messages with MockUpErrorHandler --- .../java/net/kencochrane/raven/log4j/MockUpErrorHandler.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java index c504503d732..2e39c0e9924 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java @@ -11,16 +11,20 @@ public class MockUpErrorHandler extends MockUp { @Mock public void error(String message, Exception e, int errorCode) { errorCount++; + System.err.println(message); } @Mock public void error(String message) { errorCount++; + System.err.println(message); } @Mock public void error(String message, Exception e, int errorCode, LoggingEvent event) { errorCount++; + System.err.println(message); + e.printStackTrace(System.err); } public int getErrorCount() { From 9f7cee0b9561a834f610b0c0e3209e76663e492e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 09:51:43 +0200 Subject: [PATCH 0846/2152] Add assertions on the error count --- .../net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java | 1 + .../net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java | 1 + 2 files changed, 2 insertions(+) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java index a4a7c80888f..b41ed96d6e0 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java @@ -68,6 +68,7 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { new Verifications() {{ mockRaven.sendEvent((Event) any); times = 0; + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } finally { Raven.RAVEN_THREAD.remove(); diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java index 493cd1cae1f..702048931e2 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java @@ -51,6 +51,7 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { new Verifications() {{ mockRaven.sendEvent((Event) any); times = 0; + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; } finally { Raven.RAVEN_THREAD.remove(); From 6af94a6f657f1dd3247be9d230a007b7fba07505 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 10:07:32 +0200 Subject: [PATCH 0847/2152] Fix tests style --- .../kencochrane/raven/log4j/SentryAppenderCloseTest.java | 6 +++--- .../kencochrane/raven/log4j/SentryAppenderDsnTest.java | 2 +- .../raven/log4j/SentryAppenderEventBuildingTest.java | 4 ++-- .../kencochrane/raven/log4j2/SentryAppenderCloseTest.java | 8 ++++---- .../kencochrane/raven/log4j2/SentryAppenderDsnTest.java | 2 +- .../raven/log4j2/SentryAppenderEventBuildingTest.java | 4 ++-- .../raven/log4j2/SentryAppenderFailuresTest.java | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java index d8e0a2a6472..4f170d53d17 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java @@ -23,7 +23,7 @@ public class SentryAppenderCloseTest { private Dsn mockDsn; @BeforeMethod - public void setUp() { + public void setUp() throws Exception { mockUpErrorHandler = new MockUpErrorHandler(); new NonStrictExpectations() {{ mockRaven.getConnection(); @@ -99,7 +99,7 @@ public void testClosedIfRavenInstanceNotProvided() throws Exception { @Test public void testCloseDoNotFailIfInitFailed() throws Exception { // This checks that even if sentry wasn't setup correctly its appender can still be closed. - SentryAppender sentryAppender = new SentryAppender(); + final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); new NonStrictExpectations() {{ RavenFactory.ravenInstance((Dsn) any, anyString); @@ -117,7 +117,7 @@ public void testCloseDoNotFailIfInitFailed() throws Exception { @Test public void testCloseDoNotFailIfNoInit() throws Exception { - SentryAppender sentryAppender = new SentryAppender(); + final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.close(); diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java index b5ecd32f951..146d6c24275 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java @@ -29,7 +29,7 @@ public class SentryAppenderDsnTest { private Dsn mockDsn; @BeforeMethod - public void setUp() { + public void setUp() throws Exception { sentryAppender = new SentryAppender(); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java index 1026baf201b..8322c57034c 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java @@ -37,14 +37,14 @@ public class SentryAppenderEventBuildingTest { private Logger mockLogger = null; @BeforeMethod - public void setUp() { + public void setUp() throws Exception { sentryAppender = new SentryAppender(mockRaven); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); } @AfterMethod - public void tearDown() { + public void tearDown() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java index 7c4db7fb238..52b933b3148 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java @@ -20,7 +20,7 @@ public class SentryAppenderCloseTest { private Raven mockRaven = null; @BeforeMethod - public void setUp() { + public void setUp() throws Exception { mockUpErrorHandler = new MockUpErrorHandler(); new NonStrictExpectations() {{ mockRaven.getConnection(); @@ -29,13 +29,13 @@ public void setUp() { } @AfterMethod - public void tearDown() { + public void tearDown() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test public void testCloseNotCalled() throws Exception { - SentryAppender sentryAppender = new SentryAppender(mockRaven, false); + final SentryAppender sentryAppender = new SentryAppender(mockRaven, false); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.stop(); @@ -48,7 +48,7 @@ public void testCloseNotCalled() throws Exception { @Test public void testClose() throws Exception { - SentryAppender sentryAppender = new SentryAppender(mockRaven, true); + final SentryAppender sentryAppender = new SentryAppender(mockRaven, true); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.stop(); diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java index 96b7a948356..78bfe9ee791 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java @@ -25,7 +25,7 @@ public class SentryAppenderDsnTest { private Dsn dsn; @AfterMethod - public void tearDown() { + public void tearDown() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java index c72f20f02b7..6dff5ce1987 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java @@ -33,14 +33,14 @@ public class SentryAppenderEventBuildingTest { private Raven mockRaven = null; @BeforeMethod - public void setUp() { + public void setUp() throws Exception { sentryAppender = new SentryAppender(mockRaven); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); } @AfterMethod - public void tearDown() { + public void tearDown() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java index 702048931e2..9310db382bb 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java @@ -23,7 +23,7 @@ public class SentryAppenderFailuresTest { private Raven mockRaven = null; @BeforeMethod - public void setUp() { + public void setUp() throws Exception { sentryAppender = new SentryAppender(mockRaven); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); From c6f66e04285cf9ed865aa8be555f44c53627c795 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 10:08:35 +0200 Subject: [PATCH 0848/2152] Add assert no error as tearDown --- .../raven/log4j/SentryAppenderDsnTest.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java index 146d6c24275..391e2a661fd 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java @@ -3,13 +3,13 @@ import mockit.Expectations; import mockit.Injectable; import mockit.Mocked; -import mockit.Verifications; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.spi.LoggingEvent; +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -35,6 +35,11 @@ public void setUp() throws Exception { sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); } + @AfterMethod + public void tearDown() throws Exception { + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + } + @Test public void testDsnDetected() throws Exception { final String dsnUri = "protocol://public:private@host/1"; @@ -47,10 +52,6 @@ public void testDsnDetected() throws Exception { sentryAppender.activateOptions(); sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); - - new Verifications() {{ - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - }}; } @Test @@ -64,9 +65,5 @@ public void testDsnProvided() throws Exception { sentryAppender.activateOptions(); sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); - - new Verifications() {{ - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - }}; } } From 616202dec7f01db40fef8e3f90d0585a9354eda6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 10:09:27 +0200 Subject: [PATCH 0849/2152] Start appender properly before testing it --- .../raven/log4j/SentryAppenderDsnTest.java | 11 ++--------- .../raven/log4j/SentryAppenderEventBuildingTest.java | 1 + .../raven/log4j/SentryAppenderFailuresTest.java | 1 + .../raven/log4j2/SentryAppenderCloseTest.java | 2 ++ .../raven/log4j2/SentryAppenderDsnTest.java | 9 ++------- 5 files changed, 8 insertions(+), 16 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java index 391e2a661fd..d9e8e78259d 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java @@ -6,9 +6,6 @@ import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.apache.log4j.spi.LoggingEvent; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -21,8 +18,6 @@ public class SentryAppenderDsnTest { private MockUpErrorHandler mockUpErrorHandler; @Injectable private Raven mockRaven = null; - @Injectable - private Logger mockLogger = null; @Mocked("ravenInstance") private RavenFactory mockRavenFactory; @Mocked("dsnLookup") @@ -50,8 +45,7 @@ public void testDsnDetected() throws Exception { result = mockRaven; }}; - sentryAppender.activateOptions(); - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); + sentryAppender.initRaven(); } @Test @@ -63,7 +57,6 @@ public void testDsnProvided() throws Exception { result = mockRaven; }}; - sentryAppender.activateOptions(); - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); + sentryAppender.initRaven(); } } diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java index 8322c57034c..cc6f8cfd74a 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java @@ -41,6 +41,7 @@ public void setUp() throws Exception { sentryAppender = new SentryAppender(mockRaven); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.activateOptions(); } @AfterMethod diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java index b41ed96d6e0..27e0741f957 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java @@ -30,6 +30,7 @@ public void setUp() throws Exception { sentryAppender = new SentryAppender(mockRaven); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.activateOptions(); } @Test diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java index 52b933b3148..c2ad6c61678 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java @@ -37,6 +37,7 @@ public void tearDown() throws Exception { public void testCloseNotCalled() throws Exception { final SentryAppender sentryAppender = new SentryAppender(mockRaven, false); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.start(); sentryAppender.stop(); @@ -50,6 +51,7 @@ public void testCloseNotCalled() throws Exception { public void testClose() throws Exception { final SentryAppender sentryAppender = new SentryAppender(mockRaven, true); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.start(); sentryAppender.stop(); diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java index 78bfe9ee791..6d4ccee41ac 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java @@ -6,9 +6,6 @@ import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.message.SimpleMessage; import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; @@ -40,8 +37,7 @@ public void testLazyInitialisation() throws Exception { result = mockRaven; }}; - sentryAppender.start(); - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); + sentryAppender.initRaven(); } @Test @@ -56,7 +52,6 @@ public void testDsnAutoDetection() throws Exception { result = mockRaven; }}; - sentryAppender.start(); - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); + sentryAppender.initRaven(); } } From 339f974e73e049ec421d658feb71bd2bfc7f685d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 16:44:34 +0200 Subject: [PATCH 0850/2152] Call ActivateOptions before running a test --- .../net/kencochrane/raven/log4j/SentryAppenderCloseTest.java | 1 + .../net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java index 4f170d53d17..b97cceae244 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java @@ -119,6 +119,7 @@ public void testCloseDoNotFailIfNoInit() throws Exception { final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.activateOptions(); sentryAppender.close(); diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java index 27e0741f957..952c3730f78 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java @@ -55,7 +55,7 @@ public void testRavenFactoryFailureDoesNotPropagate() throws Exception { sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.setDsn("protocol://public:private@host/1"); - sentryAppender.activateOptions(); + sentryAppender.initRaven(); assertThat(mockUpErrorHandler.getErrorCount(), is(1)); } From 87c290b68bda129efbac55d3a2f94733d927c212 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 16:46:49 +0200 Subject: [PATCH 0851/2152] Add test to check that RavenFactory exception do not propagate --- .../log4j2/SentryAppenderFailuresTest.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java index 9310db382bb..8b57d93bfc2 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java @@ -1,10 +1,9 @@ package net.kencochrane.raven.log4j2; -import mockit.Expectations; -import mockit.Injectable; -import mockit.NonStrict; -import mockit.Verifications; +import mockit.*; import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.impl.Log4jLogEvent; @@ -21,6 +20,8 @@ public class SentryAppenderFailuresTest { @Injectable @NonStrict private Raven mockRaven = null; + @Mocked("ravenInstance") + private RavenFactory mockRavenFactory; @BeforeMethod public void setUp() throws Exception { @@ -41,6 +42,21 @@ public void testRavenFailureDoesNotPropagate() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(1)); } + @Test + public void testRavenFactoryFailureDoesNotPropagate() throws Exception { + new Expectations() {{ + RavenFactory.ravenInstance((Dsn) any, anyString); + result = new UnsupportedOperationException(); + }}; + SentryAppender sentryAppender = new SentryAppender(); + sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.setDsn("protocol://public:private@host/1"); + + sentryAppender.initRaven(); + + assertThat(mockUpErrorHandler.getErrorCount(), is(1)); + } + @Test public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { try { From 70dfa5a8f6fc043d7fce91c1a6ad319891d0636d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 17:19:53 +0200 Subject: [PATCH 0852/2152] Remove unecessary call to activateOptions --- .../net/kencochrane/raven/log4j/SentryAppenderCloseTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java index b97cceae244..4f170d53d17 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java @@ -119,7 +119,6 @@ public void testCloseDoNotFailIfNoInit() throws Exception { final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.activateOptions(); sentryAppender.close(); From 1b78f0060c660f531ec56aa3094825af77653407 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 20 Aug 2013 17:20:11 +0200 Subject: [PATCH 0853/2152] Flush error stream in ErrorHandler --- .../java/net/kencochrane/raven/log4j/MockUpErrorHandler.java | 4 ++++ .../java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java index 2e39c0e9924..882f3e5d85d 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java @@ -12,12 +12,15 @@ public class MockUpErrorHandler extends MockUp { public void error(String message, Exception e, int errorCode) { errorCount++; System.err.println(message); + e.printStackTrace(System.err); + System.err.flush(); } @Mock public void error(String message) { errorCount++; System.err.println(message); + System.err.flush(); } @Mock @@ -25,6 +28,7 @@ public void error(String message, Exception e, int errorCode, LoggingEvent event errorCount++; System.err.println(message); e.printStackTrace(System.err); + System.err.flush(); } public int getErrorCount() { diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java index d999bfc16d5..d448444debd 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java @@ -12,6 +12,7 @@ public class MockUpErrorHandler extends MockUp { public void error(String msg) { errorCount++; System.err.println(msg); + System.err.flush(); } @Mock @@ -19,6 +20,7 @@ public void error(String msg, Throwable t) { errorCount++; System.err.println(msg); t.printStackTrace(System.err); + System.err.flush(); } @Mock @@ -26,7 +28,7 @@ public void error(String msg, LogEvent event, Throwable t) { errorCount++; System.err.println(msg); t.printStackTrace(System.err); - + System.err.flush(); } public int getErrorCount() { From c298f865677b2fb7a50526e68c8dfbee3ea85ef6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 21 Aug 2013 00:09:18 +0200 Subject: [PATCH 0854/2152] Properly catch exceptions during the initRaven call --- .../raven/log4j2/SentryAppender.java | 18 +++++++++++++----- .../raven/logback/SentryAppender.java | 19 +++++++++++++------ .../kencochrane/raven/jul/SentryHandler.java | 3 +++ 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 757ad263b36..49242b7d533 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -3,6 +3,7 @@ import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; +import net.kencochrane.raven.dsn.InvalidDsnException; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; @@ -168,9 +169,10 @@ public void append(LogEvent logEvent) { if (Raven.RAVEN_THREAD.get()) return; + if (raven == null) + initRaven(); + try { - if (raven == null) - initRaven(); Event event = buildEvent(logEvent); raven.sendEvent(event); } catch (Exception e) { @@ -182,10 +184,16 @@ public void append(LogEvent logEvent) { * Initialises the Raven instance. */ protected void initRaven() { - if (dsn == null) - dsn = Dsn.dsnLookup(); + try { + if (dsn == null) + dsn = Dsn.dsnLookup(); - raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + } catch (InvalidDsnException e) { + error("An exception occurred during the retrieval of the DSN for Raven", e); + } catch (Exception e) { + error("An exception occurred during the creation of a Raven instance", e); + } } /** diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index c3ab4a59753..0d3f9cd69f1 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -7,6 +7,7 @@ import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; +import net.kencochrane.raven.dsn.InvalidDsnException; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; @@ -115,10 +116,10 @@ protected void append(ILoggingEvent iLoggingEvent) { if (Raven.RAVEN_THREAD.get()) return; - try { - if (raven == null) - initRaven(); + if (raven == null) + initRaven(); + try { Event event = buildEvent(iLoggingEvent); raven.sendEvent(event); } catch (Exception e) { @@ -130,10 +131,16 @@ protected void append(ILoggingEvent iLoggingEvent) { * Initialises the Raven instance. */ protected void initRaven() { - if (dsn == null) - dsn = Dsn.dsnLookup(); + try { + if (dsn == null) + dsn = Dsn.dsnLookup(); - raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + } catch (InvalidDsnException e) { + addError("An exception occurred during the retrieval of the DSN for Raven", e); + } catch (Exception e) { + addError("An exception occurred during the creation of a Raven instance", e); + } } /** diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index bffe28dd484..dc4bae08bb5 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -3,6 +3,7 @@ import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; +import net.kencochrane.raven.dsn.InvalidDsnException; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; @@ -128,6 +129,8 @@ protected void initRaven() { dsn = Dsn.dsnLookup(); raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + } catch (InvalidDsnException e) { + reportError("An exception occurred during the retrieval of the DSN for Raven", e, ErrorManager.OPEN_FAILURE); } catch (Exception e) { reportError("An exception occurred during the creation of a Raven instance", e, ErrorManager.OPEN_FAILURE); } From 26f063c96b672b4343d03d816adf96cca1cda982 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 21 Aug 2013 00:27:15 +0200 Subject: [PATCH 0855/2152] Use initRaven for init tests --- .../java/net/kencochrane/raven/logback/SentryAppenderTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java index 95a718cd918..cd65e36b372 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java @@ -286,7 +286,7 @@ public void testFailedInitialisation() throws Exception { sentryAppender.setDsn(dsnUri); sentryAppender.setRavenFactory("invalid factory"); - sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null)); + sentryAppender.initRaven(); assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(1)); } From 7a9efee515ea91ba4e98cd66f09f0aff01846ca2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 31 Aug 2013 21:37:35 +0200 Subject: [PATCH 0856/2152] Assert outside of the Verification block if possible --- .../raven/log4j/SentryAppenderCloseTest.java | 22 +++++++++---------- .../log4j/SentryAppenderFailuresTest.java | 2 +- .../raven/log4j2/SentryAppenderCloseTest.java | 6 ++--- .../log4j2/SentryAppenderFailuresTest.java | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java index 4f170d53d17..99cfd8433b5 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java @@ -31,6 +31,10 @@ public void setUp() throws Exception { }}; } + private void assertNoErrorsInErrorHandler() throws Exception { + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + } + @Test public void testNotClosedIfRavenInstanceIsProvided() throws Exception { final SentryAppender sentryAppender = new SentryAppender(mockRaven); @@ -42,8 +46,8 @@ public void testNotClosedIfRavenInstanceIsProvided() throws Exception { new Verifications() {{ mockConnection.close(); times = 0; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; + assertNoErrorsInErrorHandler(); } @Test @@ -56,8 +60,8 @@ public void testClosedIfRavenInstanceProvidedAndForceClose() throws Exception { new Verifications() {{ mockConnection.close(); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; + assertNoErrorsInErrorHandler(); } @Test @@ -71,8 +75,8 @@ public void testNotClosedIfRavenInstanceProvidedAndNotForceClose() throws Except new Verifications() {{ mockConnection.close(); times = 0; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; + assertNoErrorsInErrorHandler(); } @Test @@ -92,8 +96,8 @@ public void testClosedIfRavenInstanceNotProvided() throws Exception { new Verifications() {{ mockConnection.close(); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; + assertNoErrorsInErrorHandler(); } @Test @@ -109,9 +113,7 @@ public void testCloseDoNotFailIfInitFailed() throws Exception { sentryAppender.close(); - new Verifications() {{ - assertThat(mockUpErrorHandler.getErrorCount(), is(1)); - }}; + assertThat(mockUpErrorHandler.getErrorCount(), is(1)); } @Test @@ -122,9 +124,7 @@ public void testCloseDoNotFailIfNoInit() sentryAppender.close(); - new Verifications() {{ - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - }}; + assertNoErrorsInErrorHandler(); } @Test @@ -139,7 +139,7 @@ public void testCloseDoNotFailWhenMultipleCalls() throws Exception { new Verifications() {{ mockConnection.close(); times = 1; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; + assertNoErrorsInErrorHandler(); } } diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java index 952c3730f78..da4ab1d00a0 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java @@ -69,8 +69,8 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { new Verifications() {{ mockRaven.sendEvent((Event) any); times = 0; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } finally { Raven.RAVEN_THREAD.remove(); } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java index c2ad6c61678..9fa6ff47c10 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java @@ -5,7 +5,6 @@ import mockit.Verifications; import net.kencochrane.raven.Raven; import net.kencochrane.raven.connection.Connection; -import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -28,8 +27,7 @@ public void setUp() throws Exception { }}; } - @AfterMethod - public void tearDown() throws Exception { + private void assertNoErrorsInErrorHandler() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @@ -45,6 +43,7 @@ public void testCloseNotCalled() throws Exception { mockConnection.close(); times = 0; }}; + assertNoErrorsInErrorHandler(); } @Test @@ -58,5 +57,6 @@ public void testClose() throws Exception { new Verifications() {{ mockConnection.close(); }}; + assertNoErrorsInErrorHandler(); } } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java index 8b57d93bfc2..96866a7c97e 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java @@ -67,8 +67,8 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { new Verifications() {{ mockRaven.sendEvent((Event) any); times = 0; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); }}; + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } finally { Raven.RAVEN_THREAD.remove(); } From 236e4b770ee24f14b2e4cb0a2a2d1ca0a78fc8b1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 31 Aug 2013 22:12:44 +0200 Subject: [PATCH 0857/2152] Do not assert in tearDown --- .../raven/log4j/SentryAppenderDsnTest.java | 8 +++++--- .../log4j/SentryAppenderEventBuildingTest.java | 12 +++++++++--- .../raven/log4j2/SentryAppenderDsnTest.java | 8 +++++--- .../log4j2/SentryAppenderEventBuildingTest.java | 17 +++++++++++------ 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java index d9e8e78259d..0235875096c 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java @@ -6,7 +6,6 @@ import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; -import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -30,8 +29,7 @@ public void setUp() throws Exception { sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); } - @AfterMethod - public void tearDown() throws Exception { + private void assertNoErrorsInErrorHandler() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @@ -46,6 +44,8 @@ public void testDsnDetected() throws Exception { }}; sentryAppender.initRaven(); + + assertNoErrorsInErrorHandler(); } @Test @@ -58,5 +58,7 @@ public void testDsnProvided() throws Exception { }}; sentryAppender.initRaven(); + + assertNoErrorsInErrorHandler(); } } diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java index cc6f8cfd74a..0d9057c2411 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java @@ -15,7 +15,6 @@ import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; import org.hamcrest.Matchers; -import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -44,8 +43,7 @@ public void setUp() throws Exception { sentryAppender.activateOptions(); } - @AfterMethod - public void tearDown() throws Exception { + private void assertNoErrorsInErrorHandler() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @@ -72,6 +70,7 @@ public void testSimpleMessageLogging() throws Exception { assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); }}; + assertNoErrorsInErrorHandler(); } @DataProvider(name = "levels") @@ -94,6 +93,7 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th mockRaven.sendEvent(event = withCapture()); assertThat(event.getLevel(), is(expectedLevel)); }}; + assertNoErrorsInErrorHandler(); } @Test @@ -112,6 +112,7 @@ public void testExceptionLogging() throws Exception { assertThat(throwable.getMessage(), is(exception.getMessage())); assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); }}; + assertNoErrorsInErrorHandler(); } @Test @@ -127,6 +128,7 @@ public void testMdcAddedToExtra() throws Exception { mockRaven.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); }}; + assertNoErrorsInErrorHandler(); } @Test @@ -141,6 +143,7 @@ public void testNdcAddedToExtra() throws Exception { mockRaven.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.LOG4J_NDC, ndcEntries)); }}; + assertNoErrorsInErrorHandler(); } @Test @@ -173,6 +176,7 @@ public void testSourceUsedAsStacktrace(@Injectable final LocationInfo locationIn assertThat(stackTraceInterface.getStackTrace()[0], is(new StackTraceElement(className, methodName, fileName, line))); }}; + assertNoErrorsInErrorHandler(); } @Test @@ -201,6 +205,7 @@ public void testCulpritWithSource(@Injectable final LocationInfo locationInfo) t mockRaven.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is("a.b(c:42)")); }}; + assertNoErrorsInErrorHandler(); } @Test @@ -218,5 +223,6 @@ public void testCulpritWithoutSource() throws Exception { mockRaven.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is(loggerName)); }}; + assertNoErrorsInErrorHandler(); } } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java index 6d4ccee41ac..92e4e45fb9c 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java @@ -6,7 +6,6 @@ import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; -import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; import static org.hamcrest.MatcherAssert.assertThat; @@ -21,8 +20,7 @@ public class SentryAppenderDsnTest { @Mocked("dsnLookup") private Dsn dsn; - @AfterMethod - public void tearDown() throws Exception { + private void assertNoErrorsInErrorHandler() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @@ -38,6 +36,8 @@ public void testLazyInitialisation() throws Exception { }}; sentryAppender.initRaven(); + + assertNoErrorsInErrorHandler(); } @Test @@ -53,5 +53,7 @@ public void testDsnAutoDetection() throws Exception { }}; sentryAppender.initRaven(); + + assertNoErrorsInErrorHandler(); } } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java index 6dff5ce1987..a7b3759c8fa 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java @@ -16,7 +16,6 @@ import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.spi.DefaultThreadContextStack; import org.hamcrest.Matchers; -import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -39,8 +38,7 @@ public void setUp() throws Exception { sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); } - @AfterMethod - public void tearDown() throws Exception { + private void assertNoErrorsInErrorHandler() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @@ -63,6 +61,7 @@ public void testSimpleMessageLogging() throws Exception { assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); }}; + assertNoErrorsInErrorHandler(); } @DataProvider(name = "levels") @@ -85,6 +84,7 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th mockRaven.sendEvent(event = withCapture()); assertThat(event.getLevel(), is(expectedLevel)); }}; + assertNoErrorsInErrorHandler(); } @Test @@ -103,6 +103,7 @@ public void testExceptionLogging() throws Exception { assertThat(throwable.getMessage(), is(exception.getMessage())); assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); }}; + assertNoErrorsInErrorHandler(); } @Test @@ -124,6 +125,7 @@ public void testLogParametrisedMessage() throws Exception { assertThat(messageInterface.getParameters(), is(Arrays.asList(parameters[0].toString(), parameters[1].toString(), null))); }}; + assertNoErrorsInErrorHandler(); } @Test @@ -138,6 +140,7 @@ public void testMarkerAddedToTag() throws Exception { mockRaven.sendEvent(event = withCapture()); assertThat(event.getTags(), Matchers.hasEntry(SentryAppender.LOG4J_MARKER, markerName)); }}; + assertNoErrorsInErrorHandler(); } @Test @@ -153,6 +156,7 @@ public void testMdcAddedToExtra() throws Exception { mockRaven.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); }}; + assertNoErrorsInErrorHandler(); } @Test @@ -171,6 +175,7 @@ public void testNdcAddedToExtra() throws Exception { mockRaven.sendEvent(event = withCapture()); assertThat((List) event.getExtra().get(SentryAppender.LOG4J_NDC), equalTo(contextStack.asList())); }}; + assertNoErrorsInErrorHandler(); } @Test @@ -188,8 +193,8 @@ public void testSourceUsedAsStacktrace() throws Exception { .get(StackTraceInterface.STACKTRACE_INTERFACE); assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); assertThat(stackTraceInterface.getStackTrace()[0], is(location)); - }}; + assertNoErrorsInErrorHandler(); } @Test @@ -203,8 +208,8 @@ public void testCulpritWithSource() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is("a.b(c:42)")); - }}; + assertNoErrorsInErrorHandler(); } @Test @@ -217,7 +222,7 @@ public void testCulpritWithoutSource() throws Exception { Event event; mockRaven.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is(loggerName)); - }}; + assertNoErrorsInErrorHandler(); } } From b9f1b04354f18dd1f27cfee2fcf574c530e0b7b4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 31 Aug 2013 22:13:03 +0200 Subject: [PATCH 0858/2152] Rewrite logback tests to use JMockit --- raven-logback/pom.xml | 4 +- .../raven/logback/MockUpLoggingEvent.java | 107 ++++++ .../logback/SentryAppenderCloseTest.java | 69 ++++ .../raven/logback/SentryAppenderDsnTest.java | 78 +++++ .../SentryAppenderEventBuildingTest.java | 232 +++++++++++++ .../logback/SentryAppenderFailuresTest.java | 93 +++++ .../raven/logback/SentryAppenderTest.java | 322 ------------------ 7 files changed, 581 insertions(+), 324 deletions(-) create mode 100644 raven-logback/src/test/java/net/kencochrane/raven/logback/MockUpLoggingEvent.java create mode 100644 raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java create mode 100644 raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java create mode 100644 raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java create mode 100644 raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java delete mode 100644 raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index c86d27d4e60..51f1a1ec33a 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -41,8 +41,8 @@ test - org.mockito - mockito-core + com.googlecode.jmockit + jmockit test diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/MockUpLoggingEvent.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/MockUpLoggingEvent.java new file mode 100644 index 00000000000..2f1fb284403 --- /dev/null +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/MockUpLoggingEvent.java @@ -0,0 +1,107 @@ +package net.kencochrane.raven.logback; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.IThrowableProxy; +import ch.qos.logback.classic.spi.ThrowableProxy; +import mockit.Mock; +import mockit.MockUp; +import org.slf4j.Marker; +import org.slf4j.helpers.MessageFormatter; + +import java.util.Collections; +import java.util.Map; + +public class MockUpLoggingEvent extends MockUp { + private String loggerName; + private Marker marker; + private Level level; + private String message; + private Object[] argumentArray; + private Throwable throwable; + private Map mdcPropertyMap; + private String threadName; + private StackTraceElement[] callerData; + private long timestamp; + + + public MockUpLoggingEvent(String loggerName, Marker marker, Level level, String message, + Object[] argumentArray, Throwable t) { + this(loggerName, marker, level, message, argumentArray, t, null, null, null, System.currentTimeMillis()); + } + + public MockUpLoggingEvent(String loggerName, Marker marker, Level level, String message, Object[] argumentArray, + Throwable throwable, Map mdcPropertyMap, String threadName, + StackTraceElement[] callerData, long timestamp) { + this.loggerName = loggerName; + this.marker = marker; + this.level = level; + this.message = message; + this.argumentArray = argumentArray; + this.throwable = throwable; + this.mdcPropertyMap = mdcPropertyMap; + this.threadName = threadName; + this.callerData = callerData; + this.timestamp = timestamp; + } + + @Mock + public String getThreadName() { + return threadName; + } + + @Mock + public Level getLevel() { + return level; + } + + @Mock + public String getMessage() { + return message; + } + + @Mock + public Object[] getArgumentArray() { + return argumentArray; + } + + @Mock + public String getFormattedMessage() { + return argumentArray != null ? MessageFormatter.arrayFormat(message, argumentArray).getMessage() : message; + } + + @Mock + public String getLoggerName() { + return loggerName; + } + + @Mock + public IThrowableProxy getThrowableProxy() { + return throwable != null ? new ThrowableProxy(throwable) : null; + } + + @Mock + public StackTraceElement[] getCallerData() { + return callerData != null ? callerData : new StackTraceElement[0]; + } + + @Mock + public boolean hasCallerData() { + return callerData != null && callerData.length > 0; + } + + @Mock + public Marker getMarker() { + return marker; + } + + @Mock + public Map getMDCPropertyMap() { + return mdcPropertyMap != null ? mdcPropertyMap : Collections.emptyMap(); + } + + @Mock + public long getTimeStamp() { + return timestamp; + } +} diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java new file mode 100644 index 00000000000..aef9027d999 --- /dev/null +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java @@ -0,0 +1,69 @@ +package net.kencochrane.raven.logback; + +import ch.qos.logback.core.BasicStatusManager; +import ch.qos.logback.core.Context; +import ch.qos.logback.core.status.OnConsoleStatusListener; +import mockit.Injectable; +import mockit.NonStrictExpectations; +import mockit.Verifications; +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.connection.Connection; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SentryAppenderCloseTest { + @Injectable + private Raven mockRaven = null; + @Injectable + private Context mockContext = null; + @Injectable + private Connection mockConnection = null; + + @BeforeMethod + public void setUp() throws Exception { + new NonStrictExpectations() {{ + final BasicStatusManager statusManager = new BasicStatusManager(); + final OnConsoleStatusListener listener = new OnConsoleStatusListener(); + listener.start(); + statusManager.add(listener); + + mockContext.getStatusManager(); + result = statusManager; + mockRaven.getConnection(); + result = mockConnection; + }}; + } + + private void assertNoErrorsInStatusManager() throws Exception{ + assertThat(mockContext.getStatusManager().getCount(), is(0)); + } + + @Test + public void testClose() throws Exception { + final SentryAppender sentryAppender = new SentryAppender(mockRaven); + sentryAppender.setContext(mockContext); + + sentryAppender.stop(); + + new Verifications(){{ + mockConnection.close(); + times = 0; + }}; + assertNoErrorsInStatusManager(); + } + @Test + public void testClose2() throws Exception { + final SentryAppender sentryAppender = new SentryAppender(mockRaven, true); + sentryAppender.setContext(mockContext); + + sentryAppender.stop(); + + new Verifications(){{ + mockConnection.close(); + }}; + assertNoErrorsInStatusManager(); + } +} diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java new file mode 100644 index 00000000000..0ad8d994e19 --- /dev/null +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java @@ -0,0 +1,78 @@ +package net.kencochrane.raven.logback; + +import ch.qos.logback.core.BasicStatusManager; +import ch.qos.logback.core.Context; +import ch.qos.logback.core.status.OnConsoleStatusListener; +import mockit.Expectations; +import mockit.Injectable; +import mockit.Mocked; +import mockit.NonStrictExpectations; +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.dsn.Dsn; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SentryAppenderDsnTest { + private SentryAppender sentryAppender; + @Injectable + private Raven mockRaven = null; + @Injectable + private Context mockContext = null; + @Mocked("ravenInstance") + private RavenFactory mockRavenFactory; + @Mocked("dsnLookup") + private Dsn mockDsn; + + @BeforeMethod + public void setUp() throws Exception { + sentryAppender = new SentryAppender(); + sentryAppender.setContext(mockContext); + + new NonStrictExpectations() {{ + final BasicStatusManager statusManager = new BasicStatusManager(); + final OnConsoleStatusListener listener = new OnConsoleStatusListener(); + listener.start(); + statusManager.add(listener); + + mockContext.getStatusManager(); + result = statusManager; + }}; + } + + private void assertNoErrorsInStatusManager() throws Exception { + assertThat(mockContext.getStatusManager().getCount(), is(0)); + } + + @Test + public void testDsnDetected() throws Exception { + final String dsnUri = "protocol://public:private@host/1"; + new Expectations() {{ + Dsn.dsnLookup(); + result = dsnUri; + RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); + result = mockRaven; + }}; + + sentryAppender.initRaven(); + + assertNoErrorsInStatusManager(); + } + + @Test + public void testDsnProvided() throws Exception { + final String dsnUri = "protocol://public:private@host/2"; + sentryAppender.setDsn(dsnUri); + new Expectations() {{ + RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); + result = mockRaven; + }}; + + sentryAppender.initRaven(); + + assertNoErrorsInStatusManager(); + } +} diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java new file mode 100644 index 00000000000..297eb82c245 --- /dev/null +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java @@ -0,0 +1,232 @@ +package net.kencochrane.raven.logback; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.BasicStatusManager; +import ch.qos.logback.core.Context; +import ch.qos.logback.core.status.OnConsoleStatusListener; +import mockit.Injectable; +import mockit.NonStrictExpectations; +import mockit.Verifications; +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.MessageInterface; +import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import org.hamcrest.Matchers; +import org.slf4j.MarkerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SentryAppenderEventBuildingTest { + private SentryAppender sentryAppender; + @Injectable + private Raven mockRaven = null; + @Injectable + private Context mockContext = null; + + @BeforeMethod + public void setUp() throws Exception { + sentryAppender = new SentryAppender(mockRaven); + sentryAppender.setContext(mockContext); + sentryAppender.initRaven(); + + new NonStrictExpectations() {{ + final BasicStatusManager statusManager = new BasicStatusManager(); + final OnConsoleStatusListener listener = new OnConsoleStatusListener(); + listener.start(); + statusManager.add(listener); + + mockContext.getStatusManager(); + result = statusManager; + }}; + } + + private void assertNoErrorsInStatusManager() throws Exception { + assertThat(mockContext.getStatusManager().getCount(), is(0)); + } + + @Test + public void testSimpleMessageLogging() throws Exception { + final String message = UUID.randomUUID().toString(); + final String loggerName = UUID.randomUUID().toString(); + final String threadName = UUID.randomUUID().toString(); + final Date date = new Date(1373883196416L); + + ILoggingEvent loggingEvent = new MockUpLoggingEvent(loggerName, null, Level.INFO, message, null, null, null, + threadName, null, date.getTime()).getMockInstance(); + sentryAppender.append(loggingEvent); + + new Verifications() {{ + Event event; + mockRaven.runBuilderHelpers((EventBuilder) any); + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getMessage(), is(message)); + assertThat(event.getLogger(), is(loggerName)); + assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); + assertThat(event.getTimestamp(), is(date)); + }}; + assertNoErrorsInStatusManager(); + } + + @DataProvider(name = "levels") + private Object[][] levelConversions() { + return new Object[][]{ + {Event.Level.DEBUG, Level.TRACE}, + {Event.Level.DEBUG, Level.DEBUG}, + {Event.Level.INFO, Level.INFO}, + {Event.Level.WARNING, Level.WARN}, + {Event.Level.ERROR, Level.ERROR}}; + } + + @Test(dataProvider = "levels") + public void testLevelConversion(final Event.Level expectedLevel, Level level) throws Exception { + sentryAppender.append(new MockUpLoggingEvent(null, null, level, null, null, null).getMockInstance()); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getLevel(), is(expectedLevel)); + }}; + assertNoErrorsInStatusManager(); + } + + @Test + public void testExceptionLogging() throws Exception { + final Exception exception = new Exception(); + + sentryAppender.append(new MockUpLoggingEvent(null, null, Level.ERROR, null, null, exception).getMockInstance()); + + new Verifications() {{ + Event event; + mockRaven.runBuilderHelpers((EventBuilder) any); + mockRaven.sendEvent(event = withCapture()); + ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() + .get(ExceptionInterface.EXCEPTION_INTERFACE); + Throwable capturedException = exceptionInterface.getThrowable(); + + assertThat(capturedException.getMessage(), is(exception.getMessage())); + assertThat(capturedException.getStackTrace(), is(exception.getStackTrace())); + }}; + assertNoErrorsInStatusManager(); + } + + @Test + public void testLogParametrisedMessage() throws Exception { + final String messagePattern = "Formatted message {} {} {}"; + final Object[] parameters = {"first parameter", new Object[0], null}; + + sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, messagePattern, parameters, null) + .getMockInstance()); + + new Verifications() {{ + Event event; + mockRaven.runBuilderHelpers((EventBuilder) any); + mockRaven.sendEvent(event = withCapture()); + MessageInterface messageInterface = (MessageInterface) event.getSentryInterfaces() + .get(MessageInterface.MESSAGE_INTERFACE); + + assertThat(event.getMessage(), is("Formatted message first parameter [] null")); + assertThat(messageInterface.getMessage(), is(messagePattern)); + assertThat(messageInterface.getParameters(), + is(Arrays.asList(parameters[0].toString(), parameters[1].toString(), null))); + }}; + assertNoErrorsInStatusManager(); + } + + @Test + public void testMarkerAddedToTag() throws Exception { + final String markerName = UUID.randomUUID().toString(); + + sentryAppender.append( + new MockUpLoggingEvent(null, MarkerFactory.getMarker(markerName), Level.INFO, null, null, null) + .getMockInstance()); + + new Verifications() {{ + Event event; + mockRaven.runBuilderHelpers((EventBuilder) any); + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getTags(), Matchers.hasEntry(SentryAppender.LOGBACK_MARKER, markerName)); + }}; + assertNoErrorsInStatusManager(); + } + + @Test + public void testMdcAddedToExtra() throws Exception { + final String extraKey = UUID.randomUUID().toString(); + final String extraValue = UUID.randomUUID().toString(); + + sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null, + Collections.singletonMap(extraKey, extraValue), null, null, 0).getMockInstance()); + + new Verifications() {{ + Event event; + mockRaven.runBuilderHelpers((EventBuilder) any); + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); + }}; + assertNoErrorsInStatusManager(); + } + + @Test + public void testSourceUsedAsStacktrace() throws Exception { + final StackTraceElement[] location = {new StackTraceElement(UUID.randomUUID().toString(), + UUID.randomUUID().toString(), + UUID.randomUUID().toString(), 42)}; + + sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null, null, null, location, 0) + .getMockInstance()); + + new Verifications() {{ + Event event; + mockRaven.runBuilderHelpers((EventBuilder) any); + mockRaven.sendEvent(event = withCapture()); + StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() + .get(StackTraceInterface.STACKTRACE_INTERFACE); + assertThat(stackTraceInterface.getStackTrace(), is(location)); + }}; + assertNoErrorsInStatusManager(); + } + + @Test + public void testCulpritWithSource() throws Exception { + final StackTraceElement[] location = {new StackTraceElement("a", "b", "c", 42), + new StackTraceElement("d", "e", "f", 69)}; + + sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null, null, null, location, 0) + .getMockInstance()); + + new Verifications() {{ + Event event; + mockRaven.runBuilderHelpers((EventBuilder) any); + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getCulprit(), is("a.b(c:42)")); + }}; + assertNoErrorsInStatusManager(); + } + + @Test + public void testCulpritWithoutSource() throws Exception { + final String loggerName = UUID.randomUUID().toString(); + + sentryAppender.append(new MockUpLoggingEvent(loggerName, null, Level.INFO, null, null, null).getMockInstance()); + + new Verifications() {{ + Event event; + mockRaven.runBuilderHelpers((EventBuilder) any); + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getCulprit(), is(loggerName)); + }}; + assertNoErrorsInStatusManager(); + } +} diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java new file mode 100644 index 00000000000..0fc1c0e6991 --- /dev/null +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java @@ -0,0 +1,93 @@ +package net.kencochrane.raven.logback; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.core.BasicStatusManager; +import ch.qos.logback.core.Context; +import ch.qos.logback.core.status.OnConsoleStatusListener; +import mockit.*; +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.dsn.Dsn; +import net.kencochrane.raven.event.Event; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SentryAppenderFailuresTest { + @Injectable + private Raven mockRaven = null; + @Injectable + private Context mockContext = null; + @Mocked("ravenInstance") + private RavenFactory mockRavenFactory; + + @BeforeMethod + public void setUp() throws Exception{ + new NonStrictExpectations() {{ + final BasicStatusManager statusManager = new BasicStatusManager(); + final OnConsoleStatusListener listener = new OnConsoleStatusListener(); + listener.start(); + statusManager.add(listener); + + mockContext.getStatusManager(); + result = statusManager; + }}; + } + + @Test + public void testRavenFailureDoesNotPropagate() throws Exception { + final SentryAppender sentryAppender = new SentryAppender(mockRaven); + sentryAppender.setContext(mockContext); + new NonStrictExpectations() {{ + mockRaven.sendEvent((Event) any); + result = new UnsupportedOperationException(); + }}; + sentryAppender.start(); + + sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null).getMockInstance()); + + new Verifications() {{ + mockRaven.sendEvent((Event) any); + }}; + assertThat(mockContext.getStatusManager().getCount(), is(1)); + } + + @Test + public void testRavenFactoryFailureDoesNotPropagate() throws Exception { + final String dsnUri = "proto://private:public@host/1"; + final SentryAppender sentryAppender = new SentryAppender(); + sentryAppender.setContext(mockContext); + sentryAppender.setDsn(dsnUri); + new Expectations() {{ + RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); + result = new UnsupportedOperationException(); + }}; + sentryAppender.start(); + + sentryAppender.initRaven(); + + assertThat(mockContext.getStatusManager().getCount(), is(1)); + } + + @Test + public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { + try { + Raven.RAVEN_THREAD.set(true); + final SentryAppender sentryAppender = new SentryAppender(mockRaven); + sentryAppender.setContext(mockContext); + sentryAppender.start(); + + sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null).getMockInstance()); + + new Verifications() {{ + mockRaven.sendEvent((Event) any); + times = 0; + }}; + assertThat(mockContext.getStatusManager().getCount(), is(0)); + } finally { + Raven.RAVEN_THREAD.remove(); + } + } +} diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java deleted file mode 100644 index cd65e36b372..00000000000 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderTest.java +++ /dev/null @@ -1,322 +0,0 @@ -package net.kencochrane.raven.logback; - -import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.classic.spi.ThrowableProxy; -import ch.qos.logback.core.BasicStatusManager; -import ch.qos.logback.core.Context; -import ch.qos.logback.core.status.OnConsoleStatusListener; -import net.kencochrane.raven.Raven; -import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.dsn.Dsn; -import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.EventBuilder; -import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.MessageInterface; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; -import org.hamcrest.Matchers; -import org.mockito.Answers; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.slf4j.Marker; -import org.slf4j.MarkerFactory; -import org.slf4j.helpers.MessageFormatter; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.util.*; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.*; - -public class SentryAppenderTest { - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private Raven mockRaven; - @Mock - private RavenFactory mockRavenFactory; - private SentryAppender sentryAppender; - - @BeforeMethod - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - sentryAppender = new SentryAppender(mockRaven); - setMockContextOnAppender(sentryAppender); - - when(mockRavenFactory.createRavenInstance(any(Dsn.class))).thenReturn(mockRaven); - RavenFactory.registerFactory(mockRavenFactory); - } - - private void setMockContextOnAppender(SentryAppender sentryAppender) { - Context mockContext = mock(Context.class); - sentryAppender.setContext(mockContext); - BasicStatusManager statusManager = new BasicStatusManager(); - OnConsoleStatusListener listener = new OnConsoleStatusListener(); - listener.start(); - statusManager.add(listener); - when(mockContext.getStatusManager()).thenReturn(statusManager); - } - - @Test - public void testSimpleMessageLogging() throws Exception { - String message = UUID.randomUUID().toString(); - String loggerName = UUID.randomUUID().toString(); - String threadName = UUID.randomUUID().toString(); - Date date = new Date(1373883196416L); - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - Event event; - - ILoggingEvent loggingEvent = newLoggingEvent(loggerName, null, Level.INFO, message, null, null, null, - threadName, null, date.getTime()); - sentryAppender.append(loggingEvent); - - verify(mockRaven).runBuilderHelpers(any(EventBuilder.class)); - verify(mockRaven).sendEvent(eventCaptor.capture()); - event = eventCaptor.getValue(); - assertThat(event.getMessage(), is(message)); - assertThat(event.getLogger(), is(loggerName)); - assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); - assertThat(event.getTimestamp(), is(date)); - - assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); - } - - @Test - public void testLogLevelConversions() throws Exception { - assertLevelConverted(Event.Level.DEBUG, Level.TRACE); - assertLevelConverted(Event.Level.DEBUG, Level.DEBUG); - assertLevelConverted(Event.Level.INFO, Level.INFO); - assertLevelConverted(Event.Level.WARNING, Level.WARN); - assertLevelConverted(Event.Level.ERROR, Level.ERROR); - } - - private void assertLevelConverted(Event.Level expectedLevel, Level level) { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - sentryAppender.append(newLoggingEvent(null, null, level, null, null, null)); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getLevel(), is(expectedLevel)); - assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); - reset(mockRaven); - } - - @Test - public void testExceptionLogging() throws Exception { - Exception exception = new Exception(); - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - sentryAppender.append(newLoggingEvent(null, null, Level.ERROR, null, null, exception)); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - ExceptionInterface exceptionInterface = (ExceptionInterface) eventCaptor.getValue().getSentryInterfaces() - .get(ExceptionInterface.EXCEPTION_INTERFACE); - Throwable capturedException = exceptionInterface.getThrowable(); - - assertThat(capturedException.getMessage(), is(exception.getMessage())); - assertThat(capturedException.getStackTrace(), is(exception.getStackTrace())); - assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); - } - - @Test - public void testLogParametrisedMessage() throws Exception { - String messagePattern = "Formatted message {} {} {}"; - Object[] parameters = {"first parameter", new Object[0], null}; - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - sentryAppender.append(newLoggingEvent(null, null, Level.INFO, messagePattern, parameters, null)); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - MessageInterface messageInterface = (MessageInterface) eventCaptor.getValue().getSentryInterfaces() - .get(MessageInterface.MESSAGE_INTERFACE); - - assertThat(eventCaptor.getValue().getMessage(), is("Formatted message first parameter [] null")); - assertThat(messageInterface.getMessage(), is(messagePattern)); - assertThat(messageInterface.getParameters(), - is(Arrays.asList(parameters[0].toString(), parameters[1].toString(), null))); - assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); - } - - @Test - public void testMarkerAddedToTag() throws Exception { - String markerName = UUID.randomUUID().toString(); - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - sentryAppender.append(newLoggingEvent(null, MarkerFactory.getMarker(markerName), Level.INFO, null, null, null)); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getTags(), - Matchers.hasEntry(SentryAppender.LOGBACK_MARKER, markerName)); - assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); - } - - @Test - public void testMdcAddedToExtra() throws Exception { - String extraKey = UUID.randomUUID().toString(); - String extraValue = UUID.randomUUID().toString(); - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null, - Collections.singletonMap(extraKey, extraValue), null, null, 0)); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getExtra(), Matchers.hasEntry(extraKey, extraValue)); - assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); - } - - @Test - public void testSourceUsedAsStacktrace() throws Exception { - StackTraceElement[] location = {new StackTraceElement(UUID.randomUUID().toString(), - UUID.randomUUID().toString(), - UUID.randomUUID().toString(), 42)}; - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null, null, null, location, 0)); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - StackTraceInterface stackTraceInterface = (StackTraceInterface) eventCaptor.getValue().getSentryInterfaces() - .get(StackTraceInterface.STACKTRACE_INTERFACE); - assertThat(stackTraceInterface.getStackTrace(), is(location)); - assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); - } - - @Test - public void testCulpritWithSource() throws Exception { - StackTraceElement[] location = {new StackTraceElement("a", "b", "c", 42), - new StackTraceElement("d", "e", "f", 69)}; - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null, null, null, location, 0)); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getCulprit(), is("a.b(c:42)")); - assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); - } - - @Test - public void testCulpritWithoutSource() throws Exception { - String loggerName = UUID.randomUUID().toString(); - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - sentryAppender.append(newLoggingEvent(loggerName, null, Level.INFO, null, null, null)); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getCulprit(), is(loggerName)); - assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); - } - - @Test - public void testClose() throws Exception { - sentryAppender.stop(); - verify(mockRaven.getConnection(), never()).close(); - - sentryAppender = new SentryAppender(mockRaven, true); - setMockContextOnAppender(sentryAppender); - - sentryAppender.stop(); - verify(mockRaven.getConnection()).close(); - assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); - } - - @Test - public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { - try { - Raven.RAVEN_THREAD.set(true); - - sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null)); - - verify(mockRaven, never()).sendEvent(any(Event.class)); - assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); - } finally { - Raven.RAVEN_THREAD.remove(); - } - } - - @Test - public void testRavenFailureDoesNotPropagate() throws Exception { - doThrow(new UnsupportedOperationException()).when(mockRaven).sendEvent(any(Event.class)); - - sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null)); - - assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(1)); - } - - @Test - public void testLazyInitialisation() throws Exception { - String dsnUri = "proto://private:public@host/1"; - sentryAppender = new SentryAppender(); - setMockContextOnAppender(sentryAppender); - sentryAppender.setDsn(dsnUri); - sentryAppender.setRavenFactory(mockRavenFactory.getClass().getName()); - - sentryAppender.start(); - verify(mockRavenFactory, never()).createRavenInstance(any(Dsn.class)); - - sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null)); - verify(mockRavenFactory).createRavenInstance(eq(new Dsn(dsnUri))); - assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); - } - - @Test - public void testDsnAutoDetection() throws Exception { - try { - String dsnUri = "proto://private:public@host/1"; - System.setProperty(Dsn.DSN_VARIABLE, dsnUri); - sentryAppender = new SentryAppender(); - setMockContextOnAppender(sentryAppender); - sentryAppender.setRavenFactory(mockRavenFactory.getClass().getName()); - - sentryAppender.start(); - sentryAppender.append(newLoggingEvent(null, null, Level.INFO, null, null, null)); - - verify(mockRavenFactory).createRavenInstance(eq(new Dsn(dsnUri))); - assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(0)); - } finally { - System.clearProperty(Dsn.DSN_VARIABLE); - } - } - - @Test - public void testFailedInitialisation() throws Exception { - String dsnUri = "proto://private:public@host/1"; - sentryAppender = new SentryAppender(); - setMockContextOnAppender(sentryAppender); - sentryAppender.setDsn(dsnUri); - sentryAppender.setRavenFactory("invalid factory"); - - sentryAppender.initRaven(); - - assertThat(sentryAppender.getContext().getStatusManager().getCount(), is(1)); - } - - private ILoggingEvent newLoggingEvent(String loggerName, Marker marker, Level level, String message, - Object[] argumentArray, Throwable t) { - return newLoggingEvent(loggerName, marker, level, message, argumentArray, t, - null, null, null, System.currentTimeMillis()); - } - - private ILoggingEvent newLoggingEvent(String loggerName, Marker marker, Level level, - String message, Object[] argumentArray, Throwable throwable, - Map mdcPropertyMap, String threadName, - StackTraceElement[] callerData, long timestamp) { - ILoggingEvent iLoggingEvent = mock(ILoggingEvent.class); - when(iLoggingEvent.getThreadName()).thenReturn(threadName); - when(iLoggingEvent.getLevel()).thenReturn(level); - when(iLoggingEvent.getMessage()).thenReturn(message); - when(iLoggingEvent.getArgumentArray()).thenReturn(argumentArray); - when(iLoggingEvent.getFormattedMessage()).thenReturn( - argumentArray != null ? MessageFormatter.arrayFormat(message, argumentArray).getMessage() : message); - when(iLoggingEvent.getLoggerName()).thenReturn(loggerName); - when(iLoggingEvent.getThrowableProxy()).thenReturn(throwable != null ? new ThrowableProxy(throwable) : null); - when(iLoggingEvent.getCallerData()).thenReturn(callerData != null ? callerData : new StackTraceElement[0]); - when(iLoggingEvent.hasCallerData()).thenReturn(callerData != null && callerData.length > 0); - when(iLoggingEvent.getMarker()).thenReturn(marker); - when(iLoggingEvent.getMDCPropertyMap()) - .thenReturn(mdcPropertyMap != null ? mdcPropertyMap : Collections.emptyMap()); - when(iLoggingEvent.getTimeStamp()).thenReturn(timestamp); - - return iLoggingEvent; - } -} From 0ea5eb2a715d9382ce927e55c91ecb2fd22d27cf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 31 Aug 2013 22:13:20 +0200 Subject: [PATCH 0859/2152] Fix style --- .../kencochrane/raven/logback/SentryAppenderCloseTest.java | 7 ++++--- .../raven/logback/SentryAppenderFailuresTest.java | 2 +- .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java index aef9027d999..fcb7deb916e 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java @@ -37,7 +37,7 @@ public void setUp() throws Exception { }}; } - private void assertNoErrorsInStatusManager() throws Exception{ + private void assertNoErrorsInStatusManager() throws Exception { assertThat(mockContext.getStatusManager().getCount(), is(0)); } @@ -48,12 +48,13 @@ public void testClose() throws Exception { sentryAppender.stop(); - new Verifications(){{ + new Verifications() {{ mockConnection.close(); times = 0; }}; assertNoErrorsInStatusManager(); } + @Test public void testClose2() throws Exception { final SentryAppender sentryAppender = new SentryAppender(mockRaven, true); @@ -61,7 +62,7 @@ public void testClose2() throws Exception { sentryAppender.stop(); - new Verifications(){{ + new Verifications() {{ mockConnection.close(); }}; assertNoErrorsInStatusManager(); diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java index 0fc1c0e6991..c2f87eafdc8 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java @@ -24,7 +24,7 @@ public class SentryAppenderFailuresTest { private RavenFactory mockRavenFactory; @BeforeMethod - public void setUp() throws Exception{ + public void setUp() throws Exception { new NonStrictExpectations() {{ final BasicStatusManager statusManager = new BasicStatusManager(); final OnConsoleStatusListener listener = new OnConsoleStatusListener(); diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index dc4bae08bb5..bffbf9588cd 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -130,7 +130,8 @@ protected void initRaven() { raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); } catch (InvalidDsnException e) { - reportError("An exception occurred during the retrieval of the DSN for Raven", e, ErrorManager.OPEN_FAILURE); + reportError("An exception occurred during the retrieval of the DSN for Raven", + e, ErrorManager.OPEN_FAILURE); } catch (Exception e) { reportError("An exception occurred during the creation of a Raven instance", e, ErrorManager.OPEN_FAILURE); } From 1486717ad79b1e7b81be71424ddafddf32081954 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 31 Aug 2013 22:20:37 +0200 Subject: [PATCH 0860/2152] Throw an exception from the HttpConnection when the server failed to answer --- .../net/kencochrane/raven/connection/HttpConnection.java | 2 +- .../net/kencochrane/raven/connection/HttpConnectionTest.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 0096ae564ce..568fa0b9031 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -104,7 +104,7 @@ protected void doSend(Event event) { connection.getInputStream().close(); } catch (IOException e) { if (connection.getErrorStream() != null) { - logger.error(getErrorMessageFromStream(connection.getErrorStream()), e); + throw new ConnectionException(getErrorMessageFromStream(connection.getErrorStream()), e); } else { throw new ConnectionException("An exception occurred while submitting the event to the sentry server." , e); diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index f8eb3ac0007..73af598fae1 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -106,7 +106,10 @@ public void testAuthHeaderSent() throws Exception { verify(mockUrlConnection).setRequestProperty("X-Sentry-Auth", expectedAuthRequest); } - @Test + @Test(enabled = false) + /** + * TODO: HTTP errors are thrown exceptions caught in the abstract class and not logged directly. + */ public void testHttpErrorLogged() throws Exception { final String httpErrorMessage = UUID.randomUUID().toString(); ArgumentCaptor logRecordCaptor = ArgumentCaptor.forClass(LogRecord.class); From 56046ac9141e441397aec1fb3c4358dfee985679 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 1 Sep 2013 00:26:42 +0200 Subject: [PATCH 0861/2152] Move AsyncConnection tests to jmockit --- raven/pom.xml | 5 ++ .../raven/connection/AsyncConnectionTest.java | 61 +++++++++---------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/raven/pom.xml b/raven/pom.xml index 4645b5d35a2..673475bcaee 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -48,6 +48,11 @@ mockito-core test + + com.googlecode.jmockit + jmockit + test + org.testng testng diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index 32b6e14e4ff..d2898cb7bbf 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -1,31 +1,23 @@ package net.kencochrane.raven.connection; +import mockit.*; import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.EventBuilder; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.verify; - public class AsyncConnectionTest { + @Tested private AsyncConnection asyncConnection; - @Mock + @Injectable private Connection mockConnection; - @Mock + @Injectable private ExecutorService mockExecutorService; @BeforeMethod public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); asyncConnection = new AsyncConnection(mockConnection); asyncConnection.setExecutorService(mockExecutorService); } @@ -34,32 +26,37 @@ public void setUp() throws Exception { public void testCloseOperation() throws Exception { asyncConnection.close(); - verify(mockConnection).close(); - verify(mockExecutorService).awaitTermination(any(Long.class), any(TimeUnit.class)); + new Verifications() {{ + mockConnection.close(); + mockExecutorService.awaitTermination(anyLong, (TimeUnit) any); + }}; } - @Test - public void testSendEventQueued() throws Exception { - Event event = new EventBuilder().build(); - asyncConnection.send(event); + @Test + public void testSendEventQueued(@Injectable final Event mockEvent) throws Exception { + asyncConnection.send(mockEvent); - verify(mockExecutorService).execute(any(Runnable.class)); + new Verifications(){{ + mockExecutorService.execute((Runnable) any); + }}; } @Test - public void testQueuedEventExecuted() throws Exception { - Event event = new EventBuilder().build(); - doAnswer(new Answer() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - ((Runnable) invocation.getArguments()[0]).run(); - return null; - } - }).when(mockExecutorService).execute(any(Runnable.class)); - - asyncConnection.send(event); - - verify(mockConnection).send(event); + public void testQueuedEventExecuted(@Injectable final Event mockEvent) throws Exception { + new Expectations(){{ + mockExecutorService.execute((Runnable) any); + result = new Delegate() { + public void execute(Runnable runnable){ + runnable.run(); + } + }; + }}; + + asyncConnection.send(mockEvent); + + new Verifications(){{ + mockConnection.send(mockEvent); + }}; } } From 2b6d0b55d9804c583c0fa1c7d4d18eb023b3533c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 4 Sep 2013 11:24:53 +1000 Subject: [PATCH 0862/2152] Use JMockit with HttpConnectionTest --- .../raven/connection/HttpConnectionTest.java | 199 ++++++++++-------- 1 file changed, 108 insertions(+), 91 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index 73af598fae1..399d544e67a 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -1,147 +1,164 @@ package net.kencochrane.raven.connection; +import mockit.Expectations; +import mockit.Injectable; +import mockit.NonStrictExpectations; +import mockit.Verifications; import net.kencochrane.raven.Raven; -import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.marshaller.Marshaller; -import org.hamcrest.CustomTypeSafeMatcher; -import org.hamcrest.Matcher; -import org.mockito.Answers; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSession; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URL; -import java.net.URLConnection; -import java.net.URLStreamHandler; import java.util.UUID; -import java.util.logging.Handler; -import java.util.logging.LogRecord; -import java.util.logging.Logger; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.core.Is.is; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.*; +import static org.hamcrest.Matchers.is; public class HttpConnectionTest { + private final String publicKey = UUID.randomUUID().toString(); + private final String secretKey = UUID.randomUUID().toString(); private HttpConnection httpConnection; - private String publicKey = UUID.randomUUID().toString(); - private String secretKey = UUID.randomUUID().toString(); - @Mock(answer = Answers.RETURNS_MOCKS) + @Injectable private HttpsURLConnection mockUrlConnection; - @Mock + @Injectable private Marshaller mockMarshaller; @BeforeMethod public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - URLStreamHandler stubUrlHandler = new URLStreamHandler() { - @Override - protected URLConnection openConnection(URL u) throws IOException { - return mockUrlConnection; + new NonStrictExpectations() { + @Injectable + private URL mockUrl; + @Injectable + private OutputStream mockOutputStream; + @Injectable + private InputStream mockInputStream; + + { + mockUrl.openConnection(); + result = mockUrlConnection; + mockUrlConnection.getOutputStream(); + result = mockOutputStream; + mockUrlConnection.getInputStream(); + result = mockInputStream; + + httpConnection = new HttpConnection(mockUrl, publicKey, secretKey); + httpConnection.setMarshaller(mockMarshaller); } }; - - URL mockUrl = new URL(null, "http://", stubUrlHandler); - httpConnection = new HttpConnection(mockUrl, publicKey, secretKey); - httpConnection.setMarshaller(mockMarshaller); } @Test - public void testTimeout() throws Exception { - int timeout = 12; + public void testTimeout(@Injectable final Event mockEvent) throws Exception { + final int timeout = 12; httpConnection.setTimeout(timeout); - httpConnection.send(new EventBuilder().build()); - verify(mockUrlConnection).setConnectTimeout(timeout); + httpConnection.send(mockEvent); + + new Verifications() {{ + mockUrlConnection.setConnectTimeout(timeout); + }}; } @Test - public void testByPassSecurity() throws Exception { - ArgumentCaptor hostnameVerifierCaptor = ArgumentCaptor.forClass(HostnameVerifier.class); - httpConnection.send(new EventBuilder().build()); - verify(mockUrlConnection, never()).setHostnameVerifier(any(HostnameVerifier.class)); + public void testByPassSecurityDefaultsToFalse(@Injectable final Event mockEvent) throws Exception { + httpConnection.send(mockEvent); - reset(mockUrlConnection); + new Verifications() {{ + mockUrlConnection.setHostnameVerifier((HostnameVerifier) any); + times = 0; + }}; + } + + @Test + public void testByPassSecurity(@Injectable final Event mockEvent) throws Exception { httpConnection.setBypassSecurity(true); - httpConnection.send(new EventBuilder().build()); - verify(mockUrlConnection).setHostnameVerifier(hostnameVerifierCaptor.capture()); - assertThat(hostnameVerifierCaptor.getValue().verify(null, null), is(true)); - reset(mockUrlConnection); - httpConnection.setBypassSecurity(false); - httpConnection.send(new EventBuilder().build()); - verify(mockUrlConnection, never()).setHostnameVerifier(any(HostnameVerifier.class)); + httpConnection.send(mockEvent); + + new Verifications() { + @Injectable + private String mockString; + @Injectable + private SSLSession mockSslSession; + + { + HostnameVerifier hostnameVerifier; + mockUrlConnection.setHostnameVerifier(hostnameVerifier = withCapture()); + assertThat(hostnameVerifier.verify(mockString, mockSslSession), is(true)); + } + }; } @Test - public void testContentMarshalled() throws Exception { - Event event = new EventBuilder().build(); + public void testDontByPassSecurity(@Injectable final Event mockEvent) throws Exception { + httpConnection.setBypassSecurity(false); - httpConnection.send(event); + httpConnection.send(mockEvent); - verify(mockMarshaller).marshall(eq(event), any(OutputStream.class)); + new Verifications() {{ + mockUrlConnection.setHostnameVerifier((HostnameVerifier) any); + times = 0; + }}; } @Test - public void testAuthHeaderSent() throws Exception { - httpConnection.send(new EventBuilder().build()); - - verify(mockUrlConnection).setRequestProperty("User-Agent", Raven.NAME); - String expectedAuthRequest = "Sentry sentry_version=4," - + "sentry_client=" + Raven.NAME + "," - + "sentry_key=" + publicKey + "," - + "sentry_secret=" + secretKey; - verify(mockUrlConnection).setRequestProperty("X-Sentry-Auth", expectedAuthRequest); + public void testContentMarshalled(@Injectable final Event mockEvent) throws Exception { + httpConnection.send(mockEvent); + + new Verifications() {{ + mockMarshaller.marshall(mockEvent, (OutputStream) any); + }}; + } + + @Test + public void testAuthHeaderSent(@Injectable final Event mockEvent) throws Exception { + httpConnection.send(mockEvent); + + + new Verifications() {{ + mockUrlConnection.setRequestProperty("User-Agent", Raven.NAME); + + String expectedAuthRequest = "Sentry sentry_version=4," + + "sentry_client=" + Raven.NAME + "," + + "sentry_key=" + publicKey + "," + + "sentry_secret=" + secretKey; + mockUrlConnection.setRequestProperty("X-Sentry-Auth", expectedAuthRequest); + }}; } - @Test(enabled = false) - /** - * TODO: HTTP errors are thrown exceptions caught in the abstract class and not logged directly. - */ - public void testHttpErrorLogged() throws Exception { + @Test(expectedExceptions = {ConnectionException.class}) + public void testHttpErrorThrowsAnException(@Injectable final Event mockEvent) throws Exception { final String httpErrorMessage = UUID.randomUUID().toString(); - ArgumentCaptor logRecordCaptor = ArgumentCaptor.forClass(LogRecord.class); - Handler handler = mock(Handler.class); - Logger.getLogger(HttpConnection.class.getCanonicalName()).addHandler(handler); - when(mockUrlConnection.getOutputStream()).thenThrow(new IOException()); - when(mockUrlConnection.getErrorStream()).thenReturn(new ByteArrayInputStream(httpErrorMessage.getBytes())); - - httpConnection.send(new EventBuilder().build()); - - verify(handler).publish(logRecordCaptor.capture()); - Matcher> matcher = hasItem( - new CustomTypeSafeMatcher("Looks for message '" + httpErrorMessage + "'") { - @Override - protected boolean matchesSafely(LogRecord logRecord) { - return httpErrorMessage.equals(logRecord.getMessage()); - } - }); - assertThat(logRecordCaptor.getAllValues(), matcher); - - Logger.getLogger(HttpConnection.class.getCanonicalName()).removeHandler(handler); + new NonStrictExpectations() {{ + mockUrlConnection.getOutputStream(); + result = new IOException(); + mockUrlConnection.getErrorStream(); + result = new ByteArrayInputStream(httpErrorMessage.getBytes()); + }}; + + httpConnection.doSend(mockEvent); } @Test - public void testApiUrlCreation() throws Exception { - Dsn dsn = mock(Dsn.class); - String projectId = UUID.randomUUID().toString(); - String uri = "http://host/sentry/"; - when(dsn.getUri()).thenReturn(new URI(uri)); - when(dsn.getProjectId()).thenReturn(projectId); - - URL sentryApiUrl = HttpConnection.getSentryApiUrl(dsn.getUri(), dsn.getProjectId()); + public void testApiUrlCreation(@Injectable final URI sentryUri) throws Exception { + final String uri = "http://host/sentry/"; + final String projectId = UUID.randomUUID().toString(); + new Expectations() {{ + sentryUri.toString(); + result = "http://host/sentry/"; + }}; + + URL sentryApiUrl = HttpConnection.getSentryApiUrl(sentryUri, projectId); assertThat(sentryApiUrl.toString(), is(uri + "api/" + projectId + "/store/")); } From 6731820c3e3c1524e7d9deafdeb2162befeb3800 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 4 Sep 2013 11:27:05 +1000 Subject: [PATCH 0863/2152] Add tests for AbstractConnection --- .../connection/AbstractConnectionTest.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java new file mode 100644 index 00000000000..d222d4c3e16 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java @@ -0,0 +1,60 @@ +package net.kencochrane.raven.connection; + +import mockit.*; +import net.kencochrane.raven.event.Event; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.util.UUID; +import java.util.concurrent.locks.ReentrantLock; + +import static mockit.Deencapsulation.setField; +import static org.mockito.Mockito.verify; + +public class AbstractConnectionTest { + private final String publicKey = UUID.randomUUID().toString(); + private final String secretKey = UUID.randomUUID().toString(); + private AbstractConnection abstractConnection; + //Spying with mockito as jMockit doesn't support mocks of ReentrantLock + @Spy + private ReentrantLock reentrantLock = new ReentrantLock(); + + @BeforeMethod + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + abstractConnection = new DummyAbstractConnection(publicKey, secretKey); + setField(abstractConnection, "lock", reentrantLock); + } + + @Test + public void testExceptionOnSendStartLockDown(@Injectable final Event mockEvent) throws Exception { + new MockUp() { + @SuppressWarnings("unused") + @Mock + protected void doSend(Event event) throws ConnectionException { + throw new ConnectionException(); + } + }; + + abstractConnection.send(mockEvent); + + verify(reentrantLock).tryLock(); + } + + private static final class DummyAbstractConnection extends AbstractConnection { + public DummyAbstractConnection(String publicKey, String secretKey) { + super(publicKey, secretKey); + } + + @Override + protected void doSend(Event event) throws ConnectionException { + } + + @Override + public void close() throws IOException { + } + } +} From d17cbd2824f338385cba99f27e62754516a27191 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 4 Sep 2013 11:27:29 +1000 Subject: [PATCH 0864/2152] Improve exception message to not mention DSN if not necessary --- .../java/net/kencochrane/raven/connection/HttpConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 568fa0b9031..a4caaba3a90 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -72,7 +72,7 @@ public static URL getSentryApiUrl(URI sentryUri, String projectId) { String url = sentryUri.toString() + "api/" + projectId + "/store/"; return new URL(url); } catch (MalformedURLException e) { - throw new IllegalArgumentException("Couldn't get a valid URL from the DSN.", e); + throw new IllegalArgumentException("Couldn't build a valid URL from the Sentry API.", e); } } From 062e6163239210be08df9b784945e26c090fb9d5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 4 Sep 2013 11:28:23 +1000 Subject: [PATCH 0865/2152] Add a TODO due to an issue encountered during tests execution --- .../test/java/net/kencochrane/raven/event/EventBuilderTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index 6774ed94d2e..722e85b3ab6 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -29,6 +29,7 @@ public void testMandatoryValuesAutomaticallySet() throws Exception { assertThat(event.getId(), is(notNullValue())); assertThat(event.getTimestamp(), is(notNullValue())); assertThat(event.getPlatform(), is(EventBuilder.DEFAULT_PLATFORM)); + //TODO: This test can fail if HostnameCache times out (happened once), mock InetAddress.getLocalHost().getCanonicalHostName() for instant reliable results) assertThat(event.getServerName(), is(InetAddress.getLocalHost().getCanonicalHostName())); } From 636fab0fdcdd279d940cadb78eaea472c9295ca8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 4 Sep 2013 11:28:47 +1000 Subject: [PATCH 0866/2152] Remove unecessary Tested annotation --- .../net/kencochrane/raven/connection/AsyncConnectionTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index d2898cb7bbf..b21fe39c677 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -9,7 +9,6 @@ import java.util.concurrent.TimeUnit; public class AsyncConnectionTest { - @Tested private AsyncConnection asyncConnection; @Injectable private Connection mockConnection; From 74095246f7ff2e32409a08c057bdd93a7fcc2f7c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 4 Sep 2013 11:35:32 +1000 Subject: [PATCH 0867/2152] Update dependencies and libraries --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 0d01651a1f6..5cf65b38c5d 100644 --- a/pom.xml +++ b/pom.xml @@ -107,13 +107,13 @@ 1.7.5 14.0.1 - 2.2.2 + 2.2.3 3.0.1 1.0.13 1.2.17 2.0-beta8 1.9.5 - 1.3 + 1.4 6.8.5 1.3 @@ -298,12 +298,12 @@ org.eclipse.jetty jetty-maven-plugin - 9.0.4.v20130625 + 9.0.5.v20130815 org.apache.maven.plugins maven-failsafe-plugin - 2.15 + 2.16 org.apache.maven.plugins @@ -344,7 +344,7 @@ org.apache.maven.plugins maven-surefire-report-plugin - 2.15 + 2.16 org.apache.maven.plugins From a681eb273c9150343910e1cc50a2dd7d6e8263d2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 4 Sep 2013 12:24:12 +1000 Subject: [PATCH 0868/2152] Fix and reorganise depednencies --- raven-log4j/pom.xml | 41 +++++++++++++++++++++++++---------------- raven-log4j2/pom.xml | 40 +++++++++++++++++++--------------------- raven-logback/pom.xml | 27 +++++++++++++++++---------- raven/pom.xml | 2 ++ 4 files changed, 63 insertions(+), 47 deletions(-) diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 3b13e36dddf..a23ef49e901 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -23,41 +23,50 @@ log4j log4j + + - ${project.groupId} - raven - test-jar + com.googlecode.jmockit + jmockit test - - org.slf4j - slf4j-log4j12 + org.testng + testng test - com.fasterxml.jackson.core - jackson-databind + org.hamcrest + hamcrest-core test - com.googlecode.jmockit - jmockit + org.hamcrest + hamcrest-library test - org.testng - testng + com.google.guava + guava test + + + - org.hamcrest - hamcrest-core + ${project.groupId} + raven + test-jar test - org.hamcrest - hamcrest-library + com.fasterxml.jackson.core + jackson-databind + test + + + org.slf4j + slf4j-log4j12 test diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 2dea9cf4d11..071f508b45b 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -28,46 +28,44 @@ log4j-core + - ${project.groupId} - raven - test-jar - test - - - org.apache.logging.log4j - log4j-slf4j-impl + com.googlecode.jmockit + jmockit test - - com.fasterxml.jackson.core - jackson-databind + org.testng + testng test - org.mockito - mockito-core + org.hamcrest + hamcrest-core test - com.googlecode.jmockit - jmockit + org.hamcrest + hamcrest-library test + + + - org.testng - testng + ${project.groupId} + raven + test-jar test - org.hamcrest - hamcrest-core + com.fasterxml.jackson.core + jackson-databind test - org.hamcrest - hamcrest-library + org.apache.logging.log4j + log4j-slf4j-impl test diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 51f1a1ec33a..961f3f9d41f 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -20,10 +20,8 @@ raven - ${project.groupId} - raven - test-jar - test + org.slf4j + slf4j-api ch.qos.logback @@ -34,12 +32,7 @@ logback-classic - - - com.fasterxml.jackson.core - jackson-databind - test - + com.googlecode.jmockit jmockit @@ -60,6 +53,20 @@ hamcrest-library test + + + + + ${project.groupId} + raven + test-jar + test + + + com.fasterxml.jackson.core + jackson-databind + test + diff --git a/raven/pom.xml b/raven/pom.xml index 673475bcaee..5844da8f2bf 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -33,6 +33,8 @@ provided true + + org.slf4j slf4j-jdk14 From 4f8c86c4cae1f981ae95b91a352372baa774c744 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 4 Sep 2013 12:25:43 +1000 Subject: [PATCH 0869/2152] Do not use CoreMatchers (provided by junit) but Matchers (provided by hamcrest) --- .../net/kencochrane/raven/log4j/SentryAppenderCloseTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java index 99cfd8433b5..7b736d9557f 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java @@ -8,8 +8,8 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; public class SentryAppenderCloseTest { private MockUpErrorHandler mockUpErrorHandler; From 0ceef02d31d34920330ceab10a477d1d5d8b5e20 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 5 Sep 2013 15:06:58 +1000 Subject: [PATCH 0870/2152] Set the shutdownHook as a Raven Thread --- .../java/net/kencochrane/raven/connection/AsyncConnection.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 675fbd8ca44..4ee62a0f4a3 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -74,9 +74,12 @@ private void addShutdownHook() { Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { try { + Raven.RAVEN_THREAD.set(true); AsyncConnection.this.close(); } catch (IOException e) { logger.error("An exception occurred while closing the connection.", e); + } finally { + Raven.RAVEN_THREAD.remove(); } } }); From e350d8153b8f8ce374c3a877e4260c22331614fc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 5 Sep 2013 15:07:37 +1000 Subject: [PATCH 0871/2152] Reverse the order in which exceptions are sent to sentry --- .../json/ExceptionInterfaceBinding.java | 85 +++++++++++++++---- 1 file changed, 68 insertions(+), 17 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java index 0d1ab6eaf58..483577aa5d2 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java @@ -8,6 +8,8 @@ import org.slf4j.LoggerFactory; import java.io.IOException; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.HashSet; import java.util.Set; @@ -28,30 +30,79 @@ public ExceptionInterfaceBinding(InterfaceBinding stackTrac @Override public void writeInterface(JsonGenerator generator, ExceptionInterface exceptionInterface) throws IOException { - Set dejaVu = new HashSet(); + Deque exceptions = unfoldExceptionInterface(exceptionInterface); + + //Unstack the exceptions + generator.writeStartArray(); + while (!exceptions.isEmpty()) { + writeException(generator, exceptions.pop()); + } + generator.writeEndArray(); + } + + /** + * Stack the exception and its causes with their StackTraces to provide them in the order expected by Sentry. + *

    + * Sentry expects to get the exception from the first generated (cause) to the last generated. To provide the + * exceptions in this order, those are stacked then later the stack is emptied. + *

    + *

    + * Each exception provides a {@link StackTraceInterface}. + *

    + * + * @param exceptionInterface Sentry interface containing the captured exception. + * @return a Stack of Exceptions with their {@link StackTraceInterface}. + */ + private Deque unfoldExceptionInterface(ExceptionInterface exceptionInterface) { + Deque exceptions = new ArrayDeque(); + Set circularityDetector = new HashSet(); ImmutableThrowable throwable = exceptionInterface.getThrowable(); StackTraceElement[] enclosingStackTrace = new StackTraceElement[0]; - generator.writeStartArray(); + //Stack the exceptions to send them in the reverse order while (throwable != null) { - dejaVu.add(throwable); - - generator.writeStartObject(); - generator.writeStringField(TYPE_PARAMETER, throwable.getActualClass().getSimpleName()); - generator.writeStringField(VALUE_PARAMETER, throwable.getMessage()); - generator.writeStringField(MODULE_PARAMETER, throwable.getActualClass().getPackage().getName()); - generator.writeFieldName(STACKTRACE_PARAMETER); - stackTraceInterfaceBinding.writeInterface(generator, - new StackTraceInterface(throwable.getStackTrace(), enclosingStackTrace)); - generator.writeEndObject(); + if (!circularityDetector.add(throwable)) { + logger.warn("Exiting a circular exception!"); + break; + } + + StackTraceInterface stackTrace = new StackTraceInterface(throwable.getStackTrace(), enclosingStackTrace); + exceptions.push(new ExceptionWithStackTrace(throwable, stackTrace)); enclosingStackTrace = throwable.getStackTrace(); throwable = throwable.getCause(); + } - if (dejaVu.contains(throwable)) { - logger.warn("Exiting a circular referencing exception!"); - break; - } + return exceptions; + } + + /** + * Outputs an exception with its StackTrace on a JSon stream. + * + * @param generator JSonGenerator. + * @param ewst Exception with its associated {@link StackTraceInterface}. + * @throws IOException + */ + private void writeException(JsonGenerator generator, ExceptionWithStackTrace ewst) throws IOException { + generator.writeStartObject(); + generator.writeStringField(TYPE_PARAMETER, ewst.exception.getActualClass().getSimpleName()); + generator.writeStringField(VALUE_PARAMETER, ewst.exception.getMessage()); + generator.writeStringField(MODULE_PARAMETER, ewst.exception.getActualClass().getPackage().getName()); + + generator.writeFieldName(STACKTRACE_PARAMETER); + stackTraceInterfaceBinding.writeInterface(generator, ewst.stackTrace); + generator.writeEndObject(); + } + + /** + * Class associating an exception to its {@link StackTraceInterface}. + */ + private final class ExceptionWithStackTrace { + private ImmutableThrowable exception; + private StackTraceInterface stackTrace; + + private ExceptionWithStackTrace(ImmutableThrowable exception, StackTraceInterface stackTrace) { + this.exception = exception; + this.stackTrace = stackTrace; } - generator.writeEndArray(); } } From 1ce40575327c77cb0b39b08382c68e7e6f212886 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 6 Sep 2013 05:16:50 +1000 Subject: [PATCH 0872/2152] Use Jmockit in the JSon marshaller tests Also use a tool to compare JSon json output to fixtures --- .../json/AbstractInterfaceBindingTest.java | 35 -------- .../json/ExceptionInterfaceBindingTest.java | 71 +++++++-------- .../raven/marshaller/json/JsonComparator.java | 61 +++++++++++++ .../json/MessageInterfaceBindingTest.java | 44 ++++------ .../json/StackTraceInterfaceBindingTest.java | 86 ++++++++----------- .../raven/marshaller/json/Exception1.json | 8 ++ .../raven/marshaller/json/Message1.json | 4 + .../raven/marshaller/json/StackTrace1.json | 8 ++ .../raven/marshaller/json/StackTrace2.json | 14 +++ .../raven/marshaller/json/StackTrace3.json | 14 +++ 10 files changed, 193 insertions(+), 152 deletions(-) delete mode 100644 raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractInterfaceBindingTest.java create mode 100644 raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonComparator.java create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/Exception1.json create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/Message1.json create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/StackTrace1.json create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/StackTrace2.json create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/StackTrace3.json diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractInterfaceBindingTest.java deleted file mode 100644 index 36fff587028..00000000000 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/AbstractInterfaceBindingTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package net.kencochrane.raven.marshaller.json; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.testng.annotations.BeforeMethod; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -public abstract class AbstractInterfaceBindingTest { - private JsonFactory jsonFactory; - private ObjectMapper mapper; - private ByteArrayOutputStream jsonContentStream; - - @BeforeMethod - protected void setUp() throws Exception { - jsonFactory = new JsonFactory(); - mapper = new ObjectMapper(); - } - - protected JsonGenerator getJsonGenerator() throws IOException { - jsonContentStream = new ByteArrayOutputStream(); - return jsonFactory.createGenerator(jsonContentStream); - } - - protected JsonParser getJsonParser() throws IOException { - return jsonFactory.createParser(jsonContentStream.toByteArray()); - } - - protected ObjectMapper getMapper() { - return mapper; - } -} diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index abac09fa2ef..22449cb6b0f 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -1,69 +1,56 @@ package net.kencochrane.raven.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonNode; +import mockit.Delegate; +import mockit.Injectable; +import mockit.NonStrictExpectations; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.ImmutableThrowable; import net.kencochrane.raven.event.interfaces.StackTraceInterface; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import java.util.UUID; +import java.io.IOException; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.when; - -public class ExceptionInterfaceBindingTest extends AbstractInterfaceBindingTest { +public class ExceptionInterfaceBindingTest { private ExceptionInterfaceBinding interfaceBinding; - @Mock + @Injectable private ExceptionInterface mockExceptionInterface; - @Mock - private InterfaceBinding stackTraceInterfaceBinding; + @Injectable + private InterfaceBinding mockStackTraceInterfaceBinding; @BeforeMethod public void setUp() throws Exception { - super.setUp(); - MockitoAnnotations.initMocks(this); - interfaceBinding = new ExceptionInterfaceBinding(stackTraceInterfaceBinding); + interfaceBinding = new ExceptionInterfaceBinding(mockStackTraceInterfaceBinding); - doAnswer(new Answer() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - JsonGenerator jsonGenerator = (JsonGenerator) invocation.getArguments()[0]; - if (invocation.getArguments()[1] != null) { + new NonStrictExpectations() {{ + mockStackTraceInterfaceBinding.writeInterface(withInstanceOf(JsonGenerator.class), (StackTraceInterface) any); + forEachInvocation = new Delegate() { + public void writeInterface(JsonGenerator jsonGenerator, StackTraceInterface stackTraceInterface) + throws IOException { jsonGenerator.writeStartObject(); jsonGenerator.writeEndObject(); - } else { - jsonGenerator.writeNull(); } - return null; - } - }).when(stackTraceInterfaceBinding).writeInterface(any(JsonGenerator.class), any(StackTraceInterface.class)); + }; + }}; } @Test public void testSimpleException() throws Exception { - String message = UUID.randomUUID().toString(); - Throwable throwable = new IllegalStateException(message); - when(mockExceptionInterface.getThrowable()).thenReturn(new ImmutableThrowable(throwable)); + final JsonComparator jsonComparator = new JsonComparator(); + final String message = "6e65f60d-9f22-495a-9556-7a61eeea2a14"; + final Throwable throwable = new IllegalStateException(message); + new NonStrictExpectations() {{ + mockExceptionInterface.getThrowable(); + result = new Delegate() { + public ImmutableThrowable getThrowable() { + return new ImmutableThrowable(throwable); + } + }; + }}; - JsonGenerator jsonGenerator = getJsonGenerator(); - interfaceBinding.writeInterface(jsonGenerator, mockExceptionInterface); - jsonGenerator.close(); + interfaceBinding.writeInterface(jsonComparator.getGenerator(), mockExceptionInterface); - JsonNode rootNode = getMapper().readValue(getJsonParser(), JsonNode.class); - assertThat(rootNode.isArray(), is(true)); - JsonNode exceptionNode = rootNode.get(0); - assertThat(exceptionNode.get("module").asText(), is(throwable.getClass().getPackage().getName())); - assertThat(exceptionNode.get("type").asText(), is(throwable.getClass().getSimpleName())); - assertThat(exceptionNode.get("value").asText(), is(message)); - assertThat(exceptionNode.get("stacktrace").isObject(), is(true)); + jsonComparator.assertSameAsResource("/net/kencochrane/raven/marshaller/json/Exception1.json"); } } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonComparator.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonComparator.java new file mode 100644 index 00000000000..d2a92ac383a --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonComparator.java @@ -0,0 +1,61 @@ +package net.kencochrane.raven.marshaller.json; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class JsonComparator { + private static final Logger logger = LoggerFactory.getLogger(JsonComparator.class); + private final StringWriter jsonOutput = new StringWriter(); + private final ObjectMapper objectMapper = new ObjectMapper(); + private final JsonGenerator jsonGenerator; + + public JsonComparator() throws IOException { + JsonFactory jsonFactory = new JsonFactory(); + jsonGenerator = jsonFactory.createGenerator(jsonOutput); + } + + public JsonGenerator getGenerator() { + return jsonGenerator; + } + + public void assertSameAsResource(String resource) throws IOException { + assertSameAs(JsonComparator.class.getResourceAsStream(resource)); + } + + public void assertSameAs(InputStream expected) throws IOException { + assertSame(objectMapper.readTree(expected)); + } + + public void assertSameAs(String expected) throws IOException { + assertSame(objectMapper.readTree(expected)); + } + + public void assertSameAs(File expected) throws IOException { + assertSame(objectMapper.readTree(expected)); + } + + public void assertSame(JsonNode jsonNode) throws IOException { + assertThat(objectMapper.readTree(getValue()), is(jsonNode)); + } + + public String getValue() { + try { + jsonGenerator.close(); + } catch (Exception e) { + logger.warn("An error occurred while closing the JSon Generator"); + } + return jsonOutput.toString(); + } +} diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java index b91222d5a40..7f2ea0ebf14 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java @@ -1,47 +1,39 @@ package net.kencochrane.raven.marshaller.json; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonNode; +import mockit.Injectable; +import mockit.NonStrictExpectations; import net.kencochrane.raven.event.interfaces.MessageInterface; -import org.hamcrest.Matchers; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.Arrays; import java.util.List; -import java.util.UUID; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.when; - -public class MessageInterfaceBindingTest extends AbstractInterfaceBindingTest { +public class MessageInterfaceBindingTest { private MessageInterfaceBinding interfaceBinding; - @Mock + @Injectable private MessageInterface mockMessageInterface; @BeforeMethod public void setUp() throws Exception { - super.setUp(); - MockitoAnnotations.initMocks(this); interfaceBinding = new MessageInterfaceBinding(); } @Test public void testSimpleMessage() throws Exception { - String message = UUID.randomUUID().toString(); - List parameters = Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()); - when(mockMessageInterface.getMessage()).thenReturn(message); - when(mockMessageInterface.getParameters()).thenReturn(parameters); - - JsonGenerator jSonGenerator = getJsonGenerator(); - interfaceBinding.writeInterface(jSonGenerator, mockMessageInterface); - jSonGenerator.close(); - - JsonNode rootNode = getMapper().readValue(getJsonParser(), JsonNode.class); - assertThat(rootNode.get("message").asText(), is(message)); - assertThat(getMapper().convertValue(rootNode.get("params"), List.class), Matchers.is(parameters)); + final JsonComparator jsonComparator = new JsonComparator(); + final String message = "550ee459-cbb5-438e-91d2-b0bbdefab670"; + final List parameters = Arrays.asList("33ed929b-d803-46b6-a57b-9c0feab1f468", + "5fc10379-6392-470d-9de5-e4cb805ab78c"); + new NonStrictExpectations() {{ + mockMessageInterface.getMessage(); + result = message; + mockMessageInterface.getParameters(); + result = parameters; + }}; + + interfaceBinding.writeInterface(jsonComparator.getGenerator(), mockMessageInterface); + + jsonComparator.assertSameAsResource("/net/kencochrane/raven/marshaller/json/Message1.json"); } } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java index 9e7fe695c41..21454725992 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java @@ -1,81 +1,69 @@ package net.kencochrane.raven.marshaller.json; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonNode; +import mockit.Injectable; +import mockit.NonStrictExpectations; import net.kencochrane.raven.event.interfaces.StackTraceInterface; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import java.util.UUID; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.when; - -public class StackTraceInterfaceBindingTest extends AbstractInterfaceBindingTest { +public class StackTraceInterfaceBindingTest { private StackTraceInterfaceBinding interfaceBinding; - @Mock + @Injectable private StackTraceInterface mockStackTraceInterface; @BeforeMethod public void setUp() throws Exception { - super.setUp(); - MockitoAnnotations.initMocks(this); interfaceBinding = new StackTraceInterfaceBinding(); } @Test public void testSingleStackFrame() throws Exception { - String methodName = UUID.randomUUID().toString(); - String className = UUID.randomUUID().toString(); - int lineNumber = 1; - StackTraceElement stackTraceElement = new StackTraceElement(className, methodName, null, lineNumber); - when(mockStackTraceInterface.getStackTrace()).thenReturn(new StackTraceElement[]{stackTraceElement}); + final JsonComparator jsonComparator = new JsonComparator(); + final String methodName = "0cce55c9-478f-4386-8ede-4b6f000da3e6"; + final String className = "31b26f01-9b97-442b-9f36-8a317f94ad76"; + final int lineNumber = 1; + final StackTraceElement stackTraceElement = new StackTraceElement(className, methodName, null, lineNumber); + new NonStrictExpectations() {{ + mockStackTraceInterface.getStackTrace(); + result = new StackTraceElement[]{stackTraceElement}; + }}; - JsonGenerator jSonGenerator = getJsonGenerator(); - interfaceBinding.writeInterface(jSonGenerator, mockStackTraceInterface); - jSonGenerator.close(); + interfaceBinding.writeInterface(jsonComparator.getGenerator(), mockStackTraceInterface); - JsonNode frames = getMapper().readValue(getJsonParser(), JsonNode.class).get("frames"); - assertThat(frames.size(), is(1)); - assertThat(frames.get(0).get("module").asText(), is(className)); - assertThat(frames.get(0).get("function").asText(), is(methodName)); - assertThat(frames.get(0).get("lineno").asInt(), is(lineNumber)); + jsonComparator.assertSameAsResource("/net/kencochrane/raven/marshaller/json/StackTrace1.json"); } @Test public void testFramesCommonWithEnclosing() throws Exception { - StackTraceElement stackTraceElement = new StackTraceElement("", "", null, 0); - when(mockStackTraceInterface.getStackTrace()).thenReturn(new StackTraceElement[]{stackTraceElement, stackTraceElement}); - when(mockStackTraceInterface.getFramesCommonWithEnclosing()).thenReturn(1); - - JsonGenerator jSonGenerator = getJsonGenerator(); + final JsonComparator jsonComparator = new JsonComparator(); + final StackTraceElement stackTraceElement = new StackTraceElement("", "", null, 0); + new NonStrictExpectations() {{ + mockStackTraceInterface.getStackTrace(); + result = new StackTraceElement[]{stackTraceElement, stackTraceElement}; + mockStackTraceInterface.getFramesCommonWithEnclosing(); + result = 1; + }}; interfaceBinding.setRemoveCommonFramesWithEnclosing(true); - interfaceBinding.writeInterface(jSonGenerator, mockStackTraceInterface); - jSonGenerator.close(); - JsonNode frames = getMapper().readValue(getJsonParser(), JsonNode.class).get("frames"); - assertThat(frames.size(), is(2)); - assertThat(frames.get(0).get("in_app").asBoolean(), is(false)); - assertThat(frames.get(1).get("in_app").asBoolean(), is(true)); + interfaceBinding.writeInterface(jsonComparator.getGenerator(), mockStackTraceInterface); + + jsonComparator.assertSameAsResource("/net/kencochrane/raven/marshaller/json/StackTrace2.json"); } @Test public void testFramesCommonWithEnclosingDisabled() throws Exception { - StackTraceElement stackTraceElement = new StackTraceElement("", "", null, 0); - when(mockStackTraceInterface.getStackTrace()).thenReturn(new StackTraceElement[]{stackTraceElement, stackTraceElement}); - when(mockStackTraceInterface.getFramesCommonWithEnclosing()).thenReturn(1); - - JsonGenerator jSonGenerator = getJsonGenerator(); + final JsonComparator jsonComparator = new JsonComparator(); + final StackTraceElement stackTraceElement = new StackTraceElement("", "", null, 0); + new NonStrictExpectations() {{ + mockStackTraceInterface.getStackTrace(); + result = new StackTraceElement[]{stackTraceElement, stackTraceElement}; + mockStackTraceInterface.getFramesCommonWithEnclosing(); + result = 1; + }}; interfaceBinding.setRemoveCommonFramesWithEnclosing(false); - interfaceBinding.writeInterface(jSonGenerator, mockStackTraceInterface); - jSonGenerator.close(); - JsonNode frames = getMapper().readValue(getJsonParser(), JsonNode.class).get("frames"); - assertThat(frames.size(), is(2)); - assertThat(frames.get(0).get("in_app").asBoolean(), is(true)); - assertThat(frames.get(1).get("in_app").asBoolean(), is(true)); + interfaceBinding.writeInterface(jsonComparator.getGenerator(), mockStackTraceInterface); + + jsonComparator.assertSameAsResource("/net/kencochrane/raven/marshaller/json/StackTrace3.json"); } } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/Exception1.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/Exception1.json new file mode 100644 index 00000000000..8fa4ba89290 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/Exception1.json @@ -0,0 +1,8 @@ +[ + { + "type": "IllegalStateException", + "value": "6e65f60d-9f22-495a-9556-7a61eeea2a14", + "module": "java.lang", + "stacktrace": {} + } +] diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/Message1.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/Message1.json new file mode 100644 index 00000000000..352dd3234b1 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/Message1.json @@ -0,0 +1,4 @@ +{ + "message": "550ee459-cbb5-438e-91d2-b0bbdefab670", + "params": ["33ed929b-d803-46b6-a57b-9c0feab1f468", "5fc10379-6392-470d-9de5-e4cb805ab78c"] +} diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/StackTrace1.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/StackTrace1.json new file mode 100644 index 00000000000..d201d906eda --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/StackTrace1.json @@ -0,0 +1,8 @@ +{"frames": [ + { + "module": "31b26f01-9b97-442b-9f36-8a317f94ad76", + "in_app": true, + "function": "0cce55c9-478f-4386-8ede-4b6f000da3e6", + "lineno": 1 + } +]} diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/StackTrace2.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/StackTrace2.json new file mode 100644 index 00000000000..f262b2f019b --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/StackTrace2.json @@ -0,0 +1,14 @@ +{"frames": [ + { + "module": "", + "in_app": false, + "function": "", + "lineno": 0 + }, + { + "module": "", + "in_app": true, + "function": "", + "lineno": 0 + } +]} diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/StackTrace3.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/StackTrace3.json new file mode 100644 index 00000000000..b924c37b55b --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/StackTrace3.json @@ -0,0 +1,14 @@ +{"frames": [ + { + "module": "", + "in_app": true, + "function": "", + "lineno": 0 + }, + { + "module": "", + "in_app": true, + "function": "", + "lineno": 0 + } +]} From 3308fe8fdf5b550736e99ea16fe2788c1927143d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 6 Sep 2013 05:18:24 +1000 Subject: [PATCH 0873/2152] Use jmockit in Dsn tests --- .../java/net/kencochrane/raven/dsn/DsnTest.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java index 663557c0cff..1fbcf504c9c 100644 --- a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java @@ -1,7 +1,7 @@ package net.kencochrane.raven.dsn; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import mockit.Expectations; +import mockit.Mocked; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -13,17 +13,15 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import static org.mockito.Mockito.when; public class DsnTest { - @Mock + @Mocked private Context mockContext; @BeforeMethod public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); System.setProperty("java.naming.factory.initial", InitialContextMockFactory.class.getName()); - InitialContextMockFactory.context = mockContext; + InitialContextFactory.context = mockContext; } @Test(expectedExceptions = InvalidDsnException.class) @@ -50,8 +48,11 @@ public void testDsnLookupWithNothingSet() throws Exception { @Test public void testDsnLookupWithJndi() throws Exception { - String dsn = UUID.randomUUID().toString(); - when(mockContext.lookup("java:comp/env/sentry/dsn")).thenReturn(dsn); + final String dsn = UUID.randomUUID().toString(); + new Expectations() {{ + mockContext.lookup("java:comp/env/sentry/dsn"); + result = dsn; + }}; assertThat(Dsn.dsnLookup(), is(dsn)); } From 01c9ce9a4fd1801c9537d20c2660c58bb1cd5cf6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 6 Sep 2013 05:19:13 +1000 Subject: [PATCH 0874/2152] Automatically detect the InitialContextFactory --- raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java | 1 - ...itialContextMockFactory.java => InitialContextFactory.java} | 3 +-- raven/src/test/resources/jndi.properties | 1 + 3 files changed, 2 insertions(+), 3 deletions(-) rename raven/src/test/java/net/kencochrane/raven/dsn/{InitialContextMockFactory.java => InitialContextFactory.java} (77%) create mode 100644 raven/src/test/resources/jndi.properties diff --git a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java index 1fbcf504c9c..ddc67a93a42 100644 --- a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java @@ -20,7 +20,6 @@ public class DsnTest { @BeforeMethod public void setUp() throws Exception { - System.setProperty("java.naming.factory.initial", InitialContextMockFactory.class.getName()); InitialContextFactory.context = mockContext; } diff --git a/raven/src/test/java/net/kencochrane/raven/dsn/InitialContextMockFactory.java b/raven/src/test/java/net/kencochrane/raven/dsn/InitialContextFactory.java similarity index 77% rename from raven/src/test/java/net/kencochrane/raven/dsn/InitialContextMockFactory.java rename to raven/src/test/java/net/kencochrane/raven/dsn/InitialContextFactory.java index abd76db5be2..7ce3c5d9ba6 100644 --- a/raven/src/test/java/net/kencochrane/raven/dsn/InitialContextMockFactory.java +++ b/raven/src/test/java/net/kencochrane/raven/dsn/InitialContextFactory.java @@ -2,7 +2,6 @@ import javax.naming.Context; import javax.naming.NamingException; -import javax.naming.spi.InitialContextFactory; import java.util.Hashtable; /** @@ -10,7 +9,7 @@ * * @see DsnTest#setUp() */ -public class InitialContextMockFactory implements InitialContextFactory { +public class InitialContextFactory implements javax.naming.spi.InitialContextFactory { public static Context context; @Override diff --git a/raven/src/test/resources/jndi.properties b/raven/src/test/resources/jndi.properties new file mode 100644 index 00000000000..faff7ac9308 --- /dev/null +++ b/raven/src/test/resources/jndi.properties @@ -0,0 +1 @@ +java.naming.factory.initial=net.kencochrane.raven.dsn.InitialContextFactory From 6184fdae478a2b21f003d51f43e745a142f70c93 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 6 Sep 2013 05:19:46 +1000 Subject: [PATCH 0875/2152] Use jmockit for HttpEventBuilderHelper tests --- .../helper/HttpEventBuilderHelperTest.java | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java index ef5247390a1..3fdf82034b1 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java @@ -1,36 +1,27 @@ package net.kencochrane.raven.event.helper; +import mockit.Expectations; +import mockit.Injectable; +import mockit.Mocked; +import mockit.Verifications; import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.interfaces.HttpInterface; import net.kencochrane.raven.event.interfaces.SentryInterface; import net.kencochrane.raven.servlet.RavenServletRequestListener; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import javax.servlet.ServletRequestEvent; import javax.servlet.http.HttpServletRequest; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.mockito.Mockito.*; - public class HttpEventBuilderHelperTest { private HttpEventBuilderHelper httpEventBuilderHelper; - @Mock + @Injectable private EventBuilder mockEventBuilder; - - private static void simulateRequest() { - ServletRequestEvent servletRequestEvent = mock(ServletRequestEvent.class); - when(servletRequestEvent.getServletRequest()).thenReturn(mock(HttpServletRequest.class)); - new RavenServletRequestListener().requestInitialized(servletRequestEvent); - } + @Mocked("getServletRequest") + private RavenServletRequestListener ravenServletRequestListener; @BeforeMethod public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); httpEventBuilderHelper = new HttpEventBuilderHelper(); } @@ -38,17 +29,23 @@ public void setUp() throws Exception { public void testNoRequest() throws Exception { httpEventBuilderHelper.helpBuildingEvent(mockEventBuilder); - verify(mockEventBuilder, never()).addSentryInterface(any(SentryInterface.class)); + new Verifications() {{ + mockEventBuilder.addSentryInterface((SentryInterface) any); + times = 0; + }}; } @Test - public void testWithRequest() throws Exception { - simulateRequest(); - ArgumentCaptor interfaceCaptor = ArgumentCaptor.forClass(SentryInterface.class); + public void testWithRequest(@Injectable final HttpServletRequest mockHttpServletRequest) throws Exception { + new Expectations() {{ + RavenServletRequestListener.getServletRequest(); + result = mockHttpServletRequest; + }}; httpEventBuilderHelper.helpBuildingEvent(mockEventBuilder); - verify(mockEventBuilder).addSentryInterface(interfaceCaptor.capture()); - assertThat(interfaceCaptor.getValue(), is(notNullValue())); + new Verifications() {{ + mockEventBuilder.addSentryInterface(this.withNotNull()); + }}; } } From e550e141753cb6919e51ed326a91c90a75310406 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 6 Sep 2013 08:32:13 +1000 Subject: [PATCH 0876/2152] Rewrite SentryHandler tests to use jmockit --- .../kencochrane/raven/AbstractLoggerTest.java | 118 ------------- .../jul/SentryHandlerEventBuildingTest.java | 158 ++++++++++++++++++ .../raven/jul/SentryHandlerTest.java | 89 ---------- 3 files changed, 158 insertions(+), 207 deletions(-) delete mode 100644 raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java create mode 100644 raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java delete mode 100644 raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java b/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java deleted file mode 100644 index 58e09304135..00000000000 --- a/raven/src/test/java/net/kencochrane/raven/AbstractLoggerTest.java +++ /dev/null @@ -1,118 +0,0 @@ -package net.kencochrane.raven; - -import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.MessageInterface; -import net.kencochrane.raven.event.interfaces.SentryInterface; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.util.Arrays; -import java.util.List; -import java.util.UUID; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; - -public abstract class AbstractLoggerTest { - @Mock - protected Raven mockRaven; - - @BeforeMethod - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testSimpleMessageLogging() throws Exception { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - Event event; - - String message = UUID.randomUUID().toString(); - logAnyLevel(message); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - event = eventCaptor.getValue(); - - assertThat(event.getLogger(), is(getCurrentLoggerName())); - assertThat(event.getMessage(), is(message)); - } - - protected Raven getMockRaven() { - return mockRaven; - } - - public abstract void logAnyLevel(String message); - - public abstract void logAnyLevel(String message, Throwable exception); - - public abstract void logAnyLevel(String message, List parameters); - - public abstract String getCurrentLoggerName(); - - /** - * Returns the format of the parametrised message "Some content %s %s %s" with '%s' being the wildcard. - * - * @return the format of the parametrised message. - */ - public abstract String getUnformattedMessage(); - - @Test - public abstract void testLogLevelConversions() throws Exception; - - protected void assertLogLevel(Event.Level expectedLevel) { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getLevel(), is(expectedLevel)); - reset(mockRaven); - } - - @Test - public void testLogException() throws Exception { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - Exception exception = new Exception(); - Event event; - - logAnyLevel("message", exception); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - event = eventCaptor.getValue(); - SentryInterface exceptionInterface = event.getSentryInterfaces().get(ExceptionInterface.EXCEPTION_INTERFACE); - assertThat(exceptionInterface, instanceOf(ExceptionInterface.class)); - - Throwable capturedException = ((ExceptionInterface) exceptionInterface).getThrowable(); - - assertThat(capturedException.getMessage(), is(exception.getMessage())); - assertThat(capturedException.getStackTrace(), is(exception.getStackTrace())); - } - - @Test - public void testLogParametrisedMessage() throws Exception { - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - String message = getUnformattedMessage(); - List parameters = Arrays.asList(null, UUID.randomUUID().toString(), UUID.randomUUID().toString()); - Event event; - - logAnyLevel(message, parameters); - - verify(mockRaven).sendEvent(eventCaptor.capture()); - event = eventCaptor.getValue(); - assertThat(event.getSentryInterfaces().get(MessageInterface.MESSAGE_INTERFACE), instanceOf(MessageInterface.class)); - MessageInterface messageInterface = - (MessageInterface) event.getSentryInterfaces().get(MessageInterface.MESSAGE_INTERFACE); - - assertThat(messageInterface.getMessage(), is(message)); - assertThat(messageInterface.getParameters(), is(parameters)); - assertThat(event.getMessage(), is( - "Some content " + parameters.get(0) - + " " + parameters.get(1) - + " " + parameters.get(2))); - } -} diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java new file mode 100644 index 00000000000..532cb4bce08 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java @@ -0,0 +1,158 @@ +package net.kencochrane.raven.jul; + +import mockit.Injectable; +import mockit.Verifications; +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import org.hamcrest.Matchers; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.Date; +import java.util.UUID; +import java.util.logging.ErrorManager; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SentryHandlerEventBuildingTest { + private SentryHandler sentryHandler; + @Injectable + private ErrorManager errorManager = null; + @Injectable + private Raven mockRaven = null; + + @BeforeMethod + public void setUp() throws Exception { + sentryHandler = new SentryHandler(mockRaven); + sentryHandler.setErrorManager(errorManager); + } + + private void assertNoErrorsInErrorManager() throws Exception { + new Verifications() {{ + errorManager.error(anyString, (Exception) any, anyInt); + times = 0; + }}; + } + + @Test + public void testSimpleMessageLogging() throws Exception { + final String loggerName = UUID.randomUUID().toString(); + final String message = UUID.randomUUID().toString(); + final Date date = new Date(1373883196416L); + final long threadId = 12; + + sentryHandler.publish(newLogRecord(loggerName, Level.INFO, message, null, null, null, threadId, date.getTime())); + + new Verifications() {{ + Event event; + mockRaven.runBuilderHelpers((EventBuilder) any); + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getMessage(), is(message)); + assertThat(event.getLogger(), is(loggerName)); + assertThat(event.getExtra(), Matchers.hasEntry(SentryHandler.THREAD_ID, (int) threadId)); + assertThat(event.getTimestamp(), is(date)); + }}; + assertNoErrorsInErrorManager(); + } + + @DataProvider(name = "levels") + private Object[][] levelConversions() { + return new Object[][]{ + {Event.Level.DEBUG, Level.FINEST}, + {Event.Level.DEBUG, Level.FINER}, + {Event.Level.DEBUG, Level.FINE}, + {Event.Level.DEBUG, Level.CONFIG}, + {Event.Level.INFO, Level.INFO}, + {Event.Level.WARNING, Level.WARNING}, + {Event.Level.ERROR, Level.SEVERE}}; + } + + @Test(dataProvider = "levels") + public void testLevelConversion(final Event.Level expectedLevel, Level level) throws Exception { + sentryHandler.publish(newLogRecord(null, level, null, null, null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getLevel(), is(expectedLevel)); + }}; + assertNoErrorsInErrorManager(); + } + + @Test + public void testExceptionLogging() throws Exception { + final Exception exception = new Exception(UUID.randomUUID().toString()); + + sentryHandler.publish(newLogRecord(null, Level.SEVERE, null, null, exception)); + + new Verifications() {{ + Event event; + Throwable throwable; + mockRaven.sendEvent(event = withCapture()); + ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() + .get(ExceptionInterface.EXCEPTION_INTERFACE); + throwable = exceptionInterface.getThrowable(); + assertThat(throwable.getMessage(), is(exception.getMessage())); + assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); + }}; + assertNoErrorsInErrorManager(); + } + + @Test + public void testCulpritWithSource() throws Exception { + final String className = "a"; + final String methodName = "b"; + final StackTraceElement stackTraceElement = new StackTraceElement(className, methodName, null, 0); + + sentryHandler.publish(newLogRecord(null, Level.SEVERE, null, null, null, + new StackTraceElement[]{stackTraceElement}, 0, 0)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getCulprit(), is("a.b")); + }}; + assertNoErrorsInErrorManager(); + } + + @Test + public void testCulpritWithoutSource() throws Exception { + final String loggerName = UUID.randomUUID().toString(); + + sentryHandler.publish(newLogRecord(loggerName, Level.SEVERE, null, null, null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getCulprit(), is(loggerName)); + }}; + assertNoErrorsInErrorManager(); + } + + private LogRecord newLogRecord(String loggerName, Level level, String message, + Object[] argumentArray, Throwable t) { + return newLogRecord(loggerName, level, message, argumentArray, t, null, + Thread.currentThread().getId(), System.currentTimeMillis()); + } + + private LogRecord newLogRecord(String loggerName, Level level, String message, Object[] argumentArray, + Throwable throwable, StackTraceElement[] callerData, long threadId, long timestamp) { + LogRecord logRecord = new LogRecord(level, message); + logRecord.setLoggerName(loggerName); + logRecord.setParameters(argumentArray); + logRecord.setThrown(throwable); + logRecord.setMillis(timestamp); + logRecord.setThreadID((int) threadId); + if (callerData != null && callerData.length > 0) { + logRecord.setSourceClassName(callerData[0].getClassName()); + logRecord.setSourceMethodName(callerData[0].getMethodName()); + } + return logRecord; + } +} diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java deleted file mode 100644 index cba99a73ceb..00000000000 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerTest.java +++ /dev/null @@ -1,89 +0,0 @@ -package net.kencochrane.raven.jul; - -import net.kencochrane.raven.AbstractLoggerTest; -import net.kencochrane.raven.connection.Connection; -import net.kencochrane.raven.event.Event; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.util.List; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static org.mockito.Mockito.*; - -public class SentryHandlerTest extends AbstractLoggerTest { - private Logger logger; - - @BeforeMethod - public void setUp() throws Exception { - super.setUp(); - logger = Logger.getAnonymousLogger(); - logger.setUseParentHandlers(false); - logger.setLevel(Level.ALL); - logger.addHandler(new SentryHandler(getMockRaven())); - } - - @Override - public void logAnyLevel(String message) { - logger.log(Level.INFO, message); - } - - @Override - public void logAnyLevel(String message, Throwable exception) { - logger.log(Level.SEVERE, message, exception); - } - - @Override - public void logAnyLevel(String message, List parameters) { - logger.log(Level.INFO, message, parameters.toArray()); - } - - @Override - public String getCurrentLoggerName() { - return logger.getName(); - } - - @Override - public String getUnformattedMessage() { - return "Some content {0} {1} {2}"; - } - - @Override - @Test - public void testLogLevelConversions() throws Exception { - assertLevelConverted(Event.Level.DEBUG, Level.FINEST); - assertLevelConverted(Event.Level.DEBUG, Level.FINER); - assertLevelConverted(Event.Level.DEBUG, Level.FINE); - assertLevelConverted(Event.Level.DEBUG, Level.CONFIG); - assertLevelConverted(Event.Level.INFO, Level.INFO); - assertLevelConverted(Event.Level.WARNING, Level.WARNING); - assertLevelConverted(Event.Level.ERROR, Level.SEVERE); - } - - @Test - public void testClosePropagates() throws Exception { - when(getMockRaven().getConnection()).thenReturn(mock(Connection.class)); - Handler handler = new SentryHandler(getMockRaven(), true); - - handler.close(); - - verify(getMockRaven().getConnection()).close(); - } - - @Test - public void testCloseDoesntPropagate() throws Exception { - when(getMockRaven().getConnection()).thenReturn(mock(Connection.class)); - Handler handler = new SentryHandler(getMockRaven(), false); - - handler.close(); - - verify(getMockRaven().getConnection(), never()).close(); - } - - private void assertLevelConverted(Event.Level expectedLevel, Level level) { - logger.log(level, null); - assertLogLevel(expectedLevel); - } -} From 8cfcea47744445ba950e12086fc06d91a6133ca4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 6 Sep 2013 08:32:36 +1000 Subject: [PATCH 0877/2152] Add the Thread-ID to extra details with JUL --- .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index bffbf9588cd..b6a45cb657e 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -20,6 +20,10 @@ * Logging handler in charge of sending the java.util.logging records to a Sentry server. */ public class SentryHandler extends Handler { + /** + * Name of the {@link Event#extra} property containing the Thread id. + */ + public static final String THREAD_ID = "Raven-Threadid"; /** * Current instance of {@link Raven}. * @@ -171,6 +175,8 @@ protected Event buildEvent(LogRecord record) { eventBuilder.setCulprit(record.getLoggerName()); } + eventBuilder.addExtra(THREAD_ID, record.getThreadID()); + raven.runBuilderHelpers(eventBuilder); return eventBuilder.build(); } From f51de1765b13eae97fe4f04a5dbd7fc7adb9f465 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 6 Sep 2013 08:49:14 +1000 Subject: [PATCH 0878/2152] Replace mockito with Jmockit when possible --- raven/pom.xml | 1 + .../java/net/kencochrane/raven/RavenTest.java | 73 ++++++++++--------- .../raven/connection/UdpConnectionTest.java | 20 ++--- .../raven/event/EventBuilderTest.java | 19 +++-- 4 files changed, 59 insertions(+), 54 deletions(-) diff --git a/raven/pom.xml b/raven/pom.xml index 5844da8f2bf..20099b54bfd 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -45,6 +45,7 @@ jackson-databind test
    + org.mockito mockito-core diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index ec0fe62d548..449dfa884b1 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -1,13 +1,12 @@ package net.kencochrane.raven; +import mockit.Injectable; +import mockit.Verifications; import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.helper.EventBuilderHelper; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -15,20 +14,16 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import static org.mockito.Mockito.*; public class RavenTest { private Raven raven; - @Mock + @Injectable private Connection mockConnection; - @Mock - private EventBuilderHelper mockBuilderHelper; - @Mock + @Injectable private Event mockEvent; @BeforeMethod public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); raven = new Raven(); raven.setConnection(mockConnection); } @@ -37,46 +32,56 @@ public void setUp() throws Exception { public void testSendEvent() throws Exception { raven.sendEvent(mockEvent); - verify(mockConnection).send(mockEvent); + new Verifications() {{ + mockConnection.send(mockEvent); + }}; } @Test public void testSendMessage() throws Exception { - String message = UUID.randomUUID().toString(); - ArgumentCaptor eventArgumentCaptor = ArgumentCaptor.forClass(Event.class); + final String message = UUID.randomUUID().toString(); + raven.sendMessage(message); - verify(mockConnection).send(eventArgumentCaptor.capture()); - assertThat(eventArgumentCaptor.getValue().getLevel(), equalTo(Event.Level.INFO)); - assertThat(eventArgumentCaptor.getValue().getMessage(), equalTo(message)); + new Verifications() {{ + Event event; + mockConnection.send(event = withCapture()); + assertThat(event.getLevel(), equalTo(Event.Level.INFO)); + assertThat(event.getMessage(), equalTo(message)); + }}; } @Test public void testSendException() throws Exception { - String message = UUID.randomUUID().toString(); - Exception exception = new Exception(message); - ArgumentCaptor eventArgumentCaptor = ArgumentCaptor.forClass(Event.class); + final String message = UUID.randomUUID().toString(); + final Exception exception = new Exception(message); + raven.sendException(exception); - verify(mockConnection).send(eventArgumentCaptor.capture()); - assertThat(eventArgumentCaptor.getValue().getLevel(), equalTo(Event.Level.ERROR)); - assertThat(eventArgumentCaptor.getValue().getMessage(), equalTo(message)); - assertThat(eventArgumentCaptor.getValue().getSentryInterfaces(), hasKey(ExceptionInterface.EXCEPTION_INTERFACE)); + new Verifications() {{ + Event event; + mockConnection.send(event = withCapture()); + assertThat(event.getLevel(), equalTo(Event.Level.ERROR)); + assertThat(event.getMessage(), equalTo(message)); + assertThat(event.getSentryInterfaces(), hasKey(ExceptionInterface.EXCEPTION_INTERFACE)); + }}; } @Test - public void testChangeConnection() throws Exception { - Connection mockNewConnection = mock(Connection.class); - + public void testChangeConnection(@Injectable final Connection mockNewConnection) throws Exception { raven.setConnection(mockNewConnection); + raven.sendEvent(mockEvent); - verify(mockConnection, never()).send(mockEvent); - verify(mockNewConnection).send(mockEvent); + new Verifications() {{ + mockConnection.send((Event) any); + times = 0; + mockNewConnection.send(mockEvent); + }}; } @Test - public void testAddRemoveBuilderHelpers() throws Exception { + public void testAddRemoveBuilderHelpers(@Injectable final EventBuilderHelper mockBuilderHelper) throws Exception { assertThat(raven.getBuilderHelpers(), not(contains(mockBuilderHelper))); raven.addBuilderHelper(mockBuilderHelper); @@ -86,17 +91,19 @@ public void testAddRemoveBuilderHelpers() throws Exception { } @Test(expectedExceptions = UnsupportedOperationException.class) - public void testCantModifyBuilderHelpersDirectly() throws Exception { + public void testCantModifyBuilderHelpersDirectly(@Injectable final EventBuilderHelper mockBuilderHelper) throws Exception { raven.getBuilderHelpers().add(mockBuilderHelper); } @Test - public void testRunBuilderHelpers() throws Exception { - EventBuilder eventBuilder = mock(EventBuilder.class); + public void testRunBuilderHelpers(@Injectable final EventBuilderHelper mockBuilderHelper, + @Injectable final EventBuilder mockEventBuilder) throws Exception { raven.addBuilderHelper(mockBuilderHelper); - raven.runBuilderHelpers(eventBuilder); + raven.runBuilderHelpers(mockEventBuilder); - verify(mockBuilderHelper).helpBuildingEvent(eventBuilder); + new Verifications(){{ + mockBuilderHelper.helpBuildingEvent(mockEventBuilder); + }}; } } diff --git a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java index b3a6d47c722..53707a9c177 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java @@ -1,37 +1,31 @@ package net.kencochrane.raven.connection; +import mockit.Injectable; +import mockit.Verifications; import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.marshaller.Marshaller; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.OutputStream; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.verify; - public class UdpConnectionTest { private UdpConnection udpConnection; - @Mock + @Injectable private Marshaller mockMarshaller; @BeforeMethod public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); udpConnection = new UdpConnection("", "", ""); udpConnection.setMarshaller(mockMarshaller); } @Test - public void testContentMarshalled() throws Exception { - Event event = new EventBuilder().build(); - + public void testContentMarshalled(@Injectable final Event event) throws Exception { udpConnection.send(event); - verify(mockMarshaller).marshall(eq(event), any(OutputStream.class)); + new Verifications() {{ + mockMarshaller.marshall(event, (OutputStream) any); + }}; } } diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index 722e85b3ab6..e8e40fde5a4 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -1,5 +1,7 @@ package net.kencochrane.raven.event; +import mockit.Expectations; +import mockit.Injectable; import net.kencochrane.raven.event.interfaces.SentryInterface; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -11,8 +13,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; public class EventBuilderTest { private EventBuilder eventBuilder; @@ -119,21 +119,24 @@ public void testTagsAreImmutable() throws Exception { @Test(expectedExceptions = UnsupportedOperationException.class) public void testExtrasAreImmutable() throws Exception { - String extraKey = UUID.randomUUID().toString(); - Object extraValue = mock(Object.class); + final String extraKey = UUID.randomUUID().toString(); + final Object extraValue = new Object(); Map extra = eventBuilder.addExtra(extraKey, extraValue).build().getExtra(); assertThat(extra.size(), is(1)); assertThat(extra.get(extraKey), is(extraValue)); - extra.put(UUID.randomUUID().toString(), mock(Object.class)); + extra.put(UUID.randomUUID().toString(), new Object()); } @Test(expectedExceptions = UnsupportedOperationException.class) - public void testSentryInterfacesAreImmutable() throws Exception { - SentryInterface sentryInterface = mock(SentryInterface.class); - when(sentryInterface.getInterfaceName()).thenReturn(UUID.randomUUID().toString()); + public void testSentryInterfacesAreImmutable(@Injectable final SentryInterface sentryInterface) throws Exception { + final String interfaceName = UUID.randomUUID().toString(); + new Expectations(){{ + sentryInterface.getInterfaceName(); + result = interfaceName; + }}; Map sentryInterfaces = eventBuilder .addSentryInterface(sentryInterface) From 4a7c88a8618263fc3ef86187b15778795dbd591c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 6 Sep 2013 10:00:30 +1000 Subject: [PATCH 0879/2152] Remove unecessary mention of the current class --- raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java index af792f25c10..f6424d481c5 100644 --- a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java @@ -86,11 +86,11 @@ public static String dsnLookup() { // Try to obtain the DSN from a System Environment Variable if (dsn == null) - dsn = System.getenv(Dsn.DSN_VARIABLE); + dsn = System.getenv(DSN_VARIABLE); // Try to obtain the DSN from a Java System Property if (dsn == null) - dsn = System.getProperty(Dsn.DSN_VARIABLE); + dsn = System.getProperty(DSN_VARIABLE); return dsn; } From 78f0c220026ff49e45f86fcc7912febe4fbe5b17 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 6 Sep 2013 10:01:15 +1000 Subject: [PATCH 0880/2152] Improve documentation --- .../java/net/kencochrane/raven/dsn/Dsn.java | 3 +- .../event/helper/EventBuilderHelper.java | 9 ++++++ .../event/helper/HttpEventBuilderHelper.java | 4 +++ .../event/interfaces/ImmutableThrowable.java | 15 ++++++++- .../event/interfaces/MessageInterface.java | 31 ++++++++++++++++++- 5 files changed, 59 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java index f6424d481c5..19b5bf6296e 100644 --- a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java @@ -15,7 +15,7 @@ */ public class Dsn { /** - * Name of the environment or system variable containing the DSN. + * Name of the environment and system variables containing the DSN. */ public static final String DSN_VARIABLE = "SENTRY_DSN"; private static final Logger logger = LoggerFactory.getLogger(Raven.class); @@ -78,6 +78,7 @@ public static String dsnLookup() { // Try to obtain the DSN from JNDI try { + // Check that JNDI is available (not available on Android) by loading InitialContext Class.forName("javax.naming.InitialContext", false, Dsn.class.getClassLoader()); dsn = JndiLookup.jndiLookup(); } catch (ClassNotFoundException e) { diff --git a/raven/src/main/java/net/kencochrane/raven/event/helper/EventBuilderHelper.java b/raven/src/main/java/net/kencochrane/raven/event/helper/EventBuilderHelper.java index b5dd370922e..9330c3e085a 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/helper/EventBuilderHelper.java +++ b/raven/src/main/java/net/kencochrane/raven/event/helper/EventBuilderHelper.java @@ -7,5 +7,14 @@ * {@link net.kencochrane.raven.event.Event} itself. */ public interface EventBuilderHelper { + /** + * Adds extra elements to the {@link EventBuilder} before calling {@link EventBuilder#build()}. + *

    + * EventBuilderHelper are supposed to only add details to the Event before it's built. Calling the + * {@link EventBuilder#build()} method from the helper will prevent the event from being built properly. + *

    + * + * @param eventBuilder event builder to enhance before the event is built. + */ void helpBuildingEvent(EventBuilder eventBuilder); } diff --git a/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java b/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java index e6043c109b1..47a6c97558a 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java +++ b/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java @@ -8,6 +8,10 @@ /** * EventBuilderHelper allowing to retrieve the current {@link HttpServletRequest}. + *

    + * The {@link HttpServletRequest} is retrieved from a {@link ThreadLocal} storage. This means that this builder must + * be called from the thread in which the HTTP request has been handled. + *

    */ public class HttpEventBuilderHelper implements EventBuilderHelper { @Override diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java index 328c7e68d83..5220230c1b4 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java @@ -5,7 +5,11 @@ import java.util.Arrays; /** - * Wrapper for {@code Throwable} to make it immutable. + * Wrapper for {@link Throwable} to make it immutable. + *

    + * This throwable is made for the sole purpose of making actual {@link Throwable} instance immutable.
    + * Under no circumstances an instance of ImmutableThrowable should be used for flow interruption. + *

    */ public class ImmutableThrowable extends Throwable { private final Throwable actualThrowable; @@ -19,6 +23,15 @@ public ImmutableThrowable(Throwable actualThrowable) { this.actualThrowable = actualThrowable; } + /** + * Returns the class of the original Throwable. + *

    + * As ImmutableThrowable is encapsulating a Throwable, it's important to be able to retrieve the type of the + * original exception. + *

    + * + * @return the class of the original Throwable + */ public Class getActualClass() { return actualThrowable.getClass(); } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java index 96242933fca..0f374402dad 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java @@ -6,7 +6,24 @@ import java.util.List; /** - * The Message interface for Sentry allows to add a message that will be formatted by sentry. + * The Message interface for Sentry allows to send the original pattern to Sentry, allowing the events to be grouped + * by original message (rather than the formatted version). + *

    + * Sentry's ability to regroup event with the same messages is based on the content of the message, meaning that an + * {@link net.kencochrane.raven.event.Event} with the message "User1 failed to provide an email address" + * won't be grouped with an Event with the message "User2 failed to provide an email address". + *

    + *

    + * To allow this kind of grouping, sentry supports the message interface which will provide both the pattern of the + * message and the parameters. In this example the pattern could be:
    + * {} failed to provide an email address
    + * And the parameters would be User1 in the first Event and User2 in the second Event.
    + * This way, Sentry will be able to put the two events in the same category. + *

    + *

    + * Note: Sentry won't attempt to format the message, this is why the formatted message should be set through + * {@link net.kencochrane.raven.event.EventBuilder#setMessage(String)} in any case. + *

    */ public class MessageInterface implements SentryInterface { /** @@ -31,10 +48,22 @@ public MessageInterface(String message) { this(message, Collections.emptyList()); } + /** + * Creates a parametrised message. + * + * @param message original message. + * @param params parameters of the message. + */ public MessageInterface(String message, String... params) { this(message, Arrays.asList(params)); } + /** + * Creates a parametrised message. + * + * @param message original message. + * @param parameters parameters of the message. + */ public MessageInterface(String message, List parameters) { this.message = message; this.parameters = Collections.unmodifiableList(new ArrayList(parameters)); From e8fa4b9c1d43daaccbfeeb01f438f97a616861ca Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 6 Sep 2013 10:01:29 +1000 Subject: [PATCH 0881/2152] Use NonStrictExpectations insteamd of Expectations --- .../java/net/kencochrane/raven/event/EventBuilderTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index e8e40fde5a4..3ba91d526d5 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -1,7 +1,7 @@ package net.kencochrane.raven.event; -import mockit.Expectations; import mockit.Injectable; +import mockit.NonStrictExpectations; import net.kencochrane.raven.event.interfaces.SentryInterface; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -133,7 +133,7 @@ public void testExtrasAreImmutable() throws Exception { @Test(expectedExceptions = UnsupportedOperationException.class) public void testSentryInterfacesAreImmutable(@Injectable final SentryInterface sentryInterface) throws Exception { final String interfaceName = UUID.randomUUID().toString(); - new Expectations(){{ + new NonStrictExpectations() {{ sentryInterface.getInterfaceName(); result = interfaceName; }}; From c48245f9115bad5f7647f80a12893c7773ecc4a0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 7 Sep 2013 03:33:47 +1000 Subject: [PATCH 0882/2152] Add comments on the StackTraceInterface --- .../event/interfaces/StackTraceInterface.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java index 8856f793805..f019c709c92 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java @@ -13,10 +13,26 @@ public class StackTraceInterface implements SentryInterface { private final StackTraceElement[] stackTrace; private final int framesCommonWithEnclosing; + /** + * Creates a StackTrace. + * + * @param stackTrace StackTrace to provide to Sentry. + */ public StackTraceInterface(StackTraceElement[] stackTrace) { this(stackTrace, new StackTraceElement[0]); } + /** + * Creates a StackTrace. + *

    + * With the help of the enclosing StackTrace, figure out which frames are in common with the parent exception + * to potentially hide them later in Sentry. + *

    + * + * @param stackTrace StackTrace to provide to Sentry. + * @param enclosingStackTrace StackTrace of the enclosing exception, to determine how many Stack frames + * are in common. + */ public StackTraceInterface(StackTraceElement[] stackTrace, StackTraceElement[] enclosingStackTrace) { this.stackTrace = Arrays.copyOf(stackTrace, stackTrace.length); From f3f9987d06bd3cd48832805e129510b0680da887 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 7 Sep 2013 04:31:18 +1000 Subject: [PATCH 0883/2152] Add missing documentation --- .../raven/log4j/SentryAppender.java | 15 +++++++ .../raven/log4j2/SentryAppender.java | 16 ++++++- .../raven/logback/SentryAppender.java | 15 +++++++ .../raven/DefaultRavenFactory.java | 43 ++++++++++++++++++- .../net/kencochrane/raven/RavenFactory.java | 40 +++++++++++++++++ .../raven/connection/ConnectionException.java | 3 +- .../raven/connection/HttpConnection.java | 14 ++++++ .../raven/connection/UdpConnection.java | 15 +++++++ .../raven/dsn/InvalidDsnException.java | 2 + .../event/interfaces/ExceptionInterface.java | 5 +++ .../raven/event/interfaces/HttpInterface.java | 5 +++ .../event/interfaces/MessageInterface.java | 4 +- .../event/interfaces/StackTraceInterface.java | 4 +- .../kencochrane/raven/jul/SentryHandler.java | 15 +++++++ .../raven/marshaller/Marshaller.java | 5 +++ .../json/ExceptionInterfaceBinding.java | 10 +++++ .../raven/marshaller/json/JsonMarshaller.java | 7 +++ 17 files changed, 211 insertions(+), 7 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index c01cf5bd0f4..7555fcc62b7 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -53,14 +53,29 @@ public class SentryAppender extends AppenderSkeleton { private final boolean propagateClose; private boolean guard; + /** + * Creates an instance of SentryAppender. + */ public SentryAppender() { this.propagateClose = true; } + /** + * Creates an instance of SentryAppender. + * + * @param raven instance of Raven to use with this appender. + */ public SentryAppender(Raven raven) { this(raven, false); } + /** + * Creates an instance of SentryAppender. + * + * @param raven instance of Raven to use with this appender. + * @param propagateClose true if the {@link net.kencochrane.raven.connection.Connection#close()} should be called + * when the appender is closed. + */ public SentryAppender(Raven raven, boolean propagateClose) { this.raven = raven; this.propagateClose = propagateClose; diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 49242b7d533..2fbefa49d6e 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -68,15 +68,29 @@ public class SentryAppender extends AbstractAppender { protected String ravenFactory; private final boolean propagateClose; + /** + * Creates an instance of SentryAppender. + */ public SentryAppender() { this(APPENDER_NAME, null, true); } - + /** + * Creates an instance of SentryAppender. + * + * @param raven instance of Raven to use with this appender. + */ public SentryAppender(Raven raven) { this(raven, false); } + /** + * Creates an instance of SentryAppender. + * + * @param raven instance of Raven to use with this appender. + * @param propagateClose true if the {@link net.kencochrane.raven.connection.Connection#close()} should be called + * when the appender is closed. + */ public SentryAppender(Raven raven, boolean propagateClose) { this(APPENDER_NAME, null, propagateClose); this.raven = raven; diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 0d3f9cd69f1..d79a983ecfb 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -54,14 +54,29 @@ public class SentryAppender extends AppenderBase { protected String ravenFactory; private final boolean propagateClose; + /** + * Creates an instance of SentryAppender. + */ public SentryAppender() { propagateClose = true; } + /** + * Creates an instance of SentryAppender. + * + * @param raven instance of Raven to use with this appender. + */ public SentryAppender(Raven raven) { this(raven, false); } + /** + * Creates an instance of SentryAppender. + * + * @param raven instance of Raven to use with this appender. + * @param propagateClose true if the {@link net.kencochrane.raven.connection.Connection#close()} should be called + * when the appender is closed. + */ public SentryAppender(Raven raven, boolean propagateClose) { this.raven = raven; this.propagateClose = propagateClose; diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index f83fd52d6d6..53f7b32eabb 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -70,7 +70,6 @@ public Raven createRavenInstance(Dsn dsn) { raven.setConnection(createConnection(dsn)); try { Class.forName("javax.servlet.Servlet", false, this.getClass().getClassLoader()); - //TODO: Is it enough? Shouldn't it look for Servlet >= 3.0 ? raven.addBuilderHelper(new HttpEventBuilderHelper()); } catch (ClassNotFoundException e) { logger.trace("It seems that the current environment doesn't provide access to servlets."); @@ -78,6 +77,12 @@ public Raven createRavenInstance(Dsn dsn) { return raven; } + /** + * Creates a connection to the given DSN by determining the protocol. + * + * @param dsn Data Source Name of the Sentry server to use. + * @return a connection to the server. + */ protected Connection createConnection(Dsn dsn) { String protocol = dsn.getProtocol(); Connection connection; @@ -100,6 +105,14 @@ protected Connection createConnection(Dsn dsn) { return connection; } + /** + * Encapsulates an already existing connection in an {@link AsyncConnection} and get the async options from the + * Sentry DSN. + * + * @param dsn Data Source Name of the Sentry server. + * @param connection Connection to encapsulate in an {@link AsyncConnection}. + * @return the asynchronous connection. + */ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { AsyncConnection asyncConnection = new AsyncConnection(connection, true); @@ -133,6 +146,12 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { return asyncConnection; } + /** + * Creates an HTTP connection to the Sentry server. + * + * @param dsn Data Source Name of the Sentry server. + * @return an {@link HttpConnection} to the server. + */ protected Connection createHttpConnection(Dsn dsn) { URL sentryApiUrl = HttpConnection.getSentryApiUrl(dsn.getUri(), dsn.getProjectId()); HttpConnection httpConnection = new HttpConnection(sentryApiUrl, dsn.getPublicKey(), dsn.getSecretKey()); @@ -146,6 +165,12 @@ protected Connection createHttpConnection(Dsn dsn) { return httpConnection; } + /** + * Creates an UDP connection to the Sentry server. + * + * @param dsn Data Source Name of the Sentry server. + * @return an {@link UdpConnection} to the server. + */ protected Connection createUdpConnection(Dsn dsn) { int port = dsn.getPort() != -1 ? dsn.getPort() : UdpConnection.DEFAULT_UDP_PORT; UdpConnection udpConnection = new UdpConnection(dsn.getHost(), port, dsn.getPublicKey(), dsn.getSecretKey()); @@ -153,6 +178,13 @@ protected Connection createUdpConnection(Dsn dsn) { return udpConnection; } + /** + * Creates a JSON marshaller that will convert every {@link net.kencochrane.raven.event.Event} in a format + * handled by the Sentry server. + * + * @param dsn Data Source Name of the Sentry server. + * @return a {@link JsonMarshaller} to process the events. + */ protected Marshaller createMarshaller(Dsn dsn) { JsonMarshaller marshaller = new JsonMarshaller(); @@ -177,6 +209,15 @@ protected Marshaller createMarshaller(Dsn dsn) { return marshaller; } + /** + * Provides a list of package names to consider as "not in-app". + *

    + * Those packages will be used with the {@inheritDoc StackTraceInterface} to hide frames that aren't a part of + * the main application. + *

    + * + * @return the list of "not in-app" packages. + */ protected Collection getNotInAppFrames() { return Arrays.asList("com.sun.", "java.", diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index e1925b62c9a..b42c96995a4 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -19,22 +19,56 @@ public abstract class RavenFactory { private static final Map MANUALLY_REGISTERED_FACTORIES = new HashMap(); private static final Logger logger = LoggerFactory.getLogger(RavenFactory.class); + /** + * Manually adds a RavenFactory to the system. + *

    + * Usually RavenFactories are automatically detected with the {@link ServiceLoader} system, but some systems + * such as Android do not provide a fully working ServiceLoader.
    + * If the factory isn't detected automatically, it's possible to add it through this method. + *

    + * + * @param ravenFactory ravenFactory to support. + */ public static void registerFactory(RavenFactory ravenFactory) { MANUALLY_REGISTERED_FACTORIES.put(ravenFactory.getClass().getName(), ravenFactory); } + /** + * Creates an instance of Raven using the DSN obtain through {@link net.kencochrane.raven.dsn.Dsn#dsnLookup()}. + * + * @return an instance of Raven. + */ public static Raven ravenInstance() { return ravenInstance(Dsn.dsnLookup()); } + /** + * Creates an instance of Raven using the provided DSN. + * + * @param dsn Data Source Name of the Sentry server. + * @return an instance of Raven. + */ public static Raven ravenInstance(String dsn) { return ravenInstance(new Dsn(dsn)); } + /** + * Creates an instance of Raven using the provided DSN. + * + * @param dsn Data Source Name of the Sentry server. + * @return an instance of Raven. + */ public static Raven ravenInstance(Dsn dsn) { return ravenInstance(dsn, null); } + /** + * Creates an instance of Raven using the provided DSN and the specified factory. + * + * @param dsn Data Source Name of the Sentry server. + * @param ravenFactoryName name of the raven factory to use to generate an instance of Raven. + * @return an instance of Raven. + */ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { Raven raven = ravenInstanceFromManualFactories(dsn, ravenFactoryName); @@ -87,5 +121,11 @@ private static Raven ravenInstanceFromAutoFactories(Dsn dsn, String ravenFactory return raven; } + /** + * Creates an instance of Raven given a DSN. + * + * @param dsn Data Source Name of the Sentry server. + * @return an instance of Raven or {@code null} if it isn't possible to create one. + */ public abstract Raven createRavenInstance(Dsn dsn); } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/ConnectionException.java b/raven/src/main/java/net/kencochrane/raven/connection/ConnectionException.java index e297830f7da..41d9ee0fdf6 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/ConnectionException.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/ConnectionException.java @@ -7,7 +7,7 @@ *

    */ public class ConnectionException extends RuntimeException { - + //CHECKSTYLE.OFF: JavadocMethod public ConnectionException() { } @@ -22,4 +22,5 @@ public ConnectionException(String message, Throwable cause) { public ConnectionException(Throwable cause) { super(cause); } + //CHECKSTYLE.ON: JavadocMethod } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index a4caaba3a90..acaaf93de9a 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -62,11 +62,25 @@ public boolean verify(String hostname, SSLSession sslSession) { */ private boolean bypassSecurity = false; + /** + * Creates an HTTP connection to a Sentry server. + * + * @param sentryUrl URL to the Sentry API. + * @param publicKey public key of the current project. + * @param secretKey private key of the current project. + */ public HttpConnection(URL sentryUrl, String publicKey, String secretKey) { super(publicKey, secretKey); this.sentryUrl = sentryUrl; } + /** + * Automatically determines the URL to the HTTP API of Sentry. + * + * @param sentryUri URI of the Sentry instance. + * @param projectId unique identifier of the current project. + * @return an URL to the HTTP API of Sentry. + */ public static URL getSentryApiUrl(URI sentryUri, String projectId) { try { String url = sentryUri.toString() + "api/" + projectId + "/store/"; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index e49c051ff94..7102a2061c0 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -22,10 +22,25 @@ public class UdpConnection extends AbstractConnection { private DatagramSocket socket; private Marshaller marshaller; + /** + * Creates an UDP connection to a Sentry server. + * + * @param hostname hostname of the Sentry server. + * @param publicKey public key of the current project. + * @param secretKey private key of the current project. + */ public UdpConnection(String hostname, String publicKey, String secretKey) { this(hostname, DEFAULT_UDP_PORT, publicKey, secretKey); } + /** + * Creates an UDP connection to a Sentry server. + * + * @param hostname hostname of the Sentry server. + * @param port Port on which the Sentry server listens. + * @param publicKey public key of the current project. + * @param secretKey private key of the current project. + */ public UdpConnection(String hostname, int port, String publicKey, String secretKey) { super(publicKey, secretKey); openSocket(hostname, port); diff --git a/raven/src/main/java/net/kencochrane/raven/dsn/InvalidDsnException.java b/raven/src/main/java/net/kencochrane/raven/dsn/InvalidDsnException.java index bc5c0f53036..3e538985e56 100644 --- a/raven/src/main/java/net/kencochrane/raven/dsn/InvalidDsnException.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/InvalidDsnException.java @@ -8,6 +8,7 @@ *

    */ public class InvalidDsnException extends RuntimeException { + //CHECKSTYLE.OFF: JavadocMethod public InvalidDsnException() { } @@ -22,4 +23,5 @@ public InvalidDsnException(String message, Throwable cause) { public InvalidDsnException(Throwable cause) { super(cause); } + //CHECKSTYLE.ON: JavadocMethod } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java index c0013aef484..8b3bcf0437f 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java @@ -10,6 +10,11 @@ public class ExceptionInterface implements SentryInterface { public static final String EXCEPTION_INTERFACE = "sentry.interfaces.Exception"; private final ImmutableThrowable throwable; + /** + * Creates a an Exception element for an {@link net.kencochrane.raven.event.Event}. + * + * @param throwable Exception from the JVM to send to Sentry. + */ public ExceptionInterface(Throwable throwable) { this.throwable = new ImmutableThrowable(throwable); } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java index 51fd6cd1fac..312ba16b344 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java @@ -12,6 +12,11 @@ public class HttpInterface implements SentryInterface { public static final String HTTP_INTERFACE = "sentry.interfaces.Http"; private final HttpServletRequest request; + /** + * Creates a an HTTP element for an {@link net.kencochrane.raven.event.Event}. + * + * @param request Catpured HTTP request to send to Sentry. + */ public HttpInterface(HttpServletRequest request) { this.request = request; } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java index 0f374402dad..12ceb82dd3f 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java @@ -49,7 +49,7 @@ public MessageInterface(String message) { } /** - * Creates a parametrised message. + * Creates a parametrised message for an {@link net.kencochrane.raven.event.Event}. * * @param message original message. * @param params parameters of the message. @@ -59,7 +59,7 @@ public MessageInterface(String message, String... params) { } /** - * Creates a parametrised message. + * Creates a parametrised message for an {@link net.kencochrane.raven.event.Event}. * * @param message original message. * @param parameters parameters of the message. diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java index f019c709c92..6922212c4f4 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java @@ -14,7 +14,7 @@ public class StackTraceInterface implements SentryInterface { private final int framesCommonWithEnclosing; /** - * Creates a StackTrace. + * Creates a StackTrace for an {@link net.kencochrane.raven.event.Event}. * * @param stackTrace StackTrace to provide to Sentry. */ @@ -23,7 +23,7 @@ public StackTraceInterface(StackTraceElement[] stackTrace) { } /** - * Creates a StackTrace. + * Creates a StackTrace for an {@link net.kencochrane.raven.event.Event}. *

    * With the help of the enclosing StackTrace, figure out which frames are in common with the parent exception * to potentially hide them later in Sentry. diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index b6a45cb657e..75601cc8c11 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -47,15 +47,30 @@ public class SentryHandler extends Handler { private final boolean propagateClose; private boolean guard = false; + /** + * Creates an instance of SentryHandler. + */ public SentryHandler() { propagateClose = true; retrieveProperties(); } + /** + * Creates an instance of SentryHandler. + * + * @param raven instance of Raven to use with this appender. + */ public SentryHandler(Raven raven) { this(raven, false); } + /** + * Creates an instance of SentryHandler. + * + * @param raven instance of Raven to use with this appender. + * @param propagateClose true if the {@link net.kencochrane.raven.connection.Connection#close()} should be called + * when the appender is closed. + */ public SentryHandler(Raven raven, boolean propagateClose) { this.raven = raven; this.propagateClose = propagateClose; diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java index 40643dfdb43..7a20c92d9f9 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java @@ -27,6 +27,11 @@ public interface Marshaller { final class UncloseableOutputStream extends OutputStream { private final OutputStream originalStream; + /** + * Creates an OutputStream which will not delegate the {@link #close()} operation. + * + * @param originalStream original stream to encapsulate. + */ public UncloseableOutputStream(OutputStream originalStream) { this.originalStream = originalStream; } diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java index 483577aa5d2..3bdfebc80f9 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java @@ -24,6 +24,16 @@ public class ExceptionInterfaceBinding implements InterfaceBinding stackTraceInterfaceBinding; + /** + * Creates a Binding system to send a {@link ExceptionInterface} on JSON stream. + *

    + * Exceptions may contain StackTraces, this means that the system should also be able to send a + * {@link StackTraceInterface} on the JSON stream. + *

    + * + * @param stackTraceInterfaceBinding InterfaceBinding allowing to send a {@link StackTraceInterface} on the JSON + * stream. + */ public ExceptionInterfaceBinding(InterfaceBinding stackTraceInterfaceBinding) { this.stackTraceInterfaceBinding = stackTraceInterfaceBinding; } diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 8f47a01ab41..dc29c173390 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -244,6 +244,13 @@ private String formatTimestamp(Date timestamp) { return ISO_FORMAT.format(timestamp); } + /** + * Add an interface binding to send a type of {@link SentryInterface} through a JSON stream. + * + * @param sentryInterfaceClass Actual type of SentryInterface supported by the {@link InterfaceBinding} + * @param binding InterfaceBinding converting SentryInterfaces of type {@code sentryInterfaceClass}. + * @param Type of SentryInterface. + */ public void addInterfaceBinding(Class sentryInterfaceClass, InterfaceBinding binding) { this.interfaceBindings.put(sentryInterfaceClass, binding); From 836808a22bce93b506c9685b0defca789d56c66a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 7 Sep 2013 04:31:37 +1000 Subject: [PATCH 0884/2152] Update Checkstyle to not apply checks on sentrystub --- src/checkstyle/checkstyle.xml | 7 +++++-- src/checkstyle/suppressions.xml | 8 ++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 src/checkstyle/suppressions.xml diff --git a/src/checkstyle/checkstyle.xml b/src/checkstyle/checkstyle.xml index 08078a0f87d..14fd6433398 100644 --- a/src/checkstyle/checkstyle.xml +++ b/src/checkstyle/checkstyle.xml @@ -1,7 +1,7 @@ + "-//Puppy Crawl//DTD Check Configuration 1.3//EN" + "http://www.puppycrawl.com/dtds/configuration_1_3.dtd"> + + + diff --git a/src/checkstyle/suppressions.xml b/src/checkstyle/suppressions.xml new file mode 100644 index 00000000000..048fbc29177 --- /dev/null +++ b/src/checkstyle/suppressions.xml @@ -0,0 +1,8 @@ + + + + + + From aa11cf4794887cebcaf7b8c8d10e781e683c575e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 7 Sep 2013 04:34:19 +1000 Subject: [PATCH 0885/2152] Force non-private method to be documented --- src/checkstyle/checkstyle.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/checkstyle/checkstyle.xml b/src/checkstyle/checkstyle.xml index 14fd6433398..2a575aea0b9 100644 --- a/src/checkstyle/checkstyle.xml +++ b/src/checkstyle/checkstyle.xml @@ -76,15 +76,8 @@ - - - - - - - From cc429f192851dc9e804549fff41c1c52d4a79619 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 8 Sep 2013 07:55:44 +1000 Subject: [PATCH 0886/2152] Automatically define the name of the application based on the git commit --- raven/pom.xml | 34 +++++++++++++++++++ .../java/net/kencochrane/raven/Raven.java | 3 +- .../src/main/resources/raven-build.properties | 1 + 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 raven/src/main/resources/raven-build.properties diff --git a/raven/pom.xml b/raven/pom.xml index 20099b54bfd..d0fab78670b 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -74,7 +74,41 @@ + + + src/main/resources + + raven-build.properties + + true + + + src/main/resources + + + + org.codehaus.mojo + buildnumber-maven-plugin + 1.2 + + + + validate + + create + + + + + Raven-Java/{0}-{1} + + ${project.version} + scmVersion + + 5 + + org.apache.maven.plugins maven-jar-plugin diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 29e0dcff65e..96f21e6c071 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -10,6 +10,7 @@ import java.util.Collections; import java.util.HashSet; +import java.util.ResourceBundle; import java.util.Set; /** @@ -34,7 +35,7 @@ protected Boolean initialValue() { * Version of this client, the major version is the current supported Sentry protocol, the minor version changes * for each release of this project. */ - public static final String NAME = "Raven-Java/4.0"; + public static final String NAME = ResourceBundle.getBundle("raven-build").getString("build.name"); private static final Logger logger = LoggerFactory.getLogger(Raven.class); private final Set builderHelpers = new HashSet(); private Connection connection; diff --git a/raven/src/main/resources/raven-build.properties b/raven/src/main/resources/raven-build.properties new file mode 100644 index 00000000000..5d8ba1d5330 --- /dev/null +++ b/raven/src/main/resources/raven-build.properties @@ -0,0 +1 @@ +build.name=${buildNumber} From 51618d6e0149644b456a5c9df3e3575a035f5d9b Mon Sep 17 00:00:00 2001 From: Michael Josephson Date: Mon, 9 Sep 2013 14:43:09 +0100 Subject: [PATCH 0887/2152] Fix NullPointerException if exception is in the default package getPackage() returns null if the class is in the default package so getPackage().getName() would result in a NullPointerException in this scenario. If the package is null use "(default)" instead --- .../json/ExceptionInterfaceBinding.java | 4 ++- .../json/ExceptionInterfaceBindingTest.java | 32 +++++++++++++++++++ .../raven/marshaller/json/Exception2.json | 8 +++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/Exception2.json diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java index 3bdfebc80f9..a7bdcb572d8 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java @@ -22,6 +22,7 @@ public class ExceptionInterfaceBinding implements InterfaceBinding stackTraceInterfaceBinding; /** @@ -96,7 +97,8 @@ private void writeException(JsonGenerator generator, ExceptionWithStackTrace ews generator.writeStartObject(); generator.writeStringField(TYPE_PARAMETER, ewst.exception.getActualClass().getSimpleName()); generator.writeStringField(VALUE_PARAMETER, ewst.exception.getMessage()); - generator.writeStringField(MODULE_PARAMETER, ewst.exception.getActualClass().getPackage().getName()); + Package aPackage = ewst.exception.getActualClass().getPackage(); + generator.writeStringField(MODULE_PARAMETER, (aPackage != null) ? aPackage.getName() : DEFAULT_PACKAGE_NAME); generator.writeFieldName(STACKTRACE_PARAMETER); stackTraceInterfaceBinding.writeInterface(generator, ewst.stackTrace); diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index 22449cb6b0f..c933472a629 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -1,6 +1,7 @@ package net.kencochrane.raven.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; +import mockit.Deencapsulation; import mockit.Delegate; import mockit.Injectable; import mockit.NonStrictExpectations; @@ -53,4 +54,35 @@ public ImmutableThrowable getThrowable() { jsonComparator.assertSameAsResource("/net/kencochrane/raven/marshaller/json/Exception1.json"); } + + @Test + public void testClassInDefaultPackage() throws Exception { + Deencapsulation.setField((Object) DefaultPackageException.class, "name", + DefaultPackageException.class.getSimpleName()); + final JsonComparator jsonComparator = new JsonComparator(); + final Throwable throwable = new DefaultPackageException(); + new NonStrictExpectations() {{ + mockExceptionInterface.getThrowable(); + result = new Delegate() { + public ImmutableThrowable getThrowable() { + return new ImmutableThrowable(throwable); + } + }; + }}; + + interfaceBinding.writeInterface(jsonComparator.getGenerator(), mockExceptionInterface); + + jsonComparator.assertSameAsResource("/net/kencochrane/raven/marshaller/json/Exception2.json"); + } +} + +/** + * Exception used to test exceptions defined in the default package. + *

    + * Obviously we can't use an Exception which is really defined in the default package within those tests + * (can't import it), so instead set the name of the class to remove the package name.
    + * {@code Deencapsulation.setField((Object) DefaultPackageException.class, "name", "DefaultPackageClass")} + *

    + */ +class DefaultPackageException extends Exception { } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/Exception2.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/Exception2.json new file mode 100644 index 00000000000..d5f64015573 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/Exception2.json @@ -0,0 +1,8 @@ +[ + { + "type": "DefaultPackageException", + "value": null, + "module": "(default)", + "stacktrace": {} + } +] From 8e0be482e923392642004547a209af5e25235231 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 19 Sep 2013 22:01:56 +1000 Subject: [PATCH 0888/2152] [maven-release-plugin] prepare release v4.1 --- pom.xml | 4 ++-- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 5cf65b38c5d..aa28e8b873f 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 4.1-SNAPSHOT + 4.1 pom Raven-Java @@ -74,7 +74,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v4.1 https://github.com/${github.repo}/issues diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index a23ef49e901..71791fc24ce 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1-SNAPSHOT + 4.1 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 071f508b45b..22d7b2ae325 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1-SNAPSHOT + 4.1 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 961f3f9d41f..65c44459af6 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1-SNAPSHOT + 4.1 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index d0fab78670b..9f72071a467 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1-SNAPSHOT + 4.1 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index aa0f969fdc8..0a5aca4c775 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1-SNAPSHOT + 4.1 sentry-stub From 815d0c6510c5ead5b0d4f3aa25630db30b57a2ae Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 19 Sep 2013 22:01:56 +1000 Subject: [PATCH 0889/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index aa28e8b873f..a208e0f0aab 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 4.1 + 4.1.1-SNAPSHOT pom Raven-Java @@ -74,7 +74,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v4.1 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 71791fc24ce..5fe0f626868 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1 + 4.1.1-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 22d7b2ae325..bd2eca4182d 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1 + 4.1.1-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 65c44459af6..d4b8d050318 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1 + 4.1.1-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 9f72071a467..c7f5e93b59c 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1 + 4.1.1-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 0a5aca4c775..06940b50f1e 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1 + 4.1.1-SNAPSHOT sentry-stub From 5b21a4622a4581e84a7c9242597ecc9bab152490 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 19 Sep 2013 22:19:18 +1000 Subject: [PATCH 0890/2152] Update documentation to mention the new version of Raven (4.1) --- raven-log4j/README.md | 4 ++-- raven-log4j2/README.md | 4 ++-- raven-logback/README.md | 4 ++-- raven/README.md | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 38a263deb5d..8fdd2854c12 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,12 +10,12 @@ for log4j to send the logged events to Sentry. net.kencochrane.raven raven-log4j - 4.0 + 4.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j%7C4.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j%7C4.1%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index c1ff1c60092..f0605c87442 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,12 +10,12 @@ for Log4j 2 to send the logged events to Sentry. net.kencochrane.raven raven-log4j2 - 4.0 + 4.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j2%7C4.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j2%7C4.1%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-logback/README.md b/raven-logback/README.md index ef441e3dd27..96844a065c7 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,12 +10,12 @@ for logback to send the logged events to Sentry. net.kencochrane.raven raven-logback - 4.0 + 4.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-logback%7C4.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-logback%7C4.1%7Cjar). ### Manual dependency management Relies on: diff --git a/raven/README.md b/raven/README.md index 53544479c4c..cda817650df 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,17 +10,17 @@ for `java.util.logging`. net.kencochrane.raven raven - 4.0 + 4.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1%7Cjar). ### Manual dependency management Relies on: - - [raven-4.0.jar](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.0%7Cjar) + - [raven-4.1.jar](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1%7Cjar) - [guava-14.0.1.jar](https://search.maven.org/#artifactdetails%7Ccom.google.guava%7Cguava%7C14.0.1%7Cjar) - [jackson-core-2.2.2.jar](https://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.2.2%7Cjar) - [slf4j-api-1.7.5.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.5%7Cjar) From e0ea9f184ab7c8609feb2a8484b14030eb7f1064 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 22 Sep 2013 09:25:32 +1000 Subject: [PATCH 0891/2152] Update to log4j2-beta9 --- pom.xml | 2 +- .../net/kencochrane/raven/log4j2/SentryAppender.java | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index a208e0f0aab..ab1477f48d9 100644 --- a/pom.xml +++ b/pom.xml @@ -111,7 +111,7 @@ 3.0.1 1.0.13 1.2.17 - 2.0-beta8 + 2.0-beta9 1.9.5 1.4 6.8.5 diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 2fbefa49d6e..73db64967fd 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -14,7 +14,7 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttr; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.message.Message; @@ -28,8 +28,8 @@ /** * Appender for log4j2 in charge of sending the logged events to a Sentry server. */ -@Plugin(name = "Raven", category = "Core", elementType = "appender") -public class SentryAppender extends AbstractAppender { +@Plugin(name = "Raven", category = "Core", elementType = "appender", printObject = true) +public class SentryAppender extends AbstractAppender { /** * Default name for the appender. */ @@ -111,9 +111,9 @@ private SentryAppender(String name, Filter filter, boolean propagateClose) { * @return The SentryAppender. */ @PluginFactory - public static SentryAppender createAppender(@PluginAttr("name") final String name, - @PluginAttr("dsn") final String dsn, - @PluginAttr("ravenFactory") final String ravenFactory, + public static SentryAppender createAppender(@PluginAttribute("name") final String name, + @PluginAttribute("dsn") final String dsn, + @PluginAttribute("ravenFactory") final String ravenFactory, @PluginElement("filters") final Filter filter) { if (name == null) { From 78d5c3a03ef2a496881e9dd67cb4d7fb5859845b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 22 Sep 2013 09:40:59 +1000 Subject: [PATCH 0892/2152] Ensure that the dependencies are declared properly --- pom.xml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pom.xml b/pom.xml index ab1477f48d9..5fbb2a5797a 100644 --- a/pom.xml +++ b/pom.xml @@ -292,6 +292,26 @@
    + + org.apache.maven.plugins + maven-dependency-plugin + 2.8 + + + analyze + verify + + analyze-only + + + ${skipTests} + true + true + + + + +
    From 8a0c3776f0a3ef61255fa82715ecb66c6398fb8e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 22 Sep 2013 09:41:25 +1000 Subject: [PATCH 0893/2152] Declare the jackson-annotations dependency --- pom.xml | 5 +++++ sentry-stub/pom.xml | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/pom.xml b/pom.xml index 5fbb2a5797a..6ab009998f4 100644 --- a/pom.xml +++ b/pom.xml @@ -157,6 +157,11 @@ jackson-core ${jackson.version}
    + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + com.fasterxml.jackson.core jackson-databind diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 06940b50f1e..12a94e0fbc9 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -23,6 +23,10 @@ com.fasterxml.jackson.core jackson-core + + com.fasterxml.jackson.core + jackson-annotations + com.fasterxml.jackson.core jackson-databind From d81fb8f58542dd300f076fccbe2057d46ae392c7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 22 Sep 2013 09:59:29 +1000 Subject: [PATCH 0894/2152] Update dependencies --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 6ab009998f4..409ce7b3d50 100644 --- a/pom.xml +++ b/pom.xml @@ -106,7 +106,7 @@ 1.7.5 - 14.0.1 + 15.0 2.2.3 3.0.1 1.0.13 @@ -114,7 +114,7 @@ 2.0-beta9 1.9.5 1.4 - 6.8.5 + 6.8.7 1.3 From 30e44c092d67c3955977c411af7f51a572af4728 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 22 Sep 2013 10:00:00 +1000 Subject: [PATCH 0895/2152] move maven-dependency-plugin in pluginManagement --- pom.xml | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/pom.xml b/pom.xml index 409ce7b3d50..e8995e1e248 100644 --- a/pom.xml +++ b/pom.xml @@ -297,26 +297,6 @@ - - org.apache.maven.plugins - maven-dependency-plugin - 2.8 - - - analyze - verify - - analyze-only - - - ${skipTests} - true - true - - - - - @@ -350,6 +330,25 @@ + + org.apache.maven.plugins + maven-dependency-plugin + 2.8 + + + analyze + verify + + analyze-only + + + ${skipTests} + true + true + + + +
    From 25a33191361c8cd530cb8b1285984a623a8a7591 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 22 Sep 2013 10:00:22 +1000 Subject: [PATCH 0896/2152] Remove redundant version declaration --- pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pom.xml b/pom.xml index e8995e1e248..9af78995436 100644 --- a/pom.xml +++ b/pom.xml @@ -441,7 +441,6 @@ org.apache.maven.plugins maven-dependency-plugin - 2.8 copy-war-for-integration-tests From d47def26aa0dcb39bd730aa06b405ca2737fde5f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 22 Sep 2013 10:00:40 +1000 Subject: [PATCH 0897/2152] Set an id to every plugin executions --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 9af78995436..b04a2e506a9 100644 --- a/pom.xml +++ b/pom.xml @@ -431,6 +431,7 @@ maven-failsafe-plugin + integration-tests integration-test verify From e3170a7f77ed3f547b850292e1d120664a37f8e3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 22 Sep 2013 10:01:07 +1000 Subject: [PATCH 0898/2152] Rename the jetty profile to make it more clear --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b04a2e506a9..960c60531bc 100644 --- a/pom.xml +++ b/pom.xml @@ -388,7 +388,7 @@ - jetty + run-jetty-integration-tests 1.7 From cac8c897fa6ebc4b4cc9992b51b6bbd8c43e82f5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 22 Sep 2013 10:12:30 +1000 Subject: [PATCH 0899/2152] Update dependency versions in READMEs --- raven-log4j2/README.md | 6 +++--- raven/README.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index f0605c87442..83a785bf277 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -21,9 +21,9 @@ Details in the [central Maven repository](https://search.maven.org/#artifactdeta Relies on: - [raven dependencies](../raven) - - [log4j-api-2.0-beta7.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7C2.0-beta7%7Cjar) - - [log4j-core-2.0-beta7.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7C2.0-beta7%7Cjar) - - [log4j-slf4j-impl-2.0-beta7.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7C2.0-beta7%7Cjar) + - [log4j-api-2.0-beta9.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7C2.0-beta9%7Cjar) + - [log4j-core-2.0-beta9.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7C2.0-beta9%7Cjar) + - [log4j-slf4j-impl-2.0-beta9.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7C2.0-beta9%7Cjar) is recommended as the implementation of slf4j (instead of slf4j-jdk14). diff --git a/raven/README.md b/raven/README.md index cda817650df..e774ad2f4cd 100644 --- a/raven/README.md +++ b/raven/README.md @@ -21,8 +21,8 @@ Details in the [central Maven repository](https://search.maven.org/#artifactdeta Relies on: - [raven-4.1.jar](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1%7Cjar) - - [guava-14.0.1.jar](https://search.maven.org/#artifactdetails%7Ccom.google.guava%7Cguava%7C14.0.1%7Cjar) - - [jackson-core-2.2.2.jar](https://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.2.2%7Cjar) + - [guava-15.0.jar](https://search.maven.org/#artifactdetails%7Ccom.google.guava%7Cguava%7C15.0%7Cjar) + - [jackson-core-2.2.3.jar](https://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.2.3%7Cjar) - [slf4j-api-1.7.5.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.5%7Cjar) - [slf4j-jdk14-1.7.5.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-jdk14%7C1.7.5%7Cjar) is recommended as the implementation of slf4j to capture the log events From c5979c09e03aa1b0704ff17761a43221cfcc90c6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 3 Oct 2013 05:01:15 +1000 Subject: [PATCH 0900/2152] Send HTTP Header values as a single String instead of a List --- .../marshaller/json/HttpInterfaceBinding.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java index 3ed7fbcdeca..548a855378d 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java @@ -6,7 +6,9 @@ import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import java.io.IOException; +import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.Map; /** @@ -71,11 +73,16 @@ private void writeEnvironment(JsonGenerator generator, HttpServletRequest reques private void writeHeaders(JsonGenerator generator, HttpServletRequest request) throws IOException { generator.writeStartObject(); for (String header : Collections.list(request.getHeaderNames())) { - generator.writeArrayFieldStart(header); - for (String headerValue : Collections.list(request.getHeaders(header))) { - generator.writeString(headerValue); + generator.writeFieldName(header); + StringBuilder sb = new StringBuilder(); + Collection headerValues = Collections.list(request.getHeaders(header)); + for (Iterator it = headerValues.iterator(); it.hasNext(); ) { + sb.append("' ").append(it.next()).append(" '"); + if (it.hasNext()) { + sb.append(","); + } } - generator.writeEndArray(); + generator.writeString(sb.toString()); } generator.writeEndObject(); } From 086796c7f6fd8bfae9de922b7ad9c47e49e99912 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 1 Oct 2013 23:25:16 +1000 Subject: [PATCH 0901/2152] Store the HttpRequest details in HttpInterface --- .../raven/event/interfaces/HttpInterface.java | 118 +++++++++++++++++- .../marshaller/json/HttpInterfaceBinding.java | 65 +++++----- 2 files changed, 143 insertions(+), 40 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java index 312ba16b344..c1b1bc5e47e 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java @@ -1,6 +1,8 @@ package net.kencochrane.raven.event.interfaces; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; +import java.util.*; /** * The HTTP interface for Sentry allows to add an HTTP request to an event. @@ -10,15 +12,57 @@ public class HttpInterface implements SentryInterface { * Name of the HTTP interface in Sentry. */ public static final String HTTP_INTERFACE = "sentry.interfaces.Http"; - private final HttpServletRequest request; + private final String requestUrl; + private final String method; + private final Map> parameters; + private final String queryString; + private final Map cookies; + private final String remoteAddr; + private final String serverName; + private final int serverPort; + private final String localAddr; + private final String localName; + private final int localPort; + private final String protocol; + private final boolean secure; + private final boolean asyncStarted; + private final String authType; + private final String remoteUser; + private final Map> headers; /** * Creates a an HTTP element for an {@link net.kencochrane.raven.event.Event}. * - * @param request Catpured HTTP request to send to Sentry. + * @param request Captured HTTP request to send to Sentry. */ public HttpInterface(HttpServletRequest request) { - this.request = request; + this.requestUrl = request.getRequestURL().toString(); + this.method = request.getMethod(); + this.parameters = new HashMap>(); + for (Map.Entry parameterMapEntry : request.getParameterMap().entrySet()) + this.parameters.put(parameterMapEntry.getKey(), Arrays.asList(parameterMapEntry.getValue())); + this.queryString = request.getQueryString(); + if (request.getCookies() != null) { + this.cookies = new HashMap(); + for (Cookie cookie : request.getCookies()) + this.cookies.put(cookie.getName(), cookie.getValue()); + } else { + this.cookies = Collections.emptyMap(); + } + this.remoteAddr = request.getRemoteAddr(); + this.serverName = request.getServerName(); + this.serverPort = request.getServerPort(); + this.localAddr = request.getLocalAddr(); + this.localName = request.getLocalName(); + this.localPort = request.getLocalPort(); + this.protocol = request.getProtocol(); + this.secure = request.isSecure(); + this.asyncStarted = request.isAsyncStarted(); + this.authType = request.getAuthType(); + this.remoteUser = request.getRemoteUser(); + this.headers = new HashMap>(); + for (String headerName : Collections.list(request.getHeaderNames())) + this.headers.put(headerName, Collections.list(request.getHeaders(headerName))); } @Override @@ -26,7 +70,71 @@ public String getInterfaceName() { return HTTP_INTERFACE; } - public HttpServletRequest getRequest() { - return request; + public String getRequestUrl() { + return requestUrl; + } + + public String getMethod() { + return method; + } + + public Map> getParameters() { + return Collections.unmodifiableMap(parameters); + } + + public String getQueryString() { + return queryString; + } + + public Map getCookies() { + return cookies; + } + + public String getRemoteAddr() { + return remoteAddr; + } + + public String getServerName() { + return serverName; + } + + public int getServerPort() { + return serverPort; + } + + public String getLocalAddr() { + return localAddr; + } + + public String getLocalName() { + return localName; + } + + public int getLocalPort() { + return localPort; + } + + public String getProtocol() { + return protocol; + } + + public boolean isSecure() { + return secure; + } + + public boolean isAsyncStarted() { + return asyncStarted; + } + + public String getAuthType() { + return authType; + } + + public String getRemoteUser() { + return remoteUser; + } + + public Map> getHeaders() { + return Collections.unmodifiableMap(headers); } } diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java index 548a855378d..c2fce7a821d 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/HttpInterfaceBinding.java @@ -3,11 +3,8 @@ import com.fasterxml.jackson.core.JsonGenerator; import net.kencochrane.raven.event.interfaces.HttpInterface; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; import java.util.Map; @@ -36,47 +33,45 @@ public class HttpInterfaceBinding implements InterfaceBinding { @Override public void writeInterface(JsonGenerator generator, HttpInterface httpInterface) throws IOException { - HttpServletRequest request = httpInterface.getRequest(); - generator.writeStartObject(); - generator.writeStringField(URL, request.getRequestURL().toString()); - generator.writeStringField(METHOD, request.getMethod()); + generator.writeStringField(URL, httpInterface.getRequestUrl()); + generator.writeStringField(METHOD, httpInterface.getMethod()); generator.writeFieldName(DATA); - writeData(generator, request.getParameterMap()); - generator.writeStringField(QUERY_STRING, request.getQueryString()); + writeData(generator, httpInterface.getParameters()); + generator.writeStringField(QUERY_STRING, httpInterface.getQueryString()); generator.writeFieldName(COOKIES); - writeCookies(generator, request.getCookies()); + writeCookies(generator, httpInterface.getCookies()); generator.writeFieldName(HEADERS); - writeHeaders(generator, request); + writeHeaders(generator, httpInterface.getHeaders()); generator.writeFieldName(ENVIRONMENT); - writeEnvironment(generator, request); + writeEnvironment(generator, httpInterface); generator.writeEndObject(); } - private void writeEnvironment(JsonGenerator generator, HttpServletRequest request) throws IOException { + private void writeEnvironment(JsonGenerator generator, HttpInterface httpInterface) throws IOException { generator.writeStartObject(); - generator.writeStringField(ENV_REMOTE_ADDR, request.getRemoteAddr()); - generator.writeStringField(ENV_SERVER_NAME, request.getServerName()); - generator.writeNumberField(ENV_SERVER_PORT, request.getServerPort()); - generator.writeStringField(ENV_LOCAL_ADDR, request.getLocalAddr()); - generator.writeStringField(ENV_LOCAL_NAME, request.getLocalName()); - generator.writeNumberField(ENV_LOCAL_PORT, request.getLocalPort()); - generator.writeStringField(ENV_SERVER_PROTOCOL, request.getProtocol()); - generator.writeBooleanField(ENV_REQUEST_SECURE, request.isSecure()); - generator.writeBooleanField(ENV_REQUEST_ASYNC, request.isAsyncStarted()); - generator.writeStringField(ENV_AUTH_TYPE, request.getAuthType()); + generator.writeStringField(ENV_REMOTE_ADDR, httpInterface.getRemoteAddr()); + generator.writeStringField(ENV_SERVER_NAME, httpInterface.getServerName()); + generator.writeNumberField(ENV_SERVER_PORT, httpInterface.getServerPort()); + generator.writeStringField(ENV_LOCAL_ADDR, httpInterface.getLocalAddr()); + generator.writeStringField(ENV_LOCAL_NAME, httpInterface.getLocalName()); + generator.writeNumberField(ENV_LOCAL_PORT, httpInterface.getLocalPort()); + generator.writeStringField(ENV_SERVER_PROTOCOL, httpInterface.getProtocol()); + generator.writeBooleanField(ENV_REQUEST_SECURE, httpInterface.isSecure()); + generator.writeBooleanField(ENV_REQUEST_ASYNC, httpInterface.isAsyncStarted()); + generator.writeStringField(ENV_AUTH_TYPE, httpInterface.getAuthType()); //TODO: Should that be really displayed here ? Consider the user interface? - generator.writeStringField(ENV_REMOTE_USER, request.getRemoteUser()); + generator.writeStringField(ENV_REMOTE_USER, httpInterface.getRemoteUser()); generator.writeEndObject(); } - private void writeHeaders(JsonGenerator generator, HttpServletRequest request) throws IOException { + private void writeHeaders(JsonGenerator generator, Map> headers) throws IOException { generator.writeStartObject(); - for (String header : Collections.list(request.getHeaderNames())) { - generator.writeFieldName(header); + for (Map.Entry> headerEntry : headers.entrySet()) { + generator.writeFieldName(headerEntry.getKey()); + StringBuilder sb = new StringBuilder(); - Collection headerValues = Collections.list(request.getHeaders(header)); - for (Iterator it = headerValues.iterator(); it.hasNext(); ) { + for (Iterator it = headerEntry.getValue().iterator(); it.hasNext(); ) { sb.append("' ").append(it.next()).append(" '"); if (it.hasNext()) { sb.append(","); @@ -87,28 +82,28 @@ private void writeHeaders(JsonGenerator generator, HttpServletRequest request) t generator.writeEndObject(); } - private void writeCookies(JsonGenerator generator, Cookie[] cookies) throws IOException { + private void writeCookies(JsonGenerator generator, Map cookies) throws IOException { //TODO: Cookies shouldn't be sent by default - if (cookies == null) { + if (cookies.isEmpty()) { generator.writeNull(); return; } generator.writeStartObject(); - for (Cookie cookie : cookies) { - generator.writeStringField(cookie.getName(), cookie.getValue()); + for (Map.Entry cookie : cookies.entrySet()) { + generator.writeStringField(cookie.getKey(), cookie.getValue()); } generator.writeEndObject(); } - private void writeData(JsonGenerator generator, Map parameterMap) throws IOException { + private void writeData(JsonGenerator generator, Map> parameterMap) throws IOException { if (parameterMap == null) { generator.writeNull(); return; } generator.writeStartObject(); - for (Map.Entry parameter : parameterMap.entrySet()) { + for (Map.Entry> parameter : parameterMap.entrySet()) { generator.writeArrayFieldStart(parameter.getKey()); for (String parameterValue : parameter.getValue()) { generator.writeString(parameterValue); From 785b1ffac67ab2f75f5e809d75385eadc0ae4c87 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 3 Oct 2013 05:28:23 +1000 Subject: [PATCH 0902/2152] Update the unit tests to take into account the copy of the HttpServletRequest --- .../raven/event/helper/HttpEventBuilderHelperTest.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java index 3fdf82034b1..e7904e15e0e 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java @@ -19,6 +19,10 @@ public class HttpEventBuilderHelperTest { private EventBuilder mockEventBuilder; @Mocked("getServletRequest") private RavenServletRequestListener ravenServletRequestListener; + @Injectable + private HttpServletRequest mockHttpServletRequest; + @Mocked + private HttpInterface mockHttpInterface; @BeforeMethod public void setUp() throws Exception { @@ -27,6 +31,10 @@ public void setUp() throws Exception { @Test public void testNoRequest() throws Exception { + new Expectations() {{ + RavenServletRequestListener.getServletRequest(); + result = null; + }}; httpEventBuilderHelper.helpBuildingEvent(mockEventBuilder); new Verifications() {{ @@ -36,7 +44,7 @@ public void testNoRequest() throws Exception { } @Test - public void testWithRequest(@Injectable final HttpServletRequest mockHttpServletRequest) throws Exception { + public void testWithRequest() throws Exception { new Expectations() {{ RavenServletRequestListener.getServletRequest(); result = mockHttpServletRequest; From 0c3b1eabeccd31aaec07a4cec1edb0eb0ed8dd99 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 3 Oct 2013 06:11:11 +1000 Subject: [PATCH 0903/2152] Add tests to check that HttpServletRequest data is properly imported --- .../event/interfaces/HttpInterfaceTest.java | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java new file mode 100644 index 00000000000..b3f2cc71d5d --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java @@ -0,0 +1,165 @@ +package net.kencochrane.raven.event.interfaces; + +import mockit.Injectable; +import mockit.NonStrictExpectations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import java.util.Arrays; +import java.util.Collections; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class HttpInterfaceTest { + @Injectable + private HttpServletRequest mockHttpServletRequest; + + @BeforeMethod + public void setUp() throws Exception { + new NonStrictExpectations() {{ + mockHttpServletRequest.getRequestURL(); + result = new StringBuffer(); + mockHttpServletRequest.getMethod(); + result = "method"; + mockHttpServletRequest.getParameterMap(); + result = Collections.emptyMap(); + mockHttpServletRequest.getQueryString(); + result = "queryString"; + mockHttpServletRequest.getCookies();//Could be null + result = null; + mockHttpServletRequest.getRemoteAddr(); + result = "remoteAddr"; + mockHttpServletRequest.getServerName(); + result = "serverName"; + mockHttpServletRequest.getServerPort(); + result = 24; + mockHttpServletRequest.getLocalAddr(); + result = "localAddr"; + mockHttpServletRequest.getLocalName(); + result = "localName"; + mockHttpServletRequest.getLocalPort(); + result = 42; + mockHttpServletRequest.getProtocol(); + result = "protocol"; + mockHttpServletRequest.isSecure(); + result = false; + mockHttpServletRequest.isAsyncStarted(); + result = false; + mockHttpServletRequest.getAuthType(); + result = "authType"; + mockHttpServletRequest.getRemoteUser(); + result = "remoteUser"; + mockHttpServletRequest.getHeaderNames(); + result = Collections.emptyEnumeration(); + mockHttpServletRequest.getHeaders(anyString); + result = Collections.emptyEnumeration(); + }}; + } + + @Test + public void testHttpServletCopied() throws Exception { + final String requestUrl = UUID.randomUUID().toString(); + final String method = UUID.randomUUID().toString(); + final String parameterName = UUID.randomUUID().toString(); + final String parameterValue = UUID.randomUUID().toString(); + final String queryString = UUID.randomUUID().toString(); + final String cookieName = UUID.randomUUID().toString(); + final String cookieValue = UUID.randomUUID().toString(); + final String remoteAddr = UUID.randomUUID().toString(); + final String serverName = UUID.randomUUID().toString(); + final int serverPort = 123; + final String localAddr = UUID.randomUUID().toString(); + final String localName = UUID.randomUUID().toString(); + final int localPort = 321; + final String protocol = UUID.randomUUID().toString(); + final boolean secure = true; + final boolean asyncStarted = true; + final String authType = UUID.randomUUID().toString(); + final String remoteUser = UUID.randomUUID().toString(); + final String headerKey = UUID.randomUUID().toString(); + final String headerValue = UUID.randomUUID().toString(); + + new NonStrictExpectations() { + @Injectable + private Cookie mockCookie; + + { + mockHttpServletRequest.getRequestURL(); + result = new StringBuffer(requestUrl); + mockHttpServletRequest.getMethod(); + result = method; + mockHttpServletRequest.getParameterMap(); + result = Collections.singletonMap(parameterName, new String[]{parameterValue}); + mockHttpServletRequest.getQueryString(); + result = queryString; + mockCookie.getName(); + result = cookieName; + mockCookie.getValue(); + result = cookieValue; + mockHttpServletRequest.getCookies(); + result = new Cookie[]{mockCookie}; + mockHttpServletRequest.getRemoteAddr(); + result = remoteAddr; + mockHttpServletRequest.getServerName(); + result = serverName; + mockHttpServletRequest.getServerPort(); + result = serverPort; + mockHttpServletRequest.getLocalAddr(); + result = localAddr; + mockHttpServletRequest.getLocalName(); + result = localName; + mockHttpServletRequest.getLocalPort(); + result = localPort; + mockHttpServletRequest.getProtocol(); + result = protocol; + mockHttpServletRequest.isSecure(); + result = secure; + mockHttpServletRequest.isAsyncStarted(); + result = asyncStarted; + mockHttpServletRequest.getAuthType(); + result = authType; + mockHttpServletRequest.getRemoteUser(); + result = remoteUser; + mockHttpServletRequest.getHeaderNames(); + result = Collections.enumeration(Arrays.asList(headerKey)); + mockHttpServletRequest.getHeaders(headerKey); + result = Collections.enumeration(Arrays.asList(headerValue)); + } + }; + + HttpInterface httpInterface = new HttpInterface(mockHttpServletRequest); + + assertThat(httpInterface.getRequestUrl(), is(requestUrl)); + assertThat(httpInterface.getMethod(), is(method)); + assertThat(httpInterface.getQueryString(), is(queryString)); + assertThat(httpInterface.getCookies(), hasEntry(cookieName, cookieValue)); + assertThat(httpInterface.getRemoteAddr(), is(remoteAddr)); + assertThat(httpInterface.getServerName(), is(serverName)); + assertThat(httpInterface.getServerPort(), is(serverPort)); + assertThat(httpInterface.getLocalAddr(), is(localAddr)); + assertThat(httpInterface.getLocalName(), is(localName)); + assertThat(httpInterface.getLocalPort(), is(localPort)); + assertThat(httpInterface.getProtocol(), is(protocol)); + assertThat(httpInterface.isSecure(), is(secure)); + assertThat(httpInterface.isAsyncStarted(), is(asyncStarted)); + assertThat(httpInterface.getAuthType(), is(authType)); + assertThat(httpInterface.getRemoteUser(), is(remoteUser)); + assertThat(httpInterface.getHeaders(), hasEntry(is(headerKey), contains(headerValue))); + } + + @Test + public void testNullCookies() throws Exception { + new NonStrictExpectations() {{ + mockHttpServletRequest.getCookies(); + result = null; + }}; + + HttpInterface httpInterface = new HttpInterface(mockHttpServletRequest); + + assertThat(httpInterface.getCookies().size(), is(0)); + } +} From f791f704bc6ae4df273e067b3c7a7dc1ae1b66df Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 3 Oct 2013 08:00:39 +1000 Subject: [PATCH 0904/2152] Do not use Collections.emptyEnumeration as it's Java7 code --- .../raven/event/interfaces/HttpInterfaceTest.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java index b3f2cc71d5d..7b478419752 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java @@ -7,9 +7,7 @@ import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; -import java.util.Arrays; -import java.util.Collections; -import java.util.UUID; +import java.util.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -54,9 +52,9 @@ public void setUp() throws Exception { mockHttpServletRequest.getRemoteUser(); result = "remoteUser"; mockHttpServletRequest.getHeaderNames(); - result = Collections.emptyEnumeration(); + result = new EmptyEnumeration(); mockHttpServletRequest.getHeaders(anyString); - result = Collections.emptyEnumeration(); + result = new EmptyEnumeration(); }}; } @@ -162,4 +160,9 @@ public void testNullCookies() throws Exception { assertThat(httpInterface.getCookies().size(), is(0)); } + + private static class EmptyEnumeration implements Enumeration { + public boolean hasMoreElements() { return false; } + public E nextElement() { throw new NoSuchElementException(); } + } } From 600bc4cba50fa5c85c8ee621ffdadaae045dc0f3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 25 Oct 2013 00:36:05 +1100 Subject: [PATCH 0905/2152] Move the DEFAULT_HOSTNAME to the EventBuilder class --- .../java/net/kencochrane/raven/event/EventBuilder.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 528f7d85614..20b66f76be9 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -23,6 +23,10 @@ public class EventBuilder { * Default platform if it isn't set manually. */ public static final String DEFAULT_PLATFORM = "java"; + /** + * Default hostname if it isn't set manually (or can't be determined). + */ + public static final String DEFAULT_HOSTNAME = "unavailable"; /** * Duration of the hostname caching. * @@ -298,10 +302,6 @@ public Event build() { *

    */ private static final class HostnameCache { - /** - * Default hostname if it isn't set manually (or can't be determined). - */ - public static final String DEFAULT_HOSTNAME = "unavailable"; /** * Time before the get hostname operation times out (in ms). */ From 4fed9677d82c39e3a050ca44c8ae997555bf6b46 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 25 Oct 2013 00:36:25 +1100 Subject: [PATCH 0906/2152] Use TimeUnit to do time conversion instead of hardcoded numbers --- .../main/java/net/kencochrane/raven/event/EventBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 20b66f76be9..6c346ad644e 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -32,7 +32,7 @@ public class EventBuilder { * * @see HostnameCache */ - public static final int HOSTNAME_CACHE_DURATION = 18000000; + public static final long HOSTNAME_CACHE_DURATION = TimeUnit.HOURS.toMillis(5); private static final HostnameCache HOSTNAME_CACHE = new HostnameCache(HOSTNAME_CACHE_DURATION); private final Event event; private boolean alreadyBuilt = false; @@ -305,7 +305,7 @@ private static final class HostnameCache { /** * Time before the get hostname operation times out (in ms). */ - public static final int GET_HOSTNAME_TIMEOUT = 1000; + public static final long GET_HOSTNAME_TIMEOUT = TimeUnit.SECONDS.toMillis(1); private static final Logger logger = LoggerFactory.getLogger(HostnameCache.class); /** * Time for which the cache is kept. From bd5b84214116b924b502b7e28cf34ce559d7f69a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 25 Oct 2013 00:37:25 +1100 Subject: [PATCH 0907/2152] Mock the getCanonicalHostName of localHost --- .../raven/event/EventBuilderTest.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index 3ba91d526d5..5842eb50bb2 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -1,6 +1,8 @@ package net.kencochrane.raven.event; +import mockit.Delegate; import mockit.Injectable; +import mockit.Mocked; import mockit.NonStrictExpectations; import net.kencochrane.raven.event.interfaces.SentryInterface; import org.testng.annotations.BeforeMethod; @@ -11,26 +13,41 @@ import java.util.Map; import java.util.UUID; +import static mockit.Deencapsulation.newInstance; +import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; public class EventBuilderTest { private EventBuilder eventBuilder; + @Mocked + private InetAddress inetAddress; @BeforeMethod public void setUp() throws Exception { eventBuilder = new EventBuilder(); + new NonStrictExpectations() {{ + InetAddress.getLocalHost(); + result = inetAddress; + }}; + //Create a temporary cache with a timeout of 0 + setField(EventBuilder.class, "HOSTNAME_CACHE", + newInstance(EventBuilder.class.getName() + "$HostnameCache", 0l)); } @Test public void testMandatoryValuesAutomaticallySet() throws Exception { + final String expectedWorkingHostname = UUID.randomUUID().toString(); + new NonStrictExpectations() {{ + inetAddress.getCanonicalHostName(); + result = expectedWorkingHostname; + }}; Event event = eventBuilder.build(); assertThat(event.getId(), is(notNullValue())); assertThat(event.getTimestamp(), is(notNullValue())); assertThat(event.getPlatform(), is(EventBuilder.DEFAULT_PLATFORM)); - //TODO: This test can fail if HostnameCache times out (happened once), mock InetAddress.getLocalHost().getCanonicalHostName() for instant reliable results) - assertThat(event.getServerName(), is(InetAddress.getLocalHost().getCanonicalHostName())); + assertThat(event.getServerName(), is(expectedWorkingHostname)); } @Test From ec233e3db12aa2beea998e57fe3b5104ee385d72 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 25 Oct 2013 00:38:03 +1100 Subject: [PATCH 0908/2152] Add a test for the case where getCanonicalHostName is too slow --- .../raven/event/EventBuilderTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index 5842eb50bb2..8678b99536a 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -50,6 +50,29 @@ public void testMandatoryValuesAutomaticallySet() throws Exception { assertThat(event.getServerName(), is(expectedWorkingHostname)); } + @Test + public void slowCallToGetCanonicalHostNameIsCaught() throws Exception { + new NonStrictExpectations() {{ + inetAddress.getCanonicalHostName(); + result = new Delegate() { + public String getCanonicalHostName() throws Exception { + synchronized (EventBuilderTest.this) { + EventBuilderTest.this.wait(); + } + return ""; + } + }; + }}; + + Event event = eventBuilder.build(); + synchronized (EventBuilderTest.this) { + EventBuilderTest.this.notify(); + } + + assertThat(event.getServerName(), is(EventBuilder.DEFAULT_HOSTNAME)); + + } + @Test public void testMandatoryValuesNotOverwritten() throws Exception { UUID uuid = UUID.randomUUID(); From 210dbe9bebf554dce0f92ad4b6eee74d781ea07b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 25 Oct 2013 00:52:54 +1100 Subject: [PATCH 0909/2152] Update jmockit, github and jetty --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 960c60531bc..3293a291400 100644 --- a/pom.xml +++ b/pom.xml @@ -113,7 +113,7 @@ 1.2.17 2.0-beta9 1.9.5 - 1.4 + 1.5 6.8.7 1.3 @@ -252,7 +252,7 @@ com.github.github site-maven-plugin - 0.8 + 0.9 Creating site for ${project.artifactId} ${project.version} ${project.distributionManagement.site.url} @@ -303,7 +303,7 @@ org.eclipse.jetty jetty-maven-plugin - 9.0.5.v20130815 + 9.0.6.v20130930 org.apache.maven.plugins From 9501ab9624aab4cfdb5c337fa3a225ac2f0f15ca Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 25 Oct 2013 09:16:50 +1100 Subject: [PATCH 0910/2152] [maven-release-plugin] prepare release v4.1.1 --- pom.xml | 4 ++-- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 3293a291400..d17bc3f75df 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 4.1.1-SNAPSHOT + 4.1.1 pom Raven-Java @@ -74,7 +74,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v4.1.1 https://github.com/${github.repo}/issues diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 5fe0f626868..f462d685aa8 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.1-SNAPSHOT + 4.1.1 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index bd2eca4182d..743a18232fe 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.1-SNAPSHOT + 4.1.1 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index d4b8d050318..ef2ee7c442a 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.1-SNAPSHOT + 4.1.1 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index c7f5e93b59c..74c8326fa1f 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.1-SNAPSHOT + 4.1.1 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 12a94e0fbc9..a4e9d0b069e 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.1-SNAPSHOT + 4.1.1 sentry-stub From c7faac4abf70a1b18cdc89ff0d08efb1c8dcfeeb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 25 Oct 2013 09:16:50 +1100 Subject: [PATCH 0911/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index d17bc3f75df..9368d233780 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 4.1.1 + 4.1.2-SNAPSHOT pom Raven-Java @@ -74,7 +74,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v4.1.1 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index f462d685aa8..e33124a695c 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.1 + 4.1.2-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 743a18232fe..5573c42e4a7 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.1 + 4.1.2-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index ef2ee7c442a..21e03e7f5b2 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.1 + 4.1.2-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 74c8326fa1f..cb96264bfa0 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.1 + 4.1.2-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index a4e9d0b069e..9454532a506 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.1 + 4.1.2-SNAPSHOT sentry-stub From d58ebfbc96e560c1e845296e488201235ff3c0e2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 25 Oct 2013 20:15:55 +1100 Subject: [PATCH 0912/2152] Update the documentation to mention raven 4.1.1 --- raven-log4j/README.md | 4 ++-- raven-log4j2/README.md | 4 ++-- raven-logback/README.md | 4 ++-- raven/README.md | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 8fdd2854c12..b11d64f44cf 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,12 +10,12 @@ for log4j to send the logged events to Sentry. net.kencochrane.raven raven-log4j - 4.1 + 4.1.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j%7C4.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j%7C4.1.1%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index 83a785bf277..c0618f87ec9 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,12 +10,12 @@ for Log4j 2 to send the logged events to Sentry. net.kencochrane.raven raven-log4j2 - 4.1 + 4.1.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j2%7C4.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j2%7C4.1.1%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-logback/README.md b/raven-logback/README.md index 96844a065c7..d07729c17ef 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,12 +10,12 @@ for logback to send the logged events to Sentry. net.kencochrane.raven raven-logback - 4.1 + 4.1.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-logback%7C4.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-logback%7C4.1.1%7Cjar). ### Manual dependency management Relies on: diff --git a/raven/README.md b/raven/README.md index e774ad2f4cd..51562ed9d20 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,17 +10,17 @@ for `java.util.logging`. net.kencochrane.raven raven - 4.1 + 4.1.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1.1%7Cjar). ### Manual dependency management Relies on: - - [raven-4.1.jar](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1%7Cjar) + - [raven-4.1.1.jar](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1.1%7Cjar) - [guava-15.0.jar](https://search.maven.org/#artifactdetails%7Ccom.google.guava%7Cguava%7C15.0%7Cjar) - [jackson-core-2.2.3.jar](https://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.2.3%7Cjar) - [slf4j-api-1.7.5.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.5%7Cjar) From 9903000b6a849facca770ff70ddeeddf619faf95 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 8 Nov 2013 19:58:20 +1100 Subject: [PATCH 0913/2152] Use @link not @inheritdoc --- .../main/java/net/kencochrane/raven/DefaultRavenFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 53f7b32eabb..ee90a17d75f 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -212,7 +212,7 @@ protected Marshaller createMarshaller(Dsn dsn) { /** * Provides a list of package names to consider as "not in-app". *

    - * Those packages will be used with the {@inheritDoc StackTraceInterface} to hide frames that aren't a part of + * Those packages will be used with the {@link StackTraceInterface} to hide frames that aren't a part of * the main application. *

    * From 6b22f0bef80e4fe13b0328a722934c38586b2a96 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 8 Nov 2013 20:01:10 +1100 Subject: [PATCH 0914/2152] Use TimeUnit rather than hardcoded numbers --- .../kencochrane/raven/connection/AbstractConnection.java | 9 +++++---- .../kencochrane/raven/connection/AsyncConnection.java | 2 +- .../net/kencochrane/raven/connection/HttpConnection.java | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index faec2bcb48f..5b33be5dfb7 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -5,6 +5,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; /** @@ -21,13 +22,13 @@ public abstract class AbstractConnection implements Connection { */ public static final String SENTRY_PROTOCOL_VERSION = "4"; /** - * Default maximum duration for a lockdown (5 minutes). + * Default maximum duration for a lockdown. */ - public static final int DEFAULT_MAX_WAITING_TIME = 300000; + public static final long DEFAULT_MAX_WAITING_TIME = TimeUnit.MINUTES.toMillis(5); /** - * Default base duration for a lockdown (10 milliseconds). + * Default base duration for a lockdown. */ - public static final int DEFAULT_BASE_WAITING_TIME = 10; + public static final long DEFAULT_BASE_WAITING_TIME = TimeUnit.MILLISECONDS.toMillis(10); private static final Logger logger = LoggerFactory.getLogger(AbstractConnection.class); private final String publicKey; private final String secretKey; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 4ee62a0f4a3..f17c5664628 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -23,7 +23,7 @@ public class AsyncConnection implements Connection { /** * Timeout of the {@link #executorService}. */ - private static final int SHUTDOWN_TIMEOUT = 1000; + private static final long SHUTDOWN_TIMEOUT = TimeUnit.SECONDS.toMillis(1); /** * Connection used to actually send the events. */ diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index acaaf93de9a..e548e0611bd 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -14,6 +14,7 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URL; +import java.util.concurrent.TimeUnit; /** * Basic connection to a Sentry server, using HTTP and HTTPS. @@ -34,7 +35,7 @@ public class HttpConnection extends AbstractConnection { /** * Default timeout of an HTTP connection to Sentry. */ - private static final int DEFAULT_TIMEOUT = 10000; + private static final int DEFAULT_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(1); /** * HostnameVerifier allowing wildcard certificates to work without adding them to the truststore. */ From e767625821bbde022fd08234ae8efa03a25024b9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 8 Nov 2013 20:07:51 +1100 Subject: [PATCH 0915/2152] If the hostname can't be found, keep the old one for 1 second and retry --- .../main/java/net/kencochrane/raven/event/EventBuilder.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 6c346ad644e..b5506f6298d 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -349,14 +349,14 @@ public String getHostname() { * Force an update of the cache to get the current value of the hostname. */ public void updateCache() { - expirationTimestamp = System.currentTimeMillis() + cacheDuration; Future future = Executors.newSingleThreadExecutor().submit(new HostRetriever()); try { hostname = future.get(GET_HOSTNAME_TIMEOUT, TimeUnit.MILLISECONDS); + expirationTimestamp = System.currentTimeMillis() + cacheDuration; } catch (Exception e) { - logger.warn("Localhost hostname lookup failed, defaulting back to '{}'", DEFAULT_HOSTNAME, e); - hostname = DEFAULT_HOSTNAME; + expirationTimestamp = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(1); + logger.warn("Localhost hostname lookup failed, keeping the value '{}'", hostname, e); } } From 49caca81d54fa71c5d38a9c02c024b6ec14bcaff Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 9 Nov 2013 17:41:48 +1100 Subject: [PATCH 0916/2152] Throw exception from the integration tests --- .../java/net/kencochrane/raven/jul/SentryHandlerIT.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java index 9db03a15a8d..720372fc5e4 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java @@ -16,25 +16,25 @@ public class SentryHandlerIT { private SentryStub sentryStub; @BeforeMethod - public void setUp() { + public void setUp() throws Exception { sentryStub = new SentryStub(); sentryStub.removeEvents(); } @AfterMethod - public void tearDown() { + public void tearDown() throws Exception { sentryStub.removeEvents(); } @Test - public void testInfoLog() { + public void testInfoLog() throws Exception { assertThat(sentryStub.getEventCount(), is(0)); logger.info("This is a test"); assertThat(sentryStub.getEventCount(), is(1)); } @Test - public void testChainedExceptions() { + public void testChainedExceptions() throws Exception { assertThat(sentryStub.getEventCount(), is(0)); logger.log(Level.SEVERE, "This is an exception", new UnsupportedOperationException("Test", new UnsupportedOperationException())); From 5a379bfa5e57c6c1d5028f67041131b0abad9a7c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 9 Nov 2013 18:17:17 +1100 Subject: [PATCH 0917/2152] Remove deprecated @NonStrict use NonStrictExpectations instead --- .../raven/log4j/SentryAppenderFailuresTest.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java index da4ab1d00a0..d807e5592a6 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java @@ -1,6 +1,9 @@ package net.kencochrane.raven.log4j; -import mockit.*; +import mockit.Injectable; +import mockit.Mocked; +import mockit.NonStrictExpectations; +import mockit.Verifications; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; @@ -18,7 +21,6 @@ public class SentryAppenderFailuresTest { private SentryAppender sentryAppender; private MockUpErrorHandler mockUpErrorHandler; @Injectable - @NonStrict private Raven mockRaven = null; @Injectable private Logger mockLogger = null; @@ -35,7 +37,7 @@ public void setUp() throws Exception { @Test public void testRavenFailureDoesNotPropagate() throws Exception { - new Expectations() {{ + new NonStrictExpectations() {{ mockRaven.sendEvent((Event) any); result = new UnsupportedOperationException(); }}; @@ -47,7 +49,7 @@ public void testRavenFailureDoesNotPropagate() throws Exception { @Test public void testRavenFactoryFailureDoesNotPropagate() throws Exception { - new Expectations() {{ + new NonStrictExpectations() {{ RavenFactory.ravenInstance((Dsn) any, anyString); result = new UnsupportedOperationException(); }}; From 7711931d713ee16ecd4be39408158011aa12989f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 9 Nov 2013 18:25:39 +1100 Subject: [PATCH 0918/2152] Update maven release plugin --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9368d233780..6ee7facecf8 100644 --- a/pom.xml +++ b/pom.xml @@ -235,7 +235,7 @@ org.apache.maven.plugins maven-release-plugin - 2.4.1 + 2.4.2 v@{project.version} true From 0ee001c3420ad471f3a2cdbf66cdf9713b1c6e18 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 9 Nov 2013 20:57:07 +1100 Subject: [PATCH 0919/2152] Replace NonStrict with NonStrictExpectations --- .../raven/log4j2/SentryAppenderFailuresTest.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java index 96866a7c97e..88f3d249a36 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java @@ -1,6 +1,9 @@ package net.kencochrane.raven.log4j2; -import mockit.*; +import mockit.Injectable; +import mockit.Mocked; +import mockit.NonStrictExpectations; +import mockit.Verifications; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; @@ -18,7 +21,6 @@ public class SentryAppenderFailuresTest { private SentryAppender sentryAppender; private MockUpErrorHandler mockUpErrorHandler; @Injectable - @NonStrict private Raven mockRaven = null; @Mocked("ravenInstance") private RavenFactory mockRavenFactory; @@ -32,7 +34,7 @@ public void setUp() throws Exception { @Test public void testRavenFailureDoesNotPropagate() throws Exception { - new Expectations() {{ + new NonStrictExpectations() {{ mockRaven.sendEvent((Event) any); result = new UnsupportedOperationException(); }}; @@ -44,7 +46,7 @@ public void testRavenFailureDoesNotPropagate() throws Exception { @Test public void testRavenFactoryFailureDoesNotPropagate() throws Exception { - new Expectations() {{ + new NonStrictExpectations() {{ RavenFactory.ravenInstance((Dsn) any, anyString); result = new UnsupportedOperationException(); }}; From d28218e55b118d3ff84a618d14fba7e1fb04fa36 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 9 Nov 2013 20:58:29 +1100 Subject: [PATCH 0920/2152] Fix documentation --- .../raven/marshaller/json/ExceptionInterfaceBindingTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index c933472a629..98f02fbf346 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -81,7 +81,7 @@ public ImmutableThrowable getThrowable() { *

    * Obviously we can't use an Exception which is really defined in the default package within those tests * (can't import it), so instead set the name of the class to remove the package name.
    - * {@code Deencapsulation.setField((Object) DefaultPackageException.class, "name", "DefaultPackageClass")} + * {@code Deencapsulation.setField(Object) DefaultPackageException.class, "name", "DefaultPackageClass")} *

    */ class DefaultPackageException extends Exception { From 55711ea50d10b1e8a4f47df56816c542e9409f14 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 00:42:29 +1100 Subject: [PATCH 0921/2152] Avoid recreating the same auth string over and over again --- .../raven/connection/AbstractConnection.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 5b33be5dfb7..41d13117470 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -33,6 +33,7 @@ public abstract class AbstractConnection implements Connection { private final String publicKey; private final String secretKey; private final ReentrantLock lock = new ReentrantLock(); + private final String authHeader; /** * Maximum duration for a lockdown. */ @@ -55,6 +56,10 @@ public abstract class AbstractConnection implements Connection { protected AbstractConnection(String publicKey, String secretKey) { this.publicKey = publicKey; this.secretKey = secretKey; + authHeader = "Sentry sentry_version=" + SENTRY_PROTOCOL_VERSION + "," + + "sentry_client=" + Raven.NAME + "," + + "sentry_key=" + publicKey + "," + + "sentry_secret=" + secretKey; } /** @@ -63,12 +68,7 @@ protected AbstractConnection(String publicKey, String secretKey) { * @return an authentication header as a String. */ protected String getAuthHeader() { - StringBuilder header = new StringBuilder(); - header.append("Sentry sentry_version=").append(SENTRY_PROTOCOL_VERSION); - header.append(",sentry_client=").append(Raven.NAME); - header.append(",sentry_key=").append(publicKey); - header.append(",sentry_secret=").append(secretKey); - return header.toString(); + return authHeader; } @Override From 28cc33b01c10fe47fcc1fa66222c4bc032e9a3ba Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 00:54:49 +1100 Subject: [PATCH 0922/2152] Do not display the name as it's null --- raven/src/main/java/net/kencochrane/raven/RavenFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index b42c96995a4..4a49460b7c3 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -92,7 +92,7 @@ private static Raven ravenInstanceFromManualFactories(Dsn dsn, String ravenFacto logger.warn("The raven factory '{}' couldn't create an instance of Raven", ravenFactory); } else if (ravenFactoryName == null) { for (RavenFactory ravenFactory : MANUALLY_REGISTERED_FACTORIES.values()) { - logger.info("Found an appropriate Raven factory for '{}': '{}'", ravenFactoryName, ravenFactory); + logger.info("Found an available Raven factory: '{}'", ravenFactory); raven = ravenFactory.createRavenInstance(dsn); if (raven != null) { break; From c6ceb0b0d83d625a7c68cb10f58488a62ac71997 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 11:10:45 +1100 Subject: [PATCH 0923/2152] Use Raven.RAVEN_THREAD to determine if the thread is managed by Raven Instead of using guards, ensure that the current thread is set as managed by raven. This allows to get rid of synchronisations and maximise the throughput --- .../net/kencochrane/raven/log4j/SentryAppender.java | 11 +++++------ .../net/kencochrane/raven/log4j2/SentryAppender.java | 12 +++++++----- .../kencochrane/raven/logback/SentryAppender.java | 12 ++++++++---- raven/src/main/java/net/kencochrane/raven/Raven.java | 2 +- .../net/kencochrane/raven/jul/SentryHandler.java | 12 +++++------- 5 files changed, 26 insertions(+), 23 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 7555fcc62b7..8adac12d588 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -139,21 +139,20 @@ protected void initRaven() { } @Override - protected synchronized void append(LoggingEvent loggingEvent) { - // Do not log the event if the current thread has been spawned by raven or if the event has been created during - // the logging of an other event. - if (Raven.RAVEN_THREAD.get() || guard) + protected void append(LoggingEvent loggingEvent) { + // Do not log the event if the current thread is managed by raven + if (Raven.RAVEN_THREAD.get()) return; try { - guard = true; + Raven.RAVEN_THREAD.set(true); Event event = buildEvent(loggingEvent); raven.sendEvent(event); } catch (Exception e) { getErrorHandler().error("An exception occurred while creating a new event in Raven", e, ErrorCode.WRITE_FAILURE); } finally { - guard = false; + Raven.RAVEN_THREAD.remove(); } } diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 73db64967fd..fffdd91ce18 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -178,19 +178,21 @@ protected static List formatMessageParameters(Object[] parameters) { */ @Override public void append(LogEvent logEvent) { - - // Do not log the event if the current thread has been spawned by raven + // Do not log the event if the current thread is managed by raven if (Raven.RAVEN_THREAD.get()) return; - if (raven == null) - initRaven(); - try { + Raven.RAVEN_THREAD.set(true); + if (raven == null) + initRaven(); + Event event = buildEvent(logEvent); raven.sendEvent(event); } catch (Exception e) { error("An exception occurred while creating a new event in Raven", logEvent, e); + } finally { + Raven.RAVEN_THREAD.remove(); } } diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index d79a983ecfb..9173bb8e64a 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -127,18 +127,22 @@ protected static Event.Level formatLevel(Level level) { */ @Override protected void append(ILoggingEvent iLoggingEvent) { - // Do not log the event if the current thread has been spawned by raven + // Do not log the event if the current thread is managed by raven if (Raven.RAVEN_THREAD.get()) return; - if (raven == null) - initRaven(); - try { + Raven.RAVEN_THREAD.set(true); + + if (raven == null) + initRaven(); + Event event = buildEvent(iLoggingEvent); raven.sendEvent(event); } catch (Exception e) { addError("An exception occurred while creating a new event in Raven", e); + } finally { + Raven.RAVEN_THREAD.remove(); } } diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 96f21e6c071..84de584ff7e 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -23,7 +23,7 @@ */ public class Raven { /** - * Indicates whether the current thread has been spawned within raven or not. + * Indicates whether the current thread is managed by raven or not. */ public static final ThreadLocal RAVEN_THREAD = new ThreadLocal() { @Override diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 75601cc8c11..4637f43f2d5 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -45,7 +45,6 @@ public class SentryHandler extends Handler { */ protected String ravenFactory; private final boolean propagateClose; - private boolean guard = false; /** * Creates an instance of SentryHandler. @@ -120,14 +119,13 @@ protected void retrieveProperties() { } @Override - public synchronized void publish(LogRecord record) { - // Do not log the event if the current thread has been spawned by raven or if the event has been created during - // the logging of an other event. - if (!isLoggable(record) || Raven.RAVEN_THREAD.get() || guard) + public void publish(LogRecord record) { + // Do not log the event if the current thread is managed by raven + if (!isLoggable(record) || Raven.RAVEN_THREAD.get()) return; try { - guard = true; + Raven.RAVEN_THREAD.set(true); if (raven == null) initRaven(); Event event = buildEvent(record); @@ -135,7 +133,7 @@ public synchronized void publish(LogRecord record) { } catch (Exception e) { reportError("An exception occurred while creating a new event in Raven", e, ErrorManager.WRITE_FAILURE); } finally { - guard = false; + Raven.RAVEN_THREAD.remove(); } } From a7a7f2980f0ca2a9cf6edce46b85dc4e8ae18568 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 11:13:07 +1100 Subject: [PATCH 0924/2152] PMD notices the potential problems of using ThreadGroup, in this case it's fine --- .../src/main/java/net/kencochrane/raven/DefaultRavenFactory.java | 1 + 1 file changed, 1 insertion(+) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index ee90a17d75f..7ce4cd7ffbc 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -235,6 +235,7 @@ protected Collection getNotInAppFrames() { * down the main application. *

    */ + @SuppressWarnings("PMD.AvoidThreadGroup") protected static final class DaemonThreadFactory implements ThreadFactory { private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1); private final ThreadGroup group; From e72b269d864f1519d7f69c7bae7af7435e799ce3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 11:14:19 +1100 Subject: [PATCH 0925/2152] Do not rely on the default charset, force everything to UTF-8 --- .../java/net/kencochrane/raven/connection/HttpConnection.java | 3 ++- .../main/java/net/kencochrane/raven/event/EventBuilder.java | 3 ++- .../net/kencochrane/raven/marshaller/json/JsonMarshaller.java | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index e548e0611bd..16d2d2ea627 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -1,5 +1,6 @@ package net.kencochrane.raven.connection; +import com.google.common.base.Charsets; import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.marshaller.Marshaller; @@ -130,7 +131,7 @@ protected void doSend(Event event) { } private String getErrorMessageFromStream(InputStream errorStream) { - BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream)); + BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream, Charsets.UTF_8)); StringBuilder sb = new StringBuilder(); try { String line; diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index b5506f6298d..1bfb7907502 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -1,5 +1,6 @@ package net.kencochrane.raven.event; +import com.google.common.base.Charsets; import net.kencochrane.raven.event.interfaces.SentryInterface; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,7 +64,7 @@ public EventBuilder(UUID eventId) { * @return a checksum allowing two events with the same properties to be grouped later. */ private static String calculateChecksum(String string) { - byte[] bytes = string.getBytes(); + byte[] bytes = string.getBytes(Charsets.UTF_8); Checksum checksum = new CRC32(); checksum.update(bytes, 0, bytes.length); return String.valueOf(checksum.getValue()); diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index dc29c173390..85d444cc8da 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; +import com.google.common.base.Charsets; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.interfaces.SentryInterface; import net.kencochrane.raven.marshaller.Marshaller; @@ -101,7 +102,8 @@ public void marshall(Event event, OutputStream destination) { destination = new UncloseableOutputStream(destination); if (compression) - destination = new DeflaterOutputStream(base64().encodingStream(new OutputStreamWriter(destination))); + destination = new DeflaterOutputStream(base64().encodingStream( + new OutputStreamWriter(destination, Charsets.UTF_8))); JsonGenerator generator = null; try { From ebdd13bcdacc161dd7e4df3ca8d261d2f39f6c4d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 11:14:37 +1100 Subject: [PATCH 0926/2152] DateFormat is flaky when used in a multithreaded context Use a new instance each time instead --- .../raven/marshaller/json/JsonMarshaller.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 85d444cc8da..d8df5602f2f 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -82,7 +82,7 @@ public class JsonMarshaller implements Marshaller { /** * Date format for ISO 8601. */ - private static final DateFormat ISO_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + private static final String ISO_8601_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; private static final Logger logger = LoggerFactory.getLogger(JsonMarshaller.class); private final JsonFactory jsonFactory = new JsonFactory(); private final Map, InterfaceBinding> interfaceBindings = @@ -92,10 +92,6 @@ public class JsonMarshaller implements Marshaller { */ private boolean compression = true; - static { - ISO_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); - } - @Override public void marshall(Event event, OutputStream destination) { // Prevent the stream from being closed automatically @@ -243,7 +239,9 @@ private String formatLevel(Event.Level level) { * @return timestamp as a formatted String. */ private String formatTimestamp(Date timestamp) { - return ISO_FORMAT.format(timestamp); + DateFormat isoFormat = new SimpleDateFormat(ISO_8601_FORMAT); + isoFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + return isoFormat.format(timestamp); } /** From d1e58d04f3da02bacc3988c018960ef83d1893a8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 11:16:02 +1100 Subject: [PATCH 0927/2152] Add comments on RAVEN_THREAD usage --- .../java/net/kencochrane/raven/connection/AsyncConnection.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index f17c5664628..1264b3480be 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -74,6 +74,7 @@ private void addShutdownHook() { Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { try { + // The current thread is managed by raven Raven.RAVEN_THREAD.set(true); AsyncConnection.this.close(); } catch (IOException e) { @@ -146,7 +147,7 @@ private EventSubmitter(Event event) { @Override public void run() { try { - // The current thread is spawned by raven + // The current thread is managed by raven Raven.RAVEN_THREAD.set(true); actualConnection.send(event); } catch (Exception e) { From 14355161f005de1d736ada3d11381403dc297d24 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 11:17:21 +1100 Subject: [PATCH 0928/2152] ExceptionWithStackTrace doesn't require to be bound to the surounding class --- .../raven/marshaller/json/ExceptionInterfaceBinding.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java index a7bdcb572d8..b69e9f162c8 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java @@ -108,7 +108,7 @@ private void writeException(JsonGenerator generator, ExceptionWithStackTrace ews /** * Class associating an exception to its {@link StackTraceInterface}. */ - private final class ExceptionWithStackTrace { + private static final class ExceptionWithStackTrace { private ImmutableThrowable exception; private StackTraceInterface stackTrace; From 8f8e373afb7d46d4e222ee5a80dfc2de0e90eda0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 11:30:57 +1100 Subject: [PATCH 0929/2152] Remove now unused public/secret Keys from abstract connection --- .../net/kencochrane/raven/connection/AbstractConnection.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 41d13117470..039957fa0f0 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -30,8 +30,6 @@ public abstract class AbstractConnection implements Connection { */ public static final long DEFAULT_BASE_WAITING_TIME = TimeUnit.MILLISECONDS.toMillis(10); private static final Logger logger = LoggerFactory.getLogger(AbstractConnection.class); - private final String publicKey; - private final String secretKey; private final ReentrantLock lock = new ReentrantLock(); private final String authHeader; /** @@ -54,8 +52,6 @@ public abstract class AbstractConnection implements Connection { * @param secretKey secret key (password) to the Sentry server. */ protected AbstractConnection(String publicKey, String secretKey) { - this.publicKey = publicKey; - this.secretKey = secretKey; authHeader = "Sentry sentry_version=" + SENTRY_PROTOCOL_VERSION + "," + "sentry_client=" + Raven.NAME + "," + "sentry_key=" + publicKey + "," From a367dc9e7d1a4e092d6bb8842f27101b5f3948b4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 13:40:50 +1100 Subject: [PATCH 0930/2152] Ensure that build can't be called in more than one thread at the time --- .../src/main/java/net/kencochrane/raven/event/EventBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 1bfb7907502..1ceb9b8b944 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -280,7 +280,7 @@ public EventBuilder addSentryInterface(SentryInterface sentryInterface) { * * @return an immutable event. */ - public Event build() { + public synchronized Event build() { if (alreadyBuilt) throw new IllegalStateException("A message can't be built twice"); From 924110222be9bfa34d5ff048ef1138f1faa7b392 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 19:47:34 +1100 Subject: [PATCH 0931/2152] Use the Hexadecimal value of the checksum instead of the decimal one --- .../src/main/java/net/kencochrane/raven/event/EventBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 1ceb9b8b944..d99895ccdbe 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -67,7 +67,7 @@ private static String calculateChecksum(String string) { byte[] bytes = string.getBytes(Charsets.UTF_8); Checksum checksum = new CRC32(); checksum.update(bytes, 0, bytes.length); - return String.valueOf(checksum.getValue()); + return Long.toHexString(checksum.getValue()).toUpperCase(); } /** From 4737bd71322becb0f39b6f13b8d598dc947f4670 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 11:48:47 +1100 Subject: [PATCH 0932/2152] Ensure that AutoGeneratedUuid works properly --- .../raven/event/EventBuilderTest2.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java new file mode 100644 index 00000000000..c0995c1ac30 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -0,0 +1,25 @@ +package net.kencochrane.raven.event; + +import mockit.Injectable; +import mockit.NonStrictExpectations; +import org.testng.annotations.Test; + +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class EventBuilderTest2 { + @Test + public void buildedEventHasRandomlyGeneratedUuid(@Injectable final UUID mockUuid) throws Exception { + new NonStrictExpectations(UUID.class) {{ + UUID.randomUUID(); + result = mockUuid; + }}; + final EventBuilder eventBuilder = new EventBuilder(); + + final Event event = eventBuilder.build(); + + assertThat(event.getId(), is(mockUuid)); + } +} From 523438cf27dbad438c94e1c8b89b1f6e3829c7ee Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 11:54:40 +1100 Subject: [PATCH 0933/2152] Ensure that custom generated event ID are properly set --- .../net/kencochrane/raven/event/EventBuilderTest2.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index c0995c1ac30..9617dfa5204 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -22,4 +22,13 @@ public void buildedEventHasRandomlyGeneratedUuid(@Injectable final UUID mockUuid assertThat(event.getId(), is(mockUuid)); } + + @Test + public void buildedEventWithCustomUuidHasProperUuid(@Injectable UUID mockUuid) throws Exception { + final EventBuilder eventBuilder = new EventBuilder(mockUuid); + + final Event event = eventBuilder.build(); + + assertThat(event.getId(), is(mockUuid)); + } } From 8c1679eca3332f7d6a34c1c8f154433a0ff9f902 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 11:58:13 +1100 Subject: [PATCH 0934/2152] Ensure that expected element are the same exact instance --- .../java/net/kencochrane/raven/event/EventBuilderTest2.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index 9617dfa5204..5cf624caf0a 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -8,6 +8,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.sameInstance; public class EventBuilderTest2 { @Test @@ -20,7 +21,7 @@ public void buildedEventHasRandomlyGeneratedUuid(@Injectable final UUID mockUuid final Event event = eventBuilder.build(); - assertThat(event.getId(), is(mockUuid)); + assertThat(event.getId(), is(sameInstance(mockUuid))); } @Test @@ -29,6 +30,6 @@ public void buildedEventWithCustomUuidHasProperUuid(@Injectable UUID mockUuid) t final Event event = eventBuilder.build(); - assertThat(event.getId(), is(mockUuid)); + assertThat(event.getId(), is(sameInstance(mockUuid))); } } From 83ec72af777d824062a5911fb20d3345eb222b9f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 12:00:30 +1100 Subject: [PATCH 0935/2152] Ensure that the event message is set properly --- .../net/kencochrane/raven/event/EventBuilderTest2.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index 5cf624caf0a..d04509f5a97 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -32,4 +32,14 @@ public void buildedEventWithCustomUuidHasProperUuid(@Injectable UUID mockUuid) t assertThat(event.getId(), is(sameInstance(mockUuid))); } + + @Test + public void buildedEventWithMessageHasProperMessage(@Injectable("message") String mockMessage) throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setMessage(mockMessage); + + final Event event = eventBuilder.build(); + + assertThat(event.getMessage(), is(sameInstance(mockMessage))); + } } From 1d29a3459a1084fe40c44531e5211da5bfde46eb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 12:08:46 +1100 Subject: [PATCH 0936/2152] Ensure that events have an ID --- .../java/net/kencochrane/raven/event/EventTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/event/EventTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventTest.java new file mode 100644 index 00000000000..419283ee964 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/event/EventTest.java @@ -0,0 +1,10 @@ +package net.kencochrane.raven.event; + +import org.testng.annotations.Test; + +public class EventTest { + @Test(expectedExceptions = IllegalArgumentException.class) + public void ensureEventIdCantBeNull() throws Exception { + final Event event = new Event(null); + } +} From 14f8ae57abc0b4c252a2503bf795ef44df31dbf8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 12:12:58 +1100 Subject: [PATCH 0937/2152] Ensure that timestamp is cloned when obtained from event --- .../kencochrane/raven/event/EventTest.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventTest.java index 419283ee964..7f694af4f60 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventTest.java @@ -1,10 +1,34 @@ package net.kencochrane.raven.event; +import mockit.Injectable; +import mockit.NonStrictExpectations; import org.testng.annotations.Test; +import java.util.Date; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.sameInstance; + public class EventTest { @Test(expectedExceptions = IllegalArgumentException.class) public void ensureEventIdCantBeNull() throws Exception { final Event event = new Event(null); } + + @Test + public void returnsCloneOfTimestamp(@Injectable final Date mockTimestamp, + @Injectable final Date mockCloneTimestamp) + throws Exception { + new NonStrictExpectations() {{ + mockTimestamp.clone(); + result = mockCloneTimestamp; + }}; + final Event event = new Event(UUID.randomUUID()); + + event.setTimestamp(mockTimestamp); + + assertThat(event.getTimestamp(), is(sameInstance(mockCloneTimestamp))); + } } From 7a93794c15149e1066e0ef53881f9b6c1cb7dec5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 12:15:17 +1100 Subject: [PATCH 0938/2152] Fix typo and indentation --- .../net/kencochrane/raven/event/EventBuilderTest2.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index d04509f5a97..56f8e45e2ed 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -12,7 +12,8 @@ public class EventBuilderTest2 { @Test - public void buildedEventHasRandomlyGeneratedUuid(@Injectable final UUID mockUuid) throws Exception { + public void builtEventHasRandomlyGeneratedUuid(@Injectable final UUID mockUuid) + throws Exception { new NonStrictExpectations(UUID.class) {{ UUID.randomUUID(); result = mockUuid; @@ -25,7 +26,8 @@ public void buildedEventHasRandomlyGeneratedUuid(@Injectable final UUID mockUuid } @Test - public void buildedEventWithCustomUuidHasProperUuid(@Injectable UUID mockUuid) throws Exception { + public void builtEventWithCustomUuidHasProperUuid(@Injectable final UUID mockUuid) + throws Exception { final EventBuilder eventBuilder = new EventBuilder(mockUuid); final Event event = eventBuilder.build(); @@ -34,7 +36,9 @@ public void buildedEventWithCustomUuidHasProperUuid(@Injectable UUID mockUuid) t } @Test - public void buildedEventWithMessageHasProperMessage(@Injectable("message") String mockMessage) throws Exception { + public void builtEventWithMessageHasProperMessage( + @Injectable("message") final String mockMessage) + throws Exception { final EventBuilder eventBuilder = new EventBuilder(); eventBuilder.setMessage(mockMessage); From dfc7c28a6e8e196499dd5bd5812ca3b630de1227 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 12:15:27 +1100 Subject: [PATCH 0939/2152] Ensure that the timestamp is kept --- .../raven/event/EventBuilderTest2.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index 56f8e45e2ed..92c28830b9b 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -4,6 +4,7 @@ import mockit.NonStrictExpectations; import org.testng.annotations.Test; +import java.util.Date; import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; @@ -46,4 +47,19 @@ public void builtEventWithMessageHasProperMessage( assertThat(event.getMessage(), is(sameInstance(mockMessage))); } + + @Test + public void builtEventWithTimestampHasProperTimestamp(@Injectable final Date mockTimestamp) + throws Exception { + new NonStrictExpectations() {{ + mockTimestamp.clone(); + result = mockTimestamp; + }}; + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setTimestamp(mockTimestamp); + + final Event event = eventBuilder.build(); + + assertThat(event.getTimestamp(), is(sameInstance(mockTimestamp))); + } } From 5a8110c3d9d1bafe9363a7e562e33aaaca07bb3f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 12:17:29 +1100 Subject: [PATCH 0940/2152] Ensure that the level is properly set --- .../kencochrane/raven/event/EventBuilderTest2.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index 92c28830b9b..4dd7ad15845 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -62,4 +62,15 @@ public void builtEventWithTimestampHasProperTimestamp(@Injectable final Date moc assertThat(event.getTimestamp(), is(sameInstance(mockTimestamp))); } + + @Test + public void builtEventWithLevelHasProperLevel(@Injectable final Event.Level mockLevel) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setLevel(mockLevel); + + final Event event = eventBuilder.build(); + + assertThat(event.getLevel(), is(sameInstance(mockLevel))); + } } From 944a9108b62d1889502647c23fd8b7d3145dd99a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 12:19:26 +1100 Subject: [PATCH 0941/2152] Ensure that event without date gets the current one --- .../raven/event/EventBuilderTest2.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index 4dd7ad15845..20a9d338d19 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -48,6 +48,23 @@ public void builtEventWithMessageHasProperMessage( assertThat(event.getMessage(), is(sameInstance(mockMessage))); } + @Test + public void builtEventWithoutTimestampHasDefaultTimestamp(@Injectable final Date mockTimestamp) + throws Exception { + new NonStrictExpectations(Date.class) {{ + new Date(); + result = mockTimestamp; + mockTimestamp.clone(); + result = mockTimestamp; + }}; + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setTimestamp(mockTimestamp); + + final Event event = eventBuilder.build(); + + assertThat(event.getTimestamp(), is(sameInstance(mockTimestamp))); + } + @Test public void builtEventWithTimestampHasProperTimestamp(@Injectable final Date mockTimestamp) throws Exception { From 672322e0bb7d0cc5b20cc3bb086d9aea5e313972 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 12:21:39 +1100 Subject: [PATCH 0942/2152] Ensure that if the message isn't set, a null value is sent --- .../kencochrane/raven/event/EventBuilderTest2.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index 20a9d338d19..1e8d668d52f 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -8,8 +8,7 @@ import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.sameInstance; +import static org.hamcrest.Matchers.*; public class EventBuilderTest2 { @Test @@ -36,6 +35,15 @@ public void builtEventWithCustomUuidHasProperUuid(@Injectable final UUID mockUui assertThat(event.getId(), is(sameInstance(mockUuid))); } + @Test + public void builtEventWithoutMessageHasNullMessage() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + + final Event event = eventBuilder.build(); + + assertThat(event.getMessage(), is(nullValue())); + } + @Test public void builtEventWithMessageHasProperMessage( @Injectable("message") final String mockMessage) From fef1e7cca05242e4e8e97a4bc3579ddeb9a96fa5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 13:13:32 +1100 Subject: [PATCH 0943/2152] Ensure that if the level isn't set it stays null --- .../net/kencochrane/raven/event/EventBuilderTest2.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index 1e8d668d52f..67fe1ed4767 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -88,6 +88,15 @@ public void builtEventWithTimestampHasProperTimestamp(@Injectable final Date moc assertThat(event.getTimestamp(), is(sameInstance(mockTimestamp))); } + @Test + public void builtEventWithoutLevelHasNullLevel() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + + final Event event = eventBuilder.build(); + + assertThat(event.getLevel(), is(nullValue())); + } + @Test public void builtEventWithLevelHasProperLevel(@Injectable final Event.Level mockLevel) throws Exception { From c6c6afae7d48a7496c26b5b7dfe77db15280b77f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 13:14:52 +1100 Subject: [PATCH 0944/2152] Ensure that logger is set properly --- .../raven/event/EventBuilderTest2.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index 67fe1ed4767..f3f24a8c7ad 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -107,4 +107,24 @@ public void builtEventWithLevelHasProperLevel(@Injectable final Event.Level mock assertThat(event.getLevel(), is(sameInstance(mockLevel))); } + + @Test + public void builtEventWithoutLoggerHasNullLogger() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + + final Event event = eventBuilder.build(); + + assertThat(event.getLogger(), is(nullValue())); + } + + @Test + public void builtEventWithLoggerHasProperLogger(@Injectable("logger") final String mockLogger) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setLogger(mockLogger); + + final Event event = eventBuilder.build(); + + assertThat(event.getLogger(), is(sameInstance(mockLogger))); + } } From 0bf89c8f6b17c6667df78520415afdb494d8acdd Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 13:16:45 +1100 Subject: [PATCH 0945/2152] Add tests to check platform --- .../raven/event/EventBuilderTest2.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index f3f24a8c7ad..ab765b68ced 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -127,4 +127,24 @@ public void builtEventWithLoggerHasProperLogger(@Injectable("logger") final Stri assertThat(event.getLogger(), is(sameInstance(mockLogger))); } + + @Test + public void builtEventWithoutPlatformHasDefaultPlatform() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + + final Event event = eventBuilder.build(); + + assertThat(event.getPlatform(), is(EventBuilder.DEFAULT_PLATFORM)); + } + + @Test + public void builtEventWithPlatformHasProperPlatform(@Injectable("platform") final String mockPlatform) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setPlatform(mockPlatform); + + final Event event = eventBuilder.build(); + + assertThat(event.getPlatform(), is(sameInstance(mockPlatform))); + } } From 99382155e5da2db557c0aa93230e62540d623bfd Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 13:23:27 +1100 Subject: [PATCH 0946/2152] Ensure that EventBuilder with a null UUID fails --- .../java/net/kencochrane/raven/event/EventBuilderTest2.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index ab765b68ced..8d73ba7d26a 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -35,6 +35,11 @@ public void builtEventWithCustomUuidHasProperUuid(@Injectable final UUID mockUui assertThat(event.getId(), is(sameInstance(mockUuid))); } + @Test(expectedExceptions = IllegalArgumentException.class) + public void builtEventWithCustomNullUuidFails() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(null); + } + @Test public void builtEventWithoutMessageHasNullMessage() throws Exception { final EventBuilder eventBuilder = new EventBuilder(); From ebbc2386d186e301bbc6979d1e4746c29e5b0721 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 13:23:50 +1100 Subject: [PATCH 0947/2152] Add tests for the Culprit setup --- .../raven/event/EventBuilderTest2.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index 8d73ba7d26a..0ad872eccb9 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -152,4 +152,24 @@ public void builtEventWithPlatformHasProperPlatform(@Injectable("platform") fina assertThat(event.getPlatform(), is(sameInstance(mockPlatform))); } + + @Test + public void builtEventWithoutCulpritHasNullCulprit() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + + final Event event = eventBuilder.build(); + + assertThat(event.getCulprit(), is(nullValue())); + } + + @Test + public void builtEventWithCulpritHasProperCulprit(@Injectable("culprit") final String mockCulprit) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setCulprit(mockCulprit); + + final Event event = eventBuilder.build(); + + assertThat(event.getCulprit(), is(sameInstance(mockCulprit))); + } } From 2d3ea0d41b38154861febb18dd088502f053ffce Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 13:32:41 +1100 Subject: [PATCH 0948/2152] Add tests to check tags are set properly --- .../raven/event/EventBuilderTest2.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index 0ad872eccb9..2985f6b6dbb 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -4,6 +4,7 @@ import mockit.NonStrictExpectations; import org.testng.annotations.Test; +import java.util.Collections; import java.util.Date; import java.util.UUID; @@ -172,4 +173,34 @@ public void builtEventWithCulpritHasProperCulprit(@Injectable("culprit") final S assertThat(event.getCulprit(), is(sameInstance(mockCulprit))); } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void builtEventHasImmutableTags() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + final Event event = eventBuilder.build(); + + event.getTags().put("tagKey", "tagValue"); + } + + @Test + public void builtEventWithoutTagsHasEmptyTags() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + + final Event event = eventBuilder.build(); + + assertThat(event.getTags().entrySet(), is(empty())); + } + + @Test + public void builtEventWithTagsHasProperTags(@Injectable("tagKey") final String mockTagKey, + @Injectable("tagValue") final String mockTagValue) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.addTag(mockTagKey, mockTagValue); + + final Event event = eventBuilder.build(); + + assertThat(event.getTags(), hasEntry(mockTagKey, mockTagValue)); + assertThat(event.getTags().entrySet(), hasSize(1)); + } } From c5b6cfcd5da3e64b399af59e996dddec16027aac Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 13:40:11 +1100 Subject: [PATCH 0949/2152] Ensure that a StackFrame can be used as a Culprit --- .../raven/event/EventBuilderTest2.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index 2985f6b6dbb..aad7a00088d 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -2,6 +2,7 @@ import mockit.Injectable; import mockit.NonStrictExpectations; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.Collections; @@ -174,6 +175,28 @@ public void builtEventWithCulpritHasProperCulprit(@Injectable("culprit") final S assertThat(event.getCulprit(), is(sameInstance(mockCulprit))); } + @DataProvider + public Object[][] stackFrameProvider() { + return new Object[][]{ + {new StackTraceElement("class", "method", "file", 12), "class.method(file:12)"}, + {new StackTraceElement("class", "method", "file", -1), "class.method(file)"}, + {new StackTraceElement("class", "method", null, 12), "class.method"}, + {new StackTraceElement("class", "method", null, -1), "class.method"} + }; + } + + @Test(dataProvider = "stackFrameProvider") + public void builtEventWithStackFrameAsCulpritHasProperCulprit(StackTraceElement mockStackFrame, + String expectedCulprit) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setCulprit(mockStackFrame); + + final Event event = eventBuilder.build(); + + assertThat(event.getCulprit(), is(expectedCulprit)); + } + @Test(expectedExceptions = UnsupportedOperationException.class) public void builtEventHasImmutableTags() throws Exception { final EventBuilder eventBuilder = new EventBuilder(); From d8c6c6744f164ea3cdba927c776e8565ad6f8814 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 13:40:34 +1100 Subject: [PATCH 0950/2152] Fix imports --- .../test/java/net/kencochrane/raven/event/EventBuilderTest2.java | 1 - 1 file changed, 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index aad7a00088d..1f6cf55f1ec 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -5,7 +5,6 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import java.util.Collections; import java.util.Date; import java.util.UUID; From 596fbdd311c775d8edae5381fe0f662040c8bcba Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 18:54:45 +1100 Subject: [PATCH 0951/2152] Ensure that the hostname is cached to avoid unecessary operations --- .../event/EventBuilderHostnameCacheTest.java | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java new file mode 100644 index 00000000000..63efe8008d3 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java @@ -0,0 +1,104 @@ +package net.kencochrane.raven.event; + +import mockit.*; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.net.InetAddress; +import java.util.concurrent.TimeUnit; + +import static mockit.Deencapsulation.getField; +import static mockit.Deencapsulation.setField; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +@Test(singleThreaded = true) +public class EventBuilderHostnameCacheTest { + @Injectable + private InetAddress mockLocalHost; + @Injectable("serverName") + private String mockLocalHostName; + @Injectable + private InetAddress mockTimingOutLocalHost; + + @BeforeMethod + public void setUp() throws Exception { + new NonStrictExpectations() {{ + mockLocalHost.getCanonicalHostName(); + result = mockLocalHostName; + + mockTimingOutLocalHost.getCanonicalHostName(); + result = new Delegate() { + public String getCanonicalHostName() throws Exception { + synchronized (EventBuilderHostnameCacheTest.this) { + EventBuilderHostnameCacheTest.this.wait(); + } + return ""; + } + }; + }}; + // Clean Hostname Cache + setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp", 0l); + } + + @Test + public void successfulHostnameRetrievalIsCachedForFiveHours( + @Mocked(methods = "currentTimeMillis") final System system) + throws Exception { + new NonStrictExpectations(InetAddress.class) {{ + System.currentTimeMillis(); + result = 1L; + InetAddress.getLocalHost(); + result = mockLocalHost; + }}; + + new EventBuilder().build(); + final long expirationTime = getField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp"); + + assertThat(expirationTime, is(TimeUnit.HOURS.toMillis(5) + System.currentTimeMillis())); + } + + public void unsucessfulHostnameRetrievalIsCachedForOneSecond( + @Mocked(methods = "currentTimeMillis") final System system) + throws Exception { + new NonStrictExpectations(InetAddress.class) {{ + System.currentTimeMillis(); + result = 1L; + InetAddress.getLocalHost(); + result = mockTimingOutLocalHost; + }}; + + new EventBuilder().build(); + unlockTimingOutLocalHost(); + final long expirationTime = getField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp"); + + assertThat(expirationTime, is(TimeUnit.SECONDS.toMillis(1) + System.currentTimeMillis())); + } + + @Test + public void unsucessfulHostameRetrievalUsesLastKnownCachedValue() throws Exception { + new NonStrictExpectations(InetAddress.class) {{ + InetAddress.getLocalHost(); + result = mockLocalHost; + result = mockTimingOutLocalHost; + }}; + + + new EventBuilder().build(); + setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp", 0l); + Event event = new EventBuilder().build(); + unlockTimingOutLocalHost(); + + assertThat(event.getServerName(), is(mockLocalHostName)); + new Verifications() {{ + mockLocalHost.getCanonicalHostName(); + mockTimingOutLocalHost.getCanonicalHostName(); + }}; + } + + private void unlockTimingOutLocalHost() { + synchronized (this) { + this.notify(); + } + } +} From a328729c3ec33e212f8cc2c658318ef3e9f5e024 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 19:07:10 +1100 Subject: [PATCH 0952/2152] Add tests for hostname discovery --- .../raven/event/EventBuilderTest2.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index 1f6cf55f1ec..66e606151eb 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -1,13 +1,17 @@ package net.kencochrane.raven.event; +import mockit.Delegate; import mockit.Injectable; import mockit.NonStrictExpectations; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.net.InetAddress; import java.util.Date; import java.util.UUID; +import static mockit.Deencapsulation.getField; +import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -225,4 +229,74 @@ public void builtEventWithTagsHasProperTags(@Injectable("tagKey") final String m assertThat(event.getTags(), hasEntry(mockTagKey, mockTagValue)); assertThat(event.getTags().entrySet(), hasSize(1)); } + + @Test + public void builtEventWithNoServerNameUsesDefaultIfSearchTimesOut() + throws Exception { + new NonStrictExpectations(InetAddress.class) { + @Injectable + private InetAddress mockTimingOutLocalHost; + + { + InetAddress.getLocalHost(); + result = mockTimingOutLocalHost; + mockTimingOutLocalHost.getCanonicalHostName(); + result = new Delegate() { + public String getCanonicalHostName() throws Exception { + synchronized (EventBuilderTest2.this) { + EventBuilderTest2.this.wait(); + } + return ""; + } + }; + } + }; + final EventBuilder eventBuilder = new EventBuilder(); + + resetHostnameCache(); + final Event event = eventBuilder.build(); + synchronized (this) { + this.notify(); + } + + assertThat(event.getServerName(), is(EventBuilder.DEFAULT_HOSTNAME)); + } + + @Test + public void builtEventWithNoServerNameUsesLocalHost(@Injectable("serverName") final String mockServerName) + throws Exception { + new NonStrictExpectations(InetAddress.class) { + @Injectable + private InetAddress mockLocalHost; + + { + InetAddress.getLocalHost(); + result = mockLocalHost; + mockLocalHost.getCanonicalHostName(); + result = mockServerName; + } + }; + final EventBuilder eventBuilder = new EventBuilder(); + + resetHostnameCache(); + final Event event = eventBuilder.build(); + + assertThat(event.getServerName(), is(mockServerName)); + } + + @Test + public void builtEventWithServerNameUsesProvidedServerName(@Injectable("serverName") final String mockServerName) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setServerName(mockServerName); + + resetHostnameCache(); + final Event event = eventBuilder.build(); + + assertThat(event.getServerName(), is(mockServerName)); + } + + private static void resetHostnameCache() { + setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp", 0l); + } } From ad6e0346b7181a4148e6d2e6be8020a96d69546e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 19:16:13 +1100 Subject: [PATCH 0953/2152] Reorganise code --- .../net/kencochrane/raven/event/EventBuilderTest2.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index 66e606151eb..34e4894036d 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -16,6 +16,10 @@ import static org.hamcrest.Matchers.*; public class EventBuilderTest2 { + private static void resetHostnameCache() { + setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp", 0l); + } + @Test public void builtEventHasRandomlyGeneratedUuid(@Injectable final UUID mockUuid) throws Exception { @@ -295,8 +299,4 @@ public void builtEventWithServerNameUsesProvidedServerName(@Injectable("serverNa assertThat(event.getServerName(), is(mockServerName)); } - - private static void resetHostnameCache() { - setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp", 0l); - } } From f09f1458d4d336524b3ec1898cecdd8cabe0e048 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 19:16:38 +1100 Subject: [PATCH 0954/2152] Add tests for Extras --- .../raven/event/EventBuilderTest2.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index 34e4894036d..03e5a00b8f3 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -299,4 +299,34 @@ public void builtEventWithServerNameUsesProvidedServerName(@Injectable("serverNa assertThat(event.getServerName(), is(mockServerName)); } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void builtEventHasImmutableExtras() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + final Event event = eventBuilder.build(); + + event.getExtra().put("extraKey", "extraKey"); + } + + @Test + public void builtEventWithoutExtrasHasEmptyExtras() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + + final Event event = eventBuilder.build(); + + assertThat(event.getExtra().entrySet(), is(empty())); + } + + @Test + public void builtEventWithExtrasHasProperExtras(@Injectable("extraKey") final String mockExtraKey, + @Injectable("extraValue") final String mockExtraValue) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.addExtra(mockExtraKey, mockExtraValue); + + final Event event = eventBuilder.build(); + + assertThat(event.getExtra(), hasEntry(mockExtraKey, (Object) mockExtraValue)); + assertThat(event.getExtra().entrySet(), hasSize(1)); + } } From 9e8a95b846c532bbea6e07b25b91d15e33406db0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 19:38:26 +1100 Subject: [PATCH 0955/2152] Ensure that checksums are working properly --- .../raven/event/EventBuilderTest2.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index 03e5a00b8f3..efee82d7021 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -329,4 +329,45 @@ public void builtEventWithExtrasHasProperExtras(@Injectable("extraKey") final St assertThat(event.getExtra(), hasEntry(mockExtraKey, (Object) mockExtraValue)); assertThat(event.getExtra().entrySet(), hasSize(1)); } + + @Test + public void builtEventWithoutCheckHasNullChecksum() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + + final Event event = eventBuilder.build(); + + assertThat(event.getChecksum(), is(nullValue())); + } + + @Test + public void builtEventWithChecksumHasProperChecksum( + @Injectable("checksum") final String mockChecksum) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setChecksum(mockChecksum); + + final Event event = eventBuilder.build(); + + assertThat(event.getChecksum(), is(sameInstance(mockChecksum))); + } + + @DataProvider + public Object[][] checksumProvider() { + return new Object[][]{ + {"", "0"}, + {"test", "D87F7E0C"}, + {"otherTest", "77B2E45B"} + }; + } + + @Test(dataProvider = "checksumProvider") + public void builtEventWithGeneratedChecksumHasCRC32Checksum(String string, String expectedChecksum) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.generateChecksum(string); + + final Event event = eventBuilder.build(); + + assertThat(event.getChecksum(), is(expectedChecksum)); + } } From a3a90a4f574c0eb69e2ee7ef1f711a2a702e534a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 19:42:54 +1100 Subject: [PATCH 0956/2152] Add tests to ensure that SentryInterfaces are set properly --- .../raven/event/EventBuilderTest2.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index efee82d7021..11bc918b0b9 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -3,6 +3,7 @@ import mockit.Delegate; import mockit.Injectable; import mockit.NonStrictExpectations; +import net.kencochrane.raven.event.interfaces.SentryInterface; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -370,4 +371,40 @@ public void builtEventWithGeneratedChecksumHasCRC32Checksum(String string, Strin assertThat(event.getChecksum(), is(expectedChecksum)); } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void builtEventHasImmutableSentryInterfaces(@Injectable final SentryInterface mockSentryInterface) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + final Event event = eventBuilder.build(); + + event.getSentryInterfaces().put("interfaceName", mockSentryInterface); + } + + @Test + public void builtEventWithoutSentryInterfacesHasEmptySentryInterfaces() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + + final Event event = eventBuilder.build(); + + assertThat(event.getSentryInterfaces().entrySet(), is(empty())); + } + + @Test + public void builtEventWithSentryInterfacesHasProperSentryInterfaces( + @Injectable("sentryInterfaceName") final String mockSentryInterfaceName, + @Injectable final SentryInterface mockSentryInterface) + throws Exception { + new NonStrictExpectations(){{ + mockSentryInterface.getInterfaceName(); + result = mockSentryInterfaceName; + }}; + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.addSentryInterface(mockSentryInterface); + + final Event event = eventBuilder.build(); + + assertThat(event.getSentryInterfaces(), hasEntry(mockSentryInterfaceName, mockSentryInterface)); + assertThat(event.getSentryInterfaces().entrySet(), hasSize(1)); + } } From 89033f26ef68c0dcab075386c3da3f5267dba874 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 19:44:39 +1100 Subject: [PATCH 0957/2152] Ensure that building the same event twice doesn't work --- .../net/kencochrane/raven/event/EventBuilderTest2.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java index 11bc918b0b9..14d5d8700bd 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java @@ -407,4 +407,11 @@ public void builtEventWithSentryInterfacesHasProperSentryInterfaces( assertThat(event.getSentryInterfaces(), hasEntry(mockSentryInterfaceName, mockSentryInterface)); assertThat(event.getSentryInterfaces().entrySet(), hasSize(1)); } + + @Test(expectedExceptions = IllegalStateException.class) + public void buildingTheEventTwiceFails() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.build(); + eventBuilder.build(); + } } From 20deebbdbb786dc7c6298d6bd8c5587905cf1193 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 19:50:47 +1100 Subject: [PATCH 0958/2152] Remove old EventBuilder tests suite and replace with the new one --- .../raven/event/EventBuilderTest.java | 467 +++++++++++++----- .../raven/event/EventBuilderTest2.java | 417 ---------------- 2 files changed, 341 insertions(+), 543 deletions(-) delete mode 100644 raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index 8678b99536a..49d969090ec 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -2,201 +2,416 @@ import mockit.Delegate; import mockit.Injectable; -import mockit.Mocked; import mockit.NonStrictExpectations; import net.kencochrane.raven.event.interfaces.SentryInterface; -import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.net.InetAddress; import java.util.Date; -import java.util.Map; import java.util.UUID; -import static mockit.Deencapsulation.newInstance; +import static mockit.Deencapsulation.getField; import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; public class EventBuilderTest { - private EventBuilder eventBuilder; - @Mocked - private InetAddress inetAddress; + private static void resetHostnameCache() { + setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp", 0l); + } - @BeforeMethod - public void setUp() throws Exception { - eventBuilder = new EventBuilder(); - new NonStrictExpectations() {{ - InetAddress.getLocalHost(); - result = inetAddress; + @Test + public void builtEventHasRandomlyGeneratedUuid(@Injectable final UUID mockUuid) + throws Exception { + new NonStrictExpectations(UUID.class) {{ + UUID.randomUUID(); + result = mockUuid; }}; - //Create a temporary cache with a timeout of 0 - setField(EventBuilder.class, "HOSTNAME_CACHE", - newInstance(EventBuilder.class.getName() + "$HostnameCache", 0l)); + final EventBuilder eventBuilder = new EventBuilder(); + + final Event event = eventBuilder.build(); + + assertThat(event.getId(), is(sameInstance(mockUuid))); } @Test - public void testMandatoryValuesAutomaticallySet() throws Exception { - final String expectedWorkingHostname = UUID.randomUUID().toString(); - new NonStrictExpectations() {{ - inetAddress.getCanonicalHostName(); - result = expectedWorkingHostname; + public void builtEventWithCustomUuidHasProperUuid(@Injectable final UUID mockUuid) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(mockUuid); + + final Event event = eventBuilder.build(); + + assertThat(event.getId(), is(sameInstance(mockUuid))); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void builtEventWithCustomNullUuidFails() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(null); + } + + @Test + public void builtEventWithoutMessageHasNullMessage() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + + final Event event = eventBuilder.build(); + + assertThat(event.getMessage(), is(nullValue())); + } + + @Test + public void builtEventWithMessageHasProperMessage( + @Injectable("message") final String mockMessage) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setMessage(mockMessage); + + final Event event = eventBuilder.build(); + + assertThat(event.getMessage(), is(sameInstance(mockMessage))); + } + + @Test + public void builtEventWithoutTimestampHasDefaultTimestamp(@Injectable final Date mockTimestamp) + throws Exception { + new NonStrictExpectations(Date.class) {{ + new Date(); + result = mockTimestamp; + mockTimestamp.clone(); + result = mockTimestamp; }}; - Event event = eventBuilder.build(); + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setTimestamp(mockTimestamp); - assertThat(event.getId(), is(notNullValue())); - assertThat(event.getTimestamp(), is(notNullValue())); - assertThat(event.getPlatform(), is(EventBuilder.DEFAULT_PLATFORM)); - assertThat(event.getServerName(), is(expectedWorkingHostname)); + final Event event = eventBuilder.build(); + + assertThat(event.getTimestamp(), is(sameInstance(mockTimestamp))); } @Test - public void slowCallToGetCanonicalHostNameIsCaught() throws Exception { + public void builtEventWithTimestampHasProperTimestamp(@Injectable final Date mockTimestamp) + throws Exception { new NonStrictExpectations() {{ - inetAddress.getCanonicalHostName(); - result = new Delegate() { - public String getCanonicalHostName() throws Exception { - synchronized (EventBuilderTest.this) { - EventBuilderTest.this.wait(); - } - return ""; - } - }; + mockTimestamp.clone(); + result = mockTimestamp; }}; + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setTimestamp(mockTimestamp); - Event event = eventBuilder.build(); - synchronized (EventBuilderTest.this) { - EventBuilderTest.this.notify(); - } + final Event event = eventBuilder.build(); - assertThat(event.getServerName(), is(EventBuilder.DEFAULT_HOSTNAME)); + assertThat(event.getTimestamp(), is(sameInstance(mockTimestamp))); + } + @Test + public void builtEventWithoutLevelHasNullLevel() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + + final Event event = eventBuilder.build(); + + assertThat(event.getLevel(), is(nullValue())); } @Test - public void testMandatoryValuesNotOverwritten() throws Exception { - UUID uuid = UUID.randomUUID(); - Date timestamp = new Date(); - String platform = UUID.randomUUID().toString(); - String serverName = UUID.randomUUID().toString(); + public void builtEventWithLevelHasProperLevel(@Injectable final Event.Level mockLevel) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setLevel(mockLevel); - Event event = new EventBuilder(uuid) - .setTimestamp(timestamp) - .setPlatform(platform) - .setServerName(serverName) - .build(); + final Event event = eventBuilder.build(); - assertThat(event.getId(), is(uuid)); - assertThat(event.getTimestamp(), is(timestamp)); - assertThat(event.getPlatform(), is(platform)); - assertThat(event.getServerName(), is(serverName)); + assertThat(event.getLevel(), is(sameInstance(mockLevel))); } @Test - public void testEventParameters() throws Exception { - String culprit = UUID.randomUUID().toString(); - String checksum = UUID.randomUUID().toString(); - Event.Level level = Event.Level.INFO; - String logger = UUID.randomUUID().toString(); - String message = UUID.randomUUID().toString(); + public void builtEventWithoutLoggerHasNullLogger() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); - Event event = eventBuilder.setCulprit(culprit) - .setChecksum(checksum) - .setLevel(level) - .setLogger(logger) - .setMessage(message) - .build(); + final Event event = eventBuilder.build(); - assertThat(event.getCulprit(), is(culprit)); - assertThat(event.getChecksum(), is(checksum)); - assertThat(event.getLevel(), is(level)); - assertThat(event.getLogger(), is(logger)); - assertThat(event.getMessage(), is(message)); + assertThat(event.getLogger(), is(nullValue())); } @Test - public void testUseStackFrameAsCulprit() { - StackTraceElement frame1 = new StackTraceElement("class", "method", "file", 1); - StackTraceElement frame2 = new StackTraceElement("class", "method", "file", -1); - StackTraceElement frame3 = new StackTraceElement("class", "method", null, 1); + public void builtEventWithLoggerHasProperLogger(@Injectable("logger") final String mockLogger) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setLogger(mockLogger); - String culprit1 = new EventBuilder().setCulprit(frame1).build().getCulprit(); - String culprit2 = new EventBuilder().setCulprit(frame2).build().getCulprit(); - String culprit3 = new EventBuilder().setCulprit(frame3).build().getCulprit(); + final Event event = eventBuilder.build(); - assertThat(culprit1, is("class.method(file:1)")); - assertThat(culprit2, is("class.method(file)")); - assertThat(culprit3, is("class.method")); + assertThat(event.getLogger(), is(sameInstance(mockLogger))); } @Test - public void testChecksumGeneration() throws Exception { - String cont = UUID.randomUUID().toString(); - Event noChecksumEvent = new EventBuilder().build(); - Event firstChecksumEvent = new EventBuilder().generateChecksum(cont).build(); - Event secondChecksumEvent = new EventBuilder().generateChecksum(cont).build(); - Event differentChecksumEvent = new EventBuilder().generateChecksum(UUID.randomUUID().toString()).build(); + public void builtEventWithoutPlatformHasDefaultPlatform() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + + final Event event = eventBuilder.build(); + + assertThat(event.getPlatform(), is(EventBuilder.DEFAULT_PLATFORM)); + } + + @Test + public void builtEventWithPlatformHasProperPlatform(@Injectable("platform") final String mockPlatform) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setPlatform(mockPlatform); + + final Event event = eventBuilder.build(); + + assertThat(event.getPlatform(), is(sameInstance(mockPlatform))); + } + + @Test + public void builtEventWithoutCulpritHasNullCulprit() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + + final Event event = eventBuilder.build(); - assertThat(noChecksumEvent.getChecksum(), is(nullValue())); - assertThat(firstChecksumEvent.getChecksum(), is(notNullValue())); - assertThat(differentChecksumEvent.getChecksum(), is(notNullValue())); - assertThat(firstChecksumEvent.getChecksum(), is(not(differentChecksumEvent.getChecksum()))); - assertThat(firstChecksumEvent.getChecksum(), is(secondChecksumEvent.getChecksum())); + assertThat(event.getCulprit(), is(nullValue())); + } + + @Test + public void builtEventWithCulpritHasProperCulprit(@Injectable("culprit") final String mockCulprit) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setCulprit(mockCulprit); + + final Event event = eventBuilder.build(); + + assertThat(event.getCulprit(), is(sameInstance(mockCulprit))); + } + + @DataProvider + public Object[][] stackFrameProvider() { + return new Object[][]{ + {new StackTraceElement("class", "method", "file", 12), "class.method(file:12)"}, + {new StackTraceElement("class", "method", "file", -1), "class.method(file)"}, + {new StackTraceElement("class", "method", null, 12), "class.method"}, + {new StackTraceElement("class", "method", null, -1), "class.method"} + }; + } + + @Test(dataProvider = "stackFrameProvider") + public void builtEventWithStackFrameAsCulpritHasProperCulprit(StackTraceElement mockStackFrame, + String expectedCulprit) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setCulprit(mockStackFrame); + + final Event event = eventBuilder.build(); + + assertThat(event.getCulprit(), is(expectedCulprit)); } @Test(expectedExceptions = UnsupportedOperationException.class) - public void testTagsAreImmutable() throws Exception { - String tagKey = UUID.randomUUID().toString(); - String tagValue = UUID.randomUUID().toString(); + public void builtEventHasImmutableTags() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + final Event event = eventBuilder.build(); - Map tags = eventBuilder.addTag(tagKey, tagValue).build().getTags(); + event.getTags().put("tagKey", "tagValue"); + } + + @Test + public void builtEventWithoutTagsHasEmptyTags() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); - assertThat(tags.size(), is(1)); - assertThat(tags.get(tagKey), is(tagValue)); + final Event event = eventBuilder.build(); - tags.put(UUID.randomUUID().toString(), UUID.randomUUID().toString()); + assertThat(event.getTags().entrySet(), is(empty())); + } + + @Test + public void builtEventWithTagsHasProperTags(@Injectable("tagKey") final String mockTagKey, + @Injectable("tagValue") final String mockTagValue) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.addTag(mockTagKey, mockTagValue); + + final Event event = eventBuilder.build(); + + assertThat(event.getTags(), hasEntry(mockTagKey, mockTagValue)); + assertThat(event.getTags().entrySet(), hasSize(1)); + } + + @Test + public void builtEventWithNoServerNameUsesDefaultIfSearchTimesOut() + throws Exception { + new NonStrictExpectations(InetAddress.class) { + @Injectable + private InetAddress mockTimingOutLocalHost; + + { + InetAddress.getLocalHost(); + result = mockTimingOutLocalHost; + mockTimingOutLocalHost.getCanonicalHostName(); + result = new Delegate() { + public String getCanonicalHostName() throws Exception { + synchronized (EventBuilderTest.this) { + EventBuilderTest.this.wait(); + } + return ""; + } + }; + } + }; + final EventBuilder eventBuilder = new EventBuilder(); + + resetHostnameCache(); + final Event event = eventBuilder.build(); + synchronized (this) { + this.notify(); + } + + assertThat(event.getServerName(), is(EventBuilder.DEFAULT_HOSTNAME)); + } + + @Test + public void builtEventWithNoServerNameUsesLocalHost(@Injectable("serverName") final String mockServerName) + throws Exception { + new NonStrictExpectations(InetAddress.class) { + @Injectable + private InetAddress mockLocalHost; + + { + InetAddress.getLocalHost(); + result = mockLocalHost; + mockLocalHost.getCanonicalHostName(); + result = mockServerName; + } + }; + final EventBuilder eventBuilder = new EventBuilder(); + + resetHostnameCache(); + final Event event = eventBuilder.build(); + + assertThat(event.getServerName(), is(mockServerName)); + } + + @Test + public void builtEventWithServerNameUsesProvidedServerName(@Injectable("serverName") final String mockServerName) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setServerName(mockServerName); + + resetHostnameCache(); + final Event event = eventBuilder.build(); + + assertThat(event.getServerName(), is(mockServerName)); } @Test(expectedExceptions = UnsupportedOperationException.class) - public void testExtrasAreImmutable() throws Exception { - final String extraKey = UUID.randomUUID().toString(); - final Object extraValue = new Object(); + public void builtEventHasImmutableExtras() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + final Event event = eventBuilder.build(); + + event.getExtra().put("extraKey", "extraKey"); + } + + @Test + public void builtEventWithoutExtrasHasEmptyExtras() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + + final Event event = eventBuilder.build(); + + assertThat(event.getExtra().entrySet(), is(empty())); + } + + @Test + public void builtEventWithExtrasHasProperExtras(@Injectable("extraKey") final String mockExtraKey, + @Injectable("extraValue") final String mockExtraValue) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.addExtra(mockExtraKey, mockExtraValue); - Map extra = eventBuilder.addExtra(extraKey, extraValue).build().getExtra(); + final Event event = eventBuilder.build(); - assertThat(extra.size(), is(1)); - assertThat(extra.get(extraKey), is(extraValue)); + assertThat(event.getExtra(), hasEntry(mockExtraKey, (Object) mockExtraValue)); + assertThat(event.getExtra().entrySet(), hasSize(1)); + } + + @Test + public void builtEventWithoutCheckHasNullChecksum() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + + final Event event = eventBuilder.build(); + + assertThat(event.getChecksum(), is(nullValue())); + } + + @Test + public void builtEventWithChecksumHasProperChecksum( + @Injectable("checksum") final String mockChecksum) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.setChecksum(mockChecksum); + + final Event event = eventBuilder.build(); + + assertThat(event.getChecksum(), is(sameInstance(mockChecksum))); + } + + @DataProvider + public Object[][] checksumProvider() { + return new Object[][]{ + {"", "0"}, + {"test", "D87F7E0C"}, + {"otherTest", "77B2E45B"} + }; + } + + @Test(dataProvider = "checksumProvider") + public void builtEventWithGeneratedChecksumHasCRC32Checksum(String string, String expectedChecksum) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.generateChecksum(string); + + final Event event = eventBuilder.build(); - extra.put(UUID.randomUUID().toString(), new Object()); + assertThat(event.getChecksum(), is(expectedChecksum)); } @Test(expectedExceptions = UnsupportedOperationException.class) - public void testSentryInterfacesAreImmutable(@Injectable final SentryInterface sentryInterface) throws Exception { - final String interfaceName = UUID.randomUUID().toString(); - new NonStrictExpectations() {{ - sentryInterface.getInterfaceName(); - result = interfaceName; - }}; + public void builtEventHasImmutableSentryInterfaces(@Injectable final SentryInterface mockSentryInterface) + throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + final Event event = eventBuilder.build(); + + event.getSentryInterfaces().put("interfaceName", mockSentryInterface); + } + + @Test + public void builtEventWithoutSentryInterfacesHasEmptySentryInterfaces() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); + + final Event event = eventBuilder.build(); - Map sentryInterfaces = eventBuilder - .addSentryInterface(sentryInterface) - .build() - .getSentryInterfaces(); + assertThat(event.getSentryInterfaces().entrySet(), is(empty())); + } + + @Test + public void builtEventWithSentryInterfacesHasProperSentryInterfaces( + @Injectable("sentryInterfaceName") final String mockSentryInterfaceName, + @Injectable final SentryInterface mockSentryInterface) + throws Exception { + new NonStrictExpectations(){{ + mockSentryInterface.getInterfaceName(); + result = mockSentryInterfaceName; + }}; + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.addSentryInterface(mockSentryInterface); - assertThat(sentryInterfaces.size(), is(1)); - assertThat(sentryInterfaces.get(sentryInterface.getInterfaceName()), is(sentryInterface)); + final Event event = eventBuilder.build(); - sentryInterfaces.put(UUID.randomUUID().toString(), null); + assertThat(event.getSentryInterfaces(), hasEntry(mockSentryInterfaceName, mockSentryInterface)); + assertThat(event.getSentryInterfaces().entrySet(), hasSize(1)); } @Test(expectedExceptions = IllegalStateException.class) - public void testBuildCanBeCalledOnlyOnce() throws Exception { + public void buildingTheEventTwiceFails() throws Exception { + final EventBuilder eventBuilder = new EventBuilder(); eventBuilder.build(); eventBuilder.build(); } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testNoUuidFails() throws Exception { - new EventBuilder(null); - } } diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java deleted file mode 100644 index 14d5d8700bd..00000000000 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest2.java +++ /dev/null @@ -1,417 +0,0 @@ -package net.kencochrane.raven.event; - -import mockit.Delegate; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import net.kencochrane.raven.event.interfaces.SentryInterface; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import java.net.InetAddress; -import java.util.Date; -import java.util.UUID; - -import static mockit.Deencapsulation.getField; -import static mockit.Deencapsulation.setField; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; - -public class EventBuilderTest2 { - private static void resetHostnameCache() { - setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp", 0l); - } - - @Test - public void builtEventHasRandomlyGeneratedUuid(@Injectable final UUID mockUuid) - throws Exception { - new NonStrictExpectations(UUID.class) {{ - UUID.randomUUID(); - result = mockUuid; - }}; - final EventBuilder eventBuilder = new EventBuilder(); - - final Event event = eventBuilder.build(); - - assertThat(event.getId(), is(sameInstance(mockUuid))); - } - - @Test - public void builtEventWithCustomUuidHasProperUuid(@Injectable final UUID mockUuid) - throws Exception { - final EventBuilder eventBuilder = new EventBuilder(mockUuid); - - final Event event = eventBuilder.build(); - - assertThat(event.getId(), is(sameInstance(mockUuid))); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void builtEventWithCustomNullUuidFails() throws Exception { - final EventBuilder eventBuilder = new EventBuilder(null); - } - - @Test - public void builtEventWithoutMessageHasNullMessage() throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - - final Event event = eventBuilder.build(); - - assertThat(event.getMessage(), is(nullValue())); - } - - @Test - public void builtEventWithMessageHasProperMessage( - @Injectable("message") final String mockMessage) - throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setMessage(mockMessage); - - final Event event = eventBuilder.build(); - - assertThat(event.getMessage(), is(sameInstance(mockMessage))); - } - - @Test - public void builtEventWithoutTimestampHasDefaultTimestamp(@Injectable final Date mockTimestamp) - throws Exception { - new NonStrictExpectations(Date.class) {{ - new Date(); - result = mockTimestamp; - mockTimestamp.clone(); - result = mockTimestamp; - }}; - final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setTimestamp(mockTimestamp); - - final Event event = eventBuilder.build(); - - assertThat(event.getTimestamp(), is(sameInstance(mockTimestamp))); - } - - @Test - public void builtEventWithTimestampHasProperTimestamp(@Injectable final Date mockTimestamp) - throws Exception { - new NonStrictExpectations() {{ - mockTimestamp.clone(); - result = mockTimestamp; - }}; - final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setTimestamp(mockTimestamp); - - final Event event = eventBuilder.build(); - - assertThat(event.getTimestamp(), is(sameInstance(mockTimestamp))); - } - - @Test - public void builtEventWithoutLevelHasNullLevel() throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - - final Event event = eventBuilder.build(); - - assertThat(event.getLevel(), is(nullValue())); - } - - @Test - public void builtEventWithLevelHasProperLevel(@Injectable final Event.Level mockLevel) - throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setLevel(mockLevel); - - final Event event = eventBuilder.build(); - - assertThat(event.getLevel(), is(sameInstance(mockLevel))); - } - - @Test - public void builtEventWithoutLoggerHasNullLogger() throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - - final Event event = eventBuilder.build(); - - assertThat(event.getLogger(), is(nullValue())); - } - - @Test - public void builtEventWithLoggerHasProperLogger(@Injectable("logger") final String mockLogger) - throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setLogger(mockLogger); - - final Event event = eventBuilder.build(); - - assertThat(event.getLogger(), is(sameInstance(mockLogger))); - } - - @Test - public void builtEventWithoutPlatformHasDefaultPlatform() throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - - final Event event = eventBuilder.build(); - - assertThat(event.getPlatform(), is(EventBuilder.DEFAULT_PLATFORM)); - } - - @Test - public void builtEventWithPlatformHasProperPlatform(@Injectable("platform") final String mockPlatform) - throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setPlatform(mockPlatform); - - final Event event = eventBuilder.build(); - - assertThat(event.getPlatform(), is(sameInstance(mockPlatform))); - } - - @Test - public void builtEventWithoutCulpritHasNullCulprit() throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - - final Event event = eventBuilder.build(); - - assertThat(event.getCulprit(), is(nullValue())); - } - - @Test - public void builtEventWithCulpritHasProperCulprit(@Injectable("culprit") final String mockCulprit) - throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setCulprit(mockCulprit); - - final Event event = eventBuilder.build(); - - assertThat(event.getCulprit(), is(sameInstance(mockCulprit))); - } - - @DataProvider - public Object[][] stackFrameProvider() { - return new Object[][]{ - {new StackTraceElement("class", "method", "file", 12), "class.method(file:12)"}, - {new StackTraceElement("class", "method", "file", -1), "class.method(file)"}, - {new StackTraceElement("class", "method", null, 12), "class.method"}, - {new StackTraceElement("class", "method", null, -1), "class.method"} - }; - } - - @Test(dataProvider = "stackFrameProvider") - public void builtEventWithStackFrameAsCulpritHasProperCulprit(StackTraceElement mockStackFrame, - String expectedCulprit) - throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setCulprit(mockStackFrame); - - final Event event = eventBuilder.build(); - - assertThat(event.getCulprit(), is(expectedCulprit)); - } - - @Test(expectedExceptions = UnsupportedOperationException.class) - public void builtEventHasImmutableTags() throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - final Event event = eventBuilder.build(); - - event.getTags().put("tagKey", "tagValue"); - } - - @Test - public void builtEventWithoutTagsHasEmptyTags() throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - - final Event event = eventBuilder.build(); - - assertThat(event.getTags().entrySet(), is(empty())); - } - - @Test - public void builtEventWithTagsHasProperTags(@Injectable("tagKey") final String mockTagKey, - @Injectable("tagValue") final String mockTagValue) - throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.addTag(mockTagKey, mockTagValue); - - final Event event = eventBuilder.build(); - - assertThat(event.getTags(), hasEntry(mockTagKey, mockTagValue)); - assertThat(event.getTags().entrySet(), hasSize(1)); - } - - @Test - public void builtEventWithNoServerNameUsesDefaultIfSearchTimesOut() - throws Exception { - new NonStrictExpectations(InetAddress.class) { - @Injectable - private InetAddress mockTimingOutLocalHost; - - { - InetAddress.getLocalHost(); - result = mockTimingOutLocalHost; - mockTimingOutLocalHost.getCanonicalHostName(); - result = new Delegate() { - public String getCanonicalHostName() throws Exception { - synchronized (EventBuilderTest2.this) { - EventBuilderTest2.this.wait(); - } - return ""; - } - }; - } - }; - final EventBuilder eventBuilder = new EventBuilder(); - - resetHostnameCache(); - final Event event = eventBuilder.build(); - synchronized (this) { - this.notify(); - } - - assertThat(event.getServerName(), is(EventBuilder.DEFAULT_HOSTNAME)); - } - - @Test - public void builtEventWithNoServerNameUsesLocalHost(@Injectable("serverName") final String mockServerName) - throws Exception { - new NonStrictExpectations(InetAddress.class) { - @Injectable - private InetAddress mockLocalHost; - - { - InetAddress.getLocalHost(); - result = mockLocalHost; - mockLocalHost.getCanonicalHostName(); - result = mockServerName; - } - }; - final EventBuilder eventBuilder = new EventBuilder(); - - resetHostnameCache(); - final Event event = eventBuilder.build(); - - assertThat(event.getServerName(), is(mockServerName)); - } - - @Test - public void builtEventWithServerNameUsesProvidedServerName(@Injectable("serverName") final String mockServerName) - throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setServerName(mockServerName); - - resetHostnameCache(); - final Event event = eventBuilder.build(); - - assertThat(event.getServerName(), is(mockServerName)); - } - - @Test(expectedExceptions = UnsupportedOperationException.class) - public void builtEventHasImmutableExtras() throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - final Event event = eventBuilder.build(); - - event.getExtra().put("extraKey", "extraKey"); - } - - @Test - public void builtEventWithoutExtrasHasEmptyExtras() throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - - final Event event = eventBuilder.build(); - - assertThat(event.getExtra().entrySet(), is(empty())); - } - - @Test - public void builtEventWithExtrasHasProperExtras(@Injectable("extraKey") final String mockExtraKey, - @Injectable("extraValue") final String mockExtraValue) - throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.addExtra(mockExtraKey, mockExtraValue); - - final Event event = eventBuilder.build(); - - assertThat(event.getExtra(), hasEntry(mockExtraKey, (Object) mockExtraValue)); - assertThat(event.getExtra().entrySet(), hasSize(1)); - } - - @Test - public void builtEventWithoutCheckHasNullChecksum() throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - - final Event event = eventBuilder.build(); - - assertThat(event.getChecksum(), is(nullValue())); - } - - @Test - public void builtEventWithChecksumHasProperChecksum( - @Injectable("checksum") final String mockChecksum) - throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setChecksum(mockChecksum); - - final Event event = eventBuilder.build(); - - assertThat(event.getChecksum(), is(sameInstance(mockChecksum))); - } - - @DataProvider - public Object[][] checksumProvider() { - return new Object[][]{ - {"", "0"}, - {"test", "D87F7E0C"}, - {"otherTest", "77B2E45B"} - }; - } - - @Test(dataProvider = "checksumProvider") - public void builtEventWithGeneratedChecksumHasCRC32Checksum(String string, String expectedChecksum) - throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.generateChecksum(string); - - final Event event = eventBuilder.build(); - - assertThat(event.getChecksum(), is(expectedChecksum)); - } - - @Test(expectedExceptions = UnsupportedOperationException.class) - public void builtEventHasImmutableSentryInterfaces(@Injectable final SentryInterface mockSentryInterface) - throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - final Event event = eventBuilder.build(); - - event.getSentryInterfaces().put("interfaceName", mockSentryInterface); - } - - @Test - public void builtEventWithoutSentryInterfacesHasEmptySentryInterfaces() throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - - final Event event = eventBuilder.build(); - - assertThat(event.getSentryInterfaces().entrySet(), is(empty())); - } - - @Test - public void builtEventWithSentryInterfacesHasProperSentryInterfaces( - @Injectable("sentryInterfaceName") final String mockSentryInterfaceName, - @Injectable final SentryInterface mockSentryInterface) - throws Exception { - new NonStrictExpectations(){{ - mockSentryInterface.getInterfaceName(); - result = mockSentryInterfaceName; - }}; - final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.addSentryInterface(mockSentryInterface); - - final Event event = eventBuilder.build(); - - assertThat(event.getSentryInterfaces(), hasEntry(mockSentryInterfaceName, mockSentryInterface)); - assertThat(event.getSentryInterfaces().entrySet(), hasSize(1)); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void buildingTheEventTwiceFails() throws Exception { - final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.build(); - eventBuilder.build(); - } -} From 18cd99895420e017874861bce3d7ead7457abf53 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 19:58:21 +1100 Subject: [PATCH 0959/2152] Reset the hostname cache properly --- .../raven/event/EventBuilderHostnameCacheTest.java | 7 ++++++- .../java/net/kencochrane/raven/event/EventBuilderTest.java | 7 ++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java index 63efe8008d3..e289a8f5638 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java @@ -21,6 +21,11 @@ public class EventBuilderHostnameCacheTest { @Injectable private InetAddress mockTimingOutLocalHost; + private static void resetHostnameCache() { + setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp", 0l); + setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "hostname", EventBuilder.DEFAULT_HOSTNAME); + } + @BeforeMethod public void setUp() throws Exception { new NonStrictExpectations() {{ @@ -38,7 +43,7 @@ public String getCanonicalHostName() throws Exception { }; }}; // Clean Hostname Cache - setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp", 0l); + resetHostnameCache(); } @Test diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index 49d969090ec..4be4d85c6c0 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -19,6 +19,7 @@ public class EventBuilderTest { private static void resetHostnameCache() { setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp", 0l); + setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "hostname", EventBuilder.DEFAULT_HOSTNAME); } @Test @@ -238,6 +239,7 @@ public void builtEventWithTagsHasProperTags(@Injectable("tagKey") final String m @Test public void builtEventWithNoServerNameUsesDefaultIfSearchTimesOut() throws Exception { + resetHostnameCache(); new NonStrictExpectations(InetAddress.class) { @Injectable private InetAddress mockTimingOutLocalHost; @@ -258,7 +260,6 @@ public String getCanonicalHostName() throws Exception { }; final EventBuilder eventBuilder = new EventBuilder(); - resetHostnameCache(); final Event event = eventBuilder.build(); synchronized (this) { this.notify(); @@ -270,6 +271,7 @@ public String getCanonicalHostName() throws Exception { @Test public void builtEventWithNoServerNameUsesLocalHost(@Injectable("serverName") final String mockServerName) throws Exception { + resetHostnameCache(); new NonStrictExpectations(InetAddress.class) { @Injectable private InetAddress mockLocalHost; @@ -283,7 +285,6 @@ public void builtEventWithNoServerNameUsesLocalHost(@Injectable("serverName") fi }; final EventBuilder eventBuilder = new EventBuilder(); - resetHostnameCache(); final Event event = eventBuilder.build(); assertThat(event.getServerName(), is(mockServerName)); @@ -292,10 +293,10 @@ public void builtEventWithNoServerNameUsesLocalHost(@Injectable("serverName") fi @Test public void builtEventWithServerNameUsesProvidedServerName(@Injectable("serverName") final String mockServerName) throws Exception { + resetHostnameCache(); final EventBuilder eventBuilder = new EventBuilder(); eventBuilder.setServerName(mockServerName); - resetHostnameCache(); final Event event = eventBuilder.build(); assertThat(event.getServerName(), is(mockServerName)); From c954443135a5337a93cdcc97351e5099c71ccb7f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 20:28:34 +1100 Subject: [PATCH 0960/2152] Force type cast for Java6 compatibility --- .../raven/event/EventBuilderHostnameCacheTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java index e289a8f5638..e5d23db3d70 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java @@ -58,7 +58,7 @@ public void successfulHostnameRetrievalIsCachedForFiveHours( }}; new EventBuilder().build(); - final long expirationTime = getField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp"); + final long expirationTime = Deencapsulation.getField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp"); assertThat(expirationTime, is(TimeUnit.HOURS.toMillis(5) + System.currentTimeMillis())); } @@ -75,7 +75,7 @@ public void unsucessfulHostnameRetrievalIsCachedForOneSecond( new EventBuilder().build(); unlockTimingOutLocalHost(); - final long expirationTime = getField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp"); + final long expirationTime = Deencapsulation.getField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp"); assertThat(expirationTime, is(TimeUnit.SECONDS.toMillis(1) + System.currentTimeMillis())); } From 7ee912dbf928f1f55281483115ee4a932456e5c7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 10 Nov 2013 21:31:02 +1100 Subject: [PATCH 0961/2152] Improve tests for HttpEventBuilderHelper --- .../helper/HttpEventBuilderHelperTest.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java index e7904e15e0e..296c813a7f8 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java @@ -1,8 +1,8 @@ package net.kencochrane.raven.event.helper; -import mockit.Expectations; import mockit.Injectable; import mockit.Mocked; +import mockit.NonStrictExpectations; import mockit.Verifications; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.HttpInterface; @@ -17,10 +17,6 @@ public class HttpEventBuilderHelperTest { private HttpEventBuilderHelper httpEventBuilderHelper; @Injectable private EventBuilder mockEventBuilder; - @Mocked("getServletRequest") - private RavenServletRequestListener ravenServletRequestListener; - @Injectable - private HttpServletRequest mockHttpServletRequest; @Mocked private HttpInterface mockHttpInterface; @@ -31,21 +27,23 @@ public void setUp() throws Exception { @Test public void testNoRequest() throws Exception { - new Expectations() {{ + new NonStrictExpectations(RavenServletRequestListener.class) {{ RavenServletRequestListener.getServletRequest(); result = null; }}; httpEventBuilderHelper.helpBuildingEvent(mockEventBuilder); new Verifications() {{ - mockEventBuilder.addSentryInterface((SentryInterface) any); + new HttpInterface(withInstanceOf(HttpServletRequest.class)); + times = 0; + mockEventBuilder.addSentryInterface(withInstanceOf(SentryInterface.class)); times = 0; }}; } @Test - public void testWithRequest() throws Exception { - new Expectations() {{ + public void testWithRequest(@Injectable final HttpServletRequest mockHttpServletRequest) throws Exception { + new NonStrictExpectations(RavenServletRequestListener.class) {{ RavenServletRequestListener.getServletRequest(); result = mockHttpServletRequest; }}; @@ -53,6 +51,7 @@ public void testWithRequest() throws Exception { httpEventBuilderHelper.helpBuildingEvent(mockEventBuilder); new Verifications() {{ + new HttpInterface(mockHttpServletRequest); mockEventBuilder.addSentryInterface(this.withNotNull()); }}; } From cea3c8b10aebb1c6d9e8b5c4278af7dad14cd160 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 12 Nov 2013 08:43:32 +1100 Subject: [PATCH 0962/2152] Do not differenciate manually registered and automatically registered factories --- .../net/kencochrane/raven/RavenFactory.java | 64 ++++++------------- 1 file changed, 18 insertions(+), 46 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index 4a49460b7c3..c3bbe5dc127 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -1,12 +1,13 @@ package net.kencochrane.raven; +import com.google.common.collect.Iterables; import net.kencochrane.raven.dsn.Dsn; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.HashMap; -import java.util.Map; +import java.util.HashSet; import java.util.ServiceLoader; +import java.util.Set; /** * Factory in charge of creating {@link Raven} instances. @@ -16,7 +17,7 @@ */ public abstract class RavenFactory { private static final ServiceLoader AUTO_REGISTERED_FACTORIES = ServiceLoader.load(RavenFactory.class); - private static final Map MANUALLY_REGISTERED_FACTORIES = new HashMap(); + private static final Set MANUALLY_REGISTERED_FACTORIES = new HashSet(); private static final Logger logger = LoggerFactory.getLogger(RavenFactory.class); /** @@ -30,7 +31,11 @@ public abstract class RavenFactory { * @param ravenFactory ravenFactory to support. */ public static void registerFactory(RavenFactory ravenFactory) { - MANUALLY_REGISTERED_FACTORIES.put(ravenFactory.getClass().getName(), ravenFactory); + MANUALLY_REGISTERED_FACTORIES.add(ravenFactory); + } + + private static Iterable getRegisteredFactories() { + return Iterables.concat(MANUALLY_REGISTERED_FACTORIES, AUTO_REGISTERED_FACTORIES); } /** @@ -70,55 +75,22 @@ public static Raven ravenInstance(Dsn dsn) { * @return an instance of Raven. */ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { - Raven raven = ravenInstanceFromManualFactories(dsn, ravenFactoryName); - - if (raven == null) - raven = ravenInstanceFromAutoFactories(dsn, ravenFactoryName); - - if (raven != null) - return raven; - else - throw new IllegalStateException("Couldn't create a raven instance of '" + ravenFactoryName - + "' for '" + dsn + "'"); - } - - private static Raven ravenInstanceFromManualFactories(Dsn dsn, String ravenFactoryName) { - Raven raven = null; - if (ravenFactoryName != null && MANUALLY_REGISTERED_FACTORIES.containsKey(ravenFactoryName)) { - RavenFactory ravenFactory = MANUALLY_REGISTERED_FACTORIES.get(ravenFactoryName); - logger.info("Found an appropriate Raven factory for '{}': '{}'", ravenFactoryName, ravenFactory); - raven = ravenFactory.createRavenInstance(dsn); - if (raven == null) - logger.warn("The raven factory '{}' couldn't create an instance of Raven", ravenFactory); - } else if (ravenFactoryName == null) { - for (RavenFactory ravenFactory : MANUALLY_REGISTERED_FACTORIES.values()) { - logger.info("Found an available Raven factory: '{}'", ravenFactory); - raven = ravenFactory.createRavenInstance(dsn); - if (raven != null) { - break; - } else { - logger.warn("The raven factory '{}' couldn't create an instance of Raven", ravenFactory); - } - } - } - return raven; - } - - private static Raven ravenInstanceFromAutoFactories(Dsn dsn, String ravenFactoryName) { - Raven raven = null; - for (RavenFactory ravenFactory : AUTO_REGISTERED_FACTORIES) { + //Loop through registered factories + logger.info("Attempting to find a working Raven factory"); + for (RavenFactory ravenFactory : getRegisteredFactories()) { if (ravenFactoryName != null && !ravenFactoryName.equals(ravenFactory.getClass().getName())) continue; - logger.info("Found an appropriate Raven factory for '{}': '{}'", ravenFactoryName, ravenFactory); - raven = ravenFactory.createRavenInstance(dsn); + logger.debug("Attempting to use '{}' as a Raven factory.", ravenFactory); + Raven raven = ravenFactory.createRavenInstance(dsn); if (raven != null) { - break; + return raven; } else { - logger.warn("The raven factory '{}' couldn't create an instance of Raven", ravenFactory); + logger.debug("The raven factory '{}' couldn't create an instance of Raven", ravenFactory); } } - return raven; + + throw new IllegalStateException("Couldn't create a raven instance for '" + dsn + "'"); } /** From 0af7ab03c0c04d048d8f67a0393ee37fd4420afb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 12 Nov 2013 08:49:10 +1100 Subject: [PATCH 0963/2152] Do not expect a Null value but an exception is something went wrong --- .../java/net/kencochrane/raven/RavenFactory.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index c3bbe5dc127..3f82525484f 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -73,6 +73,7 @@ public static Raven ravenInstance(Dsn dsn) { * @param dsn Data Source Name of the Sentry server. * @param ravenFactoryName name of the raven factory to use to generate an instance of Raven. * @return an instance of Raven. + * @throws IllegalStateException when no instance of Raven has been created. */ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { //Loop through registered factories @@ -82,11 +83,10 @@ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { continue; logger.debug("Attempting to use '{}' as a Raven factory.", ravenFactory); - Raven raven = ravenFactory.createRavenInstance(dsn); - if (raven != null) { - return raven; - } else { - logger.debug("The raven factory '{}' couldn't create an instance of Raven", ravenFactory); + try{ + return ravenFactory.createRavenInstance(dsn); + } catch (RuntimeException e){ + logger.warn("The raven factory '{}' couldn't create an instance of Raven.", ravenFactory, e); } } @@ -97,7 +97,8 @@ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { * Creates an instance of Raven given a DSN. * * @param dsn Data Source Name of the Sentry server. - * @return an instance of Raven or {@code null} if it isn't possible to create one. + * @return an instance of Raven. + * @throws RuntimeException when an instance couldn't be created. */ public abstract Raven createRavenInstance(Dsn dsn); } From b9c7a25c37c7d6ccf2cf451d38ce0f35b7989f83 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 16 Nov 2013 20:50:39 +1100 Subject: [PATCH 0964/2152] Allow getConnection to be overridden --- .../java/net/kencochrane/raven/connection/HttpConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 16d2d2ea627..726a4cd5f42 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -92,7 +92,7 @@ public static URL getSentryApiUrl(URI sentryUri, String projectId) { } } - private HttpURLConnection getConnection() { + protected HttpURLConnection getConnection() { try { HttpURLConnection connection = (HttpURLConnection) sentryUrl.openConnection(); if (bypassSecurity && connection instanceof HttpsURLConnection) { From e87379239534eca02adc1a7432ebe1263a846b56 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 16 Nov 2013 21:32:49 +1100 Subject: [PATCH 0965/2152] Fix indentation --- raven/src/main/java/net/kencochrane/raven/RavenFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index 3f82525484f..f28abf249bb 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -83,9 +83,9 @@ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { continue; logger.debug("Attempting to use '{}' as a Raven factory.", ravenFactory); - try{ + try { return ravenFactory.createRavenInstance(dsn); - } catch (RuntimeException e){ + } catch (RuntimeException e) { logger.warn("The raven factory '{}' couldn't create an instance of Raven.", ravenFactory, e); } } From 3d2e9a0cf11eca7d1d6fded2a20ec1cbccab357e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 16 Nov 2013 21:34:16 +1100 Subject: [PATCH 0966/2152] Add missing documentation --- .../net/kencochrane/raven/connection/HttpConnection.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 726a4cd5f42..3f21449974e 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -92,6 +92,11 @@ public static URL getSentryApiUrl(URI sentryUri, String projectId) { } } + /** + * Opens a connection to the Sentry API allowing to send new events. + * + * @return an HTTP connection to Sentry. + */ protected HttpURLConnection getConnection() { try { HttpURLConnection connection = (HttpURLConnection) sentryUrl.openConnection(); From fd7e38f463a3b6ce6612a3dbee6b8afb1afa4b2d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 16 Nov 2013 20:51:38 +1100 Subject: [PATCH 0967/2152] Create a getSentry project for startcom certificates --- pom.xml | 1 + raven-getsentry/pom.xml | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 raven-getsentry/pom.xml diff --git a/pom.xml b/pom.xml index 6ee7facecf8..7eaf8b5166d 100644 --- a/pom.xml +++ b/pom.xml @@ -64,6 +64,7 @@ raven + raven-getsentry raven-log4j raven-logback raven-log4j2 diff --git a/raven-getsentry/pom.xml b/raven-getsentry/pom.xml new file mode 100644 index 00000000000..434324c6d99 --- /dev/null +++ b/raven-getsentry/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + + net.kencochrane.raven + raven-all + 4.1.2-SNAPSHOT + + + raven-getsentry + jar + + Raven-Java for getSentry + getSentry connector allowing to use the getSentry certificate. + + + + ${project.groupId} + raven + + + + com.googlecode.jmockit + jmockit + test + + + org.testng + testng + test + + + From 5e0d5268098718e401c32a920af78608c10c7425 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 16 Nov 2013 20:52:06 +1100 Subject: [PATCH 0968/2152] Add the certificate to the repository --- .../src/main/resources/startcom/ca.crt | Bin 0 -> 1997 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 raven-getsentry/src/main/resources/startcom/ca.crt diff --git a/raven-getsentry/src/main/resources/startcom/ca.crt b/raven-getsentry/src/main/resources/startcom/ca.crt new file mode 100644 index 0000000000000000000000000000000000000000..b60dea2484a21bf1e4bbe23e4d25380281637cf6 GIT binary patch literal 1997 zcmc(fX;c$e6vvs#f(b-0;RG95rcssRyv_aOHYse+7Iu;z3+eTyYIet zf3V)R9oAEh8%ZDlf&c(Bs5Bo#q+yf+02GQ1oabi;)2O7CWGV}ciuPl}PF91?V#MJx z440&M zzOsJ!Wy@XKc~!*6Syg*-P5xyTETy|e3~}cr-~460MMD3KsyKALR6G{gKX*u@ zJ|1eusta8X-U>VA&%Y&Xcrp0pq7PCVFH`gApW=nx+0h#Va(aR&u^)^M&xw#6>MAVk zYW?VGMvCaH@74`&JAQpE>YUZK_eN~kS^sWx%sso1CDN)(p2Bex+XZ`TeMe|ge@Cxp z2}hin2;9Y;JYGCD8Mio?vpVLx+m^$?d@OrBqMPM&K>LnBUEoBbC71TbaF{1h=N5Wd_joopviWQ-I;L$K&k4AFo6(i| ze*9!vQ{T=m^OH+!no@(!FX|2qeN(alFqM@r1a@r_@EoowJ=Y03nL4tMh_4NgATvp)ZANi->|=iD}& z6)OcvfDKTs2V;pJu@tr^e!VM;Oo2%NFbUgPEeA507`$V!tJRoEW|N(k+CAYMdMDwq zGg<_9h|(mTi;h`fg>~DVoCGJpzD6ITx4|Ai$pcGV#C4(59)uZ!)(v?$^mr19=>mO8tmQ7s_l4Z(Jl`KVtKr$S9?Zu{bViuLQ#t;b@ z#@+=0>%-gF!hwIg7-T-1_v-8j3?Yaa7yY^-rgMh?HNhRghOMd?R@#e3It!EY=#Rd4 zTpH+-+*9L0k?vg3G7$1xm^qx5U>miScTEX49j_eq*hdR3ozrU;7xAU%(ryJQ!;GG7 zmY|)k%j@;I`QP6B@cvJvcr98!W@DaBq}sIzhLI+^CxHB&)ZS3 zyfS2a@o>y8Wo{_x@vTbN%BRS(5o2dYm9SxGP%qgxh8`%+zAPz?!1@$oX2Yn5TVQe7 z`ux^)YtOV^3zr_vDl7T9&DTvjEBtFwtk(^CQdMC~@wc2T%O1zL1?G*Ll^J(TY2Fhu z!G+C70xwoD4Ts%Q6<<&V!dh)%+g^u{i;iv=EECB?OXTTDaTlB1%fo?P<5BHxT9O%L)|-<`HsToiY; z3k}R|b7?Je^dY*a>b3DjyB~CCU8#`B`(l1vKff2Lz)&*hJzBp5uxj3fd-Kff+SP(H zmuW!$p0jEj>cSI)OB{o4ck)M?A7wO`gI8#CX8f=+;HQF#fX$K=sN?5VQe^Ojs%74; kc?U%gDU+4lWBc>o{ta19uYuQ|9Nyxxk?|bcI Date: Sat, 16 Nov 2013 20:52:26 +1100 Subject: [PATCH 0969/2152] Create GetSentryHttpsConnection to open connections to getSentry --- .../connection/GetSentryHttpsConnection.java | 80 +++++++++++++++++++ .../GetSentryHttpsConnectionTest.java | 45 +++++++++++ 2 files changed, 125 insertions(+) create mode 100644 raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnection.java create mode 100644 raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionTest.java diff --git a/raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnection.java b/raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnection.java new file mode 100644 index 00000000000..e93a26cabd4 --- /dev/null +++ b/raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnection.java @@ -0,0 +1,80 @@ +package net.kencochrane.raven.getsentry.connection; + +import net.kencochrane.raven.connection.HttpConnection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManagerFactory; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; + +/** + * Connection to GetSentry.com using the StartCom certificate. + */ +public class GetSentryHttpsConnection extends HttpConnection { + private static final Logger logger = LoggerFactory.getLogger(GetSentryHttpsConnection.class); + private static final String GETSENTRY_API_URL = "https://app.getsentry.com/api/%s/store/"; + private static final String CERTIFICATE_PATH = "/startcom/ca.crt"; + private final SSLSocketFactory startcomSslFactory; + + /** + * Creates an HTTP connection to the GetSentry server. + * + * @param projectId identifier of the project. + * @param publicKey public key of the current project. + * @param secretKey private key of the current project. + */ + public GetSentryHttpsConnection(String projectId, String publicKey, String secretKey) { + super(getSentryUrl(projectId), publicKey, secretKey); + try { + this.startcomSslFactory = getStartcomSslFactory(); + } catch (Exception e) { + throw new IllegalStateException("Couldn't create an SSL Factory for StartCom", e); + } + } + + protected static URL getSentryUrl(String projectId) { + try { + return new URL(String.format(GETSENTRY_API_URL, projectId)); + } catch (MalformedURLException e) { + throw new RuntimeException("Couldn't create the URL for API of GetSentry", e); + } + } + + @Override + protected HttpsURLConnection getConnection() { + HttpsURLConnection connection = (HttpsURLConnection) super.getConnection(); + connection.setSSLSocketFactory(startcomSslFactory); + return connection; + } + + /** + * Create an SSLSocketFactory only able to handle certificates provided by StartCom. + * + * @return + * @throws Exception + */ + private SSLSocketFactory getStartcomSslFactory() throws Exception { + logger.debug("Loading the certificate from '{}'", CERTIFICATE_PATH); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate ca = cf.generateCertificate(GetSentryHttpsConnection.class.getResourceAsStream(CERTIFICATE_PATH)); + KeyStore ks = KeyStore.getInstance("jks"); + + ks.load(null, null); + ks.setCertificateEntry("ca", ca); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); + tmf.init(ks); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, tmf.getTrustManagers(), null); + + return sslContext.getSocketFactory(); + } +} diff --git a/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionTest.java b/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionTest.java new file mode 100644 index 00000000000..b861cb1321d --- /dev/null +++ b/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionTest.java @@ -0,0 +1,45 @@ +package net.kencochrane.raven.getsentry.connection; + +import mockit.Deencapsulation; +import mockit.Injectable; +import mockit.Tested; +import org.testng.annotations.Test; + +import javax.net.ssl.SSLHandshakeException; +import java.net.URL; +import java.net.URLConnection; + +import static org.testng.Assert.fail; + +public class GetSentryHttpsConnectionTest { + @Tested + private GetSentryHttpsConnection connection; + @Injectable("projectId") + private String projectId; + @Injectable("privateKey") + private String publicKey; + @Injectable("secretKey") + private String secretKey; + + @Test + public void ensureHttpsConnectionToGetSentryIsSecure() throws Exception { + connection.getConnection().connect(); + } + + @Test(expectedExceptions = SSLHandshakeException.class) + public void ensureHttpsConnectionToGoogleComIsNotSecure() throws Exception { + final URL url = new URL("https://www.google.com"); + Deencapsulation.setField(connection, "sentryUrl", url); + + connection.getConnection().connect(); + } + + @Test(expectedExceptions = SSLHandshakeException.class) + public void ensureHttpsConnectionToGetSentryRequiresCustomSslFactory() throws Exception { + final URLConnection httpsConnection = GetSentryHttpsConnection.getSentryUrl(projectId).openConnection(); + + httpsConnection.connect(); + + fail("Ensure that the StartCom certificate hasn't been added manually to the KeyStore or skip this test."); + } +} From 7cc577cf8653129e7b948f763b68fea2a5b3cbca Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 16 Nov 2013 20:55:02 +1100 Subject: [PATCH 0970/2152] Add RavenFactory capable of creating connections to getSentry --- .../getsentry/GetSentryRavenFactory.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/GetSentryRavenFactory.java diff --git a/raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/GetSentryRavenFactory.java b/raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/GetSentryRavenFactory.java new file mode 100644 index 00000000000..47b29ade0f0 --- /dev/null +++ b/raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/GetSentryRavenFactory.java @@ -0,0 +1,52 @@ +package net.kencochrane.raven.getsentry; + +import net.kencochrane.raven.DefaultRavenFactory; +import net.kencochrane.raven.connection.Connection; +import net.kencochrane.raven.dsn.Dsn; +import net.kencochrane.raven.getsentry.connection.GetSentryHttpsConnection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Raven factory capable of handling 'getsentry' scheme, using the HTTPS connection to GetSentry.com. + */ +public class GetSentryRavenFactory extends DefaultRavenFactory { + private static final Logger logger = LoggerFactory.getLogger(GetSentryRavenFactory.class); + + @Override + protected Connection createConnection(Dsn dsn) { + String protocol = dsn.getProtocol(); + Connection connection; + + if (protocol.equalsIgnoreCase("getsentry")) { + logger.info("Using an HTTP connection to Sentry."); + connection = createHttpConnection(dsn); + + // Enable async unless its value is 'false'. + if (!Boolean.FALSE.toString().equalsIgnoreCase(dsn.getOptions().get(ASYNC_OPTION))) { + connection = createAsyncConnection(dsn, connection); + } + } else { + connection = super.createConnection(dsn); + } + + return connection; + } + + /** + * Creates an HTTPS connection to the GetSentry server. + * + * @param dsn Data Source Name of the Sentry server. + * @return a {@link GetSentryHttpsConnection} to the server. + */ + protected Connection createHttpConnection(Dsn dsn) { + GetSentryHttpsConnection httpConnection = new GetSentryHttpsConnection(dsn.getProjectId(), dsn.getPublicKey(), + dsn.getSecretKey()); + httpConnection.setMarshaller(createMarshaller(dsn)); + + // Set the HTTP timeout + if (dsn.getOptions().containsKey(TIMEOUT_OPTION)) + httpConnection.setTimeout(Integer.parseInt(dsn.getOptions().get(TIMEOUT_OPTION))); + return httpConnection; + } +} From 78fab8a800eb94e78a11ec65aece89eb6d388073 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 16 Nov 2013 20:55:54 +1100 Subject: [PATCH 0971/2152] Add the custom raven factory for getSentry to the services --- .../META-INF.services/net.kencochrane.raven.RavenFactory | 1 + 1 file changed, 1 insertion(+) create mode 100644 raven-getsentry/src/main/resources/META-INF.services/net.kencochrane.raven.RavenFactory diff --git a/raven-getsentry/src/main/resources/META-INF.services/net.kencochrane.raven.RavenFactory b/raven-getsentry/src/main/resources/META-INF.services/net.kencochrane.raven.RavenFactory new file mode 100644 index 00000000000..2e3dd3bbd57 --- /dev/null +++ b/raven-getsentry/src/main/resources/META-INF.services/net.kencochrane.raven.RavenFactory @@ -0,0 +1 @@ +net.kencochrane.raven.getsentry.GetSentryRavenFactory From 59f98319c8a6193675a48f03480cb812e305de38 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 16 Nov 2013 21:11:34 +1100 Subject: [PATCH 0972/2152] Add notes for getSentry.com --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 01ada1fd97d..ab7e08d9958 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,17 @@ Raven supports both HTTP(S) and UDP as transport protocols to the Sentry instance. +## Notes on GetSentry.com + +Due to GetSentry.com using a certificate provided by [StartCom](https://www.startcom.org/) and StartCom not being +in the list of the [CAcerts of Java 6](http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/keytool.html#cacerts) +it isn't possible to establish an HTTPS connection to GetSentry.com out of the box. + +The said certificate [might be available out of the box with Java 8](https://forum.startcom.org/viewtopic.php?f=15&t=1815) +but in the mean time, you can use the [raven-getsentry](raven-getsentry) module which embeds the certificate and allows +SSL connections to GetSentry.com + + ## Sentry Protocol and Raven versions Since 2.0, the major version of raven matches the version of the Sentry protocol. From 7d12565cc12e2ac2f6e1438a4b6056cb2eafbd13 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 16 Nov 2013 21:31:19 +1100 Subject: [PATCH 0973/2152] Add documentation to the getSentry module --- raven-getsentry/README.md | 43 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 raven-getsentry/README.md diff --git a/raven-getsentry/README.md b/raven-getsentry/README.md new file mode 100644 index 00000000000..397a7edb0b6 --- /dev/null +++ b/raven-getsentry/README.md @@ -0,0 +1,43 @@ +# GetSentry (module) +Module enabling the support of the GetSentry.com SSL certificate. + +GetSentry.com SSL certificate is provided by StartCom which isn't included in the list of the default CAs in +Java 6 and 7. + +To work around that, this module embeds the certificate and uses it only for HTTPS connections to GetSentry.com + +## Installation + +### Maven +```xml + + net.kencochrane.raven + raven-getsentry + 4.1.1 + +``` + +### Other dependency managers +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1.1%7Cjar). + +### Manual dependency management +Relies on: + + - [raven-4.1.1.jar](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1.1%7Cjar) + +## Usage + +This module provides a new `RavenFactory` which supports the use of the 'getsentry://' scheme. +To use it, the DSN should look like this: + + getsentry://public:private@getsentry.com/1 + +The DSN supports the same options as those listed in the main README file. + +## Android + +As mentioned in the main README file, Android might require some additional configuration to use a custom `RavenFactory`. + +With the GetSentry module, the factory to register is `net.kencochrane.raven.getsentry.GetSentryRavenFactory`. + +Both factories can be registered at the same time without any problem. From bda8d6921ad292ce1cef64905e0d48178a20dc19 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 16 Nov 2013 21:49:04 +1100 Subject: [PATCH 0974/2152] Fix the documentation to mention that it's Oracle JDK specific --- README.md | 4 ++-- raven-getsentry/README.md | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ab7e08d9958..c95b2f86a1b 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,10 @@ Raven supports both HTTP(S) and UDP as transport protocols to the Sentry instance. -## Notes on GetSentry.com +## Notes on GetSentry.com and Oracle JDK Due to GetSentry.com using a certificate provided by [StartCom](https://www.startcom.org/) and StartCom not being -in the list of the [CAcerts of Java 6](http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/keytool.html#cacerts) +in the list of the [CAcerts of Oracle JDK 6](http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/keytool.html#cacerts) it isn't possible to establish an HTTPS connection to GetSentry.com out of the box. The said certificate [might be available out of the box with Java 8](https://forum.startcom.org/viewtopic.php?f=15&t=1815) diff --git a/raven-getsentry/README.md b/raven-getsentry/README.md index 397a7edb0b6..016d2d5e891 100644 --- a/raven-getsentry/README.md +++ b/raven-getsentry/README.md @@ -2,10 +2,12 @@ Module enabling the support of the GetSentry.com SSL certificate. GetSentry.com SSL certificate is provided by StartCom which isn't included in the list of the default CAs in -Java 6 and 7. +Oracle JDK 6 and 7. To work around that, this module embeds the certificate and uses it only for HTTPS connections to GetSentry.com +__This module is not useful with Open JDK.__ + ## Installation ### Maven @@ -23,7 +25,7 @@ Details in the [central Maven repository](https://search.maven.org/#artifactdeta ### Manual dependency management Relies on: - - [raven-4.1.1.jar](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1.1%7Cjar) + - [raven dependencies](../raven) ## Usage From b53185e4de5e6f95d1e15096ac12bd3239b36ec2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 16 Nov 2013 21:51:10 +1100 Subject: [PATCH 0975/2152] Disable the test on ensureHttpsConnectionToGetSentryRequiresCustomSslFactory --- .../getsentry/connection/GetSentryHttpsConnectionTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionTest.java b/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionTest.java index b861cb1321d..b8f7a2f59bd 100644 --- a/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionTest.java +++ b/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionTest.java @@ -34,7 +34,11 @@ public void ensureHttpsConnectionToGoogleComIsNotSecure() throws Exception { connection.getConnection().connect(); } - @Test(expectedExceptions = SSLHandshakeException.class) + /* + * Test disabled as it will fail when the CA is available in the default KeyStore. + * It is useful to be able to run it to ensure that it's indeed necessary to keep the entire module. + */ + @Test(expectedExceptions = SSLHandshakeException.class, enabled = false) public void ensureHttpsConnectionToGetSentryRequiresCustomSslFactory() throws Exception { final URLConnection httpsConnection = GetSentryHttpsConnection.getSentryUrl(projectId).openConnection(); From 06e1f055c23557458615ed40b3d677c7aed11d40 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 16 Nov 2013 21:54:29 +1100 Subject: [PATCH 0976/2152] Update Jackson to 2.3.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6ee7facecf8..e9f04add66a 100644 --- a/pom.xml +++ b/pom.xml @@ -107,7 +107,7 @@ 1.7.5 15.0 - 2.2.3 + 2.3.0 3.0.1 1.0.13 1.2.17 From fc0363ce9355250b9f29d3506cedfa8f1f62a55e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 13:22:56 +1100 Subject: [PATCH 0977/2152] Replace the JsonComparato with better test tools --- .../json/ExceptionInterfaceBindingTest.java | 19 +++-- .../raven/marshaller/json/JsonComparator.java | 61 -------------- .../raven/marshaller/json/JsonTestTool.java | 80 +++++++++++++++++++ .../json/MessageInterfaceBindingTest.java | 12 ++- .../json/StackTraceInterfaceBindingTest.java | 24 +++--- 5 files changed, 117 insertions(+), 79 deletions(-) delete mode 100644 raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonComparator.java create mode 100644 raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonTestTool.java diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index 98f02fbf346..2dadb84b764 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -8,11 +8,18 @@ import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.ImmutableThrowable; import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import org.hamcrest.MatcherAssert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.IOException; +import static net.kencochrane.raven.marshaller.json.JsonTestTool.JsonGeneratorTool; +import static net.kencochrane.raven.marshaller.json.JsonTestTool.jsonResource; +import static net.kencochrane.raven.marshaller.json.JsonTestTool.newJsonGenerator; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + public class ExceptionInterfaceBindingTest { private ExceptionInterfaceBinding interfaceBinding; @Injectable @@ -38,7 +45,7 @@ public void writeInterface(JsonGenerator jsonGenerator, StackTraceInterface stac @Test public void testSimpleException() throws Exception { - final JsonComparator jsonComparator = new JsonComparator(); + final JsonGeneratorTool generatorTool = newJsonGenerator(); final String message = "6e65f60d-9f22-495a-9556-7a61eeea2a14"; final Throwable throwable = new IllegalStateException(message); new NonStrictExpectations() {{ @@ -50,16 +57,16 @@ public ImmutableThrowable getThrowable() { }; }}; - interfaceBinding.writeInterface(jsonComparator.getGenerator(), mockExceptionInterface); + interfaceBinding.writeInterface(generatorTool.generator(), mockExceptionInterface); - jsonComparator.assertSameAsResource("/net/kencochrane/raven/marshaller/json/Exception1.json"); + assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/Exception1.json"))); } @Test public void testClassInDefaultPackage() throws Exception { Deencapsulation.setField((Object) DefaultPackageException.class, "name", DefaultPackageException.class.getSimpleName()); - final JsonComparator jsonComparator = new JsonComparator(); + final JsonGeneratorTool generatorTool = newJsonGenerator(); final Throwable throwable = new DefaultPackageException(); new NonStrictExpectations() {{ mockExceptionInterface.getThrowable(); @@ -70,9 +77,9 @@ public ImmutableThrowable getThrowable() { }; }}; - interfaceBinding.writeInterface(jsonComparator.getGenerator(), mockExceptionInterface); + interfaceBinding.writeInterface(generatorTool.generator(), mockExceptionInterface); - jsonComparator.assertSameAsResource("/net/kencochrane/raven/marshaller/json/Exception2.json"); + assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/Exception2.json"))); } } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonComparator.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonComparator.java deleted file mode 100644 index d2a92ac383a..00000000000 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonComparator.java +++ /dev/null @@ -1,61 +0,0 @@ -package net.kencochrane.raven.marshaller.json; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.StringWriter; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -public class JsonComparator { - private static final Logger logger = LoggerFactory.getLogger(JsonComparator.class); - private final StringWriter jsonOutput = new StringWriter(); - private final ObjectMapper objectMapper = new ObjectMapper(); - private final JsonGenerator jsonGenerator; - - public JsonComparator() throws IOException { - JsonFactory jsonFactory = new JsonFactory(); - jsonGenerator = jsonFactory.createGenerator(jsonOutput); - } - - public JsonGenerator getGenerator() { - return jsonGenerator; - } - - public void assertSameAsResource(String resource) throws IOException { - assertSameAs(JsonComparator.class.getResourceAsStream(resource)); - } - - public void assertSameAs(InputStream expected) throws IOException { - assertSame(objectMapper.readTree(expected)); - } - - public void assertSameAs(String expected) throws IOException { - assertSame(objectMapper.readTree(expected)); - } - - public void assertSameAs(File expected) throws IOException { - assertSame(objectMapper.readTree(expected)); - } - - public void assertSame(JsonNode jsonNode) throws IOException { - assertThat(objectMapper.readTree(getValue()), is(jsonNode)); - } - - public String getValue() { - try { - jsonGenerator.close(); - } catch (Exception e) { - logger.warn("An error occurred while closing the JSon Generator"); - } - return jsonOutput.toString(); - } -} diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonTestTool.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonTestTool.java new file mode 100644 index 00000000000..d6b07c9db4c --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonTestTool.java @@ -0,0 +1,80 @@ +package net.kencochrane.raven.marshaller.json; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Charsets; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.StringWriter; + +public final class JsonTestTool { + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private JsonTestTool() { + } + + public static JsonNode jsonResource(String resourcePath) throws Exception { + return objectMapper.readTree(JsonTestTool.class.getResourceAsStream(resourcePath)); + } + + public static JsonGeneratorTool newJsonGenerator() throws Exception { + return new JsonGeneratorTool(); + } + + public static JsonOutpuStreamTool newJsonOutputStream() throws Exception { + return new JsonOutpuStreamTool(); + } + + public static class JsonGeneratorTool { + private final JsonGenerator jsonGenerator; + private final StringWriter stringWriter = new StringWriter(); + + private JsonGeneratorTool() throws Exception { + jsonGenerator = new JsonFactory().createGenerator(stringWriter); + } + + public JsonGenerator generator() { + return jsonGenerator; + } + + public JsonNode value() throws Exception { + return objectMapper.readTree(toString()); + } + + @Override + public String toString() { + try { + jsonGenerator.close(); + stringWriter.close(); + return stringWriter.toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + public static class JsonOutpuStreamTool { + private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + public OutputStream outputStream() { + return outputStream; + } + + public JsonNode value() throws Exception { + return objectMapper.readTree(toString()); + } + + @Override + public String toString() { + try { + outputStream.close(); + return outputStream.toString(Charsets.UTF_8.name()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java index 7f2ea0ebf14..7fbfd17dbf7 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java @@ -9,6 +9,12 @@ import java.util.Arrays; import java.util.List; +import static net.kencochrane.raven.marshaller.json.JsonTestTool.JsonGeneratorTool; +import static net.kencochrane.raven.marshaller.json.JsonTestTool.jsonResource; +import static net.kencochrane.raven.marshaller.json.JsonTestTool.newJsonGenerator; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + public class MessageInterfaceBindingTest { private MessageInterfaceBinding interfaceBinding; @Injectable @@ -21,7 +27,7 @@ public void setUp() throws Exception { @Test public void testSimpleMessage() throws Exception { - final JsonComparator jsonComparator = new JsonComparator(); + final JsonGeneratorTool generatorTool = newJsonGenerator(); final String message = "550ee459-cbb5-438e-91d2-b0bbdefab670"; final List parameters = Arrays.asList("33ed929b-d803-46b6-a57b-9c0feab1f468", "5fc10379-6392-470d-9de5-e4cb805ab78c"); @@ -32,8 +38,8 @@ public void testSimpleMessage() throws Exception { result = parameters; }}; - interfaceBinding.writeInterface(jsonComparator.getGenerator(), mockMessageInterface); + interfaceBinding.writeInterface(generatorTool.generator(), mockMessageInterface); - jsonComparator.assertSameAsResource("/net/kencochrane/raven/marshaller/json/Message1.json"); + assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/Message1.json"))); } } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java index 21454725992..f30b79b375a 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java @@ -6,6 +6,12 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import static net.kencochrane.raven.marshaller.json.JsonTestTool.JsonGeneratorTool; +import static net.kencochrane.raven.marshaller.json.JsonTestTool.jsonResource; +import static net.kencochrane.raven.marshaller.json.JsonTestTool.newJsonGenerator; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + public class StackTraceInterfaceBindingTest { private StackTraceInterfaceBinding interfaceBinding; @Injectable @@ -18,7 +24,7 @@ public void setUp() throws Exception { @Test public void testSingleStackFrame() throws Exception { - final JsonComparator jsonComparator = new JsonComparator(); + final JsonGeneratorTool generatorTool = newJsonGenerator(); final String methodName = "0cce55c9-478f-4386-8ede-4b6f000da3e6"; final String className = "31b26f01-9b97-442b-9f36-8a317f94ad76"; final int lineNumber = 1; @@ -28,14 +34,14 @@ public void testSingleStackFrame() throws Exception { result = new StackTraceElement[]{stackTraceElement}; }}; - interfaceBinding.writeInterface(jsonComparator.getGenerator(), mockStackTraceInterface); + interfaceBinding.writeInterface(generatorTool.generator(), mockStackTraceInterface); - jsonComparator.assertSameAsResource("/net/kencochrane/raven/marshaller/json/StackTrace1.json"); + assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/StackTrace1.json"))); } @Test public void testFramesCommonWithEnclosing() throws Exception { - final JsonComparator jsonComparator = new JsonComparator(); + final JsonGeneratorTool generatorTool = newJsonGenerator(); final StackTraceElement stackTraceElement = new StackTraceElement("", "", null, 0); new NonStrictExpectations() {{ mockStackTraceInterface.getStackTrace(); @@ -45,14 +51,14 @@ public void testFramesCommonWithEnclosing() throws Exception { }}; interfaceBinding.setRemoveCommonFramesWithEnclosing(true); - interfaceBinding.writeInterface(jsonComparator.getGenerator(), mockStackTraceInterface); + interfaceBinding.writeInterface(generatorTool.generator(), mockStackTraceInterface); - jsonComparator.assertSameAsResource("/net/kencochrane/raven/marshaller/json/StackTrace2.json"); + assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/StackTrace2.json"))); } @Test public void testFramesCommonWithEnclosingDisabled() throws Exception { - final JsonComparator jsonComparator = new JsonComparator(); + final JsonGeneratorTool generatorTool = newJsonGenerator(); final StackTraceElement stackTraceElement = new StackTraceElement("", "", null, 0); new NonStrictExpectations() {{ mockStackTraceInterface.getStackTrace(); @@ -62,8 +68,8 @@ public void testFramesCommonWithEnclosingDisabled() throws Exception { }}; interfaceBinding.setRemoveCommonFramesWithEnclosing(false); - interfaceBinding.writeInterface(jsonComparator.getGenerator(), mockStackTraceInterface); + interfaceBinding.writeInterface(generatorTool.generator(), mockStackTraceInterface); - jsonComparator.assertSameAsResource("/net/kencochrane/raven/marshaller/json/StackTrace3.json"); + assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/StackTrace3.json"))); } } From 2b72e5ad3c964325a37ff6aa0b459549fdaa38d9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 15:02:35 +1100 Subject: [PATCH 0978/2152] Add test to ensure the event ID is marshalled properly --- .../marshaller/json/JsonMarshallerTest.java | 47 +++++++++++++++++++ .../json/jsonmarshallertest/testEventId.json | 13 +++++ 2 files changed, 60 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testEventId.json diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java new file mode 100644 index 00000000000..da3ec4d5253 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -0,0 +1,47 @@ +package net.kencochrane.raven.marshaller.json; + +import mockit.Injectable; +import mockit.NonStrictExpectations; +import net.kencochrane.raven.event.Event; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.Date; +import java.util.UUID; + +import static net.kencochrane.raven.marshaller.json.JsonTestTool.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class JsonMarshallerTest { + private final JsonMarshaller jsonMarshaller = new JsonMarshaller(); + @Injectable + private Event mockEvent; + + @BeforeMethod + public void setUp() throws Exception { + // Do not compress by default during the tests + jsonMarshaller.setCompression(false); + new NonStrictExpectations() {{ + mockEvent.getId(); + result = UUID.fromString("00000000-0000-0000-0000-000000000000"); + mockEvent.getTimestamp(); + result = new Date(0); + }}; + } + + @Test + public void testEventIdWrittenProperly(@Injectable final UUID mockUuid) throws Exception { + final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + new NonStrictExpectations() {{ + mockEvent.getId(); + result = mockUuid; + mockUuid.toString(); + result = "3b71fba5-413e-4022-ae98-5f0b80a155a5"; + }}; + + jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + + assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testEventId.json"))); + } +} diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testEventId.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testEventId.json new file mode 100644 index 00000000000..12dc219542a --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testEventId.json @@ -0,0 +1,13 @@ +{ + "event_id": "3b71fba5413e4022ae985f0b80a155a5", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null +} From 4a6f8b5ff46fd4ab2dd4da1fc26d8ca85b3a1e43 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 15:05:21 +1100 Subject: [PATCH 0979/2152] Ensure that the message is properly sent --- .../raven/marshaller/json/JsonMarshallerTest.java | 13 +++++++++++++ .../json/jsonmarshallertest/testMessage.json | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testMessage.json diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index da3ec4d5253..5829578d8bf 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -44,4 +44,17 @@ public void testEventIdWrittenProperly(@Injectable final UUID mockUuid) throws E assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testEventId.json"))); } + + @Test + public void testEventMessageWrittenProperly(@Injectable("message") final String mockMessage) throws Exception { + final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + new NonStrictExpectations() {{ + mockEvent.getMessage(); + result = mockMessage; + }}; + + jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + + assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testMessage.json"))); + } } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testMessage.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testMessage.json new file mode 100644 index 00000000000..e3c46802090 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testMessage.json @@ -0,0 +1,13 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": "message", + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null +} From fbdedea2a4c1adafc13612aa6a6193968da21d8f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 15:13:57 +1100 Subject: [PATCH 0980/2152] Add test to ensure the timestamp is formated properly --- .../marshaller/json/JsonMarshallerTest.java | 16 ++++++++++++++++ .../json/jsonmarshallertest/testTimestamp.json | 13 +++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTimestamp.json diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index 5829578d8bf..6b5958086bd 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -57,4 +57,20 @@ public void testEventMessageWrittenProperly(@Injectable("message") final String assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testMessage.json"))); } + + @Test + public void testEventTimestampWrittenProperly(@Injectable final Date mockTimestamp) throws Exception { + final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + new NonStrictExpectations() {{ + mockEvent.getTimestamp(); + result = mockTimestamp; + mockTimestamp.getTime(); + // 2013-11-24T04:11:35.338 (UTC) + result = 1385266295338L; + }}; + + jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + + assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTimestamp.json"))); + } } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTimestamp.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTimestamp.json new file mode 100644 index 00000000000..d48f27557dd --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTimestamp.json @@ -0,0 +1,13 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "2013-11-24T04:11:35", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null +} From 5ed38430adc4137c7418dfc33bb2bf7b093f08d7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 15:39:09 +1100 Subject: [PATCH 0981/2152] Ensure that the log levels are properly marshalled --- .../marshaller/json/JsonMarshallerTest.java | 25 +++++++++++++++++++ .../jsonmarshallertest/testLevelDebug.json | 13 ++++++++++ .../jsonmarshallertest/testLevelError.json | 13 ++++++++++ .../jsonmarshallertest/testLevelFatal.json | 13 ++++++++++ .../jsonmarshallertest/testLevelInfo.json | 13 ++++++++++ .../jsonmarshallertest/testLevelWarning.json | 13 ++++++++++ 6 files changed, 90 insertions(+) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelError.json create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index 6b5958086bd..26a9a66ebbd 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -4,6 +4,7 @@ import mockit.NonStrictExpectations; import net.kencochrane.raven.event.Event; import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.Date; @@ -73,4 +74,28 @@ public void testEventTimestampWrittenProperly(@Injectable final Date mockTimesta assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTimestamp.json"))); } + + @DataProvider(name = "levelProvider") + public Object[][] levelProvider() { + return new Object[][]{ + {Event.Level.DEBUG, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json"}, + {Event.Level.INFO, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json"}, + {Event.Level.WARNING, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json"}, + {Event.Level.ERROR, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelError.json"}, + {Event.Level.FATAL, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json"}, + }; + } + + @Test(dataProvider = "levelProvider") + public void testEventLevelWrittenProperly(final Event.Level eventLevel, String levelFile) throws Exception { + final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + new NonStrictExpectations() {{ + mockEvent.getLevel(); + result = eventLevel; + }}; + + jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + + assertThat(outpuStreamTool.value(), is(jsonResource(levelFile))); + } } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json new file mode 100644 index 00000000000..b957c2bc557 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json @@ -0,0 +1,13 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": "debug", + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null +} diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelError.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelError.json new file mode 100644 index 00000000000..a80f1726bd0 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelError.json @@ -0,0 +1,13 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": "error", + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null +} diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json new file mode 100644 index 00000000000..9ed6d5373b5 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json @@ -0,0 +1,13 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": "fatal", + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null +} diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json new file mode 100644 index 00000000000..138a4ac379f --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json @@ -0,0 +1,13 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": "info", + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null +} diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json new file mode 100644 index 00000000000..386ff2cdcf3 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json @@ -0,0 +1,13 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": "warning", + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null +} From 7fc8df88abfb06629446d2a870e510432918a4c4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 15:42:35 +1100 Subject: [PATCH 0982/2152] Ensure that the logger is properly marshalled --- .../raven/marshaller/json/JsonMarshallerTest.java | 13 +++++++++++++ .../json/jsonmarshallertest/testLogger.json | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLogger.json diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index 26a9a66ebbd..f08850cef3f 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -98,4 +98,17 @@ public void testEventLevelWrittenProperly(final Event.Level eventLevel, String l assertThat(outpuStreamTool.value(), is(jsonResource(levelFile))); } + + @Test + public void testEventLoggerWrittenProperly(@Injectable("logger") final String mockLogger) throws Exception { + final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + new NonStrictExpectations() {{ + mockEvent.getLogger(); + result = mockLogger; + }}; + + jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + + assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLogger.json"))); + } } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLogger.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLogger.json new file mode 100644 index 00000000000..b8e66a11fde --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLogger.json @@ -0,0 +1,13 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": "logger", + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null +} From 395f6793cbdfaad1e73173a5d40c859b399b71f2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 16:00:37 +1100 Subject: [PATCH 0983/2152] Ensure logger is properly marshalled --- .../raven/marshaller/json/JsonMarshallerTest.java | 13 +++++++++++++ .../json/jsonmarshallertest/testPlatform.json | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testPlatform.json diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index f08850cef3f..a61e4a7eae1 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -111,4 +111,17 @@ public void testEventLoggerWrittenProperly(@Injectable("logger") final String mo assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLogger.json"))); } + + @Test + public void testEventPlaftormWrittenProperly(@Injectable("platform") final String mockPlatform) throws Exception { + final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + new NonStrictExpectations() {{ + mockEvent.getPlatform(); + result = mockPlatform; + }}; + + jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + + assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testPlatform.json"))); + } } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testPlatform.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testPlatform.json new file mode 100644 index 00000000000..d19e5bafc71 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testPlatform.json @@ -0,0 +1,13 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": "platform", + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null +} From db5ff947a1fd464b49a168c1c18382677c8d4fbf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 16:03:10 +1100 Subject: [PATCH 0984/2152] Ensure that the culprit is properly marshalled --- .../raven/marshaller/json/JsonMarshallerTest.java | 13 +++++++++++++ .../json/jsonmarshallertest/testCulprit.json | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testCulprit.json diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index a61e4a7eae1..57fb34c8627 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -124,4 +124,17 @@ public void testEventPlaftormWrittenProperly(@Injectable("platform") final Strin assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testPlatform.json"))); } + + @Test + public void testEventCulpritWrittenProperly(@Injectable("culprit") final String mockCulprit) throws Exception { + final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + new NonStrictExpectations() {{ + mockEvent.getCulprit(); + result = mockCulprit; + }}; + + jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + + assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testCulprit.json"))); + } } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testCulprit.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testCulprit.json new file mode 100644 index 00000000000..95fa489f9c5 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testCulprit.json @@ -0,0 +1,13 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": "culprit", + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null +} From f697ac8043f59a858c7cac2a3e3e820182b5c4ca Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 16:08:19 +1100 Subject: [PATCH 0985/2152] Ensure that tags are properly marshalled --- .../raven/marshaller/json/JsonMarshallerTest.java | 15 +++++++++++++++ .../json/jsonmarshallertest/testTags.json | 15 +++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTags.json diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index 57fb34c8627..7475b60d43a 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -7,6 +7,7 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.util.Collections; import java.util.Date; import java.util.UUID; @@ -137,4 +138,18 @@ public void testEventCulpritWrittenProperly(@Injectable("culprit") final String assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testCulprit.json"))); } + + @Test + public void testEventTagsWrittenProperly(@Injectable("tagName") final String mockTagName, + @Injectable("tagValue") final String mockTagValue) throws Exception { + final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + new NonStrictExpectations() {{ + mockEvent.getTags(); + result = Collections.singletonMap(mockTagName, mockTagValue); + }}; + + jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + + assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTags.json"))); + } } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTags.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTags.json new file mode 100644 index 00000000000..65ffa6b56ae --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTags.json @@ -0,0 +1,15 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": { + "tagName": "tagValue" + }, + "server_name": null, + "extra": {}, + "checksum": null +} From 69065144811a41672a57ff096e7032e1da30036f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 16:17:59 +1100 Subject: [PATCH 0986/2152] Ensure the serverName is properly marshalled --- .../raven/marshaller/json/JsonMarshallerTest.java | 13 +++++++++++++ .../json/jsonmarshallertest/testServerName.json | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testServerName.json diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index 7475b60d43a..d9c859aa6db 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -152,4 +152,17 @@ public void testEventTagsWrittenProperly(@Injectable("tagName") final String moc assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTags.json"))); } + + @Test + public void testEventServerNameWrittenProperly(@Injectable("serverName") final String mockServerName) throws Exception { + final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + new NonStrictExpectations() {{ + mockEvent.getServerName(); + result = mockServerName; + }}; + + jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + + assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testServerName.json"))); + } } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testServerName.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testServerName.json new file mode 100644 index 00000000000..6eee76a9e55 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testServerName.json @@ -0,0 +1,13 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": "serverName", + "extra": {}, + "checksum": null +} From 6be20d3fdec1d7852267ea4ec5283e142367fd33 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 16:29:22 +1100 Subject: [PATCH 0987/2152] Ensure that the checksum is marshalled properly --- .../raven/marshaller/json/JsonMarshallerTest.java | 13 +++++++++++++ .../json/jsonmarshallertest/testChecksum.json | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testChecksum.json diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index d9c859aa6db..14dc27db6d4 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -165,4 +165,17 @@ public void testEventServerNameWrittenProperly(@Injectable("serverName") final S assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testServerName.json"))); } + + @Test + public void testEventChecksumWrittenProperly(@Injectable("1234567890abcdef") final String mockChecksum) throws Exception { + final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + new NonStrictExpectations() {{ + mockEvent.getChecksum(); + result = mockChecksum; + }}; + + jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + + assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testChecksum.json"))); + } } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testChecksum.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testChecksum.json new file mode 100644 index 00000000000..e49263b8ea5 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testChecksum.json @@ -0,0 +1,13 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": "1234567890abcdef" +} From f7007b7bb3ef96650b4727e16dd1cb4b67d6d54d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 17:04:52 +1100 Subject: [PATCH 0988/2152] Ensure that extra values are properly handled --- .../marshaller/json/JsonMarshallerTest.java | 25 +++++++++++++++++++ .../jsonmarshallertest/testExtraArray.json | 19 ++++++++++++++ .../jsonmarshallertest/testExtraBoolean.json | 15 +++++++++++ .../jsonmarshallertest/testExtraIterable.json | 19 ++++++++++++++ .../jsonmarshallertest/testExtraNumber.json | 15 +++++++++++ .../jsonmarshallertest/testExtraString.json | 15 +++++++++++ 6 files changed, 108 insertions(+) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraString.json diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index 14dc27db6d4..1c2dede3f4d 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -7,6 +7,7 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.UUID; @@ -178,4 +179,28 @@ public void testEventChecksumWrittenProperly(@Injectable("1234567890abcdef") fin assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testChecksum.json"))); } + + @DataProvider(name = "extraProvider") + public Object[][] extraProvider() { + return new Object[][]{ + {"key", "string", "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraString.json"}, + {"key", 1, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json"}, + {"key", true, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json"}, + {"key", new Object[]{"string", 1, true}, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json"}, + {"key", Arrays.asList(true, 1, "string"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json"} + }; + } + + @Test(dataProvider = "extraProvider") + public void testEventStringExtraWrittenProperly(final String mockExtraKey, final Object mockExtraValue, String extraFile) throws Exception { + final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + new NonStrictExpectations() {{ + mockEvent.getExtra(); + result = Collections.singletonMap(mockExtraKey, mockExtraValue); + }}; + + jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + + assertThat(outpuStreamTool.value(), is(jsonResource(extraFile))); + } } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json new file mode 100644 index 00000000000..b49ef9334fe --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json @@ -0,0 +1,19 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": [ + "string", + 1, + true + ] + }, + "checksum": null +} diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json new file mode 100644 index 00000000000..c42ccc0a567 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json @@ -0,0 +1,15 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": true + }, + "checksum": null +} diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json new file mode 100644 index 00000000000..142a6a4e15a --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json @@ -0,0 +1,19 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": [ + true, + 1, + "string" + ] + }, + "checksum": null +} diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json new file mode 100644 index 00000000000..85f9bc51b13 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json @@ -0,0 +1,15 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": 1 + }, + "checksum": null +} diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraString.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraString.json new file mode 100644 index 00000000000..e07330cc8f5 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraString.json @@ -0,0 +1,15 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": "string" + }, + "checksum": null +} From 5264d226e8bcc9448bc4f09add45270fa2e73246 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 17:08:35 +1100 Subject: [PATCH 0989/2152] Check that a null extra value is properly handled by the json marshaller --- .../raven/marshaller/json/JsonMarshaller.java | 24 +++++++++++-------- .../marshaller/json/JsonMarshallerTest.java | 1 + .../jsonmarshallertest/testExtraNull.json | 15 ++++++++++++ 3 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNull.json diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index d8df5602f2f..a26702ba495 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -156,17 +156,21 @@ private void writeExtras(JsonGenerator generator, Map extras) th generator.writeObjectFieldStart(EXTRA); for (Map.Entry extra : extras.entrySet()) { Object value = extra.getValue(); - if (value.getClass().isArray()) { - value = Arrays.asList((Object[]) value); - } - if (value instanceof Iterable) { - generator.writeArrayFieldStart(extra.getKey()); - for (Object subValue : (Iterable) value) { - generator.writeObject(subValue); - } - generator.writeEndArray(); + if (value == null) { + generator.writeNullField(extra.getKey()); } else { - generator.writeObjectField(extra.getKey(), extra.getValue()); + if (value.getClass().isArray()) { + value = Arrays.asList((Object[]) value); + } + if (value instanceof Iterable) { + generator.writeArrayFieldStart(extra.getKey()); + for (Object subValue : (Iterable) value) { + generator.writeObject(subValue); + } + generator.writeEndArray(); + } else { + generator.writeObjectField(extra.getKey(), extra.getValue()); + } } } generator.writeEndObject(); diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index 1c2dede3f4d..fc359fcb05b 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -186,6 +186,7 @@ public Object[][] extraProvider() { {"key", "string", "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraString.json"}, {"key", 1, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json"}, {"key", true, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json"}, + {"key", null, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNull.json"}, {"key", new Object[]{"string", 1, true}, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json"}, {"key", Arrays.asList(true, 1, "string"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json"} }; diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNull.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNull.json new file mode 100644 index 00000000000..5990313ad72 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNull.json @@ -0,0 +1,15 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": null + }, + "checksum": null +} From a57071433c1dd1f8a6dd8dc4d4704cffd276a11e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 17:23:31 +1100 Subject: [PATCH 0990/2152] Extract the writeObject method from writeExtras --- .../raven/marshaller/json/JsonMarshaller.java | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index a26702ba495..a0403330e13 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -155,27 +155,30 @@ private void writeInterfaces(JsonGenerator generator, Map extras) throws IOException { generator.writeObjectFieldStart(EXTRA); for (Map.Entry extra : extras.entrySet()) { - Object value = extra.getValue(); - if (value == null) { - generator.writeNullField(extra.getKey()); - } else { - if (value.getClass().isArray()) { - value = Arrays.asList((Object[]) value); - } - if (value instanceof Iterable) { - generator.writeArrayFieldStart(extra.getKey()); - for (Object subValue : (Iterable) value) { - generator.writeObject(subValue); - } - generator.writeEndArray(); - } else { - generator.writeObjectField(extra.getKey(), extra.getValue()); - } - } + generator.writeFieldName(extra.getKey()); + safelyWriteObject(generator, extra.getValue()); } generator.writeEndObject(); } + private void safelyWriteObject(JsonGenerator generator, Object value) throws IOException { + if (value != null && value.getClass().isArray()) { + value = Arrays.asList((Object[]) value); + } + + if (value instanceof Iterable) { + generator.writeStartArray(); + for (Object subValue : (Iterable) value) { + generator.writeObject(subValue); + } + generator.writeEndArray(); + } else if (value == null) { + generator.writeNull(); + } else { + generator.writeObject(value); + } + } + private void writeTags(JsonGenerator generator, Map tags) throws IOException { generator.writeObjectFieldStart(TAGS); for (Map.Entry tag : tags.entrySet()) { From 54703feee04b52ff7d8e43fe71fc16d017b74651 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 17:30:14 +1100 Subject: [PATCH 0991/2152] Ensure that maps are properly marshalled --- .../raven/marshaller/json/JsonMarshaller.java | 5 +++++ .../marshaller/json/JsonMarshallerTest.java | 3 ++- .../json/jsonmarshallertest/testExtraMap.json | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraMap.json diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index a0403330e13..755daed4aa8 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -172,6 +172,11 @@ private void safelyWriteObject(JsonGenerator generator, Object value) throws IOE generator.writeObject(subValue); } generator.writeEndArray(); + } else if (value instanceof Map) { + generator.writeStartObject(); + for (Map.Entry entry : ((Map) value).entrySet()) + generator.writeObjectField(entry.getKey().toString(), entry.getValue()); + generator.writeEndObject(); } else if (value == null) { generator.writeNull(); } else { diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index fc359fcb05b..9dd125bed8e 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -188,7 +188,8 @@ public Object[][] extraProvider() { {"key", true, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json"}, {"key", null, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNull.json"}, {"key", new Object[]{"string", 1, true}, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json"}, - {"key", Arrays.asList(true, 1, "string"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json"} + {"key", Arrays.asList(true, 1, "string"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json"}, + {"key", Collections.singletonMap("key", "value"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraMap.json"} }; } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraMap.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraMap.json new file mode 100644 index 00000000000..1792e5e9f4f --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraMap.json @@ -0,0 +1,17 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": { + "key": "value" + } + }, + "checksum": null +} From 1f869a6effe57bbda754eefc832bddd7a0278f51 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 17:34:06 +1100 Subject: [PATCH 0992/2152] Ensure that an object key in the extra map is fine --- .../marshaller/json/JsonMarshallerTest.java | 3 ++- .../testExtraObjectKeyMap.json | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index 9dd125bed8e..35e80ffaab1 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -189,7 +189,8 @@ public Object[][] extraProvider() { {"key", null, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNull.json"}, {"key", new Object[]{"string", 1, true}, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json"}, {"key", Arrays.asList(true, 1, "string"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json"}, - {"key", Collections.singletonMap("key", "value"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraMap.json"} + {"key", Collections.singletonMap("key", "value"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraMap.json"}, + {"key", Collections.singletonMap(true, "value"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json"} }; } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json new file mode 100644 index 00000000000..5d4822a345e --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json @@ -0,0 +1,17 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": { + "true": "value" + } + }, + "checksum": null +} From c924e23180cc2e010788ad7d183b1a503b73167c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 17:38:52 +1100 Subject: [PATCH 0993/2152] Ensure that an null key in the extra map is fine --- .../raven/marshaller/json/JsonMarshaller.java | 9 +++++++-- .../marshaller/json/JsonMarshallerTest.java | 3 ++- .../jsonmarshallertest/testExtraNullKeyMap.json | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 755daed4aa8..68fd2f84a09 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -174,8 +174,13 @@ private void safelyWriteObject(JsonGenerator generator, Object value) throws IOE generator.writeEndArray(); } else if (value instanceof Map) { generator.writeStartObject(); - for (Map.Entry entry : ((Map) value).entrySet()) - generator.writeObjectField(entry.getKey().toString(), entry.getValue()); + for (Map.Entry entry : ((Map) value).entrySet()) { + if (entry.getKey() == null) + generator.writeFieldName("null"); + else + generator.writeFieldName(entry.getKey().toString()); + generator.writeObject(entry.getValue()); + } generator.writeEndObject(); } else if (value == null) { generator.writeNull(); diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index 35e80ffaab1..c0ef5bc341d 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -190,7 +190,8 @@ public Object[][] extraProvider() { {"key", new Object[]{"string", 1, true}, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json"}, {"key", Arrays.asList(true, 1, "string"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json"}, {"key", Collections.singletonMap("key", "value"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraMap.json"}, - {"key", Collections.singletonMap(true, "value"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json"} + {"key", Collections.singletonMap(true, "value"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json"}, + {"key", Collections.singletonMap(null, "value"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json"} }; } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json new file mode 100644 index 00000000000..0fb3d9f6d5c --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json @@ -0,0 +1,17 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": { + "null": "value" + } + }, + "checksum": null +} From febf0d7d707e76e2d89a791d07d392fb03ab8ff3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 17:42:13 +1100 Subject: [PATCH 0994/2152] Ensure that null values are properly converted --- .../kencochrane/raven/marshaller/json/JsonMarshallerTest.java | 4 ++-- .../marshaller/json/jsonmarshallertest/testExtraArray.json | 1 + .../marshaller/json/jsonmarshallertest/testExtraIterable.json | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index c0ef5bc341d..58080fdef57 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -187,8 +187,8 @@ public Object[][] extraProvider() { {"key", 1, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json"}, {"key", true, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json"}, {"key", null, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNull.json"}, - {"key", new Object[]{"string", 1, true}, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json"}, - {"key", Arrays.asList(true, 1, "string"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json"}, + {"key", new Object[]{"string", 1, null, true}, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json"}, + {"key", Arrays.asList(true, null, 1, "string"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json"}, {"key", Collections.singletonMap("key", "value"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraMap.json"}, {"key", Collections.singletonMap(true, "value"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json"}, {"key", Collections.singletonMap(null, "value"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json"} diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json index b49ef9334fe..2cab8733672 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json @@ -12,6 +12,7 @@ "key": [ "string", 1, + null, true ] }, diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json index 142a6a4e15a..67afbe0df4b 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json @@ -11,6 +11,7 @@ "extra": { "key": [ true, + null, 1, "string" ] From d49002a3bcc19ed6ad765fa4b422101a5f2c550a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 17:43:26 +1100 Subject: [PATCH 0995/2152] Avoid redundant check --- .../net/kencochrane/raven/marshaller/json/JsonMarshaller.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 68fd2f84a09..5801f59ce01 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -182,8 +182,6 @@ private void safelyWriteObject(JsonGenerator generator, Object value) throws IOE generator.writeObject(entry.getValue()); } generator.writeEndObject(); - } else if (value == null) { - generator.writeNull(); } else { generator.writeObject(value); } From 4704cb01112d4528de90d82828de14b8a129a6e3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 17:44:33 +1100 Subject: [PATCH 0996/2152] Ensure that maps are handled recursively --- .../raven/marshaller/json/JsonMarshaller.java | 2 +- .../marshaller/json/JsonMarshallerTest.java | 3 ++- .../testExtraRecursiveMap.json | 22 +++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 5801f59ce01..6ba2ac5904c 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -179,7 +179,7 @@ private void safelyWriteObject(JsonGenerator generator, Object value) throws IOE generator.writeFieldName("null"); else generator.writeFieldName(entry.getKey().toString()); - generator.writeObject(entry.getValue()); + safelyWriteObject(generator, entry.getValue()); } generator.writeEndObject(); } else { diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index 58080fdef57..8e5ac437be0 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -191,7 +191,8 @@ public Object[][] extraProvider() { {"key", Arrays.asList(true, null, 1, "string"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json"}, {"key", Collections.singletonMap("key", "value"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraMap.json"}, {"key", Collections.singletonMap(true, "value"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json"}, - {"key", Collections.singletonMap(null, "value"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json"} + {"key", Collections.singletonMap(null, "value"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json"}, + {"key", Collections.singletonMap("key", Arrays.asList("string", 1, true, null)), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json"} }; } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json new file mode 100644 index 00000000000..9549e8d72a7 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json @@ -0,0 +1,22 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": { + "key": [ + "string", + 1, + true, + null + ] + } + }, + "checksum": null +} From 8c889403af2ee27048e70ee33ded678e302e5ae6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 17:46:44 +1100 Subject: [PATCH 0997/2152] Ensure that arrays/iterables are handled recursively --- .../raven/marshaller/json/JsonMarshaller.java | 2 +- .../marshaller/json/JsonMarshallerTest.java | 1 + .../testExtraRecursiveArray.json | 22 +++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 6ba2ac5904c..6e31397bae7 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -169,7 +169,7 @@ private void safelyWriteObject(JsonGenerator generator, Object value) throws IOE if (value instanceof Iterable) { generator.writeStartArray(); for (Object subValue : (Iterable) value) { - generator.writeObject(subValue); + safelyWriteObject(generator, subValue); } generator.writeEndArray(); } else if (value instanceof Map) { diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index 8e5ac437be0..203e4f23b61 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -188,6 +188,7 @@ public Object[][] extraProvider() { {"key", true, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json"}, {"key", null, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNull.json"}, {"key", new Object[]{"string", 1, null, true}, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json"}, + {"key", new Object[]{new Object[]{"string", 1, null, true}}, "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json"}, {"key", Arrays.asList(true, null, 1, "string"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json"}, {"key", Collections.singletonMap("key", "value"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraMap.json"}, {"key", Collections.singletonMap(true, "value"), "/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json"}, diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json new file mode 100644 index 00000000000..f8cdb89b7b6 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json @@ -0,0 +1,22 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": [ + [ + "string", + 1, + null, + true + ] + ] + }, + "checksum": null +} From 3143a3d6977bc0c4fd8deb97d1d298d1b64876be Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 17:48:44 +1100 Subject: [PATCH 0998/2152] Fix the test method/parameters name for extra tests --- .../kencochrane/raven/marshaller/json/JsonMarshallerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index 203e4f23b61..97f59918d64 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -198,11 +198,11 @@ public Object[][] extraProvider() { } @Test(dataProvider = "extraProvider") - public void testEventStringExtraWrittenProperly(final String mockExtraKey, final Object mockExtraValue, String extraFile) throws Exception { + public void testEventExtraWrittenProperly(final String extraKey, final Object extraValue, String extraFile) throws Exception { final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getExtra(); - result = Collections.singletonMap(mockExtraKey, mockExtraValue); + result = Collections.singletonMap(extraKey, extraValue); }}; jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); From c60b48735734b0b7fe850adb87de646c264a5990 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 17:56:37 +1100 Subject: [PATCH 0999/2152] Ensure that non handled values are transformed in strings --- .../raven/marshaller/json/JsonMarshaller.java | 6 +++++- .../marshaller/json/JsonMarshallerTest.java | 16 ++++++++++++++++ .../jsonmarshallertest/testExtraCustomValue.json | 15 +++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 6e31397bae7..af58c83f2e6 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -183,7 +183,11 @@ private void safelyWriteObject(JsonGenerator generator, Object value) throws IOE } generator.writeEndObject(); } else { - generator.writeObject(value); + try { + generator.writeObject(value); + } catch (IllegalStateException e) { + generator.writeString(value.toString()); + } } } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index 97f59918d64..8c1ee58174b 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -209,4 +209,20 @@ public void testEventExtraWrittenProperly(final String extraKey, final Object ex assertThat(outpuStreamTool.value(), is(jsonResource(extraFile))); } + + @Test + public void testEventExtraWrittenProperly(@Injectable("key") final String mockExtraKey, + @Injectable final Object mockExtraValue) throws Exception { + final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + new NonStrictExpectations() {{ + mockEvent.getExtra(); + result = Collections.singletonMap(mockExtraKey, mockExtraValue); + mockExtraValue.toString(); + result = "test"; + }}; + + jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + + assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json"))); + } } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json new file mode 100644 index 00000000000..2cda2e99a79 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json @@ -0,0 +1,15 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": "test" + }, + "checksum": null +} From fd41986bb74fa9a9b5b3f711058d3a61c30e01c2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 17:58:00 +1100 Subject: [PATCH 1000/2152] Ensure that the null value is handled independently --- .../net/kencochrane/raven/marshaller/json/JsonMarshaller.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index af58c83f2e6..a9c72f51310 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -182,6 +182,8 @@ private void safelyWriteObject(JsonGenerator generator, Object value) throws IOE safelyWriteObject(generator, entry.getValue()); } generator.writeEndObject(); + } else if (value == null) { + generator.writeNull(); } else { try { generator.writeObject(value); From 1a97eda7f31ef08021722e7f165d6689e5327432 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 18:01:42 +1100 Subject: [PATCH 1001/2152] Add a log when an extra had to be converted to a String --- .../net/kencochrane/raven/marshaller/json/JsonMarshaller.java | 1 + 1 file changed, 1 insertion(+) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index a9c72f51310..12ff853ca68 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -188,6 +188,7 @@ private void safelyWriteObject(JsonGenerator generator, Object value) throws IOE try { generator.writeObject(value); } catch (IllegalStateException e) { + logger.debug("Couldn't marshal '{}' of type '{}', had to be converted into a String", value, value.getClass()); generator.writeString(value.toString()); } } From 41d35d7e65c29119c5be7895e1b0144b264c6c77 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 18:07:06 +1100 Subject: [PATCH 1002/2152] Fix line length --- .../net/kencochrane/raven/marshaller/json/JsonMarshaller.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 12ff853ca68..ec379fba7cc 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -188,7 +188,8 @@ private void safelyWriteObject(JsonGenerator generator, Object value) throws IOE try { generator.writeObject(value); } catch (IllegalStateException e) { - logger.debug("Couldn't marshal '{}' of type '{}', had to be converted into a String", value, value.getClass()); + logger.debug("Couldn't marshal '{}' of type '{}', had to be converted into a String", + value, value.getClass()); generator.writeString(value.toString()); } } From 7cef5b1cff021301151ca49c3b2c24314b70244e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 18:36:36 +1100 Subject: [PATCH 1003/2152] Allow subclasses of SentryInterface to be interpreted by any Binding that understands it --- .../kencochrane/raven/marshaller/json/JsonMarshaller.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index ec379fba7cc..3aa60dd45c3 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -272,10 +272,11 @@ private String formatTimestamp(Date timestamp) { * * @param sentryInterfaceClass Actual type of SentryInterface supported by the {@link InterfaceBinding} * @param binding InterfaceBinding converting SentryInterfaces of type {@code sentryInterfaceClass}. - * @param Type of SentryInterface. + * @param Type of SentryInterface received by the InterfaceBinding. + * @param Type of the interface stored in the event to send to the InterfaceBinding. */ - public void addInterfaceBinding(Class sentryInterfaceClass, - InterfaceBinding binding) { + public void addInterfaceBinding(Class sentryInterfaceClass, + InterfaceBinding binding) { this.interfaceBindings.put(sentryInterfaceClass, binding); } From 8ffe44e9b1700913ccbd90b3dd27470741c108f0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 19:13:30 +1100 Subject: [PATCH 1004/2152] Test the interface binding --- .../marshaller/json/JsonMarshallerTest.java | 30 +++++++++++++++++++ .../testInterfaceBinding.json | 14 +++++++++ 2 files changed, 44 insertions(+) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index 8c1ee58174b..d0fb67e648e 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -1,12 +1,17 @@ package net.kencochrane.raven.marshaller.json; +import com.fasterxml.jackson.core.JsonGenerator; +import mockit.Delegate; import mockit.Injectable; import mockit.NonStrictExpectations; +import mockit.Verifications; import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.interfaces.SentryInterface; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.Date; @@ -225,4 +230,29 @@ public void testEventExtraWrittenProperly(@Injectable("key") final String mockEx assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json"))); } + + @Test + public void testInterfaceBindingIsProperlyUsed( + @Injectable final SentryInterface mockSentryInterface, + @Injectable final InterfaceBinding mockInterfaceBinding) throws Exception { + final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + new NonStrictExpectations() {{ + mockEvent.getSentryInterfaces(); + result = Collections.singletonMap("interfaceKey", mockSentryInterface); + mockInterfaceBinding.writeInterface((JsonGenerator) any, mockSentryInterface); + result = new Delegate() { + public void writeInterface(JsonGenerator generator, SentryInterface sentryInterface) throws IOException { + generator.writeNull(); + } + }; + }}; + + jsonMarshaller.addInterfaceBinding(mockSentryInterface.getClass(), mockInterfaceBinding); + jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + + new Verifications() {{ + mockInterfaceBinding.writeInterface((JsonGenerator) any, mockSentryInterface); + }}; + assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json"))); + } } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json new file mode 100644 index 00000000000..9eccdebd662 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json @@ -0,0 +1,14 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null, + "interfaceKey": null +} From 2083fcecbc3a55bc0d84ccc7c6716fdd3100d435 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 19:39:59 +1100 Subject: [PATCH 1005/2152] Ensure that the base64 deflate is working properly --- .../marshaller/json/JsonMarshallerTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index d0fb67e648e..55ef8889d0c 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -1,6 +1,7 @@ package net.kencochrane.raven.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; +import com.google.common.base.Charsets; import mockit.Delegate; import mockit.Injectable; import mockit.NonStrictExpectations; @@ -11,6 +12,7 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Arrays; import java.util.Collections; @@ -255,4 +257,19 @@ public void writeInterface(JsonGenerator generator, SentryInterface sentryInterf }}; assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json"))); } + + @Test + public void testCompressedDataIsWorking() throws Exception { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + jsonMarshaller.setCompression(true); + + jsonMarshaller.marshall(mockEvent, outputStream); + + assertThat(new String(outputStream.toByteArray(), Charsets.UTF_8.name()), is("" + + "eJyFjcEOAiEMRP+lZ0zYk5Hv8L5psCKxsKSUjclm/" + + "12islebucy087oBrZR1jjdwYP8MGEhUKwYClxuzAY" + + "09UEylt6fL2Z7s1HW11n3UC9z5PM55CYFkuMKo90X" + + "S8L5xkagHG0MFt+0GKslKMmdMx2N6qeB36x/kn7X9" + + "MPsbwgxBSQ==")); + } } From febef0ceeee735659cdce093246aaeed1075f1cc Mon Sep 17 00:00:00 2001 From: Matthew Buckett Date: Fri, 15 Nov 2013 10:51:23 +0000 Subject: [PATCH 1006/2152] Support sending tags from the log4j client. --- raven-log4j/README.md | 3 ++ .../raven/log4j/SentryAppender.java | 31 +++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index b11d64f44cf..a6d6ea73609 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -33,6 +33,9 @@ In the `log4j.properties` file set: log4j.rootLogger=WARN, SentryAppender log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender log4j.appender.SentryAppender.dsn=https://publicKey:secretKey@host:port/1?options +log4j.appender.SentryAppender.tags=\ + deployment=production\n\ + version=1.2.7 ``` ### Additional data and information diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 8adac12d588..d5face726ae 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -15,8 +15,8 @@ import org.apache.log4j.spi.LoggingEvent; import java.io.IOException; -import java.util.Date; -import java.util.Map; +import java.io.StringReader; +import java.util.*; /** * Appender for log4j in charge of sending the logged events to a Sentry server. @@ -50,6 +50,13 @@ public class SentryAppender extends AppenderSkeleton { *

    */ protected String ravenFactory; + /** + * Additional tags to be sent to sentry. + *

    + * Might be empty in which case no tags are sent. + *

    + */ + protected Map tags = Collections.emptyMap(); private final boolean propagateClose; private boolean guard; @@ -196,6 +203,9 @@ protected Event buildEvent(LoggingEvent loggingEvent) { for (Map.Entry mdcEntry : properties.entrySet()) eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); + for (Map.Entry tagEntry: tags.entrySet()) + eventBuilder.addTag(tagEntry.getKey(), tagEntry.getValue()); + raven.runBuilderHelpers(eventBuilder); return eventBuilder.build(); } @@ -208,6 +218,23 @@ public void setRavenFactory(String ravenFactory) { this.ravenFactory = ravenFactory; } + /** + * Set the tags that should be sent along with the events. + * @param tags A String that can be parse as Java Properties. + */ + public void setTags(String tags) { + Properties props = new Properties(); + try { + props.load(new StringReader(tags)); + this.tags = new HashMap(); + for (String key : props.stringPropertyNames()) { + this.tags.put(key, props.getProperty(key)); + } + } catch (IOException shouldNeverHappen) { + throw new RuntimeException(shouldNeverHappen); + } + } + @Override public void close() { if (this.closed) From 4b2e081104c7986cafd66a5cda2e1083b52987db Mon Sep 17 00:00:00 2001 From: Matthew Buckett Date: Sun, 24 Nov 2013 09:40:07 +0000 Subject: [PATCH 1007/2152] Switch to having tags on one line. Having multiple line property value in property files isn't very nice and although this rules out the possibility of have a comma in the tags that shouldn't be a showstopper for most people. --- raven-log4j/README.md | 4 +--- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 6 ++++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index a6d6ea73609..1a239107fd2 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -33,9 +33,7 @@ In the `log4j.properties` file set: log4j.rootLogger=WARN, SentryAppender log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender log4j.appender.SentryAppender.dsn=https://publicKey:secretKey@host:port/1?options -log4j.appender.SentryAppender.tags=\ - deployment=production\n\ - version=1.2.7 +log4j.appender.SentryAppender.tags=deployment=production,version=1.2.7 ``` ### Additional data and information diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index d5face726ae..468db838ac5 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -220,12 +220,14 @@ public void setRavenFactory(String ravenFactory) { /** * Set the tags that should be sent along with the events. - * @param tags A String that can be parse as Java Properties. + * @param tags A String that can be parse as Java Properties, but with commas instead of newlines as the entry + * seperator. */ public void setTags(String tags) { Properties props = new Properties(); try { - props.load(new StringReader(tags)); + // Multiline values in properties files + props.load(new StringReader(tags.replace(',', '\n'))); this.tags = new HashMap(); for (String key : props.stringPropertyNames()) { this.tags.put(key, props.getProperty(key)); From 278af36c02e8aa209e61925be4644404863388ae Mon Sep 17 00:00:00 2001 From: Matthew Buckett Date: Sun, 24 Nov 2013 09:45:00 +0000 Subject: [PATCH 1008/2152] Added note about rebuilding tags. --- .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 468db838ac5..487a85cc9ce 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -203,6 +203,8 @@ protected Event buildEvent(LoggingEvent loggingEvent) { for (Map.Entry mdcEntry : properties.entrySet()) eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); + // Would be nice if the eventbuiller could accept a prebuilt imutable set of tags so we don't have + // to build this every time. for (Map.Entry tagEntry: tags.entrySet()) eventBuilder.addTag(tagEntry.getKey(), tagEntry.getValue()); From e424c956969951980668114e72e1fbcecb355baf Mon Sep 17 00:00:00 2001 From: Matthew Buckett Date: Sun, 24 Nov 2013 11:18:23 +0000 Subject: [PATCH 1009/2152] For nicer code use Guava. Guava is already a dependency of raven so we don't create any more problems with dependencies. --- raven-log4j/pom.xml | 9 ++++---- .../raven/log4j/SentryAppender.java | 23 +++++-------------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index e33124a695c..d5534862d8b 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -23,6 +23,10 @@ log4j log4j + + com.google.guava + guava + @@ -45,11 +49,6 @@ hamcrest-library test - - com.google.guava - guava - test - diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 487a85cc9ce..5d02419c14a 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -1,5 +1,6 @@ package net.kencochrane.raven.log4j; +import com.google.common.base.Splitter; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; @@ -15,8 +16,9 @@ import org.apache.log4j.spi.LoggingEvent; import java.io.IOException; -import java.io.StringReader; -import java.util.*; +import java.util.Collections; +import java.util.Date; +import java.util.Map; /** * Appender for log4j in charge of sending the logged events to a Sentry server. @@ -203,8 +205,6 @@ protected Event buildEvent(LoggingEvent loggingEvent) { for (Map.Entry mdcEntry : properties.entrySet()) eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); - // Would be nice if the eventbuiller could accept a prebuilt imutable set of tags so we don't have - // to build this every time. for (Map.Entry tagEntry: tags.entrySet()) eventBuilder.addTag(tagEntry.getKey(), tagEntry.getValue()); @@ -222,21 +222,10 @@ public void setRavenFactory(String ravenFactory) { /** * Set the tags that should be sent along with the events. - * @param tags A String that can be parse as Java Properties, but with commas instead of newlines as the entry - * seperator. + * @param tags A String of tags. key/values are separated by equals (=) and tags are seperated by commas(,). */ public void setTags(String tags) { - Properties props = new Properties(); - try { - // Multiline values in properties files - props.load(new StringReader(tags.replace(',', '\n'))); - this.tags = new HashMap(); - for (String key : props.stringPropertyNames()) { - this.tags.put(key, props.getProperty(key)); - } - } catch (IOException shouldNeverHappen) { - throw new RuntimeException(shouldNeverHappen); - } + this.tags = Splitter.on(",").withKeyValueSeparator("=").split(tags); } @Override From a3305b18867fe4311a26aa88122ac14584cfcc9e Mon Sep 17 00:00:00 2001 From: Matthew Buckett Date: Sun, 24 Nov 2013 11:28:05 +0000 Subject: [PATCH 1010/2152] Switch to : as key/value separator in properties. As = is already used in .properties file use colon to make it more readable. --- raven-log4j/README.md | 2 +- .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 1a239107fd2..69bc89cd03a 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -33,7 +33,7 @@ In the `log4j.properties` file set: log4j.rootLogger=WARN, SentryAppender log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender log4j.appender.SentryAppender.dsn=https://publicKey:secretKey@host:port/1?options -log4j.appender.SentryAppender.tags=deployment=production,version=1.2.7 +log4j.appender.SentryAppender.tags=deployment:production,version:1.2.7 ``` ### Additional data and information diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 5d02419c14a..bfda3a30c47 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -222,10 +222,10 @@ public void setRavenFactory(String ravenFactory) { /** * Set the tags that should be sent along with the events. - * @param tags A String of tags. key/values are separated by equals (=) and tags are seperated by commas(,). + * @param tags A String of tags. key/values are separated by colon(:) and tags are separated by commas(,). */ public void setTags(String tags) { - this.tags = Splitter.on(",").withKeyValueSeparator("=").split(tags); + this.tags = Splitter.on(",").withKeyValueSeparator(":").split(tags); } @Override From 1fd8edac5a58e89c61e27c511855b1cb0a763e44 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 23:20:31 +1100 Subject: [PATCH 1011/2152] Add tags from the JUL configuration to Events --- .../net/kencochrane/raven/jul/SentryHandler.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 4637f43f2d5..d585f01ead1 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -1,5 +1,6 @@ package net.kencochrane.raven.jul; +import com.google.common.base.Splitter; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; @@ -11,9 +12,7 @@ import java.io.IOException; import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; +import java.util.*; import java.util.logging.*; /** @@ -44,6 +43,10 @@ public class SentryHandler extends Handler { *

    */ protected String ravenFactory; + /** + * Tags to add to every event. + */ + protected Map tags = Collections.emptyMap(); private final boolean propagateClose; /** @@ -116,6 +119,9 @@ protected void retrieveProperties() { LogManager manager = LogManager.getLogManager(); dsn = manager.getProperty(SentryHandler.class.getName() + ".dsn"); ravenFactory = manager.getProperty(SentryHandler.class.getName() + ".ravenFactory"); + String tagsProperty = manager.getProperty(SentryHandler.class.getName() + ".tags"); + if (tagsProperty != null) + tags = Splitter.on(",").withKeyValueSeparator(":").split(tagsProperty); } @Override @@ -188,6 +194,10 @@ protected Event buildEvent(LogRecord record) { eventBuilder.setCulprit(record.getLoggerName()); } + for (Map.Entry tagEntry : tags.entrySet()) { + eventBuilder.addTag(tagEntry.getKey(), tagEntry.getValue()); + } + eventBuilder.addExtra(THREAD_ID, record.getThreadID()); raven.runBuilderHelpers(eventBuilder); From f9f45e3d8116bbf50b4c38097ecb8e723e388bd7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 23:22:03 +1100 Subject: [PATCH 1012/2152] Add documentation for automatic tags --- raven/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/raven/README.md b/raven/README.md index 51562ed9d20..40fde79f55b 100644 --- a/raven/README.md +++ b/raven/README.md @@ -37,6 +37,7 @@ In the `logging.properties` file set: level=WARN handlers=net.kencochrane.raven.jul.SentryHandler net.kencochrane.raven.jul.SentryHandler.dsn=https://publicKey:secretKey@host:port/1?options +net.kencochrane.raven.jul.SentryHandler.tags=tag1:value1,tag2:value2 ``` When starting your application, add the `java.util.logging.config.file` to the From c098a6684bf3cd564f50a7cdff91658464f9af59 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Nov 2013 23:24:13 +1100 Subject: [PATCH 1013/2152] Fix indentation remove unused variable --- .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index bfda3a30c47..484834d9b14 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -60,7 +60,6 @@ public class SentryAppender extends AppenderSkeleton { */ protected Map tags = Collections.emptyMap(); private final boolean propagateClose; - private boolean guard; /** * Creates an instance of SentryAppender. @@ -205,7 +204,7 @@ protected Event buildEvent(LoggingEvent loggingEvent) { for (Map.Entry mdcEntry : properties.entrySet()) eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); - for (Map.Entry tagEntry: tags.entrySet()) + for (Map.Entry tagEntry : tags.entrySet()) eventBuilder.addTag(tagEntry.getKey(), tagEntry.getValue()); raven.runBuilderHelpers(eventBuilder); @@ -222,6 +221,7 @@ public void setRavenFactory(String ravenFactory) { /** * Set the tags that should be sent along with the events. + * * @param tags A String of tags. key/values are separated by colon(:) and tags are separated by commas(,). */ public void setTags(String tags) { From cd8a668f8991860742444765ac06be56c60d067f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 28 Nov 2013 08:05:04 +1100 Subject: [PATCH 1014/2152] Fix method ordering --- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 484834d9b14..917c1f91282 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -211,14 +211,14 @@ protected Event buildEvent(LoggingEvent loggingEvent) { return eventBuilder.build(); } - public void setDsn(String dsn) { - this.dsn = dsn; - } - public void setRavenFactory(String ravenFactory) { this.ravenFactory = ravenFactory; } + public void setDsn(String dsn) { + this.dsn = dsn; + } + /** * Set the tags that should be sent along with the events. * From ac3020249309305271cfe5ce9154e35020a65d62 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 28 Nov 2013 08:08:26 +1100 Subject: [PATCH 1015/2152] Allow tags to be set on each logging system --- raven-log4j/README.md | 2 +- raven-log4j2/README.md | 3 +++ raven-log4j2/pom.xml | 4 +++ .../raven/log4j2/SentryAppender.java | 25 ++++++++++++++++--- raven-logback/README.md | 1 + raven-logback/pom.xml | 4 +++ .../raven/logback/SentryAppender.java | 22 +++++++++++++--- 7 files changed, 52 insertions(+), 9 deletions(-) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 69bc89cd03a..e0f443a1034 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -33,7 +33,7 @@ In the `log4j.properties` file set: log4j.rootLogger=WARN, SentryAppender log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender log4j.appender.SentryAppender.dsn=https://publicKey:secretKey@host:port/1?options -log4j.appender.SentryAppender.tags=deployment:production,version:1.2.7 +log4j.appender.SentryAppender.tags=tag1:value1,tag2:value2 ``` ### Additional data and information diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index c0618f87ec9..4a098129dce 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -39,6 +39,9 @@ In the `log4j2.xml` file set: https://publicKey:secretKey@host:port/1?options + + tag1:value1,tag2:value2 + diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 5573c42e4a7..818473132f6 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -27,6 +27,10 @@ org.apache.logging.log4j log4j-core + + com.google.guava + guava + diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index fffdd91ce18..ffcb10af373 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -1,5 +1,6 @@ package net.kencochrane.raven.log4j2; +import com.google.common.base.Splitter; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; @@ -20,10 +21,7 @@ import org.apache.logging.log4j.message.Message; import java.io.IOException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Appender for log4j2 in charge of sending the logged events to a Sentry server. @@ -66,6 +64,13 @@ public class SentryAppender extends AbstractAppender { *

    */ protected String ravenFactory; + /** + * Additional tags to be sent to sentry. + *

    + * Might be empty in which case no tags are sent. + *

    + */ + protected Map tags = Collections.emptyMap(); private final boolean propagateClose; /** @@ -107,6 +112,7 @@ private SentryAppender(String name, Filter filter, boolean propagateClose) { * @param name The name of the Appender. * @param dsn Data Source Name to access the Sentry server. * @param ravenFactory Name of the factory to use to build the {@link Raven} instance. + * @param tags Tags to add to each event. * @param filter The filter, if any, to use. * @return The SentryAppender. */ @@ -114,6 +120,7 @@ private SentryAppender(String name, Filter filter, boolean propagateClose) { public static SentryAppender createAppender(@PluginAttribute("name") final String name, @PluginAttribute("dsn") final String dsn, @PluginAttribute("ravenFactory") final String ravenFactory, + @PluginAttribute("tags") final String tags, @PluginElement("filters") final Filter filter) { if (name == null) { @@ -123,6 +130,7 @@ public static SentryAppender createAppender(@PluginAttribute("name") final Strin SentryAppender sentryAppender = new SentryAppender(name, filter, true); sentryAppender.setDsn(dsn); + sentryAppender.setTags(tags); sentryAppender.setRavenFactory(ravenFactory); return sentryAppender; } @@ -270,6 +278,15 @@ public void setRavenFactory(String ravenFactory) { this.ravenFactory = ravenFactory; } + /** + * Set the tags that should be sent along with the events. + * + * @param tags A String of tags. key/values are separated by colon(:) and tags are separated by commas(,). + */ + public void setTags(String tags) { + this.tags = Splitter.on(",").withKeyValueSeparator(":").split(tags); + } + @Override public void stop() { super.stop(); diff --git a/raven-logback/README.md b/raven-logback/README.md index d07729c17ef..21a72cab7b4 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -33,6 +33,7 @@ In the `logback.xml` file set: https://publicKey:secretKey@host:port/1?options + tag1:value1,tag2:value2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 21e03e7f5b2..5c9f370124e 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -31,6 +31,10 @@ ch.qos.logback logback-classic
    + + com.google.guava + guava + diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 9173bb8e64a..e8b3f38aed1 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -4,6 +4,7 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.ThrowableProxy; import ch.qos.logback.core.AppenderBase; +import com.google.common.base.Splitter; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; @@ -15,10 +16,7 @@ import net.kencochrane.raven.event.interfaces.StackTraceInterface; import java.io.IOException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Appender for logback in charge of sending the logged events to a Sentry server. @@ -52,6 +50,13 @@ public class SentryAppender extends AppenderBase { *

    */ protected String ravenFactory; + /** + * Additional tags to be sent to sentry. + *

    + * Might be empty in which case no tags are sent. + *

    + */ + protected Map tags = Collections.emptyMap(); private final boolean propagateClose; /** @@ -213,6 +218,15 @@ public void setRavenFactory(String ravenFactory) { this.ravenFactory = ravenFactory; } + /** + * Set the tags that should be sent along with the events. + * + * @param tags A String of tags. key/values are separated by colon(:) and tags are separated by commas(,). + */ + public void setTags(String tags) { + this.tags = Splitter.on(",").withKeyValueSeparator(":").split(tags); + } + @Override public void stop() { super.stop(); From b24b84e0a33faa3ab48ff4aa000b63fc132e96cc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 28 Nov 2013 08:25:16 +1100 Subject: [PATCH 1016/2152] Stop using deprecated methods from jackson --- .../kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java index ca75045f5a3..94da6e59a3b 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java @@ -70,7 +70,7 @@ public InputStream decapsulateContent(InputStream originalStream) throws IOExcep private boolean isJson(InputStream inputStream) { boolean valid = false; try { - final JsonParser parser = new JsonFactory().createJsonParser(inputStream); + final JsonParser parser = new JsonFactory().createParser(inputStream); do { parser.nextToken(); } while (parser.hasCurrentToken()); From 3fcd1914bf17dee44ab978f6fad36afd873e676e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 28 Nov 2013 08:39:50 +1100 Subject: [PATCH 1017/2152] Remove the propagateClose principle (close should always propagate) If someone want to avoid the propagation they should create their implementation with dummy close methods --- .../raven/log4j/SentryAppender.java | 16 +-------- .../raven/log4j/SentryAppenderCloseTest.java | 34 ++----------------- .../raven/log4j2/SentryAppender.java | 23 +++---------- .../raven/log4j2/SentryAppenderCloseTest.java | 19 ++--------- .../raven/logback/SentryAppender.java | 16 +-------- .../logback/SentryAppenderCloseTest.java | 16 +-------- .../raven/DefaultRavenFactory.java | 2 +- .../raven/connection/AsyncConnection.java | 19 +---------- .../kencochrane/raven/jul/SentryHandler.java | 16 +-------- 9 files changed, 15 insertions(+), 146 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 917c1f91282..077878e606b 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -59,13 +59,11 @@ public class SentryAppender extends AppenderSkeleton { *

    */ protected Map tags = Collections.emptyMap(); - private final boolean propagateClose; /** * Creates an instance of SentryAppender. */ public SentryAppender() { - this.propagateClose = true; } /** @@ -74,19 +72,7 @@ public SentryAppender() { * @param raven instance of Raven to use with this appender. */ public SentryAppender(Raven raven) { - this(raven, false); - } - - /** - * Creates an instance of SentryAppender. - * - * @param raven instance of Raven to use with this appender. - * @param propagateClose true if the {@link net.kencochrane.raven.connection.Connection#close()} should be called - * when the appender is closed. - */ - public SentryAppender(Raven raven, boolean propagateClose) { this.raven = raven; - this.propagateClose = propagateClose; } /** @@ -235,7 +221,7 @@ public void close() { this.closed = true; try { - if (propagateClose && raven != null) + if (raven != null) raven.getConnection().close(); } catch (IOException e) { getErrorHandler().error("An exception occurred while closing the Raven connection", e, diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java index 7b736d9557f..fc9c333bac0 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java @@ -36,7 +36,7 @@ private void assertNoErrorsInErrorHandler() throws Exception { } @Test - public void testNotClosedIfRavenInstanceIsProvided() throws Exception { + public void testConnectionClosedWhenAppenderClosed() throws Exception { final SentryAppender sentryAppender = new SentryAppender(mockRaven); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.activateOptions(); @@ -45,36 +45,6 @@ public void testNotClosedIfRavenInstanceIsProvided() throws Exception { new Verifications() {{ mockConnection.close(); - times = 0; - }}; - assertNoErrorsInErrorHandler(); - } - - @Test - public void testClosedIfRavenInstanceProvidedAndForceClose() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven, true); - sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.activateOptions(); - - sentryAppender.close(); - - new Verifications() {{ - mockConnection.close(); - }}; - assertNoErrorsInErrorHandler(); - } - - @Test - public void testNotClosedIfRavenInstanceProvidedAndNotForceClose() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven, false); - sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.activateOptions(); - - sentryAppender.close(); - - new Verifications() {{ - mockConnection.close(); - times = 0; }}; assertNoErrorsInErrorHandler(); } @@ -129,7 +99,7 @@ public void testCloseDoNotFailIfNoInit() @Test public void testCloseDoNotFailWhenMultipleCalls() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven, true); + final SentryAppender sentryAppender = new SentryAppender(mockRaven); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.activateOptions(); diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index ffcb10af373..44cec4b5354 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -71,13 +71,12 @@ public class SentryAppender extends AbstractAppender { *

    */ protected Map tags = Collections.emptyMap(); - private final boolean propagateClose; /** * Creates an instance of SentryAppender. */ public SentryAppender() { - this(APPENDER_NAME, null, true); + this(APPENDER_NAME, null); } /** @@ -86,24 +85,12 @@ public SentryAppender() { * @param raven instance of Raven to use with this appender. */ public SentryAppender(Raven raven) { - this(raven, false); - } - - /** - * Creates an instance of SentryAppender. - * - * @param raven instance of Raven to use with this appender. - * @param propagateClose true if the {@link net.kencochrane.raven.connection.Connection#close()} should be called - * when the appender is closed. - */ - public SentryAppender(Raven raven, boolean propagateClose) { - this(APPENDER_NAME, null, propagateClose); + this(APPENDER_NAME, null); this.raven = raven; } - private SentryAppender(String name, Filter filter, boolean propagateClose) { + private SentryAppender(String name, Filter filter) { super(name, filter, null, true); - this.propagateClose = propagateClose; } /** @@ -128,7 +115,7 @@ public static SentryAppender createAppender(@PluginAttribute("name") final Strin return null; } - SentryAppender sentryAppender = new SentryAppender(name, filter, true); + SentryAppender sentryAppender = new SentryAppender(name, filter); sentryAppender.setDsn(dsn); sentryAppender.setTags(tags); sentryAppender.setRavenFactory(ravenFactory); @@ -292,7 +279,7 @@ public void stop() { super.stop(); try { - if (propagateClose && raven != null) + if (raven != null) raven.getConnection().close(); } catch (IOException e) { error("An exception occurred while closing the Raven connection", e); diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java index 9fa6ff47c10..27494c93f7b 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java @@ -32,23 +32,8 @@ private void assertNoErrorsInErrorHandler() throws Exception { } @Test - public void testCloseNotCalled() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven, false); - sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.start(); - - sentryAppender.stop(); - - new Verifications() {{ - mockConnection.close(); - times = 0; - }}; - assertNoErrorsInErrorHandler(); - } - - @Test - public void testClose() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven, true); + public void testConnectionClosedWhenAppenderStopped() throws Exception { + final SentryAppender sentryAppender = new SentryAppender(mockRaven); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.start(); diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index e8b3f38aed1..fcf438e3550 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -57,13 +57,11 @@ public class SentryAppender extends AppenderBase { *

    */ protected Map tags = Collections.emptyMap(); - private final boolean propagateClose; /** * Creates an instance of SentryAppender. */ public SentryAppender() { - propagateClose = true; } /** @@ -72,19 +70,7 @@ public SentryAppender() { * @param raven instance of Raven to use with this appender. */ public SentryAppender(Raven raven) { - this(raven, false); - } - - /** - * Creates an instance of SentryAppender. - * - * @param raven instance of Raven to use with this appender. - * @param propagateClose true if the {@link net.kencochrane.raven.connection.Connection#close()} should be called - * when the appender is closed. - */ - public SentryAppender(Raven raven, boolean propagateClose) { this.raven = raven; - this.propagateClose = propagateClose; } /** @@ -232,7 +218,7 @@ public void stop() { super.stop(); try { - if (propagateClose && raven != null) + if (raven != null) raven.getConnection().close(); } catch (IOException e) { addError("An exception occurred while closing the Raven connection", e); diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java index fcb7deb916e..0023c73e952 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java @@ -42,26 +42,12 @@ private void assertNoErrorsInStatusManager() throws Exception { } @Test - public void testClose() throws Exception { + public void testConnectionClosedWhenAppenderStopped() throws Exception { final SentryAppender sentryAppender = new SentryAppender(mockRaven); sentryAppender.setContext(mockContext); sentryAppender.stop(); - new Verifications() {{ - mockConnection.close(); - times = 0; - }}; - assertNoErrorsInStatusManager(); - } - - @Test - public void testClose2() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven, true); - sentryAppender.setContext(mockContext); - - sentryAppender.stop(); - new Verifications() {{ mockConnection.close(); }}; diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 7ce4cd7ffbc..ad3a7ea4601 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -114,7 +114,7 @@ protected Connection createConnection(Dsn dsn) { * @return the asynchronous connection. */ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { - AsyncConnection asyncConnection = new AsyncConnection(connection, true); + AsyncConnection asyncConnection = new AsyncConnection(connection); int maxThreads; if (dsn.getOptions().containsKey(MAX_THREADS_OPTION)) { diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 1264b3480be..fbd9e68a682 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -28,10 +28,6 @@ public class AsyncConnection implements Connection { * Connection used to actually send the events. */ private final Connection actualConnection; - /** - * Option to disable the propagation of the {@link #close()} operation to the actual connection. - */ - private final boolean propagateClose; /** * Executor service in charge of running the connection in separate threads. */ @@ -50,19 +46,7 @@ public class AsyncConnection implements Connection { * @param actualConnection connection used to send the events. */ public AsyncConnection(Connection actualConnection) { - this(actualConnection, true); - } - - /** - * Creates a connection which will rely on an executor to send events. - * - * @param actualConnection connection used to send the events. - * @param propagateClose whether or not the {@link #actualConnection} should be closed - * when this connection closes. - */ - public AsyncConnection(Connection actualConnection, boolean propagateClose) { this.actualConnection = actualConnection; - this.propagateClose = propagateClose; addShutdownHook(); } @@ -124,8 +108,7 @@ public void close() throws IOException { List tasks = executorService.shutdownNow(); logger.info("{} tasks failed to execute before the shutdown.", tasks.size()); } finally { - if (propagateClose) - actualConnection.close(); + actualConnection.close(); } } diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index d585f01ead1..bb0a5f578a2 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -47,13 +47,11 @@ public class SentryHandler extends Handler { * Tags to add to every event. */ protected Map tags = Collections.emptyMap(); - private final boolean propagateClose; /** * Creates an instance of SentryHandler. */ public SentryHandler() { - propagateClose = true; retrieveProperties(); } @@ -63,19 +61,7 @@ public SentryHandler() { * @param raven instance of Raven to use with this appender. */ public SentryHandler(Raven raven) { - this(raven, false); - } - - /** - * Creates an instance of SentryHandler. - * - * @param raven instance of Raven to use with this appender. - * @param propagateClose true if the {@link net.kencochrane.raven.connection.Connection#close()} should be called - * when the appender is closed. - */ - public SentryHandler(Raven raven, boolean propagateClose) { this.raven = raven; - this.propagateClose = propagateClose; } /** @@ -211,7 +197,7 @@ public void flush() { @Override public void close() throws SecurityException { try { - if (propagateClose && raven != null) + if (raven != null) raven.getConnection().close(); } catch (IOException e) { reportError("An exception occurred while closing the Raven connection", e, ErrorManager.CLOSE_FAILURE); From abc0a6a77d446d25bd4850e0b3fe707eab33582a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 28 Nov 2013 08:40:36 +1100 Subject: [PATCH 1018/2152] If tags aren't specified don't try to read them --- .../main/java/net/kencochrane/raven/log4j2/SentryAppender.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 44cec4b5354..b5250c4ec31 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -117,7 +117,8 @@ public static SentryAppender createAppender(@PluginAttribute("name") final Strin SentryAppender sentryAppender = new SentryAppender(name, filter); sentryAppender.setDsn(dsn); - sentryAppender.setTags(tags); + if (tags != null) + sentryAppender.setTags(tags); sentryAppender.setRavenFactory(ravenFactory); return sentryAppender; } From cc4c014147785722d5480a07c7b9a15456a8ceaf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 5 Dec 2013 16:01:15 +1100 Subject: [PATCH 1019/2152] Fix issue in folder naming --- .../services}/net.kencochrane.raven.RavenFactory | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename raven-getsentry/src/main/resources/{META-INF.services => META-INF/services}/net.kencochrane.raven.RavenFactory (100%) diff --git a/raven-getsentry/src/main/resources/META-INF.services/net.kencochrane.raven.RavenFactory b/raven-getsentry/src/main/resources/META-INF/services/net.kencochrane.raven.RavenFactory similarity index 100% rename from raven-getsentry/src/main/resources/META-INF.services/net.kencochrane.raven.RavenFactory rename to raven-getsentry/src/main/resources/META-INF/services/net.kencochrane.raven.RavenFactory From d0b8f4e5eef70d66197ebf2dd1b97c657750dcce Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 5 Dec 2013 16:13:56 +1100 Subject: [PATCH 1020/2152] Ensure that the additional tags are indeed added to each event --- .../main/java/net/kencochrane/raven/log4j2/SentryAppender.java | 3 +++ .../java/net/kencochrane/raven/logback/SentryAppender.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index b5250c4ec31..b2746a148da 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -254,6 +254,9 @@ protected Event buildEvent(LogEvent event) { if (event.getMarker() != null) eventBuilder.addTag(LOG4J_MARKER, event.getMarker().getName()); + for (Map.Entry tagEntry : tags.entrySet()) + eventBuilder.addTag(tagEntry.getKey(), tagEntry.getValue()); + raven.runBuilderHelpers(eventBuilder); return eventBuilder.build(); } diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index fcf438e3550..98418371c9c 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -192,6 +192,9 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { if (iLoggingEvent.getMarker() != null) eventBuilder.addTag(LOGBACK_MARKER, iLoggingEvent.getMarker().getName()); + for (Map.Entry tagEntry : tags.entrySet()) + eventBuilder.addTag(tagEntry.getKey(), tagEntry.getValue()); + raven.runBuilderHelpers(eventBuilder); return eventBuilder.build(); } From ac2d0adbe7384735c6495d599412218b2b8bb92a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 5 Dec 2013 23:28:05 +1100 Subject: [PATCH 1021/2152] Do not create a warn for each failing RavenFactory --- raven/src/main/java/net/kencochrane/raven/RavenFactory.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index f28abf249bb..b99d5afd21c 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -84,9 +84,11 @@ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { logger.debug("Attempting to use '{}' as a Raven factory.", ravenFactory); try { - return ravenFactory.createRavenInstance(dsn); + Raven ravenInstance = ravenFactory.createRavenInstance(dsn); + logger.info("The raven factory '{}' created an instance of Raven.", ravenFactory); + return ravenInstance; } catch (RuntimeException e) { - logger.warn("The raven factory '{}' couldn't create an instance of Raven.", ravenFactory, e); + logger.debug("The raven factory '{}' couldn't create an instance of Raven.", ravenFactory, e); } } From 6d7288074944ca77583a7fd5e2352bbfe8103d2e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 5 Dec 2013 23:29:50 +1100 Subject: [PATCH 1022/2152] Use a FutureTask rather than an executor that might stay open --- .../java/net/kencochrane/raven/event/EventBuilder.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index d99895ccdbe..515da7424d5 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -10,8 +10,7 @@ import java.util.Date; import java.util.UUID; import java.util.concurrent.Callable; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.zip.CRC32; import java.util.zip.Checksum; @@ -350,10 +349,11 @@ public String getHostname() { * Force an update of the cache to get the current value of the hostname. */ public void updateCache() { - Future future = Executors.newSingleThreadExecutor().submit(new HostRetriever()); - + FutureTask futureTask = new FutureTask(new HostRetriever()); try { - hostname = future.get(GET_HOSTNAME_TIMEOUT, TimeUnit.MILLISECONDS); + futureTask.run(); + logger.debug("Updating the hostname cache"); + hostname = futureTask.get(GET_HOSTNAME_TIMEOUT, TimeUnit.MILLISECONDS); expirationTimestamp = System.currentTimeMillis() + cacheDuration; } catch (Exception e) { expirationTimestamp = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(1); From 1d29e4396d7fdcc2e586dff36b59f190116fc826 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 5 Dec 2013 23:36:05 +1100 Subject: [PATCH 1023/2152] Reduce the log level of some log entries --- raven/src/main/java/net/kencochrane/raven/RavenFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index b99d5afd21c..6711cae3754 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -77,7 +77,7 @@ public static Raven ravenInstance(Dsn dsn) { */ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { //Loop through registered factories - logger.info("Attempting to find a working Raven factory"); + logger.debug("Attempting to find a working Raven factory"); for (RavenFactory ravenFactory : getRegisteredFactories()) { if (ravenFactoryName != null && !ravenFactoryName.equals(ravenFactory.getClass().getName())) continue; @@ -85,7 +85,7 @@ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { logger.debug("Attempting to use '{}' as a Raven factory.", ravenFactory); try { Raven ravenInstance = ravenFactory.createRavenInstance(dsn); - logger.info("The raven factory '{}' created an instance of Raven.", ravenFactory); + logger.debug("The raven factory '{}' created an instance of Raven.", ravenFactory); return ravenInstance; } catch (RuntimeException e) { logger.debug("The raven factory '{}' couldn't create an instance of Raven.", ravenFactory, e); From 12738b2778baa9c011f1d1fbf8b233bd6ab38cf3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 6 Dec 2013 08:45:13 +1100 Subject: [PATCH 1024/2152] Move the FutureTask in a Thread... --- .../main/java/net/kencochrane/raven/event/EventBuilder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 515da7424d5..a75b37853cd 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -351,11 +351,12 @@ public String getHostname() { public void updateCache() { FutureTask futureTask = new FutureTask(new HostRetriever()); try { - futureTask.run(); + new Thread(futureTask).start(); logger.debug("Updating the hostname cache"); hostname = futureTask.get(GET_HOSTNAME_TIMEOUT, TimeUnit.MILLISECONDS); expirationTimestamp = System.currentTimeMillis() + cacheDuration; } catch (Exception e) { + futureTask.cancel(true); expirationTimestamp = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(1); logger.warn("Localhost hostname lookup failed, keeping the value '{}'", hostname, e); } From 2ec10d1787a89235d7899df2dd4fba3820651408 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 6 Dec 2013 09:28:17 +1100 Subject: [PATCH 1025/2152] Ensure that threaded tests fail after timeout --- .../kencochrane/raven/event/EventBuilderHostnameCacheTest.java | 3 ++- .../java/net/kencochrane/raven/event/EventBuilderTest.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java index e5d23db3d70..d6c55863f86 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java @@ -63,6 +63,7 @@ public void successfulHostnameRetrievalIsCachedForFiveHours( assertThat(expirationTime, is(TimeUnit.HOURS.toMillis(5) + System.currentTimeMillis())); } + @Test(timeOut = 5000) public void unsucessfulHostnameRetrievalIsCachedForOneSecond( @Mocked(methods = "currentTimeMillis") final System system) throws Exception { @@ -80,7 +81,7 @@ public void unsucessfulHostnameRetrievalIsCachedForOneSecond( assertThat(expirationTime, is(TimeUnit.SECONDS.toMillis(1) + System.currentTimeMillis())); } - @Test + @Test(timeOut = 5000) public void unsucessfulHostameRetrievalUsesLastKnownCachedValue() throws Exception { new NonStrictExpectations(InetAddress.class) {{ InetAddress.getLocalHost(); diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index 4be4d85c6c0..1e9650ac402 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -236,7 +236,7 @@ public void builtEventWithTagsHasProperTags(@Injectable("tagKey") final String m assertThat(event.getTags().entrySet(), hasSize(1)); } - @Test + @Test(timeOut = 5000) public void builtEventWithNoServerNameUsesDefaultIfSearchTimesOut() throws Exception { resetHostnameCache(); From cb32d452607d0f530a2b0adeb53b2a27c3958284 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 6 Dec 2013 09:29:08 +1100 Subject: [PATCH 1026/2152] Ensure that the localhost hostname is always working during tests --- .../kencochrane/raven/event/EventBuilderTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index 1e9650ac402..5855ab046d1 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -4,6 +4,7 @@ import mockit.Injectable; import mockit.NonStrictExpectations; import net.kencochrane.raven.event.interfaces.SentryInterface; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -17,11 +18,24 @@ import static org.hamcrest.Matchers.*; public class EventBuilderTest { + @Injectable + private InetAddress mockLocalHost; + private static void resetHostnameCache() { setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp", 0l); setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "hostname", EventBuilder.DEFAULT_HOSTNAME); } + @BeforeMethod + public void setUp() throws Exception { + new NonStrictExpectations(InetAddress.class){{ + InetAddress.getLocalHost(); + result = mockLocalHost; + mockLocalHost.getCanonicalHostName(); + result = "local"; + }}; + } + @Test public void builtEventHasRandomlyGeneratedUuid(@Injectable final UUID mockUuid) throws Exception { From 5167b630acabc83227bd54fc8ef937aa6c230737 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 7 Dec 2013 09:34:46 +1100 Subject: [PATCH 1027/2152] Ensure that DefaultRavenFactory is provided by ServiceLoader --- .../raven/DefaultRavenFactoryTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/DefaultRavenFactoryTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/DefaultRavenFactoryTest.java b/raven/src/test/java/net/kencochrane/raven/DefaultRavenFactoryTest.java new file mode 100644 index 00000000000..315ba2c2b78 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/DefaultRavenFactoryTest.java @@ -0,0 +1,18 @@ +package net.kencochrane.raven; + +import org.testng.annotations.Test; + +import java.util.ServiceLoader; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.instanceOf; + +public class DefaultRavenFactoryTest { + @Test + public void checkServiceLoaderProvidesFactory(){ + ServiceLoader ravenFactories = ServiceLoader.load(RavenFactory.class); + + assertThat(ravenFactories, contains(instanceOf(DefaultRavenFactory.class))); + } +} From 23f8c9e53d6bb7725c2a9d983e05bf3b7b007ca5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 7 Dec 2013 09:37:16 +1100 Subject: [PATCH 1028/2152] Add hamcrest to the getsentry module --- raven-getsentry/pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/raven-getsentry/pom.xml b/raven-getsentry/pom.xml index 434324c6d99..0a19670777c 100644 --- a/raven-getsentry/pom.xml +++ b/raven-getsentry/pom.xml @@ -30,5 +30,15 @@ testng test
    + + org.hamcrest + hamcrest-core + test + + + org.hamcrest + hamcrest-library + test + From 58f1d4836e2515e9e31c702fea8e1db18e9ea4fe Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 7 Dec 2013 09:53:24 +1100 Subject: [PATCH 1029/2152] Add test to ensure getSentry provided by ServiceLoader --- .../getsentry/GetSentryRavenFactoryTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/GetSentryRavenFactoryTest.java diff --git a/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/GetSentryRavenFactoryTest.java b/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/GetSentryRavenFactoryTest.java new file mode 100644 index 00000000000..9a707dadfd7 --- /dev/null +++ b/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/GetSentryRavenFactoryTest.java @@ -0,0 +1,19 @@ +package net.kencochrane.raven.getsentry; + +import net.kencochrane.raven.RavenFactory; +import org.hamcrest.Matchers; +import org.testng.annotations.Test; + +import java.util.ServiceLoader; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; + +public class GetSentryRavenFactoryTest { + @Test + public void checkServiceLoaderProvidesFactory() { + ServiceLoader ravenFactories = ServiceLoader.load(RavenFactory.class); + + assertThat(ravenFactories, Matchers.hasItem(instanceOf(GetSentryRavenFactory.class))); + } +} From 2ca5dbdb57f2ec2474d7a9724ddf3e952518192e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 7 Dec 2013 09:53:48 +1100 Subject: [PATCH 1030/2152] Use hasItem rather than contains --- .../java/net/kencochrane/raven/DefaultRavenFactoryTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/DefaultRavenFactoryTest.java b/raven/src/test/java/net/kencochrane/raven/DefaultRavenFactoryTest.java index 315ba2c2b78..2989b39e1d8 100644 --- a/raven/src/test/java/net/kencochrane/raven/DefaultRavenFactoryTest.java +++ b/raven/src/test/java/net/kencochrane/raven/DefaultRavenFactoryTest.java @@ -1,18 +1,18 @@ package net.kencochrane.raven; +import org.hamcrest.Matchers; import org.testng.annotations.Test; import java.util.ServiceLoader; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.instanceOf; public class DefaultRavenFactoryTest { @Test - public void checkServiceLoaderProvidesFactory(){ + public void checkServiceLoaderProvidesFactory() { ServiceLoader ravenFactories = ServiceLoader.load(RavenFactory.class); - assertThat(ravenFactories, contains(instanceOf(DefaultRavenFactory.class))); + assertThat(ravenFactories, Matchers.hasItem(instanceOf(DefaultRavenFactory.class))); } } From dc6358da1b01c3b6ad9353f6b7250077dbf6735e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 7 Dec 2013 09:54:02 +1100 Subject: [PATCH 1031/2152] Remove maven prerequisite as they're for maven plugins --- pom.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pom.xml b/pom.xml index 83ba67adeaf..e2a2a6b0e9d 100644 --- a/pom.xml +++ b/pom.xml @@ -58,10 +58,6 @@ - - 3.0 - - raven raven-getsentry From c5bed67d8f12f430b3dafbba45735ae18ca5e618 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 7 Dec 2013 09:54:21 +1100 Subject: [PATCH 1032/2152] Move testng after hamcrest declaration (junit mess) --- raven-getsentry/pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/raven-getsentry/pom.xml b/raven-getsentry/pom.xml index 0a19670777c..6422737b0c9 100644 --- a/raven-getsentry/pom.xml +++ b/raven-getsentry/pom.xml @@ -25,11 +25,6 @@ jmockit test - - org.testng - testng - test - org.hamcrest hamcrest-core @@ -40,5 +35,10 @@ hamcrest-library test + + org.testng + testng + test + From 2db2d8eb42ea74f5ef3401641c401ac89b340f0e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 8 Dec 2013 08:31:02 +1100 Subject: [PATCH 1033/2152] [maven-release-plugin] prepare release v4.1.2 --- pom.xml | 4 ++-- raven-getsentry/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index e2a2a6b0e9d..b95052d9266 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 4.1.2-SNAPSHOT + 4.1.2 pom Raven-Java @@ -71,7 +71,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v4.1.2 https://github.com/${github.repo}/issues diff --git a/raven-getsentry/pom.xml b/raven-getsentry/pom.xml index 6422737b0c9..3a7542a3ef1 100644 --- a/raven-getsentry/pom.xml +++ b/raven-getsentry/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.2-SNAPSHOT + 4.1.2 raven-getsentry diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index d5534862d8b..d29da831a52 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.2-SNAPSHOT + 4.1.2 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 818473132f6..b9fc02c1a32 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.2-SNAPSHOT + 4.1.2 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 5c9f370124e..c3694d99f33 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.2-SNAPSHOT + 4.1.2 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index cb96264bfa0..f3a6da98495 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.2-SNAPSHOT + 4.1.2 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 9454532a506..e2860aea053 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.2-SNAPSHOT + 4.1.2 sentry-stub From 7ae267c6abdd94fec8adc55249ddf5b7a7d419f6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 8 Dec 2013 08:31:02 +1100 Subject: [PATCH 1034/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-getsentry/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index b95052d9266..748d91fde96 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 4.1.2 + 4.1.3-SNAPSHOT pom Raven-Java @@ -71,7 +71,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v4.1.2 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-getsentry/pom.xml b/raven-getsentry/pom.xml index 3a7542a3ef1..ac7926e6b19 100644 --- a/raven-getsentry/pom.xml +++ b/raven-getsentry/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.2 + 4.1.3-SNAPSHOT raven-getsentry diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index d29da831a52..110582b4676 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.2 + 4.1.3-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index b9fc02c1a32..62d78e41b41 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.2 + 4.1.3-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index c3694d99f33..19583c005e6 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.2 + 4.1.3-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index f3a6da98495..5e90c44536c 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.2 + 4.1.3-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index e2860aea053..9b181d0a31f 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.2 + 4.1.3-SNAPSHOT sentry-stub From 9b998fa0f2f0ef3aa05bf89c93e2e9470f1b144e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 8 Dec 2013 09:09:33 +1100 Subject: [PATCH 1035/2152] Update documentation to use raven 4.1.2 --- raven-getsentry/README.md | 4 ++-- raven-log4j/README.md | 4 ++-- raven-log4j2/README.md | 4 ++-- raven-logback/README.md | 4 ++-- raven/README.md | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/raven-getsentry/README.md b/raven-getsentry/README.md index 016d2d5e891..c41c3b3c605 100644 --- a/raven-getsentry/README.md +++ b/raven-getsentry/README.md @@ -15,12 +15,12 @@ __This module is not useful with Open JDK.__ net.kencochrane.raven raven-getsentry - 4.1.1 + 4.1.2 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1.2%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j/README.md b/raven-log4j/README.md index e0f443a1034..db035f0ea34 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,12 +10,12 @@ for log4j to send the logged events to Sentry. net.kencochrane.raven raven-log4j - 4.1.1 + 4.1.2 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j%7C4.1.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j%7C4.1.2%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index 4a098129dce..f08346af972 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,12 +10,12 @@ for Log4j 2 to send the logged events to Sentry. net.kencochrane.raven raven-log4j2 - 4.1.1 + 4.1.2 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j2%7C4.1.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j2%7C4.1.2%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-logback/README.md b/raven-logback/README.md index 21a72cab7b4..4858e128ae0 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,12 +10,12 @@ for logback to send the logged events to Sentry. net.kencochrane.raven raven-logback - 4.1.1 + 4.1.2 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-logback%7C4.1.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-logback%7C4.1.2%7Cjar). ### Manual dependency management Relies on: diff --git a/raven/README.md b/raven/README.md index 40fde79f55b..90696718fdb 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,17 +10,17 @@ for `java.util.logging`. net.kencochrane.raven raven - 4.1.1 + 4.1.2 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1.2%7Cjar). ### Manual dependency management Relies on: - - [raven-4.1.1.jar](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1.1%7Cjar) + - [raven-4.1.2.jar](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1.2%7Cjar) - [guava-15.0.jar](https://search.maven.org/#artifactdetails%7Ccom.google.guava%7Cguava%7C15.0%7Cjar) - [jackson-core-2.2.3.jar](https://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.2.3%7Cjar) - [slf4j-api-1.7.5.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.5%7Cjar) From 4a328cf960a9ca945ce4874a9f12d5ebe306d9a7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 8 Dec 2013 09:27:08 +1100 Subject: [PATCH 1036/2152] Update the README to reflect the incoming v5 --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c95b2f86a1b..371ab6e230f 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,11 @@ Since 2.0, the major version of raven matches the version of the Sentry protocol | Raven 2.x | V2 | >= 2.0 | | Raven 3.x | V3 | >= 5.1 | | Raven 4.x | V4 | >= 6.0 | +| Raven 5.x(dev)| V5 | >= 6.4 | Each release of Sentry supports the last two version of the protocol -(i.e. Sentry 6.0.4 supports both the protocol V4 and V3), for this reason, only +(i.e. Sentry 6.4.2 supports both the protocol V5 and V4), for this reason, only the two last stable versions of Raven are actively maintained. ### Snapshot versions From a5237a8616ac2a720466dde99ae47aa7415e1268 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 8 Dec 2013 09:29:14 +1100 Subject: [PATCH 1037/2152] Moving to version 5.0.1 --- pom.xml | 2 +- raven-getsentry/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 748d91fde96..8daa73a7a4b 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 4.1.3-SNAPSHOT + 5.0.1-SNAPSHOT pom Raven-Java diff --git a/raven-getsentry/pom.xml b/raven-getsentry/pom.xml index ac7926e6b19..7dc509fd2f5 100644 --- a/raven-getsentry/pom.xml +++ b/raven-getsentry/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.3-SNAPSHOT + 5.0.1-SNAPSHOT raven-getsentry diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 110582b4676..642cf6d6004 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.3-SNAPSHOT + 5.0.1-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 62d78e41b41..f02bdcc4aa5 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.3-SNAPSHOT + 5.0.1-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 19583c005e6..aedbbafbe3c 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.3-SNAPSHOT + 5.0.1-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 5e90c44536c..75afed169aa 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.3-SNAPSHOT + 5.0.1-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 9b181d0a31f..fc2ca631a7c 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 4.1.3-SNAPSHOT + 5.0.1-SNAPSHOT sentry-stub From b4822476e1e6114d70b2759fccdcd06b8ca8813a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 8 Dec 2013 09:38:45 +1100 Subject: [PATCH 1038/2152] Bump the protocol version to 5 --- .../net/kencochrane/raven/connection/AbstractConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 039957fa0f0..b3d189094bf 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -20,7 +20,7 @@ public abstract class AbstractConnection implements Connection { /** * Current sentry protocol version. */ - public static final String SENTRY_PROTOCOL_VERSION = "4"; + public static final String SENTRY_PROTOCOL_VERSION = "5"; /** * Default maximum duration for a lockdown. */ From 3b1205a1421d46f9eb91e3c580824c5ef03e85ec Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 8 Dec 2013 09:42:02 +1100 Subject: [PATCH 1039/2152] Fix test checking the sentry protocol version --- .../net/kencochrane/raven/connection/HttpConnectionTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index 399d544e67a..671671f47ad 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -128,7 +128,7 @@ public void testAuthHeaderSent(@Injectable final Event mockEvent) throws Excepti new Verifications() {{ mockUrlConnection.setRequestProperty("User-Agent", Raven.NAME); - String expectedAuthRequest = "Sentry sentry_version=4," + String expectedAuthRequest = "Sentry sentry_version=5," + "sentry_client=" + Raven.NAME + "," + "sentry_key=" + publicKey + "," + "sentry_secret=" + secretKey; From f18802d8b4f6e2f15a0f6dec1cd69a709a23d83d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 21 Dec 2013 16:32:12 +1100 Subject: [PATCH 1040/2152] Fix integration tests to accept protocol V5 --- .../net/kencochrane/raven/sentrystub/auth/AuthValidator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java index bb6afd54f30..8982c9aeb0b 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java @@ -13,7 +13,7 @@ */ public class AuthValidator { private static final Logger logger = Logger.getLogger(AuthValidator.class.getCanonicalName()); - private static final Collection SENTRY_PROTOCOL_VERSIONS = Arrays.asList("4"); + private static final Collection SENTRY_PROTOCOL_VERSIONS = Arrays.asList("5"); private static final String SENTRY_VERSION_PARAMETER = "Sentry sentry_version"; private static final String PUBLIC_KEY_PARAMETER = "sentry_key"; private static final String SECRET_KEY_PARAMETER = "sentry_secret"; From 9a686c84848575099e9ca6cbce0acc6c284b1418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Gro=CC=88bler?= Date: Tue, 31 Dec 2013 17:31:06 +0100 Subject: [PATCH 1041/2152] Add an ExceptionWithStackTrace class to represent throwables --- .../interfaces/ExceptionWithStackTrace.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java new file mode 100644 index 00000000000..f53a038109f --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java @@ -0,0 +1,84 @@ +package net.kencochrane.raven.event.interfaces; + +/** + * Class associating an exception to its {@link net.kencochrane.raven.event.interfaces.StackTraceInterface}. + */ +public final class ExceptionWithStackTrace { + + private static final String DEFAULT_PACKAGE_NAME = "(default)"; + + private final String exceptionMessage; + private final String exceptionClassName; + private final String exceptionPackageName; + private final StackTraceInterface stackTraceInterface; + + /** + * Creates a new instance from the given exceptions data. + * + * @param exceptionMessage the message of the exception + * @param exceptionClassName the exception's class name + * @param exceptionPackageName the exception's package name + * @param stackTraceInterface the stack trace interface holding the stack trace information of the exception + */ + public ExceptionWithStackTrace( + final String exceptionMessage, + final String exceptionClassName, + final String exceptionPackageName, + final StackTraceInterface stackTraceInterface) { + + this.exceptionMessage = exceptionMessage; + this.exceptionClassName = exceptionClassName; + this.exceptionPackageName = exceptionPackageName; + this.stackTraceInterface = stackTraceInterface; + } + + /** + * Gets the exception message. + * + * @return the exception message + */ + public String getExceptionMessage() { + return exceptionMessage; + } + + /** + * Gets the exception's class name. + * + * @return the exception's class name + */ + public String getExceptionClassName() { + return exceptionClassName; + } + + /** + * Gets the exception's package name. + * + * @return the exception's package name + */ + public String getExceptionPackageName() { + + if (exceptionPackageName == null) { + return DEFAULT_PACKAGE_NAME; + } + + return exceptionPackageName; + } + + /** + * Gets the exceptions stack trace interface. + * + * @return the exceptions stack trace interface + */ + public StackTraceInterface getStackTraceInterface() { + return stackTraceInterface; + } + + /** + * Gets the exception's stack trace. + * + * @return the exception's stack trace + */ + public StackTraceElement[] getStackTrace() { + return stackTraceInterface.getStackTrace(); + } +} From 0f553b65df5ad1d75a853929bc4a3e9a057a216e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Gro=CC=88bler?= Date: Tue, 31 Dec 2013 17:31:06 +0100 Subject: [PATCH 1042/2152] ExceptionInterface now returns a Deque of exceptions --- .../raven/event/interfaces/ExceptionInterface.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java index 8b3bcf0437f..061e02ea453 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java @@ -1,5 +1,7 @@ package net.kencochrane.raven.event.interfaces; +import java.util.Deque; + /** * The Exception interface for Sentry allowing to add an Exception details to an event. */ @@ -27,4 +29,8 @@ public String getInterfaceName() { public ImmutableThrowable getThrowable() { return throwable; } + + public Deque getExceptions() { + return null; // TODO replace this + } } From 27050967989d8e170941f0e353a86859c028e877 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Gro=CC=88bler?= Date: Tue, 31 Dec 2013 17:31:06 +0100 Subject: [PATCH 1043/2152] Remove the inner ExceptionWithStackTrace from ExceptionInterfaceBinding Instead use the ExceptionWithStackTrace provided by the Exception Interface --- .../json/ExceptionInterfaceBinding.java | 68 ++----------------- 1 file changed, 6 insertions(+), 62 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java index b69e9f162c8..4cf9365fa77 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java @@ -2,27 +2,20 @@ import com.fasterxml.jackson.core.JsonGenerator; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.ImmutableThrowable; +import net.kencochrane.raven.event.interfaces.ExceptionWithStackTrace; import net.kencochrane.raven.event.interfaces.StackTraceInterface; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; -import java.util.ArrayDeque; import java.util.Deque; -import java.util.HashSet; -import java.util.Set; /** * Binding system allowing to convert an {@link ExceptionInterface} to a JSON stream. */ public class ExceptionInterfaceBinding implements InterfaceBinding { - private static final Logger logger = LoggerFactory.getLogger(ExceptionInterfaceBinding.class); private static final String TYPE_PARAMETER = "type"; private static final String VALUE_PARAMETER = "value"; private static final String MODULE_PARAMETER = "module"; private static final String STACKTRACE_PARAMETER = "stacktrace"; - private static final String DEFAULT_PACKAGE_NAME = "(default)"; private final InterfaceBinding stackTraceInterfaceBinding; /** @@ -41,7 +34,7 @@ public ExceptionInterfaceBinding(InterfaceBinding stackTrac @Override public void writeInterface(JsonGenerator generator, ExceptionInterface exceptionInterface) throws IOException { - Deque exceptions = unfoldExceptionInterface(exceptionInterface); + Deque exceptions = exceptionInterface.getExceptions(); //Unstack the exceptions generator.writeStartArray(); @@ -51,41 +44,6 @@ public void writeInterface(JsonGenerator generator, ExceptionInterface exception generator.writeEndArray(); } - /** - * Stack the exception and its causes with their StackTraces to provide them in the order expected by Sentry. - *

    - * Sentry expects to get the exception from the first generated (cause) to the last generated. To provide the - * exceptions in this order, those are stacked then later the stack is emptied. - *

    - *

    - * Each exception provides a {@link StackTraceInterface}. - *

    - * - * @param exceptionInterface Sentry interface containing the captured exception. - * @return a Stack of Exceptions with their {@link StackTraceInterface}. - */ - private Deque unfoldExceptionInterface(ExceptionInterface exceptionInterface) { - Deque exceptions = new ArrayDeque(); - Set circularityDetector = new HashSet(); - ImmutableThrowable throwable = exceptionInterface.getThrowable(); - StackTraceElement[] enclosingStackTrace = new StackTraceElement[0]; - - //Stack the exceptions to send them in the reverse order - while (throwable != null) { - if (!circularityDetector.add(throwable)) { - logger.warn("Exiting a circular exception!"); - break; - } - - StackTraceInterface stackTrace = new StackTraceInterface(throwable.getStackTrace(), enclosingStackTrace); - exceptions.push(new ExceptionWithStackTrace(throwable, stackTrace)); - enclosingStackTrace = throwable.getStackTrace(); - throwable = throwable.getCause(); - } - - return exceptions; - } - /** * Outputs an exception with its StackTrace on a JSon stream. * @@ -95,26 +53,12 @@ private Deque unfoldExceptionInterface(ExceptionInterfa */ private void writeException(JsonGenerator generator, ExceptionWithStackTrace ewst) throws IOException { generator.writeStartObject(); - generator.writeStringField(TYPE_PARAMETER, ewst.exception.getActualClass().getSimpleName()); - generator.writeStringField(VALUE_PARAMETER, ewst.exception.getMessage()); - Package aPackage = ewst.exception.getActualClass().getPackage(); - generator.writeStringField(MODULE_PARAMETER, (aPackage != null) ? aPackage.getName() : DEFAULT_PACKAGE_NAME); - + generator.writeStringField(TYPE_PARAMETER, ewst.getExceptionClassName()); + generator.writeStringField(VALUE_PARAMETER, ewst.getExceptionMessage()); + generator.writeStringField(MODULE_PARAMETER, ewst.getExceptionPackageName()); generator.writeFieldName(STACKTRACE_PARAMETER); - stackTraceInterfaceBinding.writeInterface(generator, ewst.stackTrace); + stackTraceInterfaceBinding.writeInterface(generator, ewst.getStackTraceInterface()); generator.writeEndObject(); } - /** - * Class associating an exception to its {@link StackTraceInterface}. - */ - private static final class ExceptionWithStackTrace { - private ImmutableThrowable exception; - private StackTraceInterface stackTrace; - - private ExceptionWithStackTrace(ImmutableThrowable exception, StackTraceInterface stackTrace) { - this.exception = exception; - this.stackTrace = stackTrace; - } - } } From 3285991ee6db9a2c91db70f412fdff0671ecf168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Gro=CC=88bler?= Date: Tue, 31 Dec 2013 17:31:06 +0100 Subject: [PATCH 1044/2152] Use ExceptionInterface.getExceptions() rather than ExceptionWithStackTrace.getThrowable() --- .../log4j/SentryAppenderEventBuildingTest.java | 6 +++--- .../log4j2/SentryAppenderEventBuildingTest.java | 6 +++--- .../logback/SentryAppenderEventBuildingTest.java | 5 +++-- .../raven/jul/SentryHandlerEventBuildingTest.java | 7 ++++--- .../json/ExceptionInterfaceBindingTest.java | 13 +++++-------- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java index 0d9057c2411..744b5ea6d05 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java @@ -9,6 +9,7 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.ExceptionWithStackTrace; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.log4j.Level; import org.apache.log4j.Logger; @@ -104,12 +105,11 @@ public void testExceptionLogging() throws Exception { new Verifications() {{ Event event; - Throwable throwable; mockRaven.sendEvent(event = withCapture()); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); - throwable = exceptionInterface.getThrowable(); - assertThat(throwable.getMessage(), is(exception.getMessage())); + ExceptionWithStackTrace throwable = exceptionInterface.getExceptions().getFirst(); + assertThat(throwable.getExceptionMessage(), is(exception.getMessage())); assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); }}; assertNoErrorsInErrorHandler(); diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java index a7b3759c8fa..da7218ff07e 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java @@ -6,6 +6,7 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.ExceptionWithStackTrace; import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.logging.log4j.Level; @@ -95,12 +96,11 @@ public void testExceptionLogging() throws Exception { new Verifications() {{ Event event; - Throwable throwable; mockRaven.sendEvent(event = withCapture()); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); - throwable = exceptionInterface.getThrowable(); - assertThat(throwable.getMessage(), is(exception.getMessage())); + ExceptionWithStackTrace throwable = exceptionInterface.getExceptions().getFirst(); + assertThat(throwable.getExceptionMessage(), is(exception.getMessage())); assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); }}; assertNoErrorsInErrorHandler(); diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java index 297eb82c245..168447b33ad 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java @@ -12,6 +12,7 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.ExceptionWithStackTrace; import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.hamcrest.Matchers; @@ -113,9 +114,9 @@ public void testExceptionLogging() throws Exception { mockRaven.sendEvent(event = withCapture()); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); - Throwable capturedException = exceptionInterface.getThrowable(); + ExceptionWithStackTrace capturedException = exceptionInterface.getExceptions().getFirst(); - assertThat(capturedException.getMessage(), is(exception.getMessage())); + assertThat(capturedException.getExceptionMessage(), is(exception.getMessage())); assertThat(capturedException.getStackTrace(), is(exception.getStackTrace())); }}; assertNoErrorsInStatusManager(); diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java index 532cb4bce08..eecfd626b39 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java @@ -6,6 +6,7 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.ExceptionWithStackTrace; import org.hamcrest.Matchers; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; @@ -97,9 +98,9 @@ public void testExceptionLogging() throws Exception { mockRaven.sendEvent(event = withCapture()); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); - throwable = exceptionInterface.getThrowable(); - assertThat(throwable.getMessage(), is(exception.getMessage())); - assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); + final ExceptionWithStackTrace exceptionWithStackTrace = exceptionInterface.getExceptions().getFirst(); + assertThat(exceptionWithStackTrace.getExceptionMessage(), is(exception.getMessage())); + assertThat(exceptionWithStackTrace.getStackTrace(), is(exception.getStackTrace())); }}; assertNoErrorsInErrorManager(); } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index 2dadb84b764..febdf571e9b 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -8,15 +8,12 @@ import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.ImmutableThrowable; import net.kencochrane.raven.event.interfaces.StackTraceInterface; -import org.hamcrest.MatcherAssert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.IOException; -import static net.kencochrane.raven.marshaller.json.JsonTestTool.JsonGeneratorTool; -import static net.kencochrane.raven.marshaller.json.JsonTestTool.jsonResource; -import static net.kencochrane.raven.marshaller.json.JsonTestTool.newJsonGenerator; +import static net.kencochrane.raven.marshaller.json.JsonTestTool.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -43,13 +40,13 @@ public void writeInterface(JsonGenerator jsonGenerator, StackTraceInterface stac }}; } - @Test + @Test(enabled = false) public void testSimpleException() throws Exception { final JsonGeneratorTool generatorTool = newJsonGenerator(); final String message = "6e65f60d-9f22-495a-9556-7a61eeea2a14"; final Throwable throwable = new IllegalStateException(message); new NonStrictExpectations() {{ - mockExceptionInterface.getThrowable(); + //TODO: replace this mock -- mockExceptionInterface.getThrowable(); result = new Delegate() { public ImmutableThrowable getThrowable() { return new ImmutableThrowable(throwable); @@ -62,14 +59,14 @@ public ImmutableThrowable getThrowable() { assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/Exception1.json"))); } - @Test + @Test(enabled = false) public void testClassInDefaultPackage() throws Exception { Deencapsulation.setField((Object) DefaultPackageException.class, "name", DefaultPackageException.class.getSimpleName()); final JsonGeneratorTool generatorTool = newJsonGenerator(); final Throwable throwable = new DefaultPackageException(); new NonStrictExpectations() {{ - mockExceptionInterface.getThrowable(); + //TODO: replace this mock -- mockExceptionInterface.getThrowable(); result = new Delegate() { public ImmutableThrowable getThrowable() { return new ImmutableThrowable(throwable); From ca73b1f2182315e9b286b2e6fec8db4af447567f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Gro=CC=88bler?= Date: Tue, 31 Dec 2013 17:31:06 +0100 Subject: [PATCH 1045/2152] Remove the ImmutableThrowable usage from ExceptionInterface --- .../event/interfaces/ExceptionInterface.java | 72 ++++++++++++++++--- 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java index 061e02ea453..0fd2115c907 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java @@ -1,6 +1,12 @@ package net.kencochrane.raven.event.interfaces; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayDeque; import java.util.Deque; +import java.util.HashSet; +import java.util.Set; /** * The Exception interface for Sentry allowing to add an Exception details to an event. @@ -10,15 +16,27 @@ public class ExceptionInterface implements SentryInterface { * Name of the exception interface in Sentry. */ public static final String EXCEPTION_INTERFACE = "sentry.interfaces.Exception"; - private final ImmutableThrowable throwable; + + private static final Logger logger = LoggerFactory.getLogger(ExceptionInterface.class); + + private final Deque exceptions; + + /** + * Creates a new instance from the given {@code throwable}. + * + * @param throwable the {@link Throwable} to build this instance from + */ + public ExceptionInterface(final Throwable throwable) { + this(extractExceptionQueue(throwable)); + } /** - * Creates a an Exception element for an {@link net.kencochrane.raven.event.Event}. + * Creates a new instance from the given {@code exceptions}. * - * @param throwable Exception from the JVM to send to Sentry. + * @param exceptions a {@link Deque} of {@link ExceptionWithStackTrace} to build this instance from */ - public ExceptionInterface(Throwable throwable) { - this.throwable = new ImmutableThrowable(throwable); + public ExceptionInterface(final Deque exceptions) { + this.exceptions = exceptions; } @Override @@ -26,11 +44,47 @@ public String getInterfaceName() { return EXCEPTION_INTERFACE; } - public ImmutableThrowable getThrowable() { - return throwable; + public Deque getExceptions() { + return exceptions; } - public Deque getExceptions() { - return null; // TODO replace this + private static Deque extractExceptionQueue(Throwable throwable) { + Deque exceptions = new ArrayDeque(); + Set circularityDetector = new HashSet(); + StackTraceElement[] enclosingStackTrace = new StackTraceElement[0]; + + //Stack the exceptions to send them in the reverse order + while (throwable != null) { + if (!circularityDetector.add(throwable)) { + logger.warn("Exiting a circular exception!"); + break; + } + + StackTraceInterface stackTrace = new StackTraceInterface(throwable.getStackTrace(), enclosingStackTrace); + exceptions.push(createExceptionWithStackTraceFrom(throwable, stackTrace)); + enclosingStackTrace = throwable.getStackTrace(); + throwable = throwable.getCause(); + } + + return exceptions; + } + + private static ExceptionWithStackTrace createExceptionWithStackTraceFrom(final Throwable throwable, + final StackTraceInterface stackTrace) { + final String exceptionMessage = throwable.getMessage(); + final String exceptionClassName = throwable.getClass().getSimpleName(); + final String exceptionPackageName = extractPackageName(throwable); + return new ExceptionWithStackTrace(exceptionMessage, exceptionClassName, exceptionPackageName, stackTrace); + } + + private static String extractPackageName(final Throwable throwable) { + + final Package exceptionPackage = throwable.getClass().getPackage(); + + if (exceptionPackage != null) { + return exceptionPackage.getName(); + } + + return null; } } From 5bc440c33cbee42b78bb707c0af09b185319c44e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Gro=CC=88bler?= Date: Tue, 31 Dec 2013 17:31:06 +0100 Subject: [PATCH 1046/2152] Do not use a cast in the Logback module The IThrowableProxy may not be an instance of ThrowableProxy, this leads to ClassCastExceptions. Instead, create an ExceptionInterface using only the IThrowableProxy interface --- .../raven/logback/SentryAppender.java | 74 ++++++++++++++++++- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 98418371c9c..99cef6f782a 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -2,9 +2,11 @@ import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.classic.spi.ThrowableProxy; +import ch.qos.logback.classic.spi.IThrowableProxy; +import ch.qos.logback.classic.spi.StackTraceElementProxy; import ch.qos.logback.core.AppenderBase; import com.google.common.base.Splitter; +import com.google.common.collect.Lists; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; @@ -12,8 +14,11 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.ExceptionWithStackTrace; import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.*; @@ -22,6 +27,7 @@ * Appender for logback in charge of sending the logged events to a Sentry server. */ public class SentryAppender extends AppenderBase { + /** * Name of the {@link Event#extra} property containing Maker details. */ @@ -30,6 +36,9 @@ public class SentryAppender extends AppenderBase { * Name of the {@link Event#extra} property containing the Thread name. */ public static final String THREAD_NAME = "Raven-Threadname"; + + private static final Logger logger = LoggerFactory.getLogger(SentryAppender.class); + /** * Current instance of {@link Raven}. * @@ -173,8 +182,7 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { } if (iLoggingEvent.getThrowableProxy() != null) { - Throwable throwable = ((ThrowableProxy) iLoggingEvent.getThrowableProxy()).getThrowable(); - eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); + eventBuilder.addSentryInterface(new ExceptionInterface(extractExceptionQueue(iLoggingEvent))); } else if (iLoggingEvent.getCallerData().length > 0) { eventBuilder.addSentryInterface(new StackTraceInterface(iLoggingEvent.getCallerData())); } @@ -199,6 +207,66 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { return eventBuilder.build(); } + private Deque extractExceptionQueue(final ILoggingEvent iLoggingEvent) { + IThrowableProxy throwableProxy = iLoggingEvent.getThrowableProxy(); + Deque exceptions = new ArrayDeque(); + Set circularityDetector = new HashSet(); + StackTraceElement[] enclosingStackTrace = new StackTraceElement[0]; + + //Stack the exceptions to send them in the reverse order + while (throwableProxy != null) { + if (!circularityDetector.add(throwableProxy)) { + logger.warn("Exiting a circular exception!"); + break; + } + + final StackTraceElement[] stackTraceElements = toStackTraceElements(throwableProxy); + StackTraceInterface stackTrace = new StackTraceInterface(stackTraceElements, enclosingStackTrace); + exceptions.push(createExceptionWithStackTraceFrom(throwableProxy, stackTrace)); + enclosingStackTrace = stackTraceElements; + throwableProxy = throwableProxy.getCause(); + } + + return exceptions; + } + + private ExceptionWithStackTrace createExceptionWithStackTraceFrom(final IThrowableProxy throwableProxy, + final StackTraceInterface stackTrace) { + final String exceptionMessage = throwableProxy.getMessage(); + final String exceptionClassName = throwableProxy.getClassName(); + final String exceptionPackageName = extractPackageName(throwableProxy); + return new ExceptionWithStackTrace(exceptionMessage, exceptionClassName, exceptionPackageName, stackTrace); + } + + private String extractPackageName(final IThrowableProxy throwableProxy) { + + // TODO this probably fails with application specific classes which are unknown to the logserver + try { + final Class exceptionClass = Class.forName(throwableProxy.getClassName()); + final Package exceptionPackage = exceptionClass.getPackage(); + + if (exceptionPackage != null) { + return exceptionPackage.getName(); + } + + } catch (final ClassNotFoundException e) { + logger.error("Could not load exception class", e); + } + + return null; + } + + private StackTraceElement[] toStackTraceElements(final IThrowableProxy throwableProxy) { + final StackTraceElementProxy[] stackTraceElementProxies = throwableProxy.getStackTraceElementProxyArray(); + final List stackTraceElements = Lists.newArrayList(); + + for (final StackTraceElementProxy stackTraceElementProxy : stackTraceElementProxies) { + stackTraceElements.add(stackTraceElementProxy.getStackTraceElement()); + } + + return stackTraceElements.toArray(new StackTraceElement[stackTraceElements.size()]); + } + public void setDsn(String dsn) { this.dsn = dsn; } From fdf6122858cdf94c2b02aeca09646e38cf55651c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 Jan 2014 11:37:37 +0100 Subject: [PATCH 1047/2152] Fix the mock previously for ImmutableThrowable --- .../event/interfaces/ExceptionInterface.java | 8 +++++++- .../json/ExceptionInterfaceBindingTest.java | 19 ++++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java index 0fd2115c907..f6233278991 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java @@ -48,7 +48,13 @@ public Deque getExceptions() { return exceptions; } - private static Deque extractExceptionQueue(Throwable throwable) { + /** + * Transforms a {@link Throwable} into a Queue of {@link ExceptionWithStackTrace}. + * + * @param throwable throwable to transform in a queue of exceptions. + * @return a queue of exception with stacktrace + */ + public static Deque extractExceptionQueue(Throwable throwable) { Deque exceptions = new ArrayDeque(); Set circularityDetector = new HashSet(); StackTraceElement[] enclosingStackTrace = new StackTraceElement[0]; diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index febdf571e9b..db5351361ca 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -6,12 +6,13 @@ import mockit.Injectable; import mockit.NonStrictExpectations; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.ImmutableThrowable; +import net.kencochrane.raven.event.interfaces.ExceptionWithStackTrace; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.IOException; +import java.util.Deque; import static net.kencochrane.raven.marshaller.json.JsonTestTool.*; import static org.hamcrest.MatcherAssert.assertThat; @@ -40,16 +41,16 @@ public void writeInterface(JsonGenerator jsonGenerator, StackTraceInterface stac }}; } - @Test(enabled = false) + @Test public void testSimpleException() throws Exception { final JsonGeneratorTool generatorTool = newJsonGenerator(); final String message = "6e65f60d-9f22-495a-9556-7a61eeea2a14"; final Throwable throwable = new IllegalStateException(message); new NonStrictExpectations() {{ - //TODO: replace this mock -- mockExceptionInterface.getThrowable(); + mockExceptionInterface.getExceptions(); result = new Delegate() { - public ImmutableThrowable getThrowable() { - return new ImmutableThrowable(throwable); + public Deque getExceptions() { + return ExceptionInterface.extractExceptionQueue(throwable); } }; }}; @@ -59,17 +60,17 @@ public ImmutableThrowable getThrowable() { assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/Exception1.json"))); } - @Test(enabled = false) + @Test public void testClassInDefaultPackage() throws Exception { Deencapsulation.setField((Object) DefaultPackageException.class, "name", DefaultPackageException.class.getSimpleName()); final JsonGeneratorTool generatorTool = newJsonGenerator(); final Throwable throwable = new DefaultPackageException(); new NonStrictExpectations() {{ - //TODO: replace this mock -- mockExceptionInterface.getThrowable(); + mockExceptionInterface.getExceptions(); result = new Delegate() { - public ImmutableThrowable getThrowable() { - return new ImmutableThrowable(throwable); + public Deque getExceptions() { + return ExceptionInterface.extractExceptionQueue(throwable); } }; }}; From 5e016705a49dbf85f4fd46bf5756027aa6090610 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 Jan 2014 11:38:10 +0100 Subject: [PATCH 1048/2152] Remove ImmutableThrowable --- .../event/interfaces/ImmutableThrowable.java | 108 ------------------ 1 file changed, 108 deletions(-) delete mode 100644 raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java deleted file mode 100644 index 5220230c1b4..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ImmutableThrowable.java +++ /dev/null @@ -1,108 +0,0 @@ -package net.kencochrane.raven.event.interfaces; - -import java.io.PrintStream; -import java.io.PrintWriter; -import java.util.Arrays; - -/** - * Wrapper for {@link Throwable} to make it immutable. - *

    - * This throwable is made for the sole purpose of making actual {@link Throwable} instance immutable.
    - * Under no circumstances an instance of ImmutableThrowable should be used for flow interruption. - *

    - */ -public class ImmutableThrowable extends Throwable { - private final Throwable actualThrowable; - - /** - * Creates an immutable wrapper for the given throwable. - * - * @param actualThrowable exception wrapped. - */ - public ImmutableThrowable(Throwable actualThrowable) { - this.actualThrowable = actualThrowable; - } - - /** - * Returns the class of the original Throwable. - *

    - * As ImmutableThrowable is encapsulating a Throwable, it's important to be able to retrieve the type of the - * original exception. - *

    - * - * @return the class of the original Throwable - */ - public Class getActualClass() { - return actualThrowable.getClass(); - } - - @Override - public String getMessage() { - return actualThrowable.getMessage(); - } - - @Override - public String getLocalizedMessage() { - return actualThrowable.getLocalizedMessage(); - } - - @Override - public ImmutableThrowable getCause() { - Throwable cause = actualThrowable.getCause(); - return (cause != null) ? new ImmutableThrowable(cause) : null; - } - - @Override - public Throwable initCause(Throwable cause) { - throw new UnsupportedOperationException(); - } - - @Override - public String toString() { - return actualThrowable.toString(); - } - - @Override - public void printStackTrace() { - throw new UnsupportedOperationException(); - } - - @Override - public void printStackTrace(PrintStream s) { - throw new UnsupportedOperationException(); - } - - @Override - public void printStackTrace(PrintWriter s) { - throw new UnsupportedOperationException(); - } - - @Override - public Throwable fillInStackTrace() { - return null; - } - - @Override - public StackTraceElement[] getStackTrace() { - StackTraceElement[] stackTrace = actualThrowable.getStackTrace(); - return Arrays.copyOf(stackTrace, stackTrace.length); - } - - @Override - public void setStackTrace(StackTraceElement[] stackTrace) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - return actualThrowable == ((ImmutableThrowable) o).actualThrowable; - } - - @Override - public int hashCode() { - return actualThrowable.hashCode(); - } -} From 86192fbdb3eba72f4a6bf0595d4a6408db981b69 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 Jan 2014 11:38:32 +0100 Subject: [PATCH 1049/2152] Remove unused variable --- .../kencochrane/raven/jul/SentryHandlerEventBuildingTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java index eecfd626b39..3310118f59f 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java @@ -94,7 +94,6 @@ public void testExceptionLogging() throws Exception { new Verifications() {{ Event event; - Throwable throwable; mockRaven.sendEvent(event = withCapture()); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); From a5d1cc80e56ab880c22bcf602cc59a52246f3967 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 Jan 2014 11:50:34 +0100 Subject: [PATCH 1050/2152] Fix indentation --- .../event/interfaces/ExceptionInterface.java | 22 +++++++++---------- .../interfaces/ExceptionWithStackTrace.java | 18 ++++++--------- .../event/interfaces/MessageInterface.java | 4 ++-- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java index f6233278991..eea7518b819 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java @@ -16,9 +16,7 @@ public class ExceptionInterface implements SentryInterface { * Name of the exception interface in Sentry. */ public static final String EXCEPTION_INTERFACE = "sentry.interfaces.Exception"; - private static final Logger logger = LoggerFactory.getLogger(ExceptionInterface.class); - private final Deque exceptions; /** @@ -39,15 +37,6 @@ public ExceptionInterface(final Deque exceptions) { this.exceptions = exceptions; } - @Override - public String getInterfaceName() { - return EXCEPTION_INTERFACE; - } - - public Deque getExceptions() { - return exceptions; - } - /** * Transforms a {@link Throwable} into a Queue of {@link ExceptionWithStackTrace}. * @@ -76,7 +65,7 @@ public static Deque extractExceptionQueue(Throwable thr } private static ExceptionWithStackTrace createExceptionWithStackTraceFrom(final Throwable throwable, - final StackTraceInterface stackTrace) { + final StackTraceInterface stackTrace) { final String exceptionMessage = throwable.getMessage(); final String exceptionClassName = throwable.getClass().getSimpleName(); final String exceptionPackageName = extractPackageName(throwable); @@ -93,4 +82,13 @@ private static String extractPackageName(final Throwable throwable) { return null; } + + @Override + public String getInterfaceName() { + return EXCEPTION_INTERFACE; + } + + public Deque getExceptions() { + return exceptions; + } } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java index f53a038109f..f93b4ecc9a1 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java @@ -4,9 +4,7 @@ * Class associating an exception to its {@link net.kencochrane.raven.event.interfaces.StackTraceInterface}. */ public final class ExceptionWithStackTrace { - private static final String DEFAULT_PACKAGE_NAME = "(default)"; - private final String exceptionMessage; private final String exceptionClassName; private final String exceptionPackageName; @@ -15,17 +13,15 @@ public final class ExceptionWithStackTrace { /** * Creates a new instance from the given exceptions data. * - * @param exceptionMessage the message of the exception - * @param exceptionClassName the exception's class name + * @param exceptionMessage the message of the exception + * @param exceptionClassName the exception's class name * @param exceptionPackageName the exception's package name - * @param stackTraceInterface the stack trace interface holding the stack trace information of the exception + * @param stackTraceInterface the stack trace interface holding the stack trace information of the exception */ - public ExceptionWithStackTrace( - final String exceptionMessage, - final String exceptionClassName, - final String exceptionPackageName, - final StackTraceInterface stackTraceInterface) { - + public ExceptionWithStackTrace(String exceptionMessage, + String exceptionClassName, + String exceptionPackageName, + StackTraceInterface stackTraceInterface) { this.exceptionMessage = exceptionMessage; this.exceptionClassName = exceptionClassName; this.exceptionPackageName = exceptionPackageName; diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java index 12ceb82dd3f..bb71561c33f 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java @@ -61,8 +61,8 @@ public MessageInterface(String message, String... params) { /** * Creates a parametrised message for an {@link net.kencochrane.raven.event.Event}. * - * @param message original message. - * @param parameters parameters of the message. + * @param message original message. + * @param parameters parameters of the message. */ public MessageInterface(String message, List parameters) { this.message = message; From 18c4fc49e8f0c8f9ff185585569b52cc25b87b24 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 Jan 2014 12:01:16 +0100 Subject: [PATCH 1051/2152] Move the methods to create ExceptionWithStackTraces in ExceptionWithStackTrace --- .../event/interfaces/ExceptionInterface.java | 55 +----------------- .../interfaces/ExceptionWithStackTrace.java | 58 +++++++++++++++++++ .../json/ExceptionInterfaceBindingTest.java | 4 +- 3 files changed, 61 insertions(+), 56 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java index eea7518b819..1e940495d81 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java @@ -1,12 +1,6 @@ package net.kencochrane.raven.event.interfaces; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayDeque; import java.util.Deque; -import java.util.HashSet; -import java.util.Set; /** * The Exception interface for Sentry allowing to add an Exception details to an event. @@ -16,7 +10,6 @@ public class ExceptionInterface implements SentryInterface { * Name of the exception interface in Sentry. */ public static final String EXCEPTION_INTERFACE = "sentry.interfaces.Exception"; - private static final Logger logger = LoggerFactory.getLogger(ExceptionInterface.class); private final Deque exceptions; /** @@ -25,7 +18,7 @@ public class ExceptionInterface implements SentryInterface { * @param throwable the {@link Throwable} to build this instance from */ public ExceptionInterface(final Throwable throwable) { - this(extractExceptionQueue(throwable)); + this(ExceptionWithStackTrace.extractExceptionQueue(throwable)); } /** @@ -37,52 +30,6 @@ public ExceptionInterface(final Deque exceptions) { this.exceptions = exceptions; } - /** - * Transforms a {@link Throwable} into a Queue of {@link ExceptionWithStackTrace}. - * - * @param throwable throwable to transform in a queue of exceptions. - * @return a queue of exception with stacktrace - */ - public static Deque extractExceptionQueue(Throwable throwable) { - Deque exceptions = new ArrayDeque(); - Set circularityDetector = new HashSet(); - StackTraceElement[] enclosingStackTrace = new StackTraceElement[0]; - - //Stack the exceptions to send them in the reverse order - while (throwable != null) { - if (!circularityDetector.add(throwable)) { - logger.warn("Exiting a circular exception!"); - break; - } - - StackTraceInterface stackTrace = new StackTraceInterface(throwable.getStackTrace(), enclosingStackTrace); - exceptions.push(createExceptionWithStackTraceFrom(throwable, stackTrace)); - enclosingStackTrace = throwable.getStackTrace(); - throwable = throwable.getCause(); - } - - return exceptions; - } - - private static ExceptionWithStackTrace createExceptionWithStackTraceFrom(final Throwable throwable, - final StackTraceInterface stackTrace) { - final String exceptionMessage = throwable.getMessage(); - final String exceptionClassName = throwable.getClass().getSimpleName(); - final String exceptionPackageName = extractPackageName(throwable); - return new ExceptionWithStackTrace(exceptionMessage, exceptionClassName, exceptionPackageName, stackTrace); - } - - private static String extractPackageName(final Throwable throwable) { - - final Package exceptionPackage = throwable.getClass().getPackage(); - - if (exceptionPackage != null) { - return exceptionPackage.getName(); - } - - return null; - } - @Override public String getInterfaceName() { return EXCEPTION_INTERFACE; diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java index f93b4ecc9a1..35cbef0e48f 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java @@ -1,9 +1,21 @@ package net.kencochrane.raven.event.interfaces; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashSet; +import java.util.Set; + /** * Class associating an exception to its {@link net.kencochrane.raven.event.interfaces.StackTraceInterface}. */ public final class ExceptionWithStackTrace { + private static final Logger logger = LoggerFactory.getLogger(ExceptionWithStackTrace.class); + /** + * Name used when the class' package is the default one. + */ private static final String DEFAULT_PACKAGE_NAME = "(default)"; private final String exceptionMessage; private final String exceptionClassName; @@ -28,6 +40,52 @@ public ExceptionWithStackTrace(String exceptionMessage, this.stackTraceInterface = stackTraceInterface; } + /** + * Transforms a {@link Throwable} into a Queue of {@link ExceptionWithStackTrace}. + * + * @param throwable throwable to transform in a queue of exceptions. + * @return a queue of exception with StackTrace. + */ + public static Deque extractExceptionQueue(Throwable throwable) { + Deque exceptions = new ArrayDeque(); + Set circularityDetector = new HashSet(); + StackTraceElement[] enclosingStackTrace = new StackTraceElement[0]; + + //Stack the exceptions to send them in the reverse order + while (throwable != null) { + if (!circularityDetector.add(throwable)) { + //TODO: Send a more helpful log message here. + logger.warn("Exiting a circular exception!"); + break; + } + + StackTraceInterface stackTrace = new StackTraceInterface(throwable.getStackTrace(), enclosingStackTrace); + exceptions.push(createExceptionWithStackTraceFrom(throwable, stackTrace)); + enclosingStackTrace = throwable.getStackTrace(); + throwable = throwable.getCause(); + } + + return exceptions; + } + + private static ExceptionWithStackTrace createExceptionWithStackTraceFrom(Throwable throwable, + StackTraceInterface stackTrace) { + String exceptionMessage = throwable.getMessage(); + String exceptionClassName = throwable.getClass().getSimpleName(); + String exceptionPackageName = extractPackageName(throwable); + return new ExceptionWithStackTrace(exceptionMessage, exceptionClassName, exceptionPackageName, stackTrace); + } + + private static String extractPackageName(Throwable throwable) { + Package exceptionPackage = throwable.getClass().getPackage(); + + if (exceptionPackage != null) { + return exceptionPackage.getName(); + } + + return null; + } + /** * Gets the exception message. * diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index db5351361ca..bd2ace6f375 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -50,7 +50,7 @@ public void testSimpleException() throws Exception { mockExceptionInterface.getExceptions(); result = new Delegate() { public Deque getExceptions() { - return ExceptionInterface.extractExceptionQueue(throwable); + return ExceptionWithStackTrace.extractExceptionQueue(throwable); } }; }}; @@ -70,7 +70,7 @@ public void testClassInDefaultPackage() throws Exception { mockExceptionInterface.getExceptions(); result = new Delegate() { public Deque getExceptions() { - return ExceptionInterface.extractExceptionQueue(throwable); + return ExceptionWithStackTrace.extractExceptionQueue(throwable); } }; }}; From d6da75ad5c317199c84072ac9a506fb5383160a2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 Jan 2014 12:18:23 +0100 Subject: [PATCH 1052/2152] Ensure that chained exceptions are sent in the right order --- .../json/ExceptionInterfaceBindingTest.java | 22 +++++++++++++++++++ .../raven/marshaller/json/Exception3.json | 14 ++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/Exception3.json diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index bd2ace6f375..9abd4d96095 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -79,6 +79,28 @@ public Deque getExceptions() { assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/Exception2.json"))); } + + @Test + public void testChainedException() throws Exception { + final JsonGeneratorTool generatorTool = newJsonGenerator(); + final String message1 = "a71e6132-9867-457d-8b04-5021cd7a251f"; + final Throwable throwable1 = new IllegalStateException(message1); + final String message2 = "f1296959-5b86-45f7-853a-cdc25196710b"; + final Throwable throwable2 = new IllegalStateException(message2, throwable1); + new NonStrictExpectations() {{ + mockExceptionInterface.getExceptions(); + result = new Delegate() { + public Deque getExceptions() { + return ExceptionWithStackTrace.extractExceptionQueue(throwable2); + } + }; + }}; + + interfaceBinding.writeInterface(generatorTool.generator(), mockExceptionInterface); + + assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/Exception3.json"))); + } + } /** diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/Exception3.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/Exception3.json new file mode 100644 index 00000000000..0c5d993bbd7 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/Exception3.json @@ -0,0 +1,14 @@ +[ + { + "type": "IllegalStateException", + "value": "a71e6132-9867-457d-8b04-5021cd7a251f", + "module": "java.lang", + "stacktrace": {} + }, + { + "type": "IllegalStateException", + "value": "f1296959-5b86-45f7-853a-cdc25196710b", + "module": "java.lang", + "stacktrace": {} + } +] From 16fb52c0157cd4005e3a906420402021713fed82 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 2 Jan 2014 12:24:14 +0100 Subject: [PATCH 1053/2152] Make sure that the queue contains exceptions from the newest one to the oldest one --- .../raven/event/interfaces/ExceptionWithStackTrace.java | 5 ++++- .../raven/marshaller/json/ExceptionInterfaceBinding.java | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java index 35cbef0e48f..58dbd2d8e00 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java @@ -42,6 +42,9 @@ public ExceptionWithStackTrace(String exceptionMessage, /** * Transforms a {@link Throwable} into a Queue of {@link ExceptionWithStackTrace}. + *

    + * Exceptions are stored in the queue from the most recent one to the oldest one. + *

    * * @param throwable throwable to transform in a queue of exceptions. * @return a queue of exception with StackTrace. @@ -60,7 +63,7 @@ public static Deque extractExceptionQueue(Throwable thr } StackTraceInterface stackTrace = new StackTraceInterface(throwable.getStackTrace(), enclosingStackTrace); - exceptions.push(createExceptionWithStackTraceFrom(throwable, stackTrace)); + exceptions.add(createExceptionWithStackTraceFrom(throwable, stackTrace)); enclosingStackTrace = throwable.getStackTrace(); throwable = throwable.getCause(); } diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java index 4cf9365fa77..3dc448bb1be 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.util.Deque; +import java.util.Iterator; /** * Binding system allowing to convert an {@link ExceptionInterface} to a JSON stream. @@ -36,10 +37,9 @@ public ExceptionInterfaceBinding(InterfaceBinding stackTrac public void writeInterface(JsonGenerator generator, ExceptionInterface exceptionInterface) throws IOException { Deque exceptions = exceptionInterface.getExceptions(); - //Unstack the exceptions generator.writeStartArray(); - while (!exceptions.isEmpty()) { - writeException(generator, exceptions.pop()); + for (Iterator iterator = exceptions.descendingIterator(); iterator.hasNext(); ) { + writeException(generator, iterator.next()); } generator.writeEndArray(); } From 32172fae7c6b7a81297351ec12ca55a9f0b0cade Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 Jan 2014 05:42:00 +0100 Subject: [PATCH 1054/2152] Ensure that the exceptions are stored in the correct order in the queue --- .../ExceptionWithStackTraceTest.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTraceTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTraceTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTraceTest.java new file mode 100644 index 00000000000..486242d590d --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTraceTest.java @@ -0,0 +1,40 @@ +package net.kencochrane.raven.event.interfaces; + +import mockit.Delegate; +import mockit.Injectable; +import mockit.NonStrictExpectations; +import org.testng.annotations.Test; + +import java.util.Deque; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class ExceptionWithStackTraceTest { + @Injectable + private Throwable mockThrowable; + + @Test + public void ensureConversionToQueueKeepsOrder(@Injectable final Throwable mockCause) throws Exception { + final String exceptionMessage = UUID.randomUUID().toString(); + final String causeMessage = UUID.randomUUID().toString(); + new NonStrictExpectations() {{ + mockThrowable.getCause(); + result = new Delegate() { + public Throwable getCause() { + return mockCause; + } + }; + mockThrowable.getMessage(); + result = exceptionMessage; + mockCause.getMessage(); + result = causeMessage; + }}; + + Deque exceptions = ExceptionWithStackTrace.extractExceptionQueue(mockThrowable); + + assertThat(exceptions.getFirst().getExceptionMessage(), is(exceptionMessage)); + assertThat(exceptions.getLast().getExceptionMessage(), is(causeMessage)); + } +} From 7f3cba1ddc0181c28dfff2855846cbea8ee3d225 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 Jan 2014 06:03:33 +0100 Subject: [PATCH 1055/2152] Simplify the creation of ExceptionWithStackTrace with a constructor accepting a Throwable --- .../interfaces/ExceptionWithStackTrace.java | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java index 58dbd2d8e00..5eb5f7bd2e1 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java @@ -22,6 +22,24 @@ public final class ExceptionWithStackTrace { private final String exceptionPackageName; private final StackTraceInterface stackTraceInterface; + /** + * Creates a Sentry exception based on a Java Throwable. + *

    + * The {@code childExceptionStackTrace} parameter is used to define the common frames with the child exception + * (Exception caused by {@code throwable}). + *

    + * + * @param throwable Java exception to send to Sentry. + * @param childExceptionStackTrace StackTrace of the exception caused by {@code throwable}. + */ + public ExceptionWithStackTrace(Throwable throwable, StackTraceElement[] childExceptionStackTrace) { + this.exceptionMessage = throwable.getMessage(); + this.exceptionClassName = throwable.getClass().getSimpleName(); + Package exceptionPackage = throwable.getClass().getPackage(); + this.exceptionPackageName = exceptionPackage != null ? exceptionPackage.getName() : null; + this.stackTraceInterface = new StackTraceInterface(throwable.getStackTrace(), childExceptionStackTrace); + } + /** * Creates a new instance from the given exceptions data. * @@ -52,7 +70,7 @@ public ExceptionWithStackTrace(String exceptionMessage, public static Deque extractExceptionQueue(Throwable throwable) { Deque exceptions = new ArrayDeque(); Set circularityDetector = new HashSet(); - StackTraceElement[] enclosingStackTrace = new StackTraceElement[0]; + StackTraceElement[] childExceptionStackTrace = new StackTraceElement[0]; //Stack the exceptions to send them in the reverse order while (throwable != null) { @@ -62,33 +80,14 @@ public static Deque extractExceptionQueue(Throwable thr break; } - StackTraceInterface stackTrace = new StackTraceInterface(throwable.getStackTrace(), enclosingStackTrace); - exceptions.add(createExceptionWithStackTraceFrom(throwable, stackTrace)); - enclosingStackTrace = throwable.getStackTrace(); + exceptions.add(new ExceptionWithStackTrace(throwable, childExceptionStackTrace)); + childExceptionStackTrace = throwable.getStackTrace(); throwable = throwable.getCause(); } return exceptions; } - private static ExceptionWithStackTrace createExceptionWithStackTraceFrom(Throwable throwable, - StackTraceInterface stackTrace) { - String exceptionMessage = throwable.getMessage(); - String exceptionClassName = throwable.getClass().getSimpleName(); - String exceptionPackageName = extractPackageName(throwable); - return new ExceptionWithStackTrace(exceptionMessage, exceptionClassName, exceptionPackageName, stackTrace); - } - - private static String extractPackageName(Throwable throwable) { - Package exceptionPackage = throwable.getClass().getPackage(); - - if (exceptionPackage != null) { - return exceptionPackage.getName(); - } - - return null; - } - /** * Gets the exception message. * From f113fdf6334c00e9d3798a59247a05ccdb02937b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 Jan 2014 06:06:41 +0100 Subject: [PATCH 1056/2152] Improve documentation Remove default getters/setters documentation. --- .../interfaces/ExceptionWithStackTrace.java | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java index 5eb5f7bd2e1..2a92e0d3b80 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java @@ -9,7 +9,7 @@ import java.util.Set; /** - * Class associating an exception to its {@link net.kencochrane.raven.event.interfaces.StackTraceInterface}. + * Class associating a Sentry exception to its {@link StackTraceInterface}. */ public final class ExceptionWithStackTrace { private static final Logger logger = LoggerFactory.getLogger(ExceptionWithStackTrace.class); @@ -88,28 +88,21 @@ public static Deque extractExceptionQueue(Throwable thr return exceptions; } - /** - * Gets the exception message. - * - * @return the exception message - */ public String getExceptionMessage() { return exceptionMessage; } - /** - * Gets the exception's class name. - * - * @return the exception's class name - */ public String getExceptionClassName() { return exceptionClassName; } /** - * Gets the exception's package name. + * Gets the exception package name. + *

    + * If there is no package, the value will be {@link #DEFAULT_PACKAGE_NAME}. + *

    * - * @return the exception's package name + * @return the exception package name or {@link #DEFAULT_PACKAGE_NAME} if it isn't defined. */ public String getExceptionPackageName() { @@ -120,11 +113,6 @@ public String getExceptionPackageName() { return exceptionPackageName; } - /** - * Gets the exceptions stack trace interface. - * - * @return the exceptions stack trace interface - */ public StackTraceInterface getStackTraceInterface() { return stackTraceInterface; } From ead97f351bbf3933a1f515b403dd972bd1d88cff Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 Jan 2014 06:07:27 +0100 Subject: [PATCH 1057/2152] Change the package name getter in a oneliner --- .../raven/event/interfaces/ExceptionWithStackTrace.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java index 2a92e0d3b80..e1737897228 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java @@ -105,12 +105,7 @@ public String getExceptionClassName() { * @return the exception package name or {@link #DEFAULT_PACKAGE_NAME} if it isn't defined. */ public String getExceptionPackageName() { - - if (exceptionPackageName == null) { - return DEFAULT_PACKAGE_NAME; - } - - return exceptionPackageName; + return exceptionPackageName != null ? exceptionPackageName : DEFAULT_PACKAGE_NAME; } public StackTraceInterface getStackTraceInterface() { From 0b92ae53137e6420d86cc9263615b3c187f80ec4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 Jan 2014 06:08:45 +0100 Subject: [PATCH 1058/2152] Remove the getStackTraceMethod from ExceptionWithStackTrace --- .../raven/log4j/SentryAppenderEventBuildingTest.java | 2 +- .../raven/log4j2/SentryAppenderEventBuildingTest.java | 2 +- .../raven/logback/SentryAppenderEventBuildingTest.java | 2 +- .../raven/event/interfaces/ExceptionWithStackTrace.java | 9 --------- .../raven/jul/SentryHandlerEventBuildingTest.java | 2 +- 5 files changed, 4 insertions(+), 13 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java index 744b5ea6d05..7bf596376e3 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java @@ -110,7 +110,7 @@ public void testExceptionLogging() throws Exception { .get(ExceptionInterface.EXCEPTION_INTERFACE); ExceptionWithStackTrace throwable = exceptionInterface.getExceptions().getFirst(); assertThat(throwable.getExceptionMessage(), is(exception.getMessage())); - assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); + assertThat(throwable.getStackTraceInterface().getStackTrace(), is(exception.getStackTrace())); }}; assertNoErrorsInErrorHandler(); } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java index da7218ff07e..89fe3c0f054 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java @@ -101,7 +101,7 @@ public void testExceptionLogging() throws Exception { .get(ExceptionInterface.EXCEPTION_INTERFACE); ExceptionWithStackTrace throwable = exceptionInterface.getExceptions().getFirst(); assertThat(throwable.getExceptionMessage(), is(exception.getMessage())); - assertThat(throwable.getStackTrace(), is(exception.getStackTrace())); + assertThat(throwable.getStackTraceInterface().getStackTrace(), is(exception.getStackTrace())); }}; assertNoErrorsInErrorHandler(); } diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java index 168447b33ad..eecba07e36e 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java @@ -117,7 +117,7 @@ public void testExceptionLogging() throws Exception { ExceptionWithStackTrace capturedException = exceptionInterface.getExceptions().getFirst(); assertThat(capturedException.getExceptionMessage(), is(exception.getMessage())); - assertThat(capturedException.getStackTrace(), is(exception.getStackTrace())); + assertThat(capturedException.getStackTraceInterface().getStackTrace(), is(exception.getStackTrace())); }}; assertNoErrorsInStatusManager(); } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java index e1737897228..d1f537862c7 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java @@ -111,13 +111,4 @@ public String getExceptionPackageName() { public StackTraceInterface getStackTraceInterface() { return stackTraceInterface; } - - /** - * Gets the exception's stack trace. - * - * @return the exception's stack trace - */ - public StackTraceElement[] getStackTrace() { - return stackTraceInterface.getStackTrace(); - } } diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java index 3310118f59f..d7d79901330 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java @@ -99,7 +99,7 @@ public void testExceptionLogging() throws Exception { .get(ExceptionInterface.EXCEPTION_INTERFACE); final ExceptionWithStackTrace exceptionWithStackTrace = exceptionInterface.getExceptions().getFirst(); assertThat(exceptionWithStackTrace.getExceptionMessage(), is(exception.getMessage())); - assertThat(exceptionWithStackTrace.getStackTrace(), is(exception.getStackTrace())); + assertThat(exceptionWithStackTrace.getStackTraceInterface().getStackTrace(), is(exception.getStackTrace())); }}; assertNoErrorsInErrorManager(); } From f1303f2a7c04ebc0165c4b4da3a283a0321df7cf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 Jan 2014 06:15:11 +0100 Subject: [PATCH 1059/2152] Rename ExceptionWithStackTrace into SentryException --- .../SentryAppenderEventBuildingTest.java | 8 +++---- .../SentryAppenderEventBuildingTest.java | 8 +++---- .../raven/logback/SentryAppender.java | 10 ++++----- .../SentryAppenderEventBuildingTest.java | 9 ++++---- .../event/interfaces/ExceptionInterface.java | 10 ++++----- ...thStackTrace.java => SentryException.java} | 22 +++++++++---------- .../json/ExceptionInterfaceBinding.java | 20 ++++++++--------- ...raceTest.java => SentryExceptionTest.java} | 4 ++-- .../jul/SentryHandlerEventBuildingTest.java | 8 +++---- .../json/ExceptionInterfaceBindingTest.java | 14 ++++++------ 10 files changed, 56 insertions(+), 57 deletions(-) rename raven/src/main/java/net/kencochrane/raven/event/interfaces/{ExceptionWithStackTrace.java => SentryException.java} (82%) rename raven/src/test/java/net/kencochrane/raven/event/interfaces/{ExceptionWithStackTraceTest.java => SentryExceptionTest.java} (88%) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java index 7bf596376e3..264660b071a 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java @@ -9,7 +9,7 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.ExceptionWithStackTrace; +import net.kencochrane.raven.event.interfaces.SentryException; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.log4j.Level; import org.apache.log4j.Logger; @@ -108,9 +108,9 @@ public void testExceptionLogging() throws Exception { mockRaven.sendEvent(event = withCapture()); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); - ExceptionWithStackTrace throwable = exceptionInterface.getExceptions().getFirst(); - assertThat(throwable.getExceptionMessage(), is(exception.getMessage())); - assertThat(throwable.getStackTraceInterface().getStackTrace(), is(exception.getStackTrace())); + SentryException sentryException = exceptionInterface.getExceptions().getFirst(); + assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); + assertThat(sentryException.getStackTraceInterface().getStackTrace(), is(exception.getStackTrace())); }}; assertNoErrorsInErrorHandler(); } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java index 89fe3c0f054..0c377c314ee 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java @@ -6,7 +6,7 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.ExceptionWithStackTrace; +import net.kencochrane.raven.event.interfaces.SentryException; import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.logging.log4j.Level; @@ -99,9 +99,9 @@ public void testExceptionLogging() throws Exception { mockRaven.sendEvent(event = withCapture()); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); - ExceptionWithStackTrace throwable = exceptionInterface.getExceptions().getFirst(); - assertThat(throwable.getExceptionMessage(), is(exception.getMessage())); - assertThat(throwable.getStackTraceInterface().getStackTrace(), is(exception.getStackTrace())); + SentryException sentryException = exceptionInterface.getExceptions().getFirst(); + assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); + assertThat(sentryException.getStackTraceInterface().getStackTrace(), is(exception.getStackTrace())); }}; assertNoErrorsInErrorHandler(); } diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 99cef6f782a..751a08a461c 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -14,7 +14,7 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.ExceptionWithStackTrace; +import net.kencochrane.raven.event.interfaces.SentryException; import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.slf4j.Logger; @@ -207,9 +207,9 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { return eventBuilder.build(); } - private Deque extractExceptionQueue(final ILoggingEvent iLoggingEvent) { + private Deque extractExceptionQueue(final ILoggingEvent iLoggingEvent) { IThrowableProxy throwableProxy = iLoggingEvent.getThrowableProxy(); - Deque exceptions = new ArrayDeque(); + Deque exceptions = new ArrayDeque(); Set circularityDetector = new HashSet(); StackTraceElement[] enclosingStackTrace = new StackTraceElement[0]; @@ -230,12 +230,12 @@ private Deque extractExceptionQueue(final ILoggingEvent return exceptions; } - private ExceptionWithStackTrace createExceptionWithStackTraceFrom(final IThrowableProxy throwableProxy, + private SentryException createExceptionWithStackTraceFrom(final IThrowableProxy throwableProxy, final StackTraceInterface stackTrace) { final String exceptionMessage = throwableProxy.getMessage(); final String exceptionClassName = throwableProxy.getClassName(); final String exceptionPackageName = extractPackageName(throwableProxy); - return new ExceptionWithStackTrace(exceptionMessage, exceptionClassName, exceptionPackageName, stackTrace); + return new SentryException(exceptionMessage, exceptionClassName, exceptionPackageName, stackTrace); } private String extractPackageName(final IThrowableProxy throwableProxy) { diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java index eecba07e36e..0d44df25cc6 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java @@ -12,7 +12,7 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.ExceptionWithStackTrace; +import net.kencochrane.raven.event.interfaces.SentryException; import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.hamcrest.Matchers; @@ -114,10 +114,9 @@ public void testExceptionLogging() throws Exception { mockRaven.sendEvent(event = withCapture()); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); - ExceptionWithStackTrace capturedException = exceptionInterface.getExceptions().getFirst(); - - assertThat(capturedException.getExceptionMessage(), is(exception.getMessage())); - assertThat(capturedException.getStackTraceInterface().getStackTrace(), is(exception.getStackTrace())); + SentryException sentryException = exceptionInterface.getExceptions().getFirst(); + assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); + assertThat(sentryException.getStackTraceInterface().getStackTrace(), is(exception.getStackTrace())); }}; assertNoErrorsInStatusManager(); } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java index 1e940495d81..fc9be654507 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java @@ -10,7 +10,7 @@ public class ExceptionInterface implements SentryInterface { * Name of the exception interface in Sentry. */ public static final String EXCEPTION_INTERFACE = "sentry.interfaces.Exception"; - private final Deque exceptions; + private final Deque exceptions; /** * Creates a new instance from the given {@code throwable}. @@ -18,15 +18,15 @@ public class ExceptionInterface implements SentryInterface { * @param throwable the {@link Throwable} to build this instance from */ public ExceptionInterface(final Throwable throwable) { - this(ExceptionWithStackTrace.extractExceptionQueue(throwable)); + this(SentryException.extractExceptionQueue(throwable)); } /** * Creates a new instance from the given {@code exceptions}. * - * @param exceptions a {@link Deque} of {@link ExceptionWithStackTrace} to build this instance from + * @param exceptions a {@link Deque} of {@link SentryException} to build this instance from */ - public ExceptionInterface(final Deque exceptions) { + public ExceptionInterface(final Deque exceptions) { this.exceptions = exceptions; } @@ -35,7 +35,7 @@ public String getInterfaceName() { return EXCEPTION_INTERFACE; } - public Deque getExceptions() { + public Deque getExceptions() { return exceptions; } } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java similarity index 82% rename from raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java rename to raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java index d1f537862c7..821162e6d02 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTrace.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java @@ -11,8 +11,8 @@ /** * Class associating a Sentry exception to its {@link StackTraceInterface}. */ -public final class ExceptionWithStackTrace { - private static final Logger logger = LoggerFactory.getLogger(ExceptionWithStackTrace.class); +public final class SentryException { + private static final Logger logger = LoggerFactory.getLogger(SentryException.class); /** * Name used when the class' package is the default one. */ @@ -32,7 +32,7 @@ public final class ExceptionWithStackTrace { * @param throwable Java exception to send to Sentry. * @param childExceptionStackTrace StackTrace of the exception caused by {@code throwable}. */ - public ExceptionWithStackTrace(Throwable throwable, StackTraceElement[] childExceptionStackTrace) { + public SentryException(Throwable throwable, StackTraceElement[] childExceptionStackTrace) { this.exceptionMessage = throwable.getMessage(); this.exceptionClassName = throwable.getClass().getSimpleName(); Package exceptionPackage = throwable.getClass().getPackage(); @@ -48,10 +48,10 @@ public ExceptionWithStackTrace(Throwable throwable, StackTraceElement[] childExc * @param exceptionPackageName the exception's package name * @param stackTraceInterface the stack trace interface holding the stack trace information of the exception */ - public ExceptionWithStackTrace(String exceptionMessage, - String exceptionClassName, - String exceptionPackageName, - StackTraceInterface stackTraceInterface) { + public SentryException(String exceptionMessage, + String exceptionClassName, + String exceptionPackageName, + StackTraceInterface stackTraceInterface) { this.exceptionMessage = exceptionMessage; this.exceptionClassName = exceptionClassName; this.exceptionPackageName = exceptionPackageName; @@ -59,7 +59,7 @@ public ExceptionWithStackTrace(String exceptionMessage, } /** - * Transforms a {@link Throwable} into a Queue of {@link ExceptionWithStackTrace}. + * Transforms a {@link Throwable} into a Queue of {@link SentryException}. *

    * Exceptions are stored in the queue from the most recent one to the oldest one. *

    @@ -67,8 +67,8 @@ public ExceptionWithStackTrace(String exceptionMessage, * @param throwable throwable to transform in a queue of exceptions. * @return a queue of exception with StackTrace. */ - public static Deque extractExceptionQueue(Throwable throwable) { - Deque exceptions = new ArrayDeque(); + public static Deque extractExceptionQueue(Throwable throwable) { + Deque exceptions = new ArrayDeque(); Set circularityDetector = new HashSet(); StackTraceElement[] childExceptionStackTrace = new StackTraceElement[0]; @@ -80,7 +80,7 @@ public static Deque extractExceptionQueue(Throwable thr break; } - exceptions.add(new ExceptionWithStackTrace(throwable, childExceptionStackTrace)); + exceptions.add(new SentryException(throwable, childExceptionStackTrace)); childExceptionStackTrace = throwable.getStackTrace(); throwable = throwable.getCause(); } diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java index 3dc448bb1be..613a5f22671 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.ExceptionWithStackTrace; +import net.kencochrane.raven.event.interfaces.SentryException; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import java.io.IOException; @@ -35,10 +35,10 @@ public ExceptionInterfaceBinding(InterfaceBinding stackTrac @Override public void writeInterface(JsonGenerator generator, ExceptionInterface exceptionInterface) throws IOException { - Deque exceptions = exceptionInterface.getExceptions(); + Deque exceptions = exceptionInterface.getExceptions(); generator.writeStartArray(); - for (Iterator iterator = exceptions.descendingIterator(); iterator.hasNext(); ) { + for (Iterator iterator = exceptions.descendingIterator(); iterator.hasNext(); ) { writeException(generator, iterator.next()); } generator.writeEndArray(); @@ -47,17 +47,17 @@ public void writeInterface(JsonGenerator generator, ExceptionInterface exception /** * Outputs an exception with its StackTrace on a JSon stream. * - * @param generator JSonGenerator. - * @param ewst Exception with its associated {@link StackTraceInterface}. + * @param generator JSonGenerator. + * @param sentryException Sentry exception with its associated {@link StackTraceInterface}. * @throws IOException */ - private void writeException(JsonGenerator generator, ExceptionWithStackTrace ewst) throws IOException { + private void writeException(JsonGenerator generator, SentryException sentryException) throws IOException { generator.writeStartObject(); - generator.writeStringField(TYPE_PARAMETER, ewst.getExceptionClassName()); - generator.writeStringField(VALUE_PARAMETER, ewst.getExceptionMessage()); - generator.writeStringField(MODULE_PARAMETER, ewst.getExceptionPackageName()); + generator.writeStringField(TYPE_PARAMETER, sentryException.getExceptionClassName()); + generator.writeStringField(VALUE_PARAMETER, sentryException.getExceptionMessage()); + generator.writeStringField(MODULE_PARAMETER, sentryException.getExceptionPackageName()); generator.writeFieldName(STACKTRACE_PARAMETER); - stackTraceInterfaceBinding.writeInterface(generator, ewst.getStackTraceInterface()); + stackTraceInterfaceBinding.writeInterface(generator, sentryException.getStackTraceInterface()); generator.writeEndObject(); } diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTraceTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java similarity index 88% rename from raven/src/test/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTraceTest.java rename to raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java index 486242d590d..068d6c23ba4 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/interfaces/ExceptionWithStackTraceTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java @@ -11,7 +11,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class ExceptionWithStackTraceTest { +public class SentryExceptionTest { @Injectable private Throwable mockThrowable; @@ -32,7 +32,7 @@ public Throwable getCause() { result = causeMessage; }}; - Deque exceptions = ExceptionWithStackTrace.extractExceptionQueue(mockThrowable); + Deque exceptions = SentryException.extractExceptionQueue(mockThrowable); assertThat(exceptions.getFirst().getExceptionMessage(), is(exceptionMessage)); assertThat(exceptions.getLast().getExceptionMessage(), is(causeMessage)); diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java index d7d79901330..60b8fdaa20a 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java @@ -6,7 +6,7 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.ExceptionWithStackTrace; +import net.kencochrane.raven.event.interfaces.SentryException; import org.hamcrest.Matchers; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; @@ -97,9 +97,9 @@ public void testExceptionLogging() throws Exception { mockRaven.sendEvent(event = withCapture()); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); - final ExceptionWithStackTrace exceptionWithStackTrace = exceptionInterface.getExceptions().getFirst(); - assertThat(exceptionWithStackTrace.getExceptionMessage(), is(exception.getMessage())); - assertThat(exceptionWithStackTrace.getStackTraceInterface().getStackTrace(), is(exception.getStackTrace())); + final SentryException sentryException = exceptionInterface.getExceptions().getFirst(); + assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); + assertThat(sentryException.getStackTraceInterface().getStackTrace(), is(exception.getStackTrace())); }}; assertNoErrorsInErrorManager(); } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index 9abd4d96095..c5ed8bf5143 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -6,7 +6,7 @@ import mockit.Injectable; import mockit.NonStrictExpectations; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.ExceptionWithStackTrace; +import net.kencochrane.raven.event.interfaces.SentryException; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -49,8 +49,8 @@ public void testSimpleException() throws Exception { new NonStrictExpectations() {{ mockExceptionInterface.getExceptions(); result = new Delegate() { - public Deque getExceptions() { - return ExceptionWithStackTrace.extractExceptionQueue(throwable); + public Deque getExceptions() { + return SentryException.extractExceptionQueue(throwable); } }; }}; @@ -69,8 +69,8 @@ public void testClassInDefaultPackage() throws Exception { new NonStrictExpectations() {{ mockExceptionInterface.getExceptions(); result = new Delegate() { - public Deque getExceptions() { - return ExceptionWithStackTrace.extractExceptionQueue(throwable); + public Deque getExceptions() { + return SentryException.extractExceptionQueue(throwable); } }; }}; @@ -90,8 +90,8 @@ public void testChainedException() throws Exception { new NonStrictExpectations() {{ mockExceptionInterface.getExceptions(); result = new Delegate() { - public Deque getExceptions() { - return ExceptionWithStackTrace.extractExceptionQueue(throwable2); + public Deque getExceptions() { + return SentryException.extractExceptionQueue(throwable2); } }; }}; From 350d13b9b9c4b95d2a0e98187c6b25cdbed8fef1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 Jan 2014 06:16:41 +0100 Subject: [PATCH 1060/2152] Remove logs for circular exceptions --- .../raven/event/interfaces/SentryException.java | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java index 821162e6d02..492fa4a54ca 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java @@ -1,8 +1,5 @@ package net.kencochrane.raven.event.interfaces; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; @@ -12,7 +9,6 @@ * Class associating a Sentry exception to its {@link StackTraceInterface}. */ public final class SentryException { - private static final Logger logger = LoggerFactory.getLogger(SentryException.class); /** * Name used when the class' package is the default one. */ @@ -73,13 +69,7 @@ public static Deque extractExceptionQueue(Throwable throwable) StackTraceElement[] childExceptionStackTrace = new StackTraceElement[0]; //Stack the exceptions to send them in the reverse order - while (throwable != null) { - if (!circularityDetector.add(throwable)) { - //TODO: Send a more helpful log message here. - logger.warn("Exiting a circular exception!"); - break; - } - + while (throwable != null && circularityDetector.add(throwable)) { exceptions.add(new SentryException(throwable, childExceptionStackTrace)); childExceptionStackTrace = throwable.getStackTrace(); throwable = throwable.getCause(); From 2d57d27751cb642f4f8fdc6d50e4411c72c49ad7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 Jan 2014 06:19:00 +0100 Subject: [PATCH 1061/2152] Improve documentation --- .../raven/event/interfaces/SentryException.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java index 492fa4a54ca..5e9ed1253c4 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java @@ -37,12 +37,12 @@ public SentryException(Throwable throwable, StackTraceElement[] childExceptionSt } /** - * Creates a new instance from the given exceptions data. + * Creates a Sentry exception. * - * @param exceptionMessage the message of the exception - * @param exceptionClassName the exception's class name - * @param exceptionPackageName the exception's package name - * @param stackTraceInterface the stack trace interface holding the stack trace information of the exception + * @param exceptionMessage message of the exception. + * @param exceptionClassName exception's class name (simple name). + * @param exceptionPackageName exception's package name. + * @param stackTraceInterface {@code StackTraceInterface} holding the StackTrace information of the exception. */ public SentryException(String exceptionMessage, String exceptionClassName, From f3459ab0d2f9d2c44d4353c340dc390c12e00c7f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 21 Jan 2014 07:09:41 +1100 Subject: [PATCH 1062/2152] Create an AppEngine module to support GAE task system --- pom.xml | 1 + raven-appengine/pom.xml | 52 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 raven-appengine/pom.xml diff --git a/pom.xml b/pom.xml index 8daa73a7a4b..0d1e51572d8 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,7 @@ raven raven-getsentry + raven-appengine raven-log4j raven-logback raven-log4j2 diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml new file mode 100644 index 00000000000..ec71a9ad5a8 --- /dev/null +++ b/raven-appengine/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + net.kencochrane.raven + raven-all + 5.0.1-SNAPSHOT + + + raven-appengine + jar + + Raven-Java for Google AppEngine + Raven module allowing to use GoogleApp Engine and its task queueing system. + + + 1.8.9 + + + + + ${project.groupId} + raven + + + com.google.appengine + appengine-api-1.0-sdk + ${appengine-api.version} + + + + com.googlecode.jmockit + jmockit + test + + + org.hamcrest + hamcrest-core + test + + + org.hamcrest + hamcrest-library + test + + + org.testng + testng + test + + + From aa911a7e1a7b952457f604c1cde0f80f2aa2f280 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 21 Jan 2014 07:11:22 +1100 Subject: [PATCH 1063/2152] Create a new async connection for appengine --- .../connection/AppEngineAsyncConnection.java | 156 +++++++++++++++++ .../AppEngineAsyncConnectionTest.java | 159 ++++++++++++++++++ 2 files changed, 315 insertions(+) create mode 100644 raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java create mode 100644 raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java new file mode 100644 index 00000000000..2273bb7895f --- /dev/null +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java @@ -0,0 +1,156 @@ +package net.kencochrane.raven.appengine.connection; + +import com.google.appengine.api.taskqueue.DeferredTask; +import com.google.appengine.api.taskqueue.Queue; +import com.google.appengine.api.taskqueue.QueueFactory; +import com.google.appengine.api.taskqueue.TaskHandle; +import net.kencochrane.raven.Raven; +import net.kencochrane.raven.connection.Connection; +import net.kencochrane.raven.event.Event; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import static com.google.appengine.api.taskqueue.DeferredTaskContext.setDoNotRetry; +import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withPayload; + +/** + * Asynchronous usage of a connection withing Google App Engine. + *

    + * Instead of synchronously sending each event to a connection, use a the task queue system to establish the connection + * and submit the event. + *

    + *

    + * Google App Engine serialises the tasks before queuing them, to keep a link between the task and the + * {@link AppEngineAsyncConnection} that created it, a register of the instances of {@code AppEngineAsyncConnection} is + * kept in {@link #APP_ENGINE_ASYNC_CONNECTIONS}.
    + * This register is populated when a new instance of {@code AppEngineAsyncConnection} is created and the connection + * is removed from the register when it has been closed with {@link #close()}. + *

    + */ +public class AppEngineAsyncConnection implements Connection { + private static final Logger logger = LoggerFactory.getLogger(AppEngineAsyncConnection.class); + private static final Map APP_ENGINE_ASYNC_CONNECTIONS + = new HashMap(); + private static final String TASK_TAG = "RavenTask"; + /** + * Maximum number of tasks that can be leased at once when closing the connection. + */ + private static final long MAXIMUM_TASKS_LEASED = 1000L; + /** + * Maximum days of lease when removing tasks (7 days). + */ + private static final long MAXIMUM_TASK_LEASE_PERIOD_DAYS = 7; + /** + * Identifier of the async connection. + */ + private final UUID id = UUID.randomUUID(); + /** + * Connection used to actually send the events. + */ + private final Connection actualConnection; + /** + * Queue used to send deferred tasks + */ + private Queue queue = QueueFactory.getDefaultQueue(); + /** + * Boolean used to check whether the connection is still open or not. + */ + private boolean closed; + + /** + * Creates a connection which will rely on an executor to send events. + *

    + * Will propagate the {@link #close()} operation. + *

    + * + * @param actualConnection connection used to send the events. + */ + public AppEngineAsyncConnection(Connection actualConnection) { + this.actualConnection = actualConnection; + APP_ENGINE_ASYNC_CONNECTIONS.put(id, this); + } + + /** + * {@inheritDoc} + *

    + * The event will be added to a queue and will be handled by a separate {@code Thread} later on. + *

    + */ + @Override + public void send(Event event) { + if (!closed) + queue.add(withPayload(new EventSubmitter(id, event)).tag(TASK_TAG)); + } + + /** + * {@inheritDoc}. + *

    + * Closing the {@link AppEngineAsyncConnection} will gracefully remove every task created earlier from the queue. + *

    + */ + @Override + public void close() throws IOException { + logger.info("Gracefully stopping sentry tasks."); + closed = true; + try { + List tasks; + do { + tasks = queue.leaseTasksByTag(MAXIMUM_TASK_LEASE_PERIOD_DAYS, TimeUnit.DAYS, MAXIMUM_TASKS_LEASED, TASK_TAG); + queue.deleteTask(tasks); + } while (!tasks.isEmpty()); + } finally { + actualConnection.close(); + APP_ENGINE_ASYNC_CONNECTIONS.remove(id); + } + } + + /** + * Set the queue used to send EventSubmitter tasks. + * + * @param queueName name of the queue to use. + */ + public void setQueue(String queueName) { + this.queue = QueueFactory.getQueue(queueName); + } + + /** + * Simple DeferredTask using the {@link #send(net.kencochrane.raven.event.Event)} method of the + * {@link #actualConnection}. + */ + private static final class EventSubmitter implements DeferredTask { + private final UUID connectionId; + private final Event event; + + private EventSubmitter(UUID connectionId, Event event) { + this.connectionId = connectionId; + this.event = event; + } + + @Override + public void run() { + setDoNotRetry(true); + try { + // The current thread is managed by raven + Raven.RAVEN_THREAD.set(true); + AppEngineAsyncConnection connection = APP_ENGINE_ASYNC_CONNECTIONS.get(connectionId); + if (connection == null) { + logger.warn("Couldn't find the AppEngineAsyncConnection identified by '{}'. " + + "The connection has probably been closed.", connectionId); + return; + } + connection.actualConnection.send(event); + } catch (Exception e) { + logger.error("An exception occurred while sending the event to Sentry.", e); + } finally { + Raven.RAVEN_THREAD.remove(); + } + } + } +} diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java new file mode 100644 index 00000000000..dfc85076a76 --- /dev/null +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java @@ -0,0 +1,159 @@ +package net.kencochrane.raven.appengine.connection; + +import com.google.appengine.api.taskqueue.*; +import com.google.appengine.api.taskqueue.Queue; +import mockit.*; +import net.kencochrane.raven.connection.Connection; +import net.kencochrane.raven.event.Event; +import org.hamcrest.Matchers; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.ByteArrayInputStream; +import java.io.ObjectInputStream; +import java.util.*; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class AppEngineAsyncConnectionTest { + private AppEngineAsyncConnection asyncConnection; + @Injectable + private Connection mockConnection; + @Injectable + private Queue mockQueue; + @Mocked(methods = "getDefaultQueue") + private QueueFactory queueFactory; + + private static DeferredTask extractDeferredTask(TaskOptions taskOptions) throws Exception { + ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(taskOptions.getPayload())); + return (DeferredTask) ois.readObject(); + } + + private static AppEngineAsyncConnection getTaskConnection(DeferredTask deferredTask) throws Exception { + Map appEngineAsyncConnectionRegister + = Deencapsulation.getField(AppEngineAsyncConnection.class, "APP_ENGINE_ASYNC_CONNECTIONS"); + return appEngineAsyncConnectionRegister.get(Deencapsulation.getField(deferredTask, "connectionId")); + } + + @BeforeMethod + public void setUp() throws Exception { + new NonStrictExpectations() {{ + QueueFactory.getDefaultQueue(); + result = mockQueue; + }}; + asyncConnection = new AppEngineAsyncConnection(mockConnection); + } + + @Test + public void testRegisterNewInstance(@Injectable final UUID mockUuid, @Mocked("randomUUID") UUID uuid) + throws Exception { + new NonStrictExpectations() {{ + UUID.randomUUID(); + result = mockUuid; + }}; + + AppEngineAsyncConnection asyncConnection2 = new AppEngineAsyncConnection(mockConnection); + + Map appEngineAsyncConnectionRegister + = Deencapsulation.getField(AppEngineAsyncConnection.class, "APP_ENGINE_ASYNC_CONNECTIONS"); + assertThat(appEngineAsyncConnectionRegister, hasEntry(mockUuid, asyncConnection2)); + } + + @Test + public void testUnregisterInstance(@Injectable final UUID mockUuid, @Mocked("randomUUID") UUID uuid) + throws Exception { + new NonStrictExpectations() {{ + UUID.randomUUID(); + result = mockUuid; + }}; + + new AppEngineAsyncConnection(mockConnection).close(); + + Map appEngineAsyncConnectionRegister + = Deencapsulation.getField(AppEngineAsyncConnection.class, "APP_ENGINE_ASYNC_CONNECTIONS"); + assertThat(appEngineAsyncConnectionRegister, not(hasKey(mockUuid))); + } + + @Test + public void testCloseOperation(@Injectable final List mockTaskHandleList) throws Exception { + new NonStrictExpectations() {{ + mockQueue.leaseTasksByTag(anyLong, (TimeUnit) any, anyLong, "RavenTask"); + result = mockTaskHandleList; + result = Collections.emptyList(); + mockTaskHandleList.isEmpty(); + result = false; + }}; + + asyncConnection.close(); + + new Verifications() {{ + mockConnection.close(); + mockQueue.leaseTasksByTag(anyLong, (TimeUnit) any, anyLong, "RavenTask"); + times = 2; + mockQueue.deleteTask(mockTaskHandleList); + }}; + } + + @Test + public void testSendEventQueued(@Injectable final Event mockEvent) throws Exception { + new NonStrictExpectations() {{ + setField(mockEvent, "id", UUID.randomUUID()); + }}; + + asyncConnection.send(mockEvent); + + new Verifications() {{ + TaskOptions taskOptions; + DeferredTask deferredTask; + mockQueue.add(taskOptions = withCapture()); + + assertThat(taskOptions.getTag(), is("RavenTask")); + deferredTask = extractDeferredTask(taskOptions); + assertThat(getField(deferredTask, "event"), Matchers.equalTo(mockEvent)); + }}; + } + + @Test + public void testQueuedEventSubmitted(@Injectable final Event mockEvent, + @Mocked("setDoNotRetry") DeferredTaskContext deferredTaskContext) + throws Exception { + new NonStrictExpectations() {{ + mockQueue.add((TaskOptions) any); + result = new Delegate() { + void add(TaskOptions taskOptions) throws Exception { + extractDeferredTask(taskOptions).run(); + } + }; + }}; + + asyncConnection.send(mockEvent); + + new Verifications() {{ + DeferredTaskContext.setDoNotRetry(true); + mockConnection.send((Event) any); + }}; + } + + @Test + public void testEventLinkedToCorrectConnection(@Injectable final Event mockEvent) throws Exception { + final AppEngineAsyncConnection asyncConnection2 = new AppEngineAsyncConnection(mockConnection); + + asyncConnection.send(mockEvent); + asyncConnection2.send(mockEvent); + + new Verifications() {{ + List taskOptionsList = new ArrayList(); + DeferredTask deferredTask; + + mockQueue.add(withCapture(taskOptionsList)); + + deferredTask = extractDeferredTask(taskOptionsList.get(0)); + assertThat(getTaskConnection(deferredTask), is(asyncConnection)); + + deferredTask = extractDeferredTask(taskOptionsList.get(1)); + assertThat(getTaskConnection(deferredTask), is(asyncConnection2)); + }}; + } +} From 49f753cd5e5f76f40d6037438f5a1e2b0926d5d2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 21 Jan 2014 07:11:47 +1100 Subject: [PATCH 1064/2152] Add a new Factory creating async connections in GAE --- .../appengine/AppEngineRavenFactory.java | 32 +++++++++ .../net.kencochrane.raven.RavenFactory | 1 + .../appengine/AppEngineRavenFactoryTest.java | 68 +++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java create mode 100644 raven-appengine/src/main/resources/META-INF/services/net.kencochrane.raven.RavenFactory create mode 100644 raven-appengine/src/test/java/net/kencochrane/raven/appengine/AppEngineRavenFactoryTest.java diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java new file mode 100644 index 00000000000..5e0bbda6d24 --- /dev/null +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java @@ -0,0 +1,32 @@ +package net.kencochrane.raven.appengine; + +import net.kencochrane.raven.DefaultRavenFactory; +import net.kencochrane.raven.appengine.connection.AppEngineAsyncConnection; +import net.kencochrane.raven.connection.Connection; +import net.kencochrane.raven.dsn.Dsn; + +public class AppEngineRavenFactory extends DefaultRavenFactory { + /** + * Option for the queue name used in Google App Engine of threads assigned for the connection. + */ + public static final String QUEUE_NAME = "raven.async.gaequeuename"; + + /** + * Encapsulates an already existing connection in an {@link net.kencochrane.raven.appengine.connection.AppEngineAsyncConnection} and get the async options + * from the Sentry DSN. + * + * @param dsn Data Source Name of the Sentry server. + * @param connection Connection to encapsulate in an {@link net.kencochrane.raven.appengine.connection.AppEngineAsyncConnection}. + * @return the asynchronous connection. + */ + @Override + protected Connection createAsyncConnection(Dsn dsn, Connection connection) { + AppEngineAsyncConnection asyncConnection = new AppEngineAsyncConnection(connection); + + if (dsn.getOptions().containsKey(QUEUE_NAME)) { + asyncConnection.setQueue(dsn.getOptions().get(QUEUE_NAME)); + } + + return asyncConnection; + } +} diff --git a/raven-appengine/src/main/resources/META-INF/services/net.kencochrane.raven.RavenFactory b/raven-appengine/src/main/resources/META-INF/services/net.kencochrane.raven.RavenFactory new file mode 100644 index 00000000000..25929c53ece --- /dev/null +++ b/raven-appengine/src/main/resources/META-INF/services/net.kencochrane.raven.RavenFactory @@ -0,0 +1 @@ +net.kencochrane.raven.appengine.AppEngineRavenFactory diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/AppEngineRavenFactoryTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/AppEngineRavenFactoryTest.java new file mode 100644 index 00000000000..608ed4095dd --- /dev/null +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/AppEngineRavenFactoryTest.java @@ -0,0 +1,68 @@ +package net.kencochrane.raven.appengine; + +import mockit.*; +import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.appengine.connection.AppEngineAsyncConnection; +import net.kencochrane.raven.connection.Connection; +import net.kencochrane.raven.dsn.Dsn; +import org.hamcrest.Matchers; +import org.testng.annotations.Test; + +import java.util.Collections; +import java.util.ServiceLoader; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; + +public class AppEngineRavenFactoryTest { + @Tested + private AppEngineRavenFactory appEngineRavenFactory; + @Injectable + private Connection mockConnection; + @Injectable + private Dsn mockDsn; + + @Test + public void checkServiceLoaderProvidesFactory() { + ServiceLoader ravenFactories = ServiceLoader.load(RavenFactory.class); + + assertThat(ravenFactories, Matchers.hasItem(instanceOf(AppEngineRavenFactory.class))); + } + + @Test + public void asyncConnectionCreatedByAppEngineRavenFactoryIsForAppEngine() { + Connection connection = appEngineRavenFactory.createAsyncConnection(mockDsn, mockConnection); + + assertThat(connection, is(instanceOf(AppEngineAsyncConnection.class))); + } + + @Test + public void asyncConnectionWithoutQueueNameKeepsDefaultQueue( + @Mocked final AppEngineAsyncConnection mockAppEngineAsyncConnection) { + appEngineRavenFactory.createAsyncConnection(mockDsn, mockConnection); + + new Verifications(){{ + new AppEngineAsyncConnection(mockConnection); + mockAppEngineAsyncConnection.setQueue(anyString); + times = 0; + }}; + } + + @Test + public void asyncConnectionWithQueueNameSetsQueue( + @Mocked final AppEngineAsyncConnection mockAppEngineAsyncConnection, + @Injectable("queueName") final String mockQueueName) { + new NonStrictExpectations(){{ + mockDsn.getOptions(); + result = Collections.singletonMap(AppEngineRavenFactory.QUEUE_NAME, mockQueueName); + }}; + + appEngineRavenFactory.createAsyncConnection(mockDsn, mockConnection); + + new Verifications(){{ + new AppEngineAsyncConnection(mockConnection); + mockAppEngineAsyncConnection.setQueue(mockQueueName); + }}; + } +} From c71151b7089999b9037584ef379a033d74e45416 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 21 Jan 2014 07:22:58 +1100 Subject: [PATCH 1065/2152] Add a README to the AppEngine module --- raven-appengine/README.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 raven-appengine/README.md diff --git a/raven-appengine/README.md b/raven-appengine/README.md new file mode 100644 index 00000000000..6810abecf3e --- /dev/null +++ b/raven-appengine/README.md @@ -0,0 +1,38 @@ +# AppEngine (module) +Module enabling the support of the async connections in Google App Engine. + +Google App Engine doesn't support threads but provides instead a TaskQueueing system allowing tasks to be run in the +background. + +This module replaces the async system provided by default with one relying on the tasks. + +__This module is not useful outside of Google App Engine.__ + +## Installation + +### Maven +```xml + + net.kencochrane.raven + raven-appengine + 5.0.1 + +``` + +### Other dependency managers +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1.2%7Cjar). + +### Manual dependency management +Relies on: + + - [raven dependencies](../raven) + +## Usage + +This module provides a new `RavenFactory` which replaces the default async system with a GAE compatible one. +By default, the default task queue will be used, but it's possible to specify which one will be used with the +`raven.async.gaequeuename` option: + + http://public:private@getsentry.com/1?raven.async.gaequeuename=MyQueueName + +It is necessary to force the raven factory name to `net.kencochrane.raven.appengine.AppEngineRavenFactory`. From f0e9c3a550829d559e7b63def194c924a109d8c1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 21 Jan 2014 07:25:12 +1100 Subject: [PATCH 1066/2152] Add details on async.queuesize etc. in the readme of appengine --- raven-appengine/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raven-appengine/README.md b/raven-appengine/README.md index 6810abecf3e..da398a7f3fe 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -35,4 +35,6 @@ By default, the default task queue will be used, but it's possible to specify wh http://public:private@getsentry.com/1?raven.async.gaequeuename=MyQueueName +The queue size and thread options will not be used as they are specific to the default multithreaded system. + It is necessary to force the raven factory name to `net.kencochrane.raven.appengine.AppEngineRavenFactory`. From ab1ce9cd49c83daf894da04b4525fb209d66e00b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 21 Jan 2014 07:34:07 +1100 Subject: [PATCH 1067/2152] Document the selection of a custom RavenFactory --- raven-log4j/README.md | 2 ++ raven-log4j2/README.md | 8 ++++++++ raven-logback/README.md | 2 ++ raven/README.md | 5 +++++ 4 files changed, 17 insertions(+) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index db035f0ea34..9526a52a464 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -34,6 +34,8 @@ log4j.rootLogger=WARN, SentryAppender log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender log4j.appender.SentryAppender.dsn=https://publicKey:secretKey@host:port/1?options log4j.appender.SentryAppender.tags=tag1:value1,tag2:value2 +# Optional, allows to select the ravenFactory +#log4j.appender.SentryAppender.ravenFactory=net.kencochrane.raven.DefaultRavenFactory ``` ### Additional data and information diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index f08346af972..5f4868ec817 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -42,6 +42,14 @@ In the `log4j2.xml` file set: tag1:value1,tag2:value2 + + diff --git a/raven-logback/README.md b/raven-logback/README.md index 4858e128ae0..be5590730bc 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -34,6 +34,8 @@ In the `logback.xml` file set: https://publicKey:secretKey@host:port/1?options tag1:value1,tag2:value2 + + diff --git a/raven/README.md b/raven/README.md index 90696718fdb..a5aae03ceab 100644 --- a/raven/README.md +++ b/raven/README.md @@ -38,6 +38,8 @@ level=WARN handlers=net.kencochrane.raven.jul.SentryHandler net.kencochrane.raven.jul.SentryHandler.dsn=https://publicKey:secretKey@host:port/1?options net.kencochrane.raven.jul.SentryHandler.tags=tag1:value1,tag2:value2 +# Optional, allows to select the ravenFactory +#net.kencochrane.raven.jul.SentryHandler.ravenFactory=net.kencochrane.raven.DefaultRavenFactory ``` When starting your application, add the `java.util.logging.config.file` to the @@ -146,6 +148,9 @@ public class MyClass { // It is also possible to use the DSN detection system like this raven = RavenFactory.ravenInstance(); + + // Advanced: To specify the ravenFactory used + raven = RavenFactory.ravenInstance(new Dsn(dsn), "net.kencochrane.raven.DefaultRavenFactory"); } void logSimpleMessage() { From fca7521357888074278da3958e6a776f921d58d3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 21 Jan 2014 07:38:56 +1100 Subject: [PATCH 1068/2152] Do not skip 5.0.0 --- pom.xml | 2 +- raven-getsentry/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 8daa73a7a4b..0bd929828ad 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 5.0.1-SNAPSHOT + 5.0.0-SNAPSHOT pom Raven-Java diff --git a/raven-getsentry/pom.xml b/raven-getsentry/pom.xml index 7dc509fd2f5..ccac75045b7 100644 --- a/raven-getsentry/pom.xml +++ b/raven-getsentry/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.1-SNAPSHOT + 5.0.0-SNAPSHOT raven-getsentry diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 642cf6d6004..b5eb5ef35e4 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.1-SNAPSHOT + 5.0.0-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index f02bdcc4aa5..1b1c86046c0 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.1-SNAPSHOT + 5.0.0-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index aedbbafbe3c..8c18363ea74 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.1-SNAPSHOT + 5.0.0-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 75afed169aa..ceb8bf2e7d8 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.1-SNAPSHOT + 5.0.0-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index fc2ca631a7c..d8ffc90f88a 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.1-SNAPSHOT + 5.0.0-SNAPSHOT sentry-stub From e312dbc855ee6c8447f5d9e4833fa3edc2469eff Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 21 Jan 2014 07:39:50 +1100 Subject: [PATCH 1069/2152] Do not skip 5.0.0 --- raven-appengine/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-appengine/README.md b/raven-appengine/README.md index da398a7f3fe..113aebe2240 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -15,7 +15,7 @@ __This module is not useful outside of Google App Engine.__ net.kencochrane.raven raven-appengine - 5.0.1 + 5.0.0 ``` From c81ac605a80ca29f427e3fe293c9e34c1482ad82 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 21 Jan 2014 07:39:50 +1100 Subject: [PATCH 1070/2152] Use raven-all 5.0.0-SNAPSHOT --- raven-appengine/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index ec71a9ad5a8..f09094617d7 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ net.kencochrane.raven raven-all - 5.0.1-SNAPSHOT + 5.0.0-SNAPSHOT raven-appengine From 6cc11cc72a9a929551ac0af14c030374d3b71d24 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 21 Jan 2014 08:30:00 +1100 Subject: [PATCH 1071/2152] Drop java6 support --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0bd929828ad..7c3d50cf905 100644 --- a/pom.xml +++ b/pom.xml @@ -98,8 +98,8 @@ github - 6 - 6 + 7 + 7 1.7.5 From dfffe98393cd1d8d85749f718b492e003440d3c9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 21 Jan 2014 08:46:18 +1100 Subject: [PATCH 1072/2152] Remove java 7 profile (java 7 required everywhere) --- pom.xml | 183 ++++++++++++++++++++++++-------------------------------- 1 file changed, 78 insertions(+), 105 deletions(-) diff --git a/pom.xml b/pom.xml index 7c3d50cf905..60027138ea8 100644 --- a/pom.xml +++ b/pom.xml @@ -301,28 +301,44 @@ org.eclipse.jetty jetty-maven-plugin 9.0.6.v20130930 - - - org.apache.maven.plugins - maven-failsafe-plugin - 2.16 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 2.10 - src/checkstyle/checkstyle.xml - true + 10 + foo + 9999 + ${project.build.directory}/webapps/sentry-stub.war - checkstyle + start-sentry-stub + pre-integration-test + + deploy-war + + 0 + true ${skipTests} + + + stop-sentry-stub + post-integration-test - check + stop + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.16 + + + integration-tests + + integration-test + verify @@ -344,6 +360,54 @@ true + + copy-war-for-integration-tests + package + + copy + + + + + ${project.groupId} + sentry-stub + ${project.version} + war + + + true + true + ${project.build.directory}/webapps + + + + + + ${project.groupId} + sentry-stub + ${project.version} + war + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.10 + + src/checkstyle/checkstyle.xml + true + + + + checkstyle + + ${skipTests} + + + check + + @@ -384,97 +448,6 @@ - - run-jetty-integration-tests - - 1.7 - - - - - - org.eclipse.jetty - jetty-maven-plugin - - 10 - foo - 9999 - ${project.build.directory}/webapps/sentry-stub.war - - - - start-sentry-stub - pre-integration-test - - deploy-war - - - 0 - true - ${skipTests} - - - - stop-sentry-stub - post-integration-test - - stop - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - - integration-tests - - integration-test - verify - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - copy-war-for-integration-tests - package - - copy - - - - - ${project.groupId} - sentry-stub - ${project.version} - war - - - true - true - ${project.build.directory}/webapps - - - - - - ${project.groupId} - sentry-stub - ${project.version} - war - - - - - - - - sonatype-oss-release From 35c90e7c557e88e31e61b3e38961b62e4896e15b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 21 Jan 2014 08:46:48 +1100 Subject: [PATCH 1073/2152] Drop openJDK 6 build --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index efe8227d345..85a33a2d847 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,4 +2,3 @@ language: java script: mvn verify jdk: - oraclejdk7 - - openjdk6 From be094fe7d40bd4b48620a125afc71f36f3e8f3c7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 21 Jan 2014 08:47:42 +1100 Subject: [PATCH 1074/2152] Add support of JDK8 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 85a33a2d847..097ffb4c572 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,3 +2,4 @@ language: java script: mvn verify jdk: - oraclejdk7 + - oraclejdk8 From 86a0211cf1f024b3a55030a3c973dd7660b22a62 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 21 Jan 2014 08:50:24 +1100 Subject: [PATCH 1075/2152] Set animal sniffer for java 7 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 60027138ea8..ae839add268 100644 --- a/pom.xml +++ b/pom.xml @@ -280,8 +280,8 @@ org.codehaus.mojo.signature - java16 - 1.1 + java17 + 1.0 From 39b9a00fba70f65944e64eb8a42621ff2b9f19c2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 22 Jan 2014 19:08:34 +1100 Subject: [PATCH 1076/2152] Fix typo in class name --- .../marshaller/json/JsonMarshallerTest.java | 78 +++++++++---------- .../raven/marshaller/json/JsonTestTool.java | 6 +- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index 55ef8889d0c..239e04ed772 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -42,7 +42,7 @@ public void setUp() throws Exception { @Test public void testEventIdWrittenProperly(@Injectable final UUID mockUuid) throws Exception { - final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getId(); result = mockUuid; @@ -50,27 +50,27 @@ public void testEventIdWrittenProperly(@Injectable final UUID mockUuid) throws E result = "3b71fba5-413e-4022-ae98-5f0b80a155a5"; }}; - jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); - assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testEventId.json"))); + assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testEventId.json"))); } @Test public void testEventMessageWrittenProperly(@Injectable("message") final String mockMessage) throws Exception { - final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getMessage(); result = mockMessage; }}; - jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); - assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testMessage.json"))); + assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testMessage.json"))); } @Test public void testEventTimestampWrittenProperly(@Injectable final Date mockTimestamp) throws Exception { - final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getTimestamp(); result = mockTimestamp; @@ -79,9 +79,9 @@ public void testEventTimestampWrittenProperly(@Injectable final Date mockTimesta result = 1385266295338L; }}; - jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); - assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTimestamp.json"))); + assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTimestamp.json"))); } @DataProvider(name = "levelProvider") @@ -97,94 +97,94 @@ public Object[][] levelProvider() { @Test(dataProvider = "levelProvider") public void testEventLevelWrittenProperly(final Event.Level eventLevel, String levelFile) throws Exception { - final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getLevel(); result = eventLevel; }}; - jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); - assertThat(outpuStreamTool.value(), is(jsonResource(levelFile))); + assertThat(outputStreamTool.value(), is(jsonResource(levelFile))); } @Test public void testEventLoggerWrittenProperly(@Injectable("logger") final String mockLogger) throws Exception { - final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getLogger(); result = mockLogger; }}; - jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); - assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLogger.json"))); + assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLogger.json"))); } @Test public void testEventPlaftormWrittenProperly(@Injectable("platform") final String mockPlatform) throws Exception { - final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getPlatform(); result = mockPlatform; }}; - jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); - assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testPlatform.json"))); + assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testPlatform.json"))); } @Test public void testEventCulpritWrittenProperly(@Injectable("culprit") final String mockCulprit) throws Exception { - final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getCulprit(); result = mockCulprit; }}; - jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); - assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testCulprit.json"))); + assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testCulprit.json"))); } @Test public void testEventTagsWrittenProperly(@Injectable("tagName") final String mockTagName, @Injectable("tagValue") final String mockTagValue) throws Exception { - final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getTags(); result = Collections.singletonMap(mockTagName, mockTagValue); }}; - jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); - assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTags.json"))); + assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTags.json"))); } @Test public void testEventServerNameWrittenProperly(@Injectable("serverName") final String mockServerName) throws Exception { - final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getServerName(); result = mockServerName; }}; - jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); - assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testServerName.json"))); + assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testServerName.json"))); } @Test public void testEventChecksumWrittenProperly(@Injectable("1234567890abcdef") final String mockChecksum) throws Exception { - final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getChecksum(); result = mockChecksum; }}; - jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); - assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testChecksum.json"))); + assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testChecksum.json"))); } @DataProvider(name = "extraProvider") @@ -206,21 +206,21 @@ public Object[][] extraProvider() { @Test(dataProvider = "extraProvider") public void testEventExtraWrittenProperly(final String extraKey, final Object extraValue, String extraFile) throws Exception { - final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getExtra(); result = Collections.singletonMap(extraKey, extraValue); }}; - jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); - assertThat(outpuStreamTool.value(), is(jsonResource(extraFile))); + assertThat(outputStreamTool.value(), is(jsonResource(extraFile))); } @Test public void testEventExtraWrittenProperly(@Injectable("key") final String mockExtraKey, @Injectable final Object mockExtraValue) throws Exception { - final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getExtra(); result = Collections.singletonMap(mockExtraKey, mockExtraValue); @@ -228,16 +228,16 @@ public void testEventExtraWrittenProperly(@Injectable("key") final String mockEx result = "test"; }}; - jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); - assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json"))); + assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json"))); } @Test public void testInterfaceBindingIsProperlyUsed( @Injectable final SentryInterface mockSentryInterface, @Injectable final InterfaceBinding mockInterfaceBinding) throws Exception { - final JsonOutpuStreamTool outpuStreamTool = newJsonOutputStream(); + final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getSentryInterfaces(); result = Collections.singletonMap("interfaceKey", mockSentryInterface); @@ -250,12 +250,12 @@ public void writeInterface(JsonGenerator generator, SentryInterface sentryInterf }}; jsonMarshaller.addInterfaceBinding(mockSentryInterface.getClass(), mockInterfaceBinding); - jsonMarshaller.marshall(mockEvent, outpuStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); new Verifications() {{ mockInterfaceBinding.writeInterface((JsonGenerator) any, mockSentryInterface); }}; - assertThat(outpuStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json"))); + assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json"))); } @Test diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonTestTool.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonTestTool.java index d6b07c9db4c..288d1ae280d 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonTestTool.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonTestTool.java @@ -24,8 +24,8 @@ public static JsonGeneratorTool newJsonGenerator() throws Exception { return new JsonGeneratorTool(); } - public static JsonOutpuStreamTool newJsonOutputStream() throws Exception { - return new JsonOutpuStreamTool(); + public static JsonOutputStreamTool newJsonOutputStream() throws Exception { + return new JsonOutputStreamTool(); } public static class JsonGeneratorTool { @@ -56,7 +56,7 @@ public String toString() { } } - public static class JsonOutpuStreamTool { + public static class JsonOutputStreamTool { private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); public OutputStream outputStream() { From 029b12355e45c22ac6fcc4581ce7952d46f07e68 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 22 Jan 2014 19:13:56 +1100 Subject: [PATCH 1077/2152] Add diamond operator when possible --- .../java/net/kencochrane/raven/log4j2/SentryAppender.java | 2 +- .../java/net/kencochrane/raven/logback/SentryAppender.java | 2 +- .../java/net/kencochrane/raven/DefaultRavenFactory.java | 4 ++-- raven/src/main/java/net/kencochrane/raven/Raven.java | 2 +- raven/src/main/java/net/kencochrane/raven/RavenFactory.java | 2 +- raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java | 6 +++--- raven/src/main/java/net/kencochrane/raven/event/Event.java | 6 +++--- .../main/java/net/kencochrane/raven/event/EventBuilder.java | 2 +- .../kencochrane/raven/event/interfaces/HttpInterface.java | 6 +++--- .../raven/event/interfaces/MessageInterface.java | 2 +- .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 2 +- .../raven/marshaller/json/ExceptionInterfaceBinding.java | 4 ++-- .../kencochrane/raven/marshaller/json/JsonMarshaller.java | 3 +-- .../raven/servlet/RavenServletRequestListener.java | 2 +- .../raven/sentrystub/SentryAuthenticationFilter.java | 2 +- .../java/net/kencochrane/raven/sentrystub/SentryStub.java | 2 +- .../raven/sentrystub/SentryUdpContextListener.java | 2 +- .../kencochrane/raven/sentrystub/auth/AuthValidator.java | 4 ++-- .../raven/sentrystub/auth/InvalidAuthException.java | 2 +- 19 files changed, 28 insertions(+), 29 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index b2746a148da..96a3dbf9c75 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -157,7 +157,7 @@ protected static Event.Level formatLevel(Level level) { * @return the parameters formatted as Strings in a List. */ protected static List formatMessageParameters(Object[] parameters) { - List stringParameters = new ArrayList(parameters.length); + List stringParameters = new ArrayList<>(parameters.length); for (Object parameter : parameters) stringParameters.add((parameter != null) ? parameter.toString() : null); return stringParameters; diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 98418371c9c..0f84a4338ac 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -83,7 +83,7 @@ public SentryAppender(Raven raven) { * @return the parameters formatted as Strings in a List. */ protected static List formatMessageParameters(Object[] parameters) { - List arguments = new ArrayList(parameters.length); + List arguments = new ArrayList<>(parameters.length); for (Object argument : parameters) { arguments.add((argument != null) ? argument.toString() : null); } diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index ad3a7ea4601..1381a8b4d25 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -132,9 +132,9 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { BlockingDeque queue; if (dsn.getOptions().containsKey(QUEUE_SIZE_OPTION)) { - queue = new LinkedBlockingDeque(Integer.parseInt(dsn.getOptions().get(QUEUE_SIZE_OPTION))); + queue = new LinkedBlockingDeque<>(Integer.parseInt(dsn.getOptions().get(QUEUE_SIZE_OPTION))); } else { - queue = new LinkedBlockingDeque(); + queue = new LinkedBlockingDeque<>(); } asyncConnection.setExecutorService(new ThreadPoolExecutor( diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 84de584ff7e..a16c8f92299 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -37,7 +37,7 @@ protected Boolean initialValue() { */ public static final String NAME = ResourceBundle.getBundle("raven-build").getString("build.name"); private static final Logger logger = LoggerFactory.getLogger(Raven.class); - private final Set builderHelpers = new HashSet(); + private final Set builderHelpers = new HashSet<>(); private Connection connection; /** diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index 6711cae3754..9b068cba653 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -17,7 +17,7 @@ */ public abstract class RavenFactory { private static final ServiceLoader AUTO_REGISTERED_FACTORIES = ServiceLoader.load(RavenFactory.class); - private static final Set MANUALLY_REGISTERED_FACTORIES = new HashSet(); + private static final Set MANUALLY_REGISTERED_FACTORIES = new HashSet<>(); private static final Logger logger = LoggerFactory.getLogger(RavenFactory.class); /** diff --git a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java index 19b5bf6296e..d2f1c32ab30 100644 --- a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java @@ -47,8 +47,8 @@ public Dsn(String dsn) throws InvalidDsnException { if (dsn == null) throw new InvalidDsnException("The sentry DSN must be provided and not be null"); - options = new HashMap(); - protocolSettings = new HashSet(); + options = new HashMap<>(); + protocolSettings = new HashSet<>(); URI dsnUri = URI.create(dsn); extractProtocolInfo(dsnUri); @@ -186,7 +186,7 @@ private void makeOptionsImmutable() { *

    */ private void validate() { - List missingElements = new LinkedList(); + List missingElements = new LinkedList<>(); if (host == null) missingElements.add("host"); if (publicKey == null) diff --git a/raven/src/main/java/net/kencochrane/raven/event/Event.java b/raven/src/main/java/net/kencochrane/raven/event/Event.java index f3731e44cba..1d9d3d0738b 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/Event.java +++ b/raven/src/main/java/net/kencochrane/raven/event/Event.java @@ -63,7 +63,7 @@ public class Event implements Serializable { * Automatically created with a Map that is made unmodifiable by the {@link EventBuilder}. *

    */ - private Map tags = new HashMap(); + private Map tags = new HashMap<>(); /** * Identifies the host client from which the event was recorded. */ @@ -74,7 +74,7 @@ public class Event implements Serializable { * Automatically created with a Map that is made unmodifiable by the {@link EventBuilder}. *

    */ - private Map extra = new HashMap(); + private Map extra = new HashMap<>(); /** * Checksum for the event, allowing to group events with a similar checksum. */ @@ -85,7 +85,7 @@ public class Event implements Serializable { * Automatically created with a Map that is made unmodifiable by the {@link EventBuilder}. *

    */ - private Map sentryInterfaces = new HashMap(); + private Map sentryInterfaces = new HashMap<>(); /** * Creates a new Event (should be called only through {@link EventBuilder} with the specified identifier. diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index a75b37853cd..38640e98b3f 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -349,7 +349,7 @@ public String getHostname() { * Force an update of the cache to get the current value of the hostname. */ public void updateCache() { - FutureTask futureTask = new FutureTask(new HostRetriever()); + FutureTask futureTask = new FutureTask<>(new HostRetriever()); try { new Thread(futureTask).start(); logger.debug("Updating the hostname cache"); diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java index c1b1bc5e47e..02eb8d3c794 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java @@ -38,12 +38,12 @@ public class HttpInterface implements SentryInterface { public HttpInterface(HttpServletRequest request) { this.requestUrl = request.getRequestURL().toString(); this.method = request.getMethod(); - this.parameters = new HashMap>(); + this.parameters = new HashMap<>(); for (Map.Entry parameterMapEntry : request.getParameterMap().entrySet()) this.parameters.put(parameterMapEntry.getKey(), Arrays.asList(parameterMapEntry.getValue())); this.queryString = request.getQueryString(); if (request.getCookies() != null) { - this.cookies = new HashMap(); + this.cookies = new HashMap<>(); for (Cookie cookie : request.getCookies()) this.cookies.put(cookie.getName(), cookie.getValue()); } else { @@ -60,7 +60,7 @@ public HttpInterface(HttpServletRequest request) { this.asyncStarted = request.isAsyncStarted(); this.authType = request.getAuthType(); this.remoteUser = request.getRemoteUser(); - this.headers = new HashMap>(); + this.headers = new HashMap<>(); for (String headerName : Collections.list(request.getHeaderNames())) this.headers.put(headerName, Collections.list(request.getHeaders(headerName))); } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java index 12ceb82dd3f..8f40289da32 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java @@ -66,7 +66,7 @@ public MessageInterface(String message, String... params) { */ public MessageInterface(String message, List parameters) { this.message = message; - this.parameters = Collections.unmodifiableList(new ArrayList(parameters)); + this.parameters = Collections.unmodifiableList(new ArrayList<>(parameters)); } @Override diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index bb0a5f578a2..81b70ee7d96 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -92,7 +92,7 @@ else if (level.intValue() >= Level.ALL.intValue()) * @return the parameters formatted as Strings in a List. */ protected static List formatMessageParameters(Object[] parameters) { - List formattedParameters = new ArrayList(parameters.length); + List formattedParameters = new ArrayList<>(parameters.length); for (Object parameter : parameters) formattedParameters.add((parameter != null) ? parameter.toString() : null); return formattedParameters; diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java index b69e9f162c8..4b2795ab27a 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java @@ -65,8 +65,8 @@ public void writeInterface(JsonGenerator generator, ExceptionInterface exception * @return a Stack of Exceptions with their {@link StackTraceInterface}. */ private Deque unfoldExceptionInterface(ExceptionInterface exceptionInterface) { - Deque exceptions = new ArrayDeque(); - Set circularityDetector = new HashSet(); + Deque exceptions = new ArrayDeque<>(); + Set circularityDetector = new HashSet<>(); ImmutableThrowable throwable = exceptionInterface.getThrowable(); StackTraceElement[] enclosingStackTrace = new StackTraceElement[0]; diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 3aa60dd45c3..38c0d25fba8 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -85,8 +85,7 @@ public class JsonMarshaller implements Marshaller { private static final String ISO_8601_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; private static final Logger logger = LoggerFactory.getLogger(JsonMarshaller.class); private final JsonFactory jsonFactory = new JsonFactory(); - private final Map, InterfaceBinding> interfaceBindings = - new HashMap, InterfaceBinding>(); + private final Map, InterfaceBinding> interfaceBindings = new HashMap<>(); /** * Enables disables the compression of JSON. */ diff --git a/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java b/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java index 19c7b2a5bc5..367d729cf8e 100644 --- a/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java +++ b/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java @@ -14,7 +14,7 @@ //TODO: Consider Servlet < 3? @WebListener public class RavenServletRequestListener implements ServletRequestListener { - private static final ThreadLocal THREAD_REQUEST = new ThreadLocal(); + private static final ThreadLocal THREAD_REQUEST = new ThreadLocal<>(); public static HttpServletRequest getServletRequest() { return THREAD_REQUEST.get(); diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java index 7cf7055005f..ac124ac90d3 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryAuthenticationFilter.java @@ -57,7 +57,7 @@ private Map extractSentryAuthDetails(HttpServletRequest request) } String[] authParameters = sentryAuth.split(","); - Map authDetails = new HashMap(authParameters.length); + Map authDetails = new HashMap<>(authParameters.length); for (String authParameter : authParameters) { String[] splitParameter = authParameter.split("="); authDetails.put(splitParameter[0], splitParameter[1]); diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStub.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStub.java index bd14b7cb462..46b161ffa32 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStub.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryStub.java @@ -13,7 +13,7 @@ public final class SentryStub { private static SentryStub instance = new SentryStub(); - private final Collection events = new LinkedList(); + private final Collection events = new LinkedList<>(); private final AuthValidator authValidator = new AuthValidator(); private final Unmarshaller unmarshaller = new JsonUnmarshaller(); diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java index 89040272b2f..b0fd878a5bc 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java @@ -89,7 +89,7 @@ private void validateAuthHeader(InputStream inputStream) { } private Map parseAuthHeader(InputStream inputStream) throws IOException { - Map authHeader = new HashMap(); + Map authHeader = new HashMap<>(); int i; StringBuilder sb = new StringBuilder(); diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java index 8982c9aeb0b..0522175184f 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java @@ -18,8 +18,8 @@ public class AuthValidator { private static final String PUBLIC_KEY_PARAMETER = "sentry_key"; private static final String SECRET_KEY_PARAMETER = "sentry_secret"; private static final String SENTRY_CLIENT_PARAMETER = "sentry_client"; - private final Map publicKeySecretKey = new HashMap(); - private final Map publicKeyProjectId = new HashMap(); + private final Map publicKeySecretKey = new HashMap<>(); + private final Map publicKeyProjectId = new HashMap<>(); /** * Adds a user to consider as valid of an Auth header. diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/InvalidAuthException.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/InvalidAuthException.java index 42178aa83cc..36d02d07655 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/InvalidAuthException.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/InvalidAuthException.java @@ -5,7 +5,7 @@ import java.util.LinkedList; public class InvalidAuthException extends RuntimeException { - private final Collection detailedMessages = new LinkedList(); + private final Collection detailedMessages = new LinkedList<>(); public InvalidAuthException() { } From d249b29d00286839b62cb27ecaa91efde98901a2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 22 Jan 2014 19:14:23 +1100 Subject: [PATCH 1078/2152] Fix imports --- .../raven/marshaller/json/ExceptionInterfaceBindingTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index 2dadb84b764..5f53f9afd5b 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -8,15 +8,12 @@ import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.ImmutableThrowable; import net.kencochrane.raven.event.interfaces.StackTraceInterface; -import org.hamcrest.MatcherAssert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.IOException; -import static net.kencochrane.raven.marshaller.json.JsonTestTool.JsonGeneratorTool; -import static net.kencochrane.raven.marshaller.json.JsonTestTool.jsonResource; -import static net.kencochrane.raven.marshaller.json.JsonTestTool.newJsonGenerator; +import static net.kencochrane.raven.marshaller.json.JsonTestTool.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; From be3dab54cd4dd8c0e29b5429234c83684a9ad83e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 05:12:44 +1100 Subject: [PATCH 1079/2152] Use Diamond operator when possible --- .../raven/appengine/connection/AppEngineAsyncConnection.java | 3 +-- .../appengine/connection/AppEngineAsyncConnectionTest.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java index 2273bb7895f..9bd779844cc 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java @@ -36,8 +36,7 @@ */ public class AppEngineAsyncConnection implements Connection { private static final Logger logger = LoggerFactory.getLogger(AppEngineAsyncConnection.class); - private static final Map APP_ENGINE_ASYNC_CONNECTIONS - = new HashMap(); + private static final Map APP_ENGINE_ASYNC_CONNECTIONS = new HashMap<>(); private static final String TASK_TAG = "RavenTask"; /** * Maximum number of tasks that can be leased at once when closing the connection. diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java index dfc85076a76..4875923f997 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java @@ -144,7 +144,7 @@ public void testEventLinkedToCorrectConnection(@Injectable final Event mockEvent asyncConnection2.send(mockEvent); new Verifications() {{ - List taskOptionsList = new ArrayList(); + List taskOptionsList = new ArrayList<>(); DeferredTask deferredTask; mockQueue.add(withCapture(taskOptionsList)); From d9363b874b93eae3d981b16dfe0fe58eb0d3731d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 06:00:27 +1100 Subject: [PATCH 1080/2152] Fix style and indentation --- .../raven/appengine/AppEngineRavenFactory.java | 7 +++++-- .../connection/AppEngineAsyncConnection.java | 12 ++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java index 5e0bbda6d24..e830baa934e 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java @@ -5,6 +5,9 @@ import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.dsn.Dsn; +/** + * RavenFactory dedicated to create async connections within Google App Engine. + */ public class AppEngineRavenFactory extends DefaultRavenFactory { /** * Option for the queue name used in Google App Engine of threads assigned for the connection. @@ -12,11 +15,11 @@ public class AppEngineRavenFactory extends DefaultRavenFactory { public static final String QUEUE_NAME = "raven.async.gaequeuename"; /** - * Encapsulates an already existing connection in an {@link net.kencochrane.raven.appengine.connection.AppEngineAsyncConnection} and get the async options + * Encapsulates an already existing connection in an {@link AppEngineAsyncConnection} and get the async options * from the Sentry DSN. * * @param dsn Data Source Name of the Sentry server. - * @param connection Connection to encapsulate in an {@link net.kencochrane.raven.appengine.connection.AppEngineAsyncConnection}. + * @param connection Connection to encapsulate in an {@link AppEngineAsyncConnection}. * @return the asynchronous connection. */ @Override diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java index 9bd779844cc..48681ef46a7 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java @@ -28,7 +28,7 @@ *

    *

    * Google App Engine serialises the tasks before queuing them, to keep a link between the task and the - * {@link AppEngineAsyncConnection} that created it, a register of the instances of {@code AppEngineAsyncConnection} is + * {@link AppEngineAsyncConnection} associated, a register of the instances of {@code AppEngineAsyncConnection} is * kept in {@link #APP_ENGINE_ASYNC_CONNECTIONS}.
    * This register is populated when a new instance of {@code AppEngineAsyncConnection} is created and the connection * is removed from the register when it has been closed with {@link #close()}. @@ -55,7 +55,7 @@ public class AppEngineAsyncConnection implements Connection { */ private final Connection actualConnection; /** - * Queue used to send deferred tasks + * Queue used to send deferred tasks. */ private Queue queue = QueueFactory.getDefaultQueue(); /** @@ -69,7 +69,7 @@ public class AppEngineAsyncConnection implements Connection { * Will propagate the {@link #close()} operation. *

    * - * @param actualConnection connection used to send the events. + * @param actualConnection Connection used to send the events. */ public AppEngineAsyncConnection(Connection actualConnection) { this.actualConnection = actualConnection; @@ -101,7 +101,8 @@ public void close() throws IOException { try { List tasks; do { - tasks = queue.leaseTasksByTag(MAXIMUM_TASK_LEASE_PERIOD_DAYS, TimeUnit.DAYS, MAXIMUM_TASKS_LEASED, TASK_TAG); + tasks = queue.leaseTasksByTag(MAXIMUM_TASK_LEASE_PERIOD_DAYS, TimeUnit.DAYS, + MAXIMUM_TASKS_LEASED, TASK_TAG); queue.deleteTask(tasks); } while (!tasks.isEmpty()); } finally { @@ -120,8 +121,7 @@ public void setQueue(String queueName) { } /** - * Simple DeferredTask using the {@link #send(net.kencochrane.raven.event.Event)} method of the - * {@link #actualConnection}. + * Simple DeferredTask using the {@link #send(Event)} method of the {@link #actualConnection}. */ private static final class EventSubmitter implements DeferredTask { private final UUID connectionId; From d6f1bc38d09e685d7e4f6e9f81c762b3af7ccc9f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 06:01:02 +1100 Subject: [PATCH 1081/2152] Base GAE connection register on used defined IDs rather than generated ones --- .../connection/AppEngineAsyncConnection.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java index 48681ef46a7..0c4c1fb7d17 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java @@ -14,7 +14,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.UUID; import java.util.concurrent.TimeUnit; import static com.google.appengine.api.taskqueue.DeferredTaskContext.setDoNotRetry; @@ -31,12 +30,14 @@ * {@link AppEngineAsyncConnection} associated, a register of the instances of {@code AppEngineAsyncConnection} is * kept in {@link #APP_ENGINE_ASYNC_CONNECTIONS}.
    * This register is populated when a new instance of {@code AppEngineAsyncConnection} is created and the connection - * is removed from the register when it has been closed with {@link #close()}. + * is removed from the register when it has been closed with {@link #close()}.
    + * The register works based on identifier defined by the user. There is no ID conflict handling, the user is expected + * to manage the uniqueness of those ID. *

    */ public class AppEngineAsyncConnection implements Connection { private static final Logger logger = LoggerFactory.getLogger(AppEngineAsyncConnection.class); - private static final Map APP_ENGINE_ASYNC_CONNECTIONS = new HashMap<>(); + private static final Map APP_ENGINE_ASYNC_CONNECTIONS = new HashMap<>(); private static final String TASK_TAG = "RavenTask"; /** * Maximum number of tasks that can be leased at once when closing the connection. @@ -49,7 +50,7 @@ public class AppEngineAsyncConnection implements Connection { /** * Identifier of the async connection. */ - private final UUID id = UUID.randomUUID(); + private final String id; /** * Connection used to actually send the events. */ @@ -69,10 +70,12 @@ public class AppEngineAsyncConnection implements Connection { * Will propagate the {@link #close()} operation. *

    * + * @param id Identifier of the connection shared across all the instances of the application. * @param actualConnection Connection used to send the events. */ - public AppEngineAsyncConnection(Connection actualConnection) { + public AppEngineAsyncConnection(String id, Connection actualConnection) { this.actualConnection = actualConnection; + this.id = id; APP_ENGINE_ASYNC_CONNECTIONS.put(id, this); } @@ -124,10 +127,10 @@ public void setQueue(String queueName) { * Simple DeferredTask using the {@link #send(Event)} method of the {@link #actualConnection}. */ private static final class EventSubmitter implements DeferredTask { - private final UUID connectionId; + private final String connectionId; private final Event event; - private EventSubmitter(UUID connectionId, Event event) { + private EventSubmitter(String connectionId, Event event) { this.connectionId = connectionId; this.event = event; } From e530ae075e685c27cdc7bf6e6a0258ddc06780fb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 06:01:39 +1100 Subject: [PATCH 1082/2152] Use the connectionId provided by the user in the AppEngineRavenFactory --- .../appengine/AppEngineRavenFactory.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java index e830baa934e..a14af4967cc 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java @@ -13,6 +13,19 @@ public class AppEngineRavenFactory extends DefaultRavenFactory { * Option for the queue name used in Google App Engine of threads assigned for the connection. */ public static final String QUEUE_NAME = "raven.async.gaequeuename"; + /** + * Option to define the identifier of the async connection across every instance of the application. + *

    + * It is important to set a different connection identifier for each opened connection to keep the uniqueness + * of connection ID. + *

    + *

    + * If the connection identifier is not specified, the system will define a connection identifier itself, but its + * uniqueness within an instance isn't guaranteed. + *

    + * {@see AppEngineAsyncConnection} + */ + public static final String CONNECTION_IDENTIFIER = "raven.async.gae.connectionid"; /** * Encapsulates an already existing connection in an {@link AppEngineAsyncConnection} and get the async options @@ -24,7 +37,14 @@ public class AppEngineRavenFactory extends DefaultRavenFactory { */ @Override protected Connection createAsyncConnection(Dsn dsn, Connection connection) { - AppEngineAsyncConnection asyncConnection = new AppEngineAsyncConnection(connection); + String connectionIdentifier; + if (dsn.getOptions().containsKey(QUEUE_NAME)) { + connectionIdentifier = dsn.getOptions().get(CONNECTION_IDENTIFIER); + } else { + connectionIdentifier = AppEngineRavenFactory.class.getCanonicalName() + dsn; + } + + AppEngineAsyncConnection asyncConnection = new AppEngineAsyncConnection(connectionIdentifier, connection); if (dsn.getOptions().containsKey(QUEUE_NAME)) { asyncConnection.setQueue(dsn.getOptions().get(QUEUE_NAME)); From 4d1c9be8a218786907a1750c65135167895a3de1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 06:08:20 +1100 Subject: [PATCH 1083/2152] Rename the queuename option for GAE --- .../net/kencochrane/raven/appengine/AppEngineRavenFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java index a14af4967cc..396d0a02307 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java @@ -12,7 +12,7 @@ public class AppEngineRavenFactory extends DefaultRavenFactory { /** * Option for the queue name used in Google App Engine of threads assigned for the connection. */ - public static final String QUEUE_NAME = "raven.async.gaequeuename"; + public static final String QUEUE_NAME = "raven.async.gae.queuename"; /** * Option to define the identifier of the async connection across every instance of the application. *

    From 279dfabc9a18cae146886657a6a491fa0b5c4d50 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 06:08:36 +1100 Subject: [PATCH 1084/2152] Update the documentation to reflect the new options --- raven-appengine/README.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/raven-appengine/README.md b/raven-appengine/README.md index 113aebe2240..efc3a910294 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -30,11 +30,23 @@ Relies on: ## Usage This module provides a new `RavenFactory` which replaces the default async system with a GAE compatible one. -By default, the default task queue will be used, but it's possible to specify which one will be used with the -`raven.async.gaequeuename` option: - - http://public:private@getsentry.com/1?raven.async.gaequeuename=MyQueueName The queue size and thread options will not be used as they are specific to the default multithreaded system. It is necessary to force the raven factory name to `net.kencochrane.raven.appengine.AppEngineRavenFactory`. + +### Queue name + +By default, the default task queue will be used, but it's possible to specify which one will be used with the +`raven.async.gae.queuename` option: + + http://public:private@getsentry.com/1?raven.async.gae.queuename=MyQueueName + +### Connection name + +As the queued tasks are sent across different instances of the application, it's important to be able to identify which +connection should be used when processing the event. +To do so, the GAE module will identify each connection based on an identifier either automatically generated or user defined. +TO manually set the connection identifier (only used internally) use the option `raven.async.connectionid`: + + http://public:private@getsentry.com/1?raven.async.gae.connectionid=MyConnection From 27c01c814af714e3ff771cb9f4fe3b272897d25c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 06:09:29 +1100 Subject: [PATCH 1085/2152] Add tests to ensure AppEngineRavenFactory connectionId system works --- .../appengine/AppEngineRavenFactoryTest.java | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/AppEngineRavenFactoryTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/AppEngineRavenFactoryTest.java index 608ed4095dd..a151f320cb9 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/AppEngineRavenFactoryTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/AppEngineRavenFactoryTest.java @@ -37,13 +37,43 @@ public void asyncConnectionCreatedByAppEngineRavenFactoryIsForAppEngine() { assertThat(connection, is(instanceOf(AppEngineAsyncConnection.class))); } + @Test + public void asyncConnectionWithoutConnectionIdGeneratesDefaultId() { + final String dnsString = "a1fe25d3-bc41-4040-8aa2-484e5aae87c5"; + new NonStrictExpectations() {{ + mockDsn.toString(); + result = dnsString; + }}; + + appEngineRavenFactory.createAsyncConnection(mockDsn, mockConnection); + + new Verifications() {{ + String connectionId = AppEngineRavenFactory.class.getCanonicalName() + dnsString; + new AppEngineAsyncConnection(connectionId, mockConnection); + }}; + } + + @Test + public void asyncConnectionWithConnectionIdUsesId( + @Injectable("543afd41-379d-41cb-8c99-8ce73e83a0cc") final String connectionId) { + new NonStrictExpectations() {{ + mockDsn.getOptions(); + result = Collections.singletonMap(AppEngineRavenFactory.CONNECTION_IDENTIFIER, connectionId); + }}; + + appEngineRavenFactory.createAsyncConnection(mockDsn, mockConnection); + + new Verifications() {{ + new AppEngineAsyncConnection(connectionId, mockConnection); + }}; + } + @Test public void asyncConnectionWithoutQueueNameKeepsDefaultQueue( @Mocked final AppEngineAsyncConnection mockAppEngineAsyncConnection) { appEngineRavenFactory.createAsyncConnection(mockDsn, mockConnection); - new Verifications(){{ - new AppEngineAsyncConnection(mockConnection); + new Verifications() {{ mockAppEngineAsyncConnection.setQueue(anyString); times = 0; }}; @@ -53,15 +83,14 @@ public void asyncConnectionWithoutQueueNameKeepsDefaultQueue( public void asyncConnectionWithQueueNameSetsQueue( @Mocked final AppEngineAsyncConnection mockAppEngineAsyncConnection, @Injectable("queueName") final String mockQueueName) { - new NonStrictExpectations(){{ + new NonStrictExpectations() {{ mockDsn.getOptions(); result = Collections.singletonMap(AppEngineRavenFactory.QUEUE_NAME, mockQueueName); }}; appEngineRavenFactory.createAsyncConnection(mockDsn, mockConnection); - new Verifications(){{ - new AppEngineAsyncConnection(mockConnection); + new Verifications() {{ mockAppEngineAsyncConnection.setQueue(mockQueueName); }}; } From e87cf53c7649626f6c6f1c9146ba0b5c1bfb498b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 06:10:17 +1100 Subject: [PATCH 1086/2152] Replace the UUID with a user defined String in AppEngineAsyncConnectionTest --- .../AppEngineAsyncConnectionTest.java | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java index 4875923f997..37aa4abbd89 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java @@ -25,6 +25,8 @@ public class AppEngineAsyncConnectionTest { private Queue mockQueue; @Mocked(methods = "getDefaultQueue") private QueueFactory queueFactory; + @Injectable("7b55a129-6975-4434-8edc-29ceefd38c95") + private String mockConnectionId; private static DeferredTask extractDeferredTask(TaskOptions taskOptions) throws Exception { ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(taskOptions.getPayload())); @@ -43,37 +45,27 @@ public void setUp() throws Exception { QueueFactory.getDefaultQueue(); result = mockQueue; }}; - asyncConnection = new AppEngineAsyncConnection(mockConnection); + asyncConnection = new AppEngineAsyncConnection(mockConnectionId, mockConnection); } @Test - public void testRegisterNewInstance(@Injectable final UUID mockUuid, @Mocked("randomUUID") UUID uuid) - throws Exception { - new NonStrictExpectations() {{ - UUID.randomUUID(); - result = mockUuid; - }}; + public void testRegisterNewInstance( + @Injectable("1bac02f7-c9ed-41b8-9126-e2da257a06ef") final String mockConnectionId) throws Exception { + AppEngineAsyncConnection asyncConnection2 = new AppEngineAsyncConnection(mockConnectionId, mockConnection); - AppEngineAsyncConnection asyncConnection2 = new AppEngineAsyncConnection(mockConnection); - - Map appEngineAsyncConnectionRegister + Map appEngineAsyncConnectionRegister = Deencapsulation.getField(AppEngineAsyncConnection.class, "APP_ENGINE_ASYNC_CONNECTIONS"); - assertThat(appEngineAsyncConnectionRegister, hasEntry(mockUuid, asyncConnection2)); + assertThat(appEngineAsyncConnectionRegister, hasEntry(mockConnectionId, asyncConnection2)); } @Test - public void testUnregisterInstance(@Injectable final UUID mockUuid, @Mocked("randomUUID") UUID uuid) - throws Exception { - new NonStrictExpectations() {{ - UUID.randomUUID(); - result = mockUuid; - }}; + public void testUnregisterInstance( + @Injectable("648f76e2-39ed-40e0-91a2-b1887a03b782") final String mockConnectionId) throws Exception { + new AppEngineAsyncConnection(mockConnectionId, mockConnection).close(); - new AppEngineAsyncConnection(mockConnection).close(); - - Map appEngineAsyncConnectionRegister + Map appEngineAsyncConnectionRegister = Deencapsulation.getField(AppEngineAsyncConnection.class, "APP_ENGINE_ASYNC_CONNECTIONS"); - assertThat(appEngineAsyncConnectionRegister, not(hasKey(mockUuid))); + assertThat(appEngineAsyncConnectionRegister, not(hasKey(mockConnectionId))); } @Test @@ -137,8 +129,10 @@ void add(TaskOptions taskOptions) throws Exception { } @Test - public void testEventLinkedToCorrectConnection(@Injectable final Event mockEvent) throws Exception { - final AppEngineAsyncConnection asyncConnection2 = new AppEngineAsyncConnection(mockConnection); + public void testEventLinkedToCorrectConnection( + @Injectable("eb37bfe4-7316-47e8-94e4-073aefd0fbf8") final String mockConnectionId, + @Injectable final Event mockEvent) throws Exception { + final AppEngineAsyncConnection asyncConnection2 = new AppEngineAsyncConnection(mockConnectionId, mockConnection); asyncConnection.send(mockEvent); asyncConnection2.send(mockEvent); From 5cbd226c003ffcdd8db3ae62d04bff5ef9dca902 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 06:29:03 +1100 Subject: [PATCH 1087/2152] Use ARM to marshall the data --- .../raven/marshaller/json/JsonMarshaller.java | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 38c0d25fba8..4b1bd3c6b21 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -100,19 +100,10 @@ public void marshall(Event event, OutputStream destination) { destination = new DeflaterOutputStream(base64().encodingStream( new OutputStreamWriter(destination, Charsets.UTF_8))); - JsonGenerator generator = null; - try { - generator = jsonFactory.createGenerator(destination); + try (JsonGenerator generator = jsonFactory.createGenerator(destination)){ writeContent(generator, event); } catch (IOException e) { logger.error("An exception occurred while serialising the event.", e); - } finally { - try { - if (generator != null) - generator.close(); - } catch (IOException e) { - logger.error("An exception occurred while closing the json stream.", e); - } } } From b13986b02f5dfde9305642a3601e58077aadbe53 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 06:41:25 +1100 Subject: [PATCH 1088/2152] Provide toString equals and hashCode to Event interfaces --- .../event/interfaces/ExceptionInterface.java | 23 ++++++++++ .../raven/event/interfaces/HttpInterface.java | 46 +++++++++++++++++++ .../event/interfaces/MessageInterface.java | 23 ++++++++++ .../event/interfaces/SentryException.java | 35 ++++++++++++++ .../event/interfaces/StackTraceInterface.java | 22 +++++++++ 5 files changed, 149 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java index fc9be654507..ccfff3acca8 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java @@ -38,4 +38,27 @@ public String getInterfaceName() { public Deque getExceptions() { return exceptions; } + + @Override + public String toString() { + return "ExceptionInterface{" + + "exceptions=" + exceptions + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ExceptionInterface that = (ExceptionInterface) o; + + return exceptions.equals(that.exceptions); + + } + + @Override + public int hashCode() { + return exceptions.hashCode(); + } } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java index 02eb8d3c794..5a48bf1b8ab 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java @@ -137,4 +137,50 @@ public String getRemoteUser() { public Map> getHeaders() { return Collections.unmodifiableMap(headers); } + + @Override + public String toString() { + return "HttpInterface{" + + "requestUrl='" + requestUrl + '\'' + + ", method='" + method + '\'' + + ", queryString='" + queryString + '\'' + + ", parameters=" + parameters + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + HttpInterface that = (HttpInterface) o; + + if (asyncStarted != that.asyncStarted) return false; + if (localPort != that.localPort) return false; + if (secure != that.secure) return false; + if (serverPort != that.serverPort) return false; + if (authType != null ? !authType.equals(that.authType) : that.authType != null) return false; + if (!cookies.equals(that.cookies)) return false; + if (!headers.equals(that.headers)) return false; + if (localAddr != null ? !localAddr.equals(that.localAddr) : that.localAddr != null) return false; + if (localName != null ? !localName.equals(that.localName) : that.localName != null) return false; + if (method != null ? !method.equals(that.method) : that.method != null) return false; + if (!parameters.equals(that.parameters)) return false; + if (protocol != null ? !protocol.equals(that.protocol) : that.protocol != null) return false; + if (queryString != null ? !queryString.equals(that.queryString) : that.queryString != null) return false; + if (remoteAddr != null ? !remoteAddr.equals(that.remoteAddr) : that.remoteAddr != null) return false; + if (remoteUser != null ? !remoteUser.equals(that.remoteUser) : that.remoteUser != null) return false; + if (!requestUrl.equals(that.requestUrl)) return false; + if (serverName != null ? !serverName.equals(that.serverName) : that.serverName != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = requestUrl.hashCode(); + result = 31 * result + (method != null ? method.hashCode() : 0); + result = 31 * result + parameters.hashCode(); + return result; + } } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java index 7244bb31c8c..6284a7cd5b1 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java @@ -81,4 +81,27 @@ public String getMessage() { public List getParameters() { return parameters; } + + @Override + public String toString() { + return "MessageInterface{" + + "message='" + message + '\'' + + ", parameters=" + parameters + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MessageInterface that = (MessageInterface) o; + + return message.equals(that.message) && parameters.equals(that.parameters); + } + + @Override + public int hashCode() { + return message.hashCode(); + } } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java index 198d702a094..e5005f28f9a 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java @@ -101,4 +101,39 @@ public String getExceptionPackageName() { public StackTraceInterface getStackTraceInterface() { return stackTraceInterface; } + + @Override + public String toString() { + return "SentryException{" + + "exceptionMessage='" + exceptionMessage + '\'' + + ", exceptionClassName='" + exceptionClassName + '\'' + + ", exceptionPackageName='" + exceptionPackageName + '\'' + + ", stackTraceInterface=" + stackTraceInterface + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SentryException that = (SentryException) o; + + if (!exceptionClassName.equals(that.exceptionClassName)) return false; + if (exceptionMessage != null ? !exceptionMessage.equals(that.exceptionMessage) : that.exceptionMessage != null) + return false; + if (exceptionPackageName != null ? !exceptionPackageName.equals(that.exceptionPackageName) : that.exceptionPackageName != null) + return false; + if (!stackTraceInterface.equals(that.stackTraceInterface)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = exceptionMessage != null ? exceptionMessage.hashCode() : 0; + result = 31 * result + exceptionClassName.hashCode(); + result = 31 * result + (exceptionPackageName != null ? exceptionPackageName.hashCode() : 0); + return result; + } } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java index 6922212c4f4..9aec8562e42 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java @@ -57,4 +57,26 @@ public StackTraceElement[] getStackTrace() { public int getFramesCommonWithEnclosing() { return framesCommonWithEnclosing; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + StackTraceInterface that = (StackTraceInterface) o; + + return Arrays.equals(stackTrace, that.stackTrace); + } + + @Override + public int hashCode() { + return Arrays.hashCode(stackTrace); + } + + @Override + public String toString() { + return "StackTraceInterface{" + + "stackTrace=" + Arrays.toString(stackTrace) + + '}'; + } } From e96465a66c84c17f7cc1812c25131e95c292822b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 06:56:01 +1100 Subject: [PATCH 1089/2152] Fix indentation and style --- .../raven/logback/SentryAppender.java | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 72957b008db..56d49dc037e 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -14,8 +14,8 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.SentryException; import net.kencochrane.raven.event.interfaces.MessageInterface; +import net.kencochrane.raven.event.interfaces.SentryException; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,9 +36,7 @@ public class SentryAppender extends AppenderBase { * Name of the {@link Event#extra} property containing the Thread name. */ public static final String THREAD_NAME = "Raven-Threadname"; - private static final Logger logger = LoggerFactory.getLogger(SentryAppender.class); - /** * Current instance of {@link Raven}. * @@ -207,7 +205,7 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { return eventBuilder.build(); } - private Deque extractExceptionQueue(final ILoggingEvent iLoggingEvent) { + private Deque extractExceptionQueue(ILoggingEvent iLoggingEvent) { IThrowableProxy throwableProxy = iLoggingEvent.getThrowableProxy(); Deque exceptions = new ArrayDeque<>(); Set circularityDetector = new HashSet<>(); @@ -220,9 +218,9 @@ private Deque extractExceptionQueue(final ILoggingEvent iLoggin break; } - final StackTraceElement[] stackTraceElements = toStackTraceElements(throwableProxy); + StackTraceElement[] stackTraceElements = toStackTraceElements(throwableProxy); StackTraceInterface stackTrace = new StackTraceInterface(stackTraceElements, enclosingStackTrace); - exceptions.push(createExceptionWithStackTraceFrom(throwableProxy, stackTrace)); + exceptions.push(createSentryExceptionFrom(throwableProxy, stackTrace)); enclosingStackTrace = stackTraceElements; throwableProxy = throwableProxy.getCause(); } @@ -230,37 +228,34 @@ private Deque extractExceptionQueue(final ILoggingEvent iLoggin return exceptions; } - private SentryException createExceptionWithStackTraceFrom(final IThrowableProxy throwableProxy, - final StackTraceInterface stackTrace) { - final String exceptionMessage = throwableProxy.getMessage(); - final String exceptionClassName = throwableProxy.getClassName(); - final String exceptionPackageName = extractPackageName(throwableProxy); + private SentryException createSentryExceptionFrom(IThrowableProxy throwableProxy, StackTraceInterface stackTrace) { + String exceptionMessage = throwableProxy.getMessage(); + String exceptionClassName = throwableProxy.getClassName(); + String exceptionPackageName = extractPackageName(throwableProxy); return new SentryException(exceptionMessage, exceptionClassName, exceptionPackageName, stackTrace); } - private String extractPackageName(final IThrowableProxy throwableProxy) { - + private String extractPackageName(IThrowableProxy throwableProxy) { // TODO this probably fails with application specific classes which are unknown to the logserver try { - final Class exceptionClass = Class.forName(throwableProxy.getClassName()); - final Package exceptionPackage = exceptionClass.getPackage(); + Class exceptionClass = Class.forName(throwableProxy.getClassName()); + Package exceptionPackage = exceptionClass.getPackage(); if (exceptionPackage != null) { return exceptionPackage.getName(); } - - } catch (final ClassNotFoundException e) { + } catch (ClassNotFoundException e) { logger.error("Could not load exception class", e); } return null; } - private StackTraceElement[] toStackTraceElements(final IThrowableProxy throwableProxy) { - final StackTraceElementProxy[] stackTraceElementProxies = throwableProxy.getStackTraceElementProxyArray(); - final List stackTraceElements = Lists.newArrayList(); + private StackTraceElement[] toStackTraceElements(IThrowableProxy throwableProxy) { + StackTraceElementProxy[] stackTraceElementProxies = throwableProxy.getStackTraceElementProxyArray(); + List stackTraceElements = Lists.newArrayList(); - for (final StackTraceElementProxy stackTraceElementProxy : stackTraceElementProxies) { + for (StackTraceElementProxy stackTraceElementProxy : stackTraceElementProxies) { stackTraceElements.add(stackTraceElementProxy.getStackTraceElement()); } From 74d0f0c1fe89002629c0950a68f5290493bc9851 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 07:19:05 +1100 Subject: [PATCH 1090/2152] Fix code style --- .../net/kencochrane/raven/marshaller/json/JsonMarshaller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 4b1bd3c6b21..5789b364caf 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -100,7 +100,7 @@ public void marshall(Event event, OutputStream destination) { destination = new DeflaterOutputStream(base64().encodingStream( new OutputStreamWriter(destination, Charsets.UTF_8))); - try (JsonGenerator generator = jsonFactory.createGenerator(destination)){ + try (JsonGenerator generator = jsonFactory.createGenerator(destination)) { writeContent(generator, event); } catch (IOException e) { logger.error("An exception occurred while serialising the event.", e); From 875b43f2a2c832dede3aa28482d91388938eebdf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 07:21:30 +1100 Subject: [PATCH 1091/2152] Remove unecessary list creation --- .../net/kencochrane/raven/logback/SentryAppender.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 56d49dc037e..a99779c1595 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -252,14 +252,14 @@ private String extractPackageName(IThrowableProxy throwableProxy) { } private StackTraceElement[] toStackTraceElements(IThrowableProxy throwableProxy) { - StackTraceElementProxy[] stackTraceElementProxies = throwableProxy.getStackTraceElementProxyArray(); - List stackTraceElements = Lists.newArrayList(); + StackTraceElementProxy[] stackTraceElementProxies = throwableProxy.getStackTraceElementProxyArray(); + StackTraceElement[] stackTraceElements = new StackTraceElement[stackTraceElementProxies.length]; - for (StackTraceElementProxy stackTraceElementProxy : stackTraceElementProxies) { - stackTraceElements.add(stackTraceElementProxy.getStackTraceElement()); + for (int i = 0, stackTraceElementsLength = stackTraceElementProxies.length; i < stackTraceElementsLength; i++) { + stackTraceElements[i] = stackTraceElementProxies[i].getStackTraceElement(); } - return stackTraceElements.toArray(new StackTraceElement[stackTraceElements.size()]); + return stackTraceElements; } public void setDsn(String dsn) { From 75c4639759493ce55c7a4f128cfdfdbd020a2fd5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 07:21:50 +1100 Subject: [PATCH 1092/2152] Extract package name and class name from throwableProxy classname --- .../raven/logback/SentryAppender.java | 31 +++++++++++-------- .../event/interfaces/SentryException.java | 2 +- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index a99779c1595..8d23b3cb7bc 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -6,7 +6,6 @@ import ch.qos.logback.classic.spi.StackTraceElementProxy; import ch.qos.logback.core.AppenderBase; import com.google.common.base.Splitter; -import com.google.common.collect.Lists; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; @@ -230,25 +229,31 @@ private Deque extractExceptionQueue(ILoggingEvent iLoggingEvent private SentryException createSentryExceptionFrom(IThrowableProxy throwableProxy, StackTraceInterface stackTrace) { String exceptionMessage = throwableProxy.getMessage(); - String exceptionClassName = throwableProxy.getClassName(); - String exceptionPackageName = extractPackageName(throwableProxy); + String[] packageNameSimpleName = extractPackageSimpleClassName(throwableProxy.getClassName()); + String exceptionPackageName = packageNameSimpleName[0]; + String exceptionClassName = packageNameSimpleName[1]; + return new SentryException(exceptionMessage, exceptionClassName, exceptionPackageName, stackTrace); } - private String extractPackageName(IThrowableProxy throwableProxy) { - // TODO this probably fails with application specific classes which are unknown to the logserver + private String[] extractPackageSimpleClassName(String canonicalClassName) { + String[] packageNameSimpleName = new String[2]; try { - Class exceptionClass = Class.forName(throwableProxy.getClassName()); + Class exceptionClass = Class.forName(canonicalClassName); Package exceptionPackage = exceptionClass.getPackage(); - - if (exceptionPackage != null) { - return exceptionPackage.getName(); - } + packageNameSimpleName[0] = exceptionPackage != null ? exceptionPackage.getName() : SentryException.DEFAULT_PACKAGE_NAME; + packageNameSimpleName[1] = exceptionClass.getSimpleName(); } catch (ClassNotFoundException e) { - logger.error("Could not load exception class", e); + int lastDot = canonicalClassName.lastIndexOf('.'); + if (lastDot != -1) { + packageNameSimpleName[0] = canonicalClassName.substring(0, lastDot); + packageNameSimpleName[1] = canonicalClassName.substring(lastDot); + } else { + packageNameSimpleName[0] = SentryException.DEFAULT_PACKAGE_NAME; + packageNameSimpleName[1] = canonicalClassName; + } } - - return null; + return packageNameSimpleName; } private StackTraceElement[] toStackTraceElements(IThrowableProxy throwableProxy) { diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java index e5005f28f9a..b44db30ee7e 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java @@ -12,7 +12,7 @@ public final class SentryException { /** * Name used when the class' package is the default one. */ - private static final String DEFAULT_PACKAGE_NAME = "(default)"; + public static final String DEFAULT_PACKAGE_NAME = "(default)"; private final String exceptionMessage; private final String exceptionClassName; private final String exceptionPackageName; From 7c3235b2e5144c4111ee780b66f8a63d2f4ed208 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 07:43:42 +1100 Subject: [PATCH 1093/2152] Fix indentation --- .../log4j2/SentryAppenderEventBuildingTest.java | 2 +- .../logback/SentryAppenderEventBuildingTest.java | 2 +- .../test/java/net/kencochrane/raven/RavenTest.java | 2 +- .../raven/connection/AbstractConnectionTest.java | 4 +++- .../raven/connection/AsyncConnectionTest.java | 13 ++++++++----- .../kencochrane/raven/event/EventBuilderTest.java | 4 ++-- .../raven/event/interfaces/HttpInterfaceTest.java | 9 +++++++-- .../json/MessageInterfaceBindingTest.java | 4 +--- .../json/StackTraceInterfaceBindingTest.java | 4 +--- 9 files changed, 25 insertions(+), 19 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java index 0c377c314ee..e39672f34f5 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java @@ -6,8 +6,8 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.SentryException; import net.kencochrane.raven.event.interfaces.MessageInterface; +import net.kencochrane.raven.event.interfaces.SentryException; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.MarkerManager; diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java index 0d44df25cc6..7ddafab454c 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java @@ -12,8 +12,8 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.SentryException; import net.kencochrane.raven.event.interfaces.MessageInterface; +import net.kencochrane.raven.event.interfaces.SentryException; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.hamcrest.Matchers; import org.slf4j.MarkerFactory; diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index 449dfa884b1..a2b877feb96 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -102,7 +102,7 @@ public void testRunBuilderHelpers(@Injectable final EventBuilderHelper mockBuild raven.runBuilderHelpers(mockEventBuilder); - new Verifications(){{ + new Verifications() {{ mockBuilderHelper.helpBuildingEvent(mockEventBuilder); }}; } diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java index d222d4c3e16..f6e2bb7fecd 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java @@ -1,6 +1,8 @@ package net.kencochrane.raven.connection; -import mockit.*; +import mockit.Injectable; +import mockit.Mock; +import mockit.MockUp; import net.kencochrane.raven.event.Event; import org.mockito.MockitoAnnotations; import org.mockito.Spy; diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index b21fe39c677..2c768a38042 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -1,6 +1,9 @@ package net.kencochrane.raven.connection; -import mockit.*; +import mockit.Delegate; +import mockit.Expectations; +import mockit.Injectable; +import mockit.Verifications; import net.kencochrane.raven.event.Event; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -36,17 +39,17 @@ public void testCloseOperation() throws Exception { public void testSendEventQueued(@Injectable final Event mockEvent) throws Exception { asyncConnection.send(mockEvent); - new Verifications(){{ + new Verifications() {{ mockExecutorService.execute((Runnable) any); }}; } @Test public void testQueuedEventExecuted(@Injectable final Event mockEvent) throws Exception { - new Expectations(){{ + new Expectations() {{ mockExecutorService.execute((Runnable) any); result = new Delegate() { - public void execute(Runnable runnable){ + public void execute(Runnable runnable) { runnable.run(); } }; @@ -54,7 +57,7 @@ public void execute(Runnable runnable){ asyncConnection.send(mockEvent); - new Verifications(){{ + new Verifications() {{ mockConnection.send(mockEvent); }}; } diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index 5855ab046d1..a7d35f4a3bf 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -28,7 +28,7 @@ private static void resetHostnameCache() { @BeforeMethod public void setUp() throws Exception { - new NonStrictExpectations(InetAddress.class){{ + new NonStrictExpectations(InetAddress.class) {{ InetAddress.getLocalHost(); result = mockLocalHost; mockLocalHost.getCanonicalHostName(); @@ -410,7 +410,7 @@ public void builtEventWithSentryInterfacesHasProperSentryInterfaces( @Injectable("sentryInterfaceName") final String mockSentryInterfaceName, @Injectable final SentryInterface mockSentryInterface) throws Exception { - new NonStrictExpectations(){{ + new NonStrictExpectations() {{ mockSentryInterface.getInterfaceName(); result = mockSentryInterfaceName; }}; diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java index 7b478419752..902499da8da 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java @@ -162,7 +162,12 @@ public void testNullCookies() throws Exception { } private static class EmptyEnumeration implements Enumeration { - public boolean hasMoreElements() { return false; } - public E nextElement() { throw new NoSuchElementException(); } + public boolean hasMoreElements() { + return false; + } + + public E nextElement() { + throw new NoSuchElementException(); + } } } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java index 7fbfd17dbf7..ee4f71eb32f 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java @@ -9,9 +9,7 @@ import java.util.Arrays; import java.util.List; -import static net.kencochrane.raven.marshaller.json.JsonTestTool.JsonGeneratorTool; -import static net.kencochrane.raven.marshaller.json.JsonTestTool.jsonResource; -import static net.kencochrane.raven.marshaller.json.JsonTestTool.newJsonGenerator; +import static net.kencochrane.raven.marshaller.json.JsonTestTool.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java index f30b79b375a..a986a982aed 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java @@ -6,9 +6,7 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import static net.kencochrane.raven.marshaller.json.JsonTestTool.JsonGeneratorTool; -import static net.kencochrane.raven.marshaller.json.JsonTestTool.jsonResource; -import static net.kencochrane.raven.marshaller.json.JsonTestTool.newJsonGenerator; +import static net.kencochrane.raven.marshaller.json.JsonTestTool.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; From b439a4cc4a87a8d067b0cedabb82797bfa179bd7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 07:44:21 +1100 Subject: [PATCH 1094/2152] Remove EmptyEnumeration --- .../raven/event/interfaces/HttpInterfaceTest.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java index 902499da8da..ae6f599d4be 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java @@ -52,9 +52,9 @@ public void setUp() throws Exception { mockHttpServletRequest.getRemoteUser(); result = "remoteUser"; mockHttpServletRequest.getHeaderNames(); - result = new EmptyEnumeration(); + result = Collections.emptyEnumeration(); mockHttpServletRequest.getHeaders(anyString); - result = new EmptyEnumeration(); + result = Collections.emptyEnumeration(); }}; } @@ -160,14 +160,4 @@ public void testNullCookies() throws Exception { assertThat(httpInterface.getCookies().size(), is(0)); } - - private static class EmptyEnumeration implements Enumeration { - public boolean hasMoreElements() { - return false; - } - - public E nextElement() { - throw new NoSuchElementException(); - } - } } From 42c385b2c5a78e3a10e7f28188e6d4ad0c130b29 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 07:55:57 +1100 Subject: [PATCH 1095/2152] Fix style and indentation --- .../connection/GetSentryHttpsConnection.java | 8 +++++++- .../kencochrane/raven/logback/SentryAppender.java | 3 ++- .../event/interfaces/ExceptionInterface.java | 6 +++--- .../raven/event/interfaces/HttpInterface.java | 12 ++++++------ .../raven/event/interfaces/MessageInterface.java | 8 ++++---- .../raven/event/interfaces/SentryException.java | 15 ++++++++------- .../event/interfaces/StackTraceInterface.java | 6 +++--- .../kencochrane/raven/event/EventBuilderTest.java | 2 +- .../net/kencochrane/raven/stub/SentryStub.java | 1 + 9 files changed, 35 insertions(+), 26 deletions(-) diff --git a/raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnection.java b/raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnection.java index e93a26cabd4..579f881407b 100644 --- a/raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnection.java +++ b/raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnection.java @@ -39,6 +39,12 @@ public GetSentryHttpsConnection(String projectId, String publicKey, String secre } } + /** + * Gets the URL to GetSentry for a given projectId. + * + * @param projectId Identifier of the project. + * @return The URL to getSentry for the given project. + */ protected static URL getSentryUrl(String projectId) { try { return new URL(String.format(GETSENTRY_API_URL, projectId)); @@ -57,7 +63,7 @@ protected HttpsURLConnection getConnection() { /** * Create an SSLSocketFactory only able to handle certificates provided by StartCom. * - * @return + * @return an SSL factory handling certificates from StartCom. * @throws Exception */ private SSLSocketFactory getStartcomSslFactory() throws Exception { diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 8d23b3cb7bc..50698578ab5 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -241,7 +241,8 @@ private String[] extractPackageSimpleClassName(String canonicalClassName) { try { Class exceptionClass = Class.forName(canonicalClassName); Package exceptionPackage = exceptionClass.getPackage(); - packageNameSimpleName[0] = exceptionPackage != null ? exceptionPackage.getName() : SentryException.DEFAULT_PACKAGE_NAME; + packageNameSimpleName[0] = exceptionPackage != null ? exceptionPackage.getName() + : SentryException.DEFAULT_PACKAGE_NAME; packageNameSimpleName[1] = exceptionClass.getSimpleName(); } catch (ClassNotFoundException e) { int lastDot = canonicalClassName.lastIndexOf('.'); diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java index ccfff3acca8..ba40dc950b5 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/ExceptionInterface.java @@ -41,9 +41,9 @@ public Deque getExceptions() { @Override public String toString() { - return "ExceptionInterface{" + - "exceptions=" + exceptions + - '}'; + return "ExceptionInterface{" + + "exceptions=" + exceptions + + '}'; } @Override diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java index 5a48bf1b8ab..0edf0015234 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/HttpInterface.java @@ -140,12 +140,12 @@ public Map> getHeaders() { @Override public String toString() { - return "HttpInterface{" + - "requestUrl='" + requestUrl + '\'' + - ", method='" + method + '\'' + - ", queryString='" + queryString + '\'' + - ", parameters=" + parameters + - '}'; + return "HttpInterface{" + + "requestUrl='" + requestUrl + '\'' + + ", method='" + method + '\'' + + ", queryString='" + queryString + '\'' + + ", parameters=" + parameters + + '}'; } @Override diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java index 6284a7cd5b1..4a5cfa0381b 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java @@ -84,10 +84,10 @@ public List getParameters() { @Override public String toString() { - return "MessageInterface{" + - "message='" + message + '\'' + - ", parameters=" + parameters + - '}'; + return "MessageInterface{" + + "message='" + message + '\'' + + ", parameters=" + parameters + + '}'; } @Override diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java index b44db30ee7e..895c87bb746 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java @@ -104,12 +104,12 @@ public StackTraceInterface getStackTraceInterface() { @Override public String toString() { - return "SentryException{" + - "exceptionMessage='" + exceptionMessage + '\'' + - ", exceptionClassName='" + exceptionClassName + '\'' + - ", exceptionPackageName='" + exceptionPackageName + '\'' + - ", stackTraceInterface=" + stackTraceInterface + - '}'; + return "SentryException{" + + "exceptionMessage='" + exceptionMessage + '\'' + + ", exceptionClassName='" + exceptionClassName + '\'' + + ", exceptionPackageName='" + exceptionPackageName + '\'' + + ", stackTraceInterface=" + stackTraceInterface + + '}'; } @Override @@ -122,7 +122,8 @@ public boolean equals(Object o) { if (!exceptionClassName.equals(that.exceptionClassName)) return false; if (exceptionMessage != null ? !exceptionMessage.equals(that.exceptionMessage) : that.exceptionMessage != null) return false; - if (exceptionPackageName != null ? !exceptionPackageName.equals(that.exceptionPackageName) : that.exceptionPackageName != null) + if (exceptionPackageName != null ? !exceptionPackageName.equals(that.exceptionPackageName) + : that.exceptionPackageName != null) return false; if (!stackTraceInterface.equals(that.stackTraceInterface)) return false; diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java index 9aec8562e42..d636ec27197 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java @@ -75,8 +75,8 @@ public int hashCode() { @Override public String toString() { - return "StackTraceInterface{" + - "stackTrace=" + Arrays.toString(stackTrace) + - '}'; + return "StackTraceInterface{" + + "stackTrace=" + Arrays.toString(stackTrace) + + '}'; } } diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index a7d35f4a3bf..f6ec24eae9e 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -62,7 +62,7 @@ public void builtEventWithCustomUuidHasProperUuid(@Injectable final UUID mockUui @Test(expectedExceptions = IllegalArgumentException.class) public void builtEventWithCustomNullUuidFails() throws Exception { - final EventBuilder eventBuilder = new EventBuilder(null); + new EventBuilder(null); } @Test diff --git a/raven/src/test/java/net/kencochrane/raven/stub/SentryStub.java b/raven/src/test/java/net/kencochrane/raven/stub/SentryStub.java index 08fbcd7c6ac..2dd9bb2375d 100644 --- a/raven/src/test/java/net/kencochrane/raven/stub/SentryStub.java +++ b/raven/src/test/java/net/kencochrane/raven/stub/SentryStub.java @@ -57,6 +57,7 @@ public void removeEvents() { } } + @SuppressWarnings("unchecked") private Map getContent(HttpURLConnection connection) { try { connection.setDoInput(true); From 4c24d422a239f3af04c0953c06a103fa79cae393 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 08:06:11 +1100 Subject: [PATCH 1096/2152] Test methods should throw Exception --- .../kencochrane/raven/getsentry/GetSentryRavenFactoryTest.java | 2 +- .../java/net/kencochrane/raven/DefaultRavenFactoryTest.java | 2 +- .../raven/event/interfaces/StackTraceInterfaceTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/GetSentryRavenFactoryTest.java b/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/GetSentryRavenFactoryTest.java index 9a707dadfd7..4a38b47ce04 100644 --- a/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/GetSentryRavenFactoryTest.java +++ b/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/GetSentryRavenFactoryTest.java @@ -11,7 +11,7 @@ public class GetSentryRavenFactoryTest { @Test - public void checkServiceLoaderProvidesFactory() { + public void checkServiceLoaderProvidesFactory() throws Exception { ServiceLoader ravenFactories = ServiceLoader.load(RavenFactory.class); assertThat(ravenFactories, Matchers.hasItem(instanceOf(GetSentryRavenFactory.class))); diff --git a/raven/src/test/java/net/kencochrane/raven/DefaultRavenFactoryTest.java b/raven/src/test/java/net/kencochrane/raven/DefaultRavenFactoryTest.java index 2989b39e1d8..4a69532abd1 100644 --- a/raven/src/test/java/net/kencochrane/raven/DefaultRavenFactoryTest.java +++ b/raven/src/test/java/net/kencochrane/raven/DefaultRavenFactoryTest.java @@ -10,7 +10,7 @@ public class DefaultRavenFactoryTest { @Test - public void checkServiceLoaderProvidesFactory() { + public void checkServiceLoaderProvidesFactory() throws Exception { ServiceLoader ravenFactories = ServiceLoader.load(RavenFactory.class); assertThat(ravenFactories, Matchers.hasItem(instanceOf(DefaultRavenFactory.class))); diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/StackTraceInterfaceTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/StackTraceInterfaceTest.java index 4bbacb92ce7..adfdc643708 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/interfaces/StackTraceInterfaceTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/StackTraceInterfaceTest.java @@ -8,7 +8,7 @@ public class StackTraceInterfaceTest { @Test - public void testCalculationCommonStackFrames() { + public void testCalculationCommonStackFrames() throws Exception { Exception exception = new RuntimeException("exception1"); exception = new RuntimeException("exception2", exception); From da84ebf00ddf8d25c4f1794219d4e24af737fd9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Gro=CC=88bler?= Date: Mon, 30 Dec 2013 21:59:49 +0100 Subject: [PATCH 1097/2152] added logback context properties to extras --- .../raven/logback/SentryAppender.java | 4 ++ .../raven/logback/MockUpLoggingEvent.java | 25 +++++++++++++ .../SentryAppenderEventBuildingTest.java | 37 +++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 50698578ab5..de7d035e4a2 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -190,6 +190,10 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { eventBuilder.setCulprit(iLoggingEvent.getLoggerName()); } + for (Map.Entry contextEntry : iLoggingEvent.getLoggerContextVO().getPropertyMap().entrySet()) { + eventBuilder.addExtra(contextEntry.getKey(), contextEntry.getValue()); + } + for (Map.Entry mdcEntry : iLoggingEvent.getMDCPropertyMap().entrySet()) { eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); } diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/MockUpLoggingEvent.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/MockUpLoggingEvent.java index 2f1fb284403..ea859e7dc9b 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/MockUpLoggingEvent.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/MockUpLoggingEvent.java @@ -3,6 +3,7 @@ import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.IThrowableProxy; +import ch.qos.logback.classic.spi.LoggerContextVO; import ch.qos.logback.classic.spi.ThrowableProxy; import mockit.Mock; import mockit.MockUp; @@ -10,6 +11,7 @@ import org.slf4j.helpers.MessageFormatter; import java.util.Collections; +import java.util.HashMap; import java.util.Map; public class MockUpLoggingEvent extends MockUp { @@ -23,6 +25,7 @@ public class MockUpLoggingEvent extends MockUp { private String threadName; private StackTraceElement[] callerData; private long timestamp; + private LoggerContextVO loggerContextVO; public MockUpLoggingEvent(String loggerName, Marker marker, Level level, String message, @@ -33,6 +36,22 @@ public MockUpLoggingEvent(String loggerName, Marker marker, Level level, String public MockUpLoggingEvent(String loggerName, Marker marker, Level level, String message, Object[] argumentArray, Throwable throwable, Map mdcPropertyMap, String threadName, StackTraceElement[] callerData, long timestamp) { + this(loggerName, + marker, + level, + message, + argumentArray, + throwable, + mdcPropertyMap, + threadName, + callerData, + timestamp, + new HashMap()); + } + + public MockUpLoggingEvent(String loggerName, Marker marker, Level level, String message, Object[] argumentArray, + Throwable throwable, Map mdcPropertyMap, String threadName, + StackTraceElement[] callerData, long timestamp, Map contextProperties) { this.loggerName = loggerName; this.marker = marker; this.level = level; @@ -43,6 +62,7 @@ public MockUpLoggingEvent(String loggerName, Marker marker, Level level, String this.threadName = threadName; this.callerData = callerData; this.timestamp = timestamp; + this.loggerContextVO = new LoggerContextVO("loggerContextOf" + loggerName, contextProperties, System.currentTimeMillis()); } @Mock @@ -104,4 +124,9 @@ public Map getMDCPropertyMap() { public long getTimeStamp() { return timestamp; } + + @Mock + public LoggerContextVO getLoggerContextVO() { + return loggerContextVO; + } } diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java index 7ddafab454c..4b429c28a0b 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java @@ -178,6 +178,43 @@ public void testMdcAddedToExtra() throws Exception { assertNoErrorsInStatusManager(); } + @Test + public void testContextPropertiesAddedToExtra() throws Exception { + final String extraKey = UUID.randomUUID().toString(); + final String extraValue = UUID.randomUUID().toString(); + + sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null, + null, null, null, 0, Collections.singletonMap(extraKey, extraValue)).getMockInstance()); + + new Verifications() {{ + Event event; + mockRaven.runBuilderHelpers((EventBuilder) any); + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); + }}; + assertNoErrorsInStatusManager(); + } + + @Test + public void testMdcTakesPrecedenceOverContextProperties() throws Exception { + final String mdcKey = UUID.randomUUID().toString(); + final String mdcValue = UUID.randomUUID().toString(); + final String contextKey = mdcKey; + final String contextValue = UUID.randomUUID().toString(); + + sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null, + Collections.singletonMap(mdcKey, mdcValue), null, null, 0, + Collections.singletonMap(contextKey, contextValue)).getMockInstance()); + + new Verifications() {{ + Event event; + mockRaven.runBuilderHelpers((EventBuilder) any); + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getExtra(), Matchers.hasEntry(mdcKey, mdcValue)); + }}; + assertNoErrorsInStatusManager(); + } + @Test public void testSourceUsedAsStacktrace() throws Exception { final StackTraceElement[] location = {new StackTraceElement(UUID.randomUUID().toString(), From 27c90d1bc5ef2d0987760a6ad9782a033b4abc05 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 08:39:20 +1100 Subject: [PATCH 1098/2152] Update libs and plugins --- pom.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index ae839add268..03594576821 100644 --- a/pom.xml +++ b/pom.xml @@ -103,14 +103,14 @@ 1.7.5 - 15.0 - 2.3.0 + 16.0 + 2.3.1 3.0.1 1.0.13 1.2.17 2.0-beta9 1.9.5 - 1.5 + 1.6 6.8.7 1.3 @@ -276,7 +276,7 @@ org.codehaus.mojo animal-sniffer-maven-plugin - 1.9 + 1.10 org.codehaus.mojo.signature @@ -300,7 +300,7 @@ org.eclipse.jetty jetty-maven-plugin - 9.0.6.v20130930 + 9.1.1.v20140108 10 foo @@ -393,7 +393,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 2.10 + 2.11 src/checkstyle/checkstyle.xml true @@ -434,7 +434,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 2.10 + 2.11 src/checkstyle/checkstyle.xml @@ -442,7 +442,7 @@ org.apache.maven.plugins maven-jxr-plugin - 2.3 + 2.4 From 7c8720ca5d78f8041bc0eebf8689a0b8b213a7f9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 09:00:31 +1100 Subject: [PATCH 1099/2152] Update to use the latest version of jmockit --- .../GetSentryHttpsConnectionTest.java | 4 ++-- .../SentryAppenderEventBuildingTest.java | 1 + .../event/EventBuilderHostnameCacheTest.java | 23 +++++++++++-------- .../raven/event/EventBuilderTest.java | 8 +++++-- .../json/ExceptionInterfaceBindingTest.java | 5 ++-- 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionTest.java b/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionTest.java index b8f7a2f59bd..f3e9b0ee2de 100644 --- a/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionTest.java +++ b/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionTest.java @@ -1,6 +1,5 @@ package net.kencochrane.raven.getsentry.connection; -import mockit.Deencapsulation; import mockit.Injectable; import mockit.Tested; import org.testng.annotations.Test; @@ -9,6 +8,7 @@ import java.net.URL; import java.net.URLConnection; +import static mockit.Deencapsulation.setField; import static org.testng.Assert.fail; public class GetSentryHttpsConnectionTest { @@ -29,7 +29,7 @@ public void ensureHttpsConnectionToGetSentryIsSecure() throws Exception { @Test(expectedExceptions = SSLHandshakeException.class) public void ensureHttpsConnectionToGoogleComIsNotSecure() throws Exception { final URL url = new URL("https://www.google.com"); - Deencapsulation.setField(connection, "sentryUrl", url); + setField(connection, "sentryUrl", url); connection.getConnection().connect(); } diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java index 264660b071a..25f4110f427 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java @@ -24,6 +24,7 @@ import java.util.Date; import java.util.UUID; +import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.is; diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java index d6c55863f86..465940713ed 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java @@ -22,8 +22,12 @@ public class EventBuilderHostnameCacheTest { private InetAddress mockTimingOutLocalHost; private static void resetHostnameCache() { - setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp", 0l); - setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "hostname", EventBuilder.DEFAULT_HOSTNAME); + setField(getHostnameCache(), "expirationTimestamp", 0l); + setField(getHostnameCache(), "hostname", EventBuilder.DEFAULT_HOSTNAME); + } + + private static Object getHostnameCache() { + return getField(EventBuilder.class, "HOSTNAME_CACHE"); } @BeforeMethod @@ -47,8 +51,7 @@ public String getCanonicalHostName() throws Exception { } @Test - public void successfulHostnameRetrievalIsCachedForFiveHours( - @Mocked(methods = "currentTimeMillis") final System system) + public void successfulHostnameRetrievalIsCachedForFiveHours(@Mocked("currentTimeMillis") final System system) throws Exception { new NonStrictExpectations(InetAddress.class) {{ System.currentTimeMillis(); @@ -58,14 +61,14 @@ public void successfulHostnameRetrievalIsCachedForFiveHours( }}; new EventBuilder().build(); - final long expirationTime = Deencapsulation.getField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp"); + final long expirationTime = Deencapsulation.getField(getHostnameCache(), "expirationTimestamp"); assertThat(expirationTime, is(TimeUnit.HOURS.toMillis(5) + System.currentTimeMillis())); } @Test(timeOut = 5000) - public void unsucessfulHostnameRetrievalIsCachedForOneSecond( - @Mocked(methods = "currentTimeMillis") final System system) + public void unsuccessfulHostnameRetrievalIsCachedForOneSecond( + @Mocked("currentTimeMillis") final System system) throws Exception { new NonStrictExpectations(InetAddress.class) {{ System.currentTimeMillis(); @@ -76,13 +79,13 @@ public void unsucessfulHostnameRetrievalIsCachedForOneSecond( new EventBuilder().build(); unlockTimingOutLocalHost(); - final long expirationTime = Deencapsulation.getField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp"); + final long expirationTime = Deencapsulation.getField(getHostnameCache(), "expirationTimestamp"); assertThat(expirationTime, is(TimeUnit.SECONDS.toMillis(1) + System.currentTimeMillis())); } @Test(timeOut = 5000) - public void unsucessfulHostameRetrievalUsesLastKnownCachedValue() throws Exception { + public void unsuccessfulHostnameRetrievalUsesLastKnownCachedValue() throws Exception { new NonStrictExpectations(InetAddress.class) {{ InetAddress.getLocalHost(); result = mockLocalHost; @@ -91,7 +94,7 @@ public void unsucessfulHostameRetrievalUsesLastKnownCachedValue() throws Excepti new EventBuilder().build(); - setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp", 0l); + setField(getHostnameCache(), "expirationTimestamp", 0l); Event event = new EventBuilder().build(); unlockTimingOutLocalHost(); diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index f6ec24eae9e..67a92f60780 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -22,8 +22,12 @@ public class EventBuilderTest { private InetAddress mockLocalHost; private static void resetHostnameCache() { - setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "expirationTimestamp", 0l); - setField(getField(EventBuilder.class, "HOSTNAME_CACHE"), "hostname", EventBuilder.DEFAULT_HOSTNAME); + setField(getHostnameCache(), "expirationTimestamp", 0l); + setField(getHostnameCache(), "hostname", EventBuilder.DEFAULT_HOSTNAME); + } + + private static Object getHostnameCache() { + return getField(EventBuilder.class, "HOSTNAME_CACHE"); } @BeforeMethod diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index c5ed8bf5143..5bfda324212 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -1,7 +1,6 @@ package net.kencochrane.raven.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; -import mockit.Deencapsulation; import mockit.Delegate; import mockit.Injectable; import mockit.NonStrictExpectations; @@ -14,6 +13,7 @@ import java.io.IOException; import java.util.Deque; +import static mockit.Deencapsulation.setField; import static net.kencochrane.raven.marshaller.json.JsonTestTool.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -62,8 +62,7 @@ public Deque getExceptions() { @Test public void testClassInDefaultPackage() throws Exception { - Deencapsulation.setField((Object) DefaultPackageException.class, "name", - DefaultPackageException.class.getSimpleName()); + setField((Object) DefaultPackageException.class, "name", DefaultPackageException.class.getSimpleName()); final JsonGeneratorTool generatorTool = newJsonGenerator(); final Throwable throwable = new DefaultPackageException(); new NonStrictExpectations() {{ From 9618b7468d838e2b31381bea63383c93eeacb7be Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 23 Jan 2014 09:00:59 +1100 Subject: [PATCH 1100/2152] Revert the update of Jackson as jackson annotation 2.3.1 hasn't been released yet --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 03594576821..ea0d14fd4a0 100644 --- a/pom.xml +++ b/pom.xml @@ -104,7 +104,7 @@ 1.7.5 16.0 - 2.3.1 + 2.3.0 3.0.1 1.0.13 1.2.17 From 3b498f29bc00c30e8699d485c45be078104a8ad5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 29 Jan 2014 08:14:20 +1100 Subject: [PATCH 1101/2152] Create an event builder that gathers GAE additional info --- .../helper/AppEngineEventBuilderHelper.java | 21 ++++++++++ .../AppEngineEventBuilderHelperTest.java | 40 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java create mode 100644 raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java new file mode 100644 index 00000000000..b7895d430bc --- /dev/null +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java @@ -0,0 +1,21 @@ +package net.kencochrane.raven.appengine.event.helper; + +import com.google.apphosting.api.ApiProxy; +import net.kencochrane.raven.event.EventBuilder; +import net.kencochrane.raven.event.helper.EventBuilderHelper; + +/** + * EventBuildHelper defining Google App Engine specific properties (hostname). + */ +public class AppEngineEventBuilderHelper implements EventBuilderHelper { + /** + * Property used internally by GAE to define + */ + private static final String CURRENT_VERSION_HOSTNAME_PROPERTY = "com.google.appengine.runtime.default_version_hostname"; + + @Override + public void helpBuildingEvent(EventBuilder eventBuilder) { + ApiProxy.Environment env = ApiProxy.getCurrentEnvironment(); + eventBuilder.setServerName((String) env.getAttributes().get(CURRENT_VERSION_HOSTNAME_PROPERTY)); + } +} diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java new file mode 100644 index 00000000000..a8c4db25ae9 --- /dev/null +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java @@ -0,0 +1,40 @@ +package net.kencochrane.raven.appengine.event.helper; + +import com.google.apphosting.api.ApiProxy; +import mockit.*; +import net.kencochrane.raven.event.EventBuilder; +import org.testng.annotations.Test; + +import java.util.Collections; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class AppEngineEventBuilderHelperTest { + @Tested + private AppEngineEventBuilderHelper eventBuilderHelper; + @Injectable + private EventBuilder mockEventBuilder; + @Mocked("getCurrentEnvironment") + private ApiProxy mockApiProxy; + + @Test + public void ensureHostnameDefineByApiProxyEnvironment( + @Injectable("d7b8f251-ebe1-446f-8549-2b37982bd548") final String mockHostname, + @Injectable final ApiProxy.Environment mockEnvironment) { + new NonStrictExpectations() {{ + mockEnvironment.getAttributes(); + result = Collections.singletonMap("com.google.appengine.runtime.default_version_hostname", mockHostname); + ApiProxy.getCurrentEnvironment(); + result = mockEnvironment; + }}; + + eventBuilderHelper.helpBuildingEvent(mockEventBuilder); + + new Verifications() {{ + String hostname; + mockEventBuilder.setServerName(hostname = withCapture()); + assertThat(hostname, is(mockHostname)); + }}; + } +} From 7a3c5da4aafac524445914b4cb3aa31a9ed0dd57 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 29 Jan 2014 08:14:49 +1100 Subject: [PATCH 1102/2152] Automatically use the AppEngineEventBuilderHelper in AppEngineRavenFactory --- .../raven/appengine/AppEngineRavenFactory.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java index 396d0a02307..c98538b1afd 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java @@ -1,7 +1,9 @@ package net.kencochrane.raven.appengine; import net.kencochrane.raven.DefaultRavenFactory; +import net.kencochrane.raven.Raven; import net.kencochrane.raven.appengine.connection.AppEngineAsyncConnection; +import net.kencochrane.raven.appengine.event.helper.AppEngineEventBuilderHelper; import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.dsn.Dsn; @@ -27,6 +29,13 @@ public class AppEngineRavenFactory extends DefaultRavenFactory { */ public static final String CONNECTION_IDENTIFIER = "raven.async.gae.connectionid"; + @Override + public Raven createRavenInstance(Dsn dsn) { + Raven ravenInstance = super.createRavenInstance(dsn); + ravenInstance.addBuilderHelper(new AppEngineEventBuilderHelper()); + return ravenInstance; + } + /** * Encapsulates an already existing connection in an {@link AppEngineAsyncConnection} and get the async options * from the Sentry DSN. From d9ab08a2e12ba5fdcc48c64267f1186c854794a2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 29 Jan 2014 08:16:38 +1100 Subject: [PATCH 1103/2152] Fix documentation --- .../appengine/event/helper/AppEngineEventBuilderHelper.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java index b7895d430bc..766cf5f7910 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java @@ -9,13 +9,16 @@ */ public class AppEngineEventBuilderHelper implements EventBuilderHelper { /** - * Property used internally by GAE to define + * Property used internally by GAE to define the hostname. + * + * @see GAE: App Identity Java API */ private static final String CURRENT_VERSION_HOSTNAME_PROPERTY = "com.google.appengine.runtime.default_version_hostname"; @Override public void helpBuildingEvent(EventBuilder eventBuilder) { ApiProxy.Environment env = ApiProxy.getCurrentEnvironment(); + // Set the hostname to the actual application hostname eventBuilder.setServerName((String) env.getAttributes().get(CURRENT_VERSION_HOSTNAME_PROPERTY)); } } From 0846575d9ed9edcdbd08581473d25e1da1a412ad Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 18 Feb 2014 07:54:08 +1100 Subject: [PATCH 1104/2152] Replace access to dev resources with test ones --- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 1b1c86046c0..d6295aa1c51 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -82,7 +82,7 @@ - ${project.basedir}/src/test/resources/log4j2-integration.xml + ${project.build.directory}/test-classes/log4j2-integration.xml diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 8c18363ea74..d52951b656b 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -81,7 +81,7 @@ - ${project.basedir}/src/test/resources/logback-integration.xml + ${project.build.directory}/test-classes/logback-integration.xml diff --git a/raven/pom.xml b/raven/pom.xml index ceb8bf2e7d8..5ff3447cf9c 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -127,7 +127,7 @@ - ${project.basedir}/src/test/resources/logging-integration.properties + ${project.build.directory}/test-classes/logging-integration.properties From 55ceab8e17fa7b67b3246e3ba3acc0a2b5112ffe Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 18 Feb 2014 07:58:17 +1100 Subject: [PATCH 1105/2152] Change the tests relying on the internet connection to integration tests --- raven-getsentry/pom.xml | 16 ++++++++++++++++ ...Test.java => GetSentryHttpsConnectionIT.java} | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) rename raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/{GetSentryHttpsConnectionTest.java => GetSentryHttpsConnectionIT.java} (97%) diff --git a/raven-getsentry/pom.xml b/raven-getsentry/pom.xml index ccac75045b7..9d7e9349580 100644 --- a/raven-getsentry/pom.xml +++ b/raven-getsentry/pom.xml @@ -41,4 +41,20 @@ test + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + ${project.build.directory}/test-classes/log4j2-integration.xml + + + + + + diff --git a/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionTest.java b/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionIT.java similarity index 97% rename from raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionTest.java rename to raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionIT.java index f3e9b0ee2de..989949bc951 100644 --- a/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionTest.java +++ b/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionIT.java @@ -11,7 +11,7 @@ import static mockit.Deencapsulation.setField; import static org.testng.Assert.fail; -public class GetSentryHttpsConnectionTest { +public class GetSentryHttpsConnectionIT { @Tested private GetSentryHttpsConnection connection; @Injectable("projectId") From 3bd071c97e7547798999f4f519a4b3b9096aa36a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 18 Feb 2014 07:59:58 +1100 Subject: [PATCH 1106/2152] Enable logs for getSentry module tests --- raven-getsentry/pom.xml | 5 +++++ raven-getsentry/src/test/resources/logback-test.xml | 11 +++++++++++ 2 files changed, 16 insertions(+) create mode 100644 raven-getsentry/src/test/resources/logback-test.xml diff --git a/raven-getsentry/pom.xml b/raven-getsentry/pom.xml index 9d7e9349580..b763deba2df 100644 --- a/raven-getsentry/pom.xml +++ b/raven-getsentry/pom.xml @@ -20,6 +20,11 @@ raven + + ch.qos.logback + logback-classic + test + com.googlecode.jmockit jmockit diff --git a/raven-getsentry/src/test/resources/logback-test.xml b/raven-getsentry/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..dcffc76772c --- /dev/null +++ b/raven-getsentry/src/test/resources/logback-test.xml @@ -0,0 +1,11 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + From 3bf7155d904ca5634950c029f4ce1cfbe6c7190b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 18 Feb 2014 08:01:12 +1100 Subject: [PATCH 1107/2152] Remove usage of deprecated injectable fields for InnerClasses --- .../raven/connection/HttpConnectionTest.java | 58 +++++------ .../raven/event/EventBuilderTest.java | 46 ++++----- .../event/interfaces/HttpInterfaceTest.java | 95 +++++++++---------- 3 files changed, 90 insertions(+), 109 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index 671671f47ad..3c72206360b 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -32,29 +32,26 @@ public class HttpConnectionTest { private HttpsURLConnection mockUrlConnection; @Injectable private Marshaller mockMarshaller; + @Injectable + private URL mockUrl; + @Injectable + private OutputStream mockOutputStream; + @Injectable + private InputStream mockInputStream; @BeforeMethod public void setUp() throws Exception { - new NonStrictExpectations() { - @Injectable - private URL mockUrl; - @Injectable - private OutputStream mockOutputStream; - @Injectable - private InputStream mockInputStream; - - { - mockUrl.openConnection(); - result = mockUrlConnection; - mockUrlConnection.getOutputStream(); - result = mockOutputStream; - mockUrlConnection.getInputStream(); - result = mockInputStream; - - httpConnection = new HttpConnection(mockUrl, publicKey, secretKey); - httpConnection.setMarshaller(mockMarshaller); - } - }; + new NonStrictExpectations() {{ + mockUrl.openConnection(); + result = mockUrlConnection; + mockUrlConnection.getOutputStream(); + result = mockOutputStream; + mockUrlConnection.getInputStream(); + result = mockInputStream; + + httpConnection = new HttpConnection(mockUrl, publicKey, secretKey); + httpConnection.setMarshaller(mockMarshaller); + }}; } @Test @@ -80,23 +77,18 @@ public void testByPassSecurityDefaultsToFalse(@Injectable final Event mockEvent) } @Test - public void testByPassSecurity(@Injectable final Event mockEvent) throws Exception { + public void testByPassSecurity(@Injectable final Event mockEvent, + @Injectable("fakehostna.me") final String mockHostname, + @Injectable final SSLSession mockSslSession) throws Exception { httpConnection.setBypassSecurity(true); httpConnection.send(mockEvent); - new Verifications() { - @Injectable - private String mockString; - @Injectable - private SSLSession mockSslSession; - - { - HostnameVerifier hostnameVerifier; - mockUrlConnection.setHostnameVerifier(hostnameVerifier = withCapture()); - assertThat(hostnameVerifier.verify(mockString, mockSslSession), is(true)); - } - }; + new Verifications() {{ + HostnameVerifier hostnameVerifier; + mockUrlConnection.setHostnameVerifier(hostnameVerifier = withCapture()); + assertThat(hostnameVerifier.verify(mockHostname, mockSslSession), is(true)); + }}; } @Test diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index 67a92f60780..c0806424082 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -258,24 +258,19 @@ public void builtEventWithTagsHasProperTags(@Injectable("tagKey") final String m public void builtEventWithNoServerNameUsesDefaultIfSearchTimesOut() throws Exception { resetHostnameCache(); - new NonStrictExpectations(InetAddress.class) { - @Injectable - private InetAddress mockTimingOutLocalHost; - - { - InetAddress.getLocalHost(); - result = mockTimingOutLocalHost; - mockTimingOutLocalHost.getCanonicalHostName(); - result = new Delegate() { - public String getCanonicalHostName() throws Exception { - synchronized (EventBuilderTest.this) { - EventBuilderTest.this.wait(); - } - return ""; + new NonStrictExpectations(InetAddress.class) {{ + InetAddress.getLocalHost(); + result = mockLocalHost; + mockLocalHost.getCanonicalHostName(); + result = new Delegate() { + public String getCanonicalHostName() throws Exception { + synchronized (EventBuilderTest.this) { + EventBuilderTest.this.wait(); } - }; - } - }; + return ""; + } + }; + }}; final EventBuilder eventBuilder = new EventBuilder(); final Event event = eventBuilder.build(); @@ -290,17 +285,12 @@ public String getCanonicalHostName() throws Exception { public void builtEventWithNoServerNameUsesLocalHost(@Injectable("serverName") final String mockServerName) throws Exception { resetHostnameCache(); - new NonStrictExpectations(InetAddress.class) { - @Injectable - private InetAddress mockLocalHost; - - { - InetAddress.getLocalHost(); - result = mockLocalHost; - mockLocalHost.getCanonicalHostName(); - result = mockServerName; - } - }; + new NonStrictExpectations(InetAddress.class) {{ + InetAddress.getLocalHost(); + result = mockLocalHost; + mockLocalHost.getCanonicalHostName(); + result = mockServerName; + }}; final EventBuilder eventBuilder = new EventBuilder(); final Event event = eventBuilder.build(); diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java index ae6f599d4be..a5de91c8631 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java @@ -7,7 +7,9 @@ import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -15,6 +17,8 @@ public class HttpInterfaceTest { @Injectable private HttpServletRequest mockHttpServletRequest; + @Injectable + private Cookie mockCookie; @BeforeMethod public void setUp() throws Exception { @@ -81,53 +85,48 @@ public void testHttpServletCopied() throws Exception { final String headerKey = UUID.randomUUID().toString(); final String headerValue = UUID.randomUUID().toString(); - new NonStrictExpectations() { - @Injectable - private Cookie mockCookie; - - { - mockHttpServletRequest.getRequestURL(); - result = new StringBuffer(requestUrl); - mockHttpServletRequest.getMethod(); - result = method; - mockHttpServletRequest.getParameterMap(); - result = Collections.singletonMap(parameterName, new String[]{parameterValue}); - mockHttpServletRequest.getQueryString(); - result = queryString; - mockCookie.getName(); - result = cookieName; - mockCookie.getValue(); - result = cookieValue; - mockHttpServletRequest.getCookies(); - result = new Cookie[]{mockCookie}; - mockHttpServletRequest.getRemoteAddr(); - result = remoteAddr; - mockHttpServletRequest.getServerName(); - result = serverName; - mockHttpServletRequest.getServerPort(); - result = serverPort; - mockHttpServletRequest.getLocalAddr(); - result = localAddr; - mockHttpServletRequest.getLocalName(); - result = localName; - mockHttpServletRequest.getLocalPort(); - result = localPort; - mockHttpServletRequest.getProtocol(); - result = protocol; - mockHttpServletRequest.isSecure(); - result = secure; - mockHttpServletRequest.isAsyncStarted(); - result = asyncStarted; - mockHttpServletRequest.getAuthType(); - result = authType; - mockHttpServletRequest.getRemoteUser(); - result = remoteUser; - mockHttpServletRequest.getHeaderNames(); - result = Collections.enumeration(Arrays.asList(headerKey)); - mockHttpServletRequest.getHeaders(headerKey); - result = Collections.enumeration(Arrays.asList(headerValue)); - } - }; + new NonStrictExpectations() {{ + mockHttpServletRequest.getRequestURL(); + result = new StringBuffer(requestUrl); + mockHttpServletRequest.getMethod(); + result = method; + mockHttpServletRequest.getParameterMap(); + result = Collections.singletonMap(parameterName, new String[]{parameterValue}); + mockHttpServletRequest.getQueryString(); + result = queryString; + mockCookie.getName(); + result = cookieName; + mockCookie.getValue(); + result = cookieValue; + mockHttpServletRequest.getCookies(); + result = new Cookie[]{mockCookie}; + mockHttpServletRequest.getRemoteAddr(); + result = remoteAddr; + mockHttpServletRequest.getServerName(); + result = serverName; + mockHttpServletRequest.getServerPort(); + result = serverPort; + mockHttpServletRequest.getLocalAddr(); + result = localAddr; + mockHttpServletRequest.getLocalName(); + result = localName; + mockHttpServletRequest.getLocalPort(); + result = localPort; + mockHttpServletRequest.getProtocol(); + result = protocol; + mockHttpServletRequest.isSecure(); + result = secure; + mockHttpServletRequest.isAsyncStarted(); + result = asyncStarted; + mockHttpServletRequest.getAuthType(); + result = authType; + mockHttpServletRequest.getRemoteUser(); + result = remoteUser; + mockHttpServletRequest.getHeaderNames(); + result = Collections.enumeration(Arrays.asList(headerKey)); + mockHttpServletRequest.getHeaders(headerKey); + result = Collections.enumeration(Arrays.asList(headerValue)); + }}; HttpInterface httpInterface = new HttpInterface(mockHttpServletRequest); From 6a0b3721254477ab987c6f80e684af1c21e7162a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 18 Feb 2014 08:01:44 +1100 Subject: [PATCH 1108/2152] Disable the SentryAppender during log4j tests --- raven-log4j/src/test/resources/log4j-test.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j/src/test/resources/log4j-test.properties b/raven-log4j/src/test/resources/log4j-test.properties index b197dd95f92..c4caf1949f8 100644 --- a/raven-log4j/src/test/resources/log4j-test.properties +++ b/raven-log4j/src/test/resources/log4j-test.properties @@ -1,4 +1,4 @@ -log4j.rootLogger=ALL, ConsoleAppender, SentryAppender +log4j.rootLogger=ALL, ConsoleAppender log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender From 9b3a7188970a8ec9ac1f05e3c0668eb1a500cc1e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 18 Feb 2014 09:21:40 +1100 Subject: [PATCH 1109/2152] Replace the timeouts in tests with exceptions (speed up tests) --- .../event/EventBuilderHostnameCacheTest.java | 27 ++++--------------- .../raven/event/EventBuilderTest.java | 12 ++------- 2 files changed, 7 insertions(+), 32 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java index 465940713ed..81147c0f3b1 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java @@ -22,7 +22,7 @@ public class EventBuilderHostnameCacheTest { private InetAddress mockTimingOutLocalHost; private static void resetHostnameCache() { - setField(getHostnameCache(), "expirationTimestamp", 0l); + setField(getHostnameCache(), "expirationTimestamp", 0L); setField(getHostnameCache(), "hostname", EventBuilder.DEFAULT_HOSTNAME); } @@ -37,14 +37,7 @@ public void setUp() throws Exception { result = mockLocalHostName; mockTimingOutLocalHost.getCanonicalHostName(); - result = new Delegate() { - public String getCanonicalHostName() throws Exception { - synchronized (EventBuilderHostnameCacheTest.this) { - EventBuilderHostnameCacheTest.this.wait(); - } - return ""; - } - }; + result = new RuntimeException("For all intents and purposes, an exception is the same as a timeout"); }}; // Clean Hostname Cache resetHostnameCache(); @@ -66,9 +59,8 @@ public void successfulHostnameRetrievalIsCachedForFiveHours(@Mocked("currentTime assertThat(expirationTime, is(TimeUnit.HOURS.toMillis(5) + System.currentTimeMillis())); } - @Test(timeOut = 5000) - public void unsuccessfulHostnameRetrievalIsCachedForOneSecond( - @Mocked("currentTimeMillis") final System system) + @Test + public void unsuccessfulHostnameRetrievalIsCachedForOneSecond(@Mocked("currentTimeMillis") final System system) throws Exception { new NonStrictExpectations(InetAddress.class) {{ System.currentTimeMillis(); @@ -78,13 +70,12 @@ public void unsuccessfulHostnameRetrievalIsCachedForOneSecond( }}; new EventBuilder().build(); - unlockTimingOutLocalHost(); final long expirationTime = Deencapsulation.getField(getHostnameCache(), "expirationTimestamp"); assertThat(expirationTime, is(TimeUnit.SECONDS.toMillis(1) + System.currentTimeMillis())); } - @Test(timeOut = 5000) + @Test public void unsuccessfulHostnameRetrievalUsesLastKnownCachedValue() throws Exception { new NonStrictExpectations(InetAddress.class) {{ InetAddress.getLocalHost(); @@ -92,11 +83,9 @@ public void unsuccessfulHostnameRetrievalUsesLastKnownCachedValue() throws Excep result = mockTimingOutLocalHost; }}; - new EventBuilder().build(); setField(getHostnameCache(), "expirationTimestamp", 0l); Event event = new EventBuilder().build(); - unlockTimingOutLocalHost(); assertThat(event.getServerName(), is(mockLocalHostName)); new Verifications() {{ @@ -104,10 +93,4 @@ public void unsuccessfulHostnameRetrievalUsesLastKnownCachedValue() throws Excep mockTimingOutLocalHost.getCanonicalHostName(); }}; } - - private void unlockTimingOutLocalHost() { - synchronized (this) { - this.notify(); - } - } } diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index c0806424082..bc5568ac52a 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -1,6 +1,5 @@ package net.kencochrane.raven.event; -import mockit.Delegate; import mockit.Injectable; import mockit.NonStrictExpectations; import net.kencochrane.raven.event.interfaces.SentryInterface; @@ -254,7 +253,7 @@ public void builtEventWithTagsHasProperTags(@Injectable("tagKey") final String m assertThat(event.getTags().entrySet(), hasSize(1)); } - @Test(timeOut = 5000) + @Test public void builtEventWithNoServerNameUsesDefaultIfSearchTimesOut() throws Exception { resetHostnameCache(); @@ -262,14 +261,7 @@ public void builtEventWithNoServerNameUsesDefaultIfSearchTimesOut() InetAddress.getLocalHost(); result = mockLocalHost; mockLocalHost.getCanonicalHostName(); - result = new Delegate() { - public String getCanonicalHostName() throws Exception { - synchronized (EventBuilderTest.this) { - EventBuilderTest.this.wait(); - } - return ""; - } - }; + result = new RuntimeException("For all intents and purposes, an exception is the same as a timeout"); }}; final EventBuilder eventBuilder = new EventBuilder(); From 384d6e4dad00d7631a112face6e6c2f14a8734f8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 18 Feb 2014 09:40:02 +1100 Subject: [PATCH 1110/2152] Add [RAVEN] before raven logs during tests --- pom.xml | 5 +++++ raven/pom.xml | 11 +++++++++++ .../src/test/resources/logging-integration.properties | 8 +++++--- raven/src/test/resources/logging-test.properties | 5 +++++ 4 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 raven/src/test/resources/logging-test.properties diff --git a/pom.xml b/pom.xml index ea0d14fd4a0..c87bfe02cd9 100644 --- a/pom.xml +++ b/pom.xml @@ -329,6 +329,11 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 2.16 + org.apache.maven.plugins maven-failsafe-plugin diff --git a/raven/pom.xml b/raven/pom.xml index 5ff3447cf9c..5cc006ec594 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -121,6 +121,17 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + + + ${project.build.directory}/test-classes/logging-test.properties + + + + org.apache.maven.plugins maven-failsafe-plugin diff --git a/raven/src/test/resources/logging-integration.properties b/raven/src/test/resources/logging-integration.properties index b53686462be..91736788991 100644 --- a/raven/src/test/resources/logging-integration.properties +++ b/raven/src/test/resources/logging-integration.properties @@ -1,10 +1,12 @@ handlers=java.util.logging.ConsoleHandler level=ALL - +# Ignore raven stub logs +net.kencochrane.raven.stub.handlers=java.util.logging.ConsoleHandler +net.kencochrane.raven.stub.level=ALL net.kencochrane.raven.handlers=java.util.logging.ConsoleHandler, net.kencochrane.raven.jul.SentryHandler net.kencochrane.raven.level=ALL -net.kencochrane.raven.stub.handlers=java.util.logging.ConsoleHandler -net.kencochrane.raven.stub.level=ALL +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +java.util.logging.SimpleFormatter.format=[RAVEN] %1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n[RAVEN] %4$s: %5$s%6$s%n net.kencochrane.raven.jul.SentryHandler.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false diff --git a/raven/src/test/resources/logging-test.properties b/raven/src/test/resources/logging-test.properties new file mode 100644 index 00000000000..33350331dfa --- /dev/null +++ b/raven/src/test/resources/logging-test.properties @@ -0,0 +1,5 @@ +handlers=java.util.logging.ConsoleHandler +level=ERROR + +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +java.util.logging.SimpleFormatter.format=[RAVEN] %1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n[RAVEN] %4$s: %5$s%6$s%n From 09f49f5bcac2b9e6d69e371af1d87e8dc1ce3171 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 18 Feb 2014 09:40:27 +1100 Subject: [PATCH 1111/2152] Reorder dependencies to avoid classpath issues with hamcrest --- raven/pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/raven/pom.xml b/raven/pom.xml index 5cc006ec594..ba1461aab4b 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -56,11 +56,6 @@ jmockit test - - org.testng - testng - test - org.hamcrest hamcrest-core @@ -71,6 +66,11 @@ hamcrest-library test + + org.testng + testng + test + From e9d8d9fc04cf619f2126d91bae2392fd32cd642e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 19 Feb 2014 08:15:42 +1100 Subject: [PATCH 1112/2152] Load log4j-test properly during the test phase --- raven-log4j/pom.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index b5eb5ef35e4..15e4ed1545a 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -72,6 +72,15 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + + log4j-test.properties + + + org.apache.maven.plugins maven-failsafe-plugin From 5adf897ea73c8ddf172165d324dc376d445aae14 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 19 Feb 2014 08:17:10 +1100 Subject: [PATCH 1113/2152] Ensure that errors created by the logger aren't flooding the logs --- .../raven/log4j/MockUpErrorHandler.java | 16 +++------- .../raven/log4j2/MockUpErrorHandler.java | 12 ++----- .../raven/logback/MockUpStatusPrinter.java | 31 +++++++++++++++++++ .../logback/SentryAppenderCloseTest.java | 1 + .../raven/logback/SentryAppenderDsnTest.java | 1 + .../SentryAppenderEventBuildingTest.java | 1 + .../logback/SentryAppenderFailuresTest.java | 1 + .../raven/logback/SentryAppenderIT.java | 1 + 8 files changed, 44 insertions(+), 20 deletions(-) create mode 100644 raven-logback/src/test/java/net/kencochrane/raven/logback/MockUpStatusPrinter.java diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java index 882f3e5d85d..98daf57ebd3 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java @@ -9,26 +9,20 @@ public class MockUpErrorHandler extends MockUp { private int errorCount = 0; @Mock - public void error(String message, Exception e, int errorCode) { + public void error(String message) { errorCount++; - System.err.println(message); - e.printStackTrace(System.err); + System.err.println("[RAVEN] ErrorHandler - " + message); System.err.flush(); } @Mock - public void error(String message) { - errorCount++; - System.err.println(message); - System.err.flush(); + public void error(String message, Exception e, int errorCode) { + error(message); } @Mock public void error(String message, Exception e, int errorCode, LoggingEvent event) { - errorCount++; - System.err.println(message); - e.printStackTrace(System.err); - System.err.flush(); + error(message); } public int getErrorCount() { diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java index d448444debd..e5498111012 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java @@ -11,24 +11,18 @@ public class MockUpErrorHandler extends MockUp { @Mock public void error(String msg) { errorCount++; - System.err.println(msg); + System.err.println("[RAVEN] ErrorHandler - " + msg); System.err.flush(); } @Mock public void error(String msg, Throwable t) { - errorCount++; - System.err.println(msg); - t.printStackTrace(System.err); - System.err.flush(); + error(msg); } @Mock public void error(String msg, LogEvent event, Throwable t) { - errorCount++; - System.err.println(msg); - t.printStackTrace(System.err); - System.err.flush(); + error(msg); } public int getErrorCount() { diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/MockUpStatusPrinter.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/MockUpStatusPrinter.java new file mode 100644 index 00000000000..5d8ceed0414 --- /dev/null +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/MockUpStatusPrinter.java @@ -0,0 +1,31 @@ +package net.kencochrane.raven.logback; + +import ch.qos.logback.core.CoreConstants; +import ch.qos.logback.core.status.Status; +import ch.qos.logback.core.util.StatusPrinter; +import mockit.Mock; +import mockit.MockUp; + +public class MockUpStatusPrinter extends MockUp { + @Mock + public static void buildStr(StringBuilder sb, String indentation, Status s) { + sb.append("[RAVEN] ErrorHandler ") + .append("[").append(getLevel(s)).append("] ") + .append(s.getOrigin()).append(" - ") + .append(s.getMessage()) + .append(CoreConstants.LINE_SEPARATOR); + } + + private static String getLevel(Status s) { + switch (s.getEffectiveLevel()) { + case Status.INFO: + return "INFO"; + case Status.WARN: + return "WARN"; + case Status.ERROR: + return "ERROR"; + default: + return "UNKOWN"; + } + } +} diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java index 0023c73e952..fad155d4c70 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java @@ -24,6 +24,7 @@ public class SentryAppenderCloseTest { @BeforeMethod public void setUp() throws Exception { + new MockUpStatusPrinter(); new NonStrictExpectations() {{ final BasicStatusManager statusManager = new BasicStatusManager(); final OnConsoleStatusListener listener = new OnConsoleStatusListener(); diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java index 0ad8d994e19..2e97445096a 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java @@ -29,6 +29,7 @@ public class SentryAppenderDsnTest { @BeforeMethod public void setUp() throws Exception { + new MockUpStatusPrinter(); sentryAppender = new SentryAppender(); sentryAppender.setContext(mockContext); diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java index 4b429c28a0b..e716ba3a41f 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java @@ -38,6 +38,7 @@ public class SentryAppenderEventBuildingTest { @BeforeMethod public void setUp() throws Exception { + new MockUpStatusPrinter(); sentryAppender = new SentryAppender(mockRaven); sentryAppender.setContext(mockContext); sentryAppender.initRaven(); diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java index c2f87eafdc8..185b4c714a4 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java @@ -25,6 +25,7 @@ public class SentryAppenderFailuresTest { @BeforeMethod public void setUp() throws Exception { + new MockUpStatusPrinter(); new NonStrictExpectations() {{ final BasicStatusManager statusManager = new BasicStatusManager(); final OnConsoleStatusListener listener = new OnConsoleStatusListener(); diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java index 037a24754b7..5ca175bfc6c 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java @@ -15,6 +15,7 @@ public class SentryAppenderIT { @BeforeMethod public void setUp() { + new MockUpStatusPrinter(); sentryStub = new SentryStub(); sentryStub.removeEvents(); } From e776aaa54b4d784b1ef18d69e7c779ba16c18367 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 19 Feb 2014 08:19:03 +1100 Subject: [PATCH 1114/2152] Reduce the logs and specify when they're coming from raven (rather than from maven) --- raven-getsentry/src/test/resources/logback-test.xml | 2 +- raven-log4j/src/test/resources/log4j-integration.properties | 3 ++- raven-log4j/src/test/resources/log4j-test.properties | 5 ++--- raven-log4j2/src/test/resources/log4j2-integration.xml | 6 ++++-- raven-log4j2/src/test/resources/log4j2-test.xml | 6 ++++-- raven-logback/src/test/resources/logback-integration.xml | 2 +- raven-logback/src/test/resources/logback-test.xml | 2 +- raven/src/test/resources/logging-integration.properties | 2 +- raven/src/test/resources/logging-test.properties | 2 +- 9 files changed, 17 insertions(+), 13 deletions(-) diff --git a/raven-getsentry/src/test/resources/logback-test.xml b/raven-getsentry/src/test/resources/logback-test.xml index dcffc76772c..1c1939ce813 100644 --- a/raven-getsentry/src/test/resources/logback-test.xml +++ b/raven-getsentry/src/test/resources/logback-test.xml @@ -1,7 +1,7 @@ - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + [RAVEN] %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n diff --git a/raven-log4j/src/test/resources/log4j-integration.properties b/raven-log4j/src/test/resources/log4j-integration.properties index 69678e8c7e3..4922f9ea86f 100644 --- a/raven-log4j/src/test/resources/log4j-integration.properties +++ b/raven-log4j/src/test/resources/log4j-integration.properties @@ -5,4 +5,5 @@ log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender log4j.appender.SentryAppender.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false -log4j.appender.ConsoleAppender.layout=org.apache.log4j.SimpleLayout +log4j.appender.ConsoleAppender.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.ConsoleAppender.layout.ConversionPattern=[RAVEN] [%-5p] %c - %m%n diff --git a/raven-log4j/src/test/resources/log4j-test.properties b/raven-log4j/src/test/resources/log4j-test.properties index c4caf1949f8..32a175a8ef6 100644 --- a/raven-log4j/src/test/resources/log4j-test.properties +++ b/raven-log4j/src/test/resources/log4j-test.properties @@ -1,5 +1,4 @@ log4j.rootLogger=ALL, ConsoleAppender - log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender - -log4j.appender.ConsoleAppender.layout=org.apache.log4j.SimpleLayout +log4j.appender.ConsoleAppender.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.ConsoleAppender.layout.ConversionPattern=[RAVEN] [%-5p] %c - %m%n diff --git a/raven-log4j2/src/test/resources/log4j2-integration.xml b/raven-log4j2/src/test/resources/log4j2-integration.xml index 62b90fa8b72..989959d197d 100644 --- a/raven-log4j2/src/test/resources/log4j2-integration.xml +++ b/raven-log4j2/src/test/resources/log4j2-integration.xml @@ -1,7 +1,9 @@ - + - + + + http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false diff --git a/raven-log4j2/src/test/resources/log4j2-test.xml b/raven-log4j2/src/test/resources/log4j2-test.xml index bfb6e520d05..166c2f2a551 100644 --- a/raven-log4j2/src/test/resources/log4j2-test.xml +++ b/raven-log4j2/src/test/resources/log4j2-test.xml @@ -1,7 +1,9 @@ - + - + + + diff --git a/raven-logback/src/test/resources/logback-integration.xml b/raven-logback/src/test/resources/logback-integration.xml index c1f494c01c2..84c25bbb891 100644 --- a/raven-logback/src/test/resources/logback-integration.xml +++ b/raven-logback/src/test/resources/logback-integration.xml @@ -1,7 +1,7 @@ - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + [RAVEN] [%-5level] %logger{36} - %msg%n diff --git a/raven-logback/src/test/resources/logback-test.xml b/raven-logback/src/test/resources/logback-test.xml index dcffc76772c..da8073edc58 100644 --- a/raven-logback/src/test/resources/logback-test.xml +++ b/raven-logback/src/test/resources/logback-test.xml @@ -1,7 +1,7 @@ - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + [RAVEN] [%-5level] %logger{36} - %msg%n diff --git a/raven/src/test/resources/logging-integration.properties b/raven/src/test/resources/logging-integration.properties index 91736788991..1e163c202bf 100644 --- a/raven/src/test/resources/logging-integration.properties +++ b/raven/src/test/resources/logging-integration.properties @@ -7,6 +7,6 @@ net.kencochrane.raven.handlers=java.util.logging.ConsoleHandler, net.kencochrane net.kencochrane.raven.level=ALL java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter -java.util.logging.SimpleFormatter.format=[RAVEN] %1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n[RAVEN] %4$s: %5$s%6$s%n +java.util.logging.SimpleFormatter.format=[RAVEN] [%4$.4s] %3$s - %5$s%n net.kencochrane.raven.jul.SentryHandler.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false diff --git a/raven/src/test/resources/logging-test.properties b/raven/src/test/resources/logging-test.properties index 33350331dfa..cbaf7ab647d 100644 --- a/raven/src/test/resources/logging-test.properties +++ b/raven/src/test/resources/logging-test.properties @@ -2,4 +2,4 @@ handlers=java.util.logging.ConsoleHandler level=ERROR java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter -java.util.logging.SimpleFormatter.format=[RAVEN] %1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n[RAVEN] %4$s: %5$s%6$s%n +java.util.logging.SimpleFormatter.format=[RAVEN] [%4$.4s] %3$s - %5$s%n From 7e1d5481e07b9bc0f8b8ea014a647432e6b3140b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 20 Feb 2014 08:02:25 +1100 Subject: [PATCH 1115/2152] Remove irrelevant configuration --- raven-getsentry/pom.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/raven-getsentry/pom.xml b/raven-getsentry/pom.xml index b763deba2df..0b02ff9ab62 100644 --- a/raven-getsentry/pom.xml +++ b/raven-getsentry/pom.xml @@ -52,13 +52,6 @@ org.apache.maven.plugins maven-failsafe-plugin - - - - ${project.build.directory}/test-classes/log4j2-integration.xml - - - From 68f620796dd3d12a35ec1c40c73e51c178ddf095 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 20 Feb 2014 08:03:20 +1100 Subject: [PATCH 1116/2152] Do now show exceptions during the tests --- raven-log4j/src/test/resources/log4j-integration.properties | 2 +- raven-log4j/src/test/resources/log4j-test.properties | 2 +- raven-log4j2/src/test/resources/log4j2-integration.xml | 2 +- raven-log4j2/src/test/resources/log4j2-test.xml | 2 +- raven-logback/src/test/resources/logback-integration.xml | 2 +- raven-logback/src/test/resources/logback-test.xml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/raven-log4j/src/test/resources/log4j-integration.properties b/raven-log4j/src/test/resources/log4j-integration.properties index 4922f9ea86f..3507ddd7858 100644 --- a/raven-log4j/src/test/resources/log4j-integration.properties +++ b/raven-log4j/src/test/resources/log4j-integration.properties @@ -6,4 +6,4 @@ log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender log4j.appender.SentryAppender.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false log4j.appender.ConsoleAppender.layout=org.apache.log4j.EnhancedPatternLayout -log4j.appender.ConsoleAppender.layout.ConversionPattern=[RAVEN] [%-5p] %c - %m%n +log4j.appender.ConsoleAppender.layout.ConversionPattern=[RAVEN] [%-5p] %c - %m%n%throwable{none} diff --git a/raven-log4j/src/test/resources/log4j-test.properties b/raven-log4j/src/test/resources/log4j-test.properties index 32a175a8ef6..dbc9f995d93 100644 --- a/raven-log4j/src/test/resources/log4j-test.properties +++ b/raven-log4j/src/test/resources/log4j-test.properties @@ -1,4 +1,4 @@ log4j.rootLogger=ALL, ConsoleAppender log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender log4j.appender.ConsoleAppender.layout=org.apache.log4j.EnhancedPatternLayout -log4j.appender.ConsoleAppender.layout.ConversionPattern=[RAVEN] [%-5p] %c - %m%n +log4j.appender.ConsoleAppender.layout.ConversionPattern=[RAVEN] [%-5p] %c - %m%n%throwable{none} diff --git a/raven-log4j2/src/test/resources/log4j2-integration.xml b/raven-log4j2/src/test/resources/log4j2-integration.xml index 989959d197d..a95397d6b54 100644 --- a/raven-log4j2/src/test/resources/log4j2-integration.xml +++ b/raven-log4j2/src/test/resources/log4j2-integration.xml @@ -2,7 +2,7 @@ - + diff --git a/raven-log4j2/src/test/resources/log4j2-test.xml b/raven-log4j2/src/test/resources/log4j2-test.xml index 166c2f2a551..02629b80210 100644 --- a/raven-log4j2/src/test/resources/log4j2-test.xml +++ b/raven-log4j2/src/test/resources/log4j2-test.xml @@ -2,7 +2,7 @@ - + diff --git a/raven-logback/src/test/resources/logback-integration.xml b/raven-logback/src/test/resources/logback-integration.xml index 84c25bbb891..f22ffe807ed 100644 --- a/raven-logback/src/test/resources/logback-integration.xml +++ b/raven-logback/src/test/resources/logback-integration.xml @@ -1,7 +1,7 @@ - [RAVEN] [%-5level] %logger{36} - %msg%n + [RAVEN] [%-5level] %logger{36} - %msg%n%nopex diff --git a/raven-logback/src/test/resources/logback-test.xml b/raven-logback/src/test/resources/logback-test.xml index da8073edc58..2019ec66924 100644 --- a/raven-logback/src/test/resources/logback-test.xml +++ b/raven-logback/src/test/resources/logback-test.xml @@ -1,7 +1,7 @@ - [RAVEN] [%-5level] %logger{36} - %msg%n + [RAVEN] [%-5level] %logger{36} - %msg%n%nopex From d058060a3be472fad2a33c06a875c9416d8aa492 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 20 Feb 2014 08:03:37 +1100 Subject: [PATCH 1117/2152] Remove unecessary configuration --- raven/src/test/resources/logging-integration.properties | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/raven/src/test/resources/logging-integration.properties b/raven/src/test/resources/logging-integration.properties index 1e163c202bf..7a14989f7c5 100644 --- a/raven/src/test/resources/logging-integration.properties +++ b/raven/src/test/resources/logging-integration.properties @@ -1,10 +1,8 @@ -handlers=java.util.logging.ConsoleHandler +handlers=java.util.logging.ConsoleHandler, net.kencochrane.raven.jul.SentryHandler level=ALL # Ignore raven stub logs net.kencochrane.raven.stub.handlers=java.util.logging.ConsoleHandler net.kencochrane.raven.stub.level=ALL -net.kencochrane.raven.handlers=java.util.logging.ConsoleHandler, net.kencochrane.raven.jul.SentryHandler -net.kencochrane.raven.level=ALL java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter java.util.logging.SimpleFormatter.format=[RAVEN] [%4$.4s] %3$s - %5$s%n From 6449358386f073f19f96e466eb1437d19006e0da Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 20 Feb 2014 08:03:57 +1100 Subject: [PATCH 1118/2152] Rely on debug rather than trace for the logs --- .../main/java/net/kencochrane/raven/DefaultRavenFactory.java | 2 +- raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java | 2 +- raven/src/main/java/net/kencochrane/raven/dsn/JndiLookup.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 1381a8b4d25..1d47cac4091 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -72,7 +72,7 @@ public Raven createRavenInstance(Dsn dsn) { Class.forName("javax.servlet.Servlet", false, this.getClass().getClassLoader()); raven.addBuilderHelper(new HttpEventBuilderHelper()); } catch (ClassNotFoundException e) { - logger.trace("It seems that the current environment doesn't provide access to servlets."); + logger.debug("It seems that the current environment doesn't provide access to servlets."); } return raven; } diff --git a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java index d2f1c32ab30..27d6b341d24 100644 --- a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java @@ -82,7 +82,7 @@ public static String dsnLookup() { Class.forName("javax.naming.InitialContext", false, Dsn.class.getClassLoader()); dsn = JndiLookup.jndiLookup(); } catch (ClassNotFoundException e) { - logger.trace("JNDI not available"); + logger.debug("JNDI not available"); } // Try to obtain the DSN from a System Environment Variable diff --git a/raven/src/main/java/net/kencochrane/raven/dsn/JndiLookup.java b/raven/src/main/java/net/kencochrane/raven/dsn/JndiLookup.java index 2d978486766..5314dcab9e3 100644 --- a/raven/src/main/java/net/kencochrane/raven/dsn/JndiLookup.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/JndiLookup.java @@ -36,9 +36,9 @@ public static String jndiLookup() { Context c = new InitialContext(); dsn = (String) c.lookup(JNDI_DSN_NAME); } catch (NoInitialContextException e) { - logger.trace("JNDI not configured for sentry (NoInitialContextEx)"); + logger.debug("JNDI not configured for sentry (NoInitialContextEx)"); } catch (NamingException e) { - logger.trace("No /sentry/dsn in JNDI"); + logger.debug("No /sentry/dsn in JNDI"); } catch (RuntimeException e) { logger.warn("Odd RuntimeException while testing for JNDI", e); } From 0aa4ae02d3c37cf1ad8f1169bc0eeb0163a154a2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 20 Feb 2014 08:13:53 +1100 Subject: [PATCH 1119/2152] Use the same setup/teardown for every IT --- .../net/kencochrane/raven/log4j/SentryAppenderIT.java | 8 +++++++- .../net/kencochrane/raven/log4j2/SentryAppenderIT.java | 1 - .../net/kencochrane/raven/logback/SentryAppenderIT.java | 5 +++++ .../java/net/kencochrane/raven/jul/SentryHandlerIT.java | 1 - 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java index 8ce41929745..70c3658efd6 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java @@ -3,6 +3,7 @@ import net.kencochrane.raven.stub.SentryStub; import org.apache.log4j.Logger; import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import static org.hamcrest.MatcherAssert.assertThat; @@ -10,7 +11,12 @@ public class SentryAppenderIT { private static final Logger logger = Logger.getLogger(SentryAppenderIT.class); - private SentryStub sentryStub = new SentryStub(); + private SentryStub sentryStub; + + @BeforeMethod + public void setUp() throws Exception{ + sentryStub = new SentryStub(); + } @AfterMethod public void tearDown() { diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java index 9166757ecdf..e09d996cb00 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java @@ -17,7 +17,6 @@ public class SentryAppenderIT { @BeforeMethod public void setUp() { sentryStub = new SentryStub(); - sentryStub.removeEvents(); } @AfterMethod diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java index 5ca175bfc6c..e2fb0489515 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java @@ -3,6 +3,7 @@ import net.kencochrane.raven.stub.SentryStub; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -17,6 +18,10 @@ public class SentryAppenderIT { public void setUp() { new MockUpStatusPrinter(); sentryStub = new SentryStub(); + } + + @AfterMethod + public void tearDown() throws Exception { sentryStub.removeEvents(); } diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java index 720372fc5e4..c3a2fbe14e1 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerIT.java @@ -18,7 +18,6 @@ public class SentryHandlerIT { @BeforeMethod public void setUp() throws Exception { sentryStub = new SentryStub(); - sentryStub.removeEvents(); } @AfterMethod From 8dc27b7192f931ac8534fb5742135e29832ccede Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 20 Feb 2014 08:14:41 +1100 Subject: [PATCH 1120/2152] Throw exception from each test method --- .../java/net/kencochrane/raven/log4j/SentryAppenderIT.java | 6 +++--- .../net/kencochrane/raven/log4j2/SentryAppenderIT.java | 6 +++--- .../net/kencochrane/raven/logback/SentryAppenderIT.java | 7 +++---- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java index 70c3658efd6..0506c1ee89d 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java @@ -19,19 +19,19 @@ public void setUp() throws Exception{ } @AfterMethod - public void tearDown() { + public void tearDown() throws Exception { sentryStub.removeEvents(); } @Test - public void testInfoLog() { + public void testInfoLog() throws Exception { assertThat(sentryStub.getEventCount(), is(0)); logger.info("This is a test"); assertThat(sentryStub.getEventCount(), is(1)); } @Test - public void testChainedExceptions() { + public void testChainedExceptions() throws Exception { assertThat(sentryStub.getEventCount(), is(0)); logger.error("This is an exception", new UnsupportedOperationException("Test", new UnsupportedOperationException())); diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java index e09d996cb00..523c5689923 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java @@ -20,19 +20,19 @@ public void setUp() { } @AfterMethod - public void tearDown() { + public void tearDown() throws Exception { sentryStub.removeEvents(); } @Test - public void testInfoLog() { + public void testInfoLog() throws Exception { assertThat(sentryStub.getEventCount(), is(0)); logger.info("This is a test"); assertThat(sentryStub.getEventCount(), is(1)); } @Test - public void testChainedExceptions() { + public void testChainedExceptions() throws Exception { assertThat(sentryStub.getEventCount(), is(0)); logger.error("This is an exception", new UnsupportedOperationException("Test", new UnsupportedOperationException())); diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java index e2fb0489515..9e0e078920c 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderIT.java @@ -15,8 +15,7 @@ public class SentryAppenderIT { private SentryStub sentryStub; @BeforeMethod - public void setUp() { - new MockUpStatusPrinter(); + public void setUp() throws Exception { sentryStub = new SentryStub(); } @@ -26,14 +25,14 @@ public void tearDown() throws Exception { } @Test - public void testInfoLog() { + public void testInfoLog() throws Exception { assertThat(sentryStub.getEventCount(), is(0)); logger.info("This is a test"); assertThat(sentryStub.getEventCount(), is(1)); } @Test - public void testChainedExceptions() { + public void testChainedExceptions() throws Exception { assertThat(sentryStub.getEventCount(), is(0)); logger.error("This is an exception", new UnsupportedOperationException("Test", new UnsupportedOperationException())); From a84f349164a75384448fa546107cb1837058dd02 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 1 Mar 2014 12:38:12 +1100 Subject: [PATCH 1121/2152] Add test to ensure authHeader validity --- .../connection/AbstractConnectionTest.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java index f6e2bb7fecd..e98f27bab8f 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java @@ -3,6 +3,7 @@ import mockit.Injectable; import mockit.Mock; import mockit.MockUp; +import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import org.mockito.MockitoAnnotations; import org.mockito.Spy; @@ -10,15 +11,16 @@ import org.testng.annotations.Test; import java.io.IOException; -import java.util.UUID; import java.util.concurrent.locks.ReentrantLock; import static mockit.Deencapsulation.setField; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.verify; public class AbstractConnectionTest { - private final String publicKey = UUID.randomUUID().toString(); - private final String secretKey = UUID.randomUUID().toString(); + private final String publicKey = "9bcf4a8c-f353-4f25-9dda-76a873fff905"; + private final String secretKey = "56a9d05e-9032-4fdd-8f67-867d526422f9"; private AbstractConnection abstractConnection; //Spying with mockito as jMockit doesn't support mocks of ReentrantLock @Spy @@ -27,10 +29,21 @@ public class AbstractConnectionTest { @BeforeMethod public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + setField(Raven.class, "NAME", "Raven-Java/Test"); abstractConnection = new DummyAbstractConnection(publicKey, secretKey); setField(abstractConnection, "lock", reentrantLock); } + @Test + public void testAuthHeader() throws Exception { + String authHeader = abstractConnection.getAuthHeader(); + + assertThat(authHeader, is("Sentry sentry_version=5," + + "sentry_client=Raven-Java/Test," + + "sentry_key=" + publicKey + "," + + "sentry_secret=" + secretKey)); + } + @Test public void testExceptionOnSendStartLockDown(@Injectable final Event mockEvent) throws Exception { new MockUp() { From 269c3389fa342b618968b34d82de7fe212c78a14 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 1 Mar 2014 12:47:11 +1100 Subject: [PATCH 1122/2152] Ensure that the lockdown ends in the tests --- .../net/kencochrane/raven/connection/AbstractConnectionTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java index e98f27bab8f..e8e83db084e 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java @@ -57,6 +57,7 @@ protected void doSend(Event event) throws ConnectionException { abstractConnection.send(mockEvent); verify(reentrantLock).tryLock(); + verify(reentrantLock).unlock(); } private static final class DummyAbstractConnection extends AbstractConnection { From 4c2b93c0fe9fc8c913027b86247f73b2be29400a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 1 Mar 2014 12:47:23 +1100 Subject: [PATCH 1123/2152] Add test to check that doSend is properly called when abstract connection sends an event --- .../raven/connection/AbstractConnectionTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java index e8e83db084e..d2ecaa37340 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java @@ -44,6 +44,18 @@ public void testAuthHeader() throws Exception { + "sentry_secret=" + secretKey)); } + @Test + public void testSuccessfulSendCallsDoSend(@Injectable final Event mockEvent) throws Exception { + new MockUp() { + @SuppressWarnings("unused") + @Mock(invocations = 1) + protected void doSend(Event event) throws ConnectionException { + } + }; + + abstractConnection.send(mockEvent); + } + @Test public void testExceptionOnSendStartLockDown(@Injectable final Event mockEvent) throws Exception { new MockUp() { From bd56cce08853549c7bb01fe914936aa21483322b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 09:04:51 +1100 Subject: [PATCH 1124/2152] Add test to ensure that the shutdown hook closes the connection --- .../raven/connection/AsyncConnectionTest.java | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index 2c768a38042..1655542d3c1 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -1,9 +1,6 @@ package net.kencochrane.raven.connection; -import mockit.Delegate; -import mockit.Expectations; -import mockit.Injectable; -import mockit.Verifications; +import mockit.*; import net.kencochrane.raven.event.Event; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -17,13 +14,37 @@ public class AsyncConnectionTest { private Connection mockConnection; @Injectable private ExecutorService mockExecutorService; + @Mocked + private Runtime mockRuntime; @BeforeMethod public void setUp() throws Exception { + new NonStrictExpectations() {{ + Runtime.getRuntime(); + result = mockRuntime; + }}; asyncConnection = new AsyncConnection(mockConnection); asyncConnection.setExecutorService(mockExecutorService); } + @Test + public void verifyShutdownHookClosesConnection() throws Exception { + new NonStrictExpectations() {{ + mockRuntime.addShutdownHook((Thread) any); + result = new Delegate() { + public void addShutdownHook(Thread thread) { + thread.run(); + } + }; + }}; + + new AsyncConnection(mockConnection); + + new Verifications() {{ + mockConnection.close(); + }}; + } + @Test public void testCloseOperation() throws Exception { asyncConnection.close(); @@ -34,7 +55,6 @@ public void testCloseOperation() throws Exception { }}; } - @Test public void testSendEventQueued(@Injectable final Event mockEvent) throws Exception { asyncConnection.send(mockEvent); From 4f0732050a8b06144912ed96a7925fdbdd8f2eac Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 09:06:09 +1100 Subject: [PATCH 1125/2152] Ensure that the shutdown hook is properly set --- .../raven/connection/AsyncConnectionTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index 1655542d3c1..80ec530294b 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -27,6 +27,15 @@ public void setUp() throws Exception { asyncConnection.setExecutorService(mockExecutorService); } + @Test + public void verifyShutdownHookIsAdded() throws Exception { + new AsyncConnection(mockConnection); + + new Verifications() {{ + mockRuntime.addShutdownHook((Thread) any); + }}; + } + @Test public void verifyShutdownHookClosesConnection() throws Exception { new NonStrictExpectations() {{ From 225da00883ff007f630ccc0c33d72bda16201ec4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 09:13:20 +1100 Subject: [PATCH 1126/2152] Only mock the addShutdownHook method --- .../kencochrane/raven/connection/AsyncConnectionTest.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index 80ec530294b..e51c818cca8 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -14,15 +14,11 @@ public class AsyncConnectionTest { private Connection mockConnection; @Injectable private ExecutorService mockExecutorService; - @Mocked + @Mocked("addShutdownHook") private Runtime mockRuntime; @BeforeMethod public void setUp() throws Exception { - new NonStrictExpectations() {{ - Runtime.getRuntime(); - result = mockRuntime; - }}; asyncConnection = new AsyncConnection(mockConnection); asyncConnection.setExecutorService(mockExecutorService); } From e8c37f00b3832e3968b3e9a80582ecabb273ec85 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 09:54:59 +1100 Subject: [PATCH 1127/2152] Ensure that shutdown hook threads are managed by Raven --- .../java/net/kencochrane/raven/Raven.java | 36 +++++++++++++++++++ .../raven/connection/AsyncConnection.java | 4 +-- .../raven/connection/AsyncConnectionTest.java | 21 +++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index a16c8f92299..9e43dcf44e3 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -40,6 +40,42 @@ protected Boolean initialValue() { private final Set builderHelpers = new HashSet<>(); private Connection connection; + /** + * Sets the current thread as managed by Raven. + *

    + * The logs generated by Threads managed by Raven will not send logs to Sentry. + *

    + */ + public static void manageThread() { + if (isManagingThread()) + logger.warn("Thread already managed by Raven"); + RAVEN_THREAD.set(true); + } + + /** + * Sets the current thread as not managed by Raven. + *

    + * The logs generated by Threads not managed by Raven will send logs to Sentry. + *

    + */ + public static void stopManagingThread() { + if (!isManagingThread()) { + //Start managing the thread to send the warning only + manageThread(); + logger.warn("Thread not yet managed by Raven"); + } + RAVEN_THREAD.remove(); + } + + /** + * Checks whether the current thread is managed by Raven or not. + * + * @return {@code true} if the thread is managed by Raven, {@code false} otherwise. + */ + public static boolean isManagingThread() { + return RAVEN_THREAD.get(); + } + /** * Runs the {@link EventBuilderHelper} against the {@link EventBuilder} to obtain additional information with a * MDC-like system. diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index fbd9e68a682..f16852600b5 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -59,12 +59,12 @@ private void addShutdownHook() { public void run() { try { // The current thread is managed by raven - Raven.RAVEN_THREAD.set(true); + Raven.manageThread(); AsyncConnection.this.close(); } catch (IOException e) { logger.error("An exception occurred while closing the connection.", e); } finally { - Raven.RAVEN_THREAD.remove(); + Raven.stopManagingThread(); } } }); diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index e51c818cca8..837bbc6efc3 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -1,6 +1,7 @@ package net.kencochrane.raven.connection; import mockit.*; +import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -50,6 +51,26 @@ public void addShutdownHook(Thread thread) { }}; } + @Test + public void verifyShutdownHookSetRavenManagedThreadFlag( + @Mocked({"manageThread", "stopManagingThread"}) Raven mockRaven) throws Exception { + new NonStrictExpectations() {{ + mockRuntime.addShutdownHook((Thread) any); + result = new Delegate() { + public void addShutdownHook(Thread thread) { + thread.run(); + } + }; + }}; + + new AsyncConnection(mockConnection); + + new Verifications() {{ + Raven.manageThread(); + Raven.stopManagingThread(); + }}; + } + @Test public void testCloseOperation() throws Exception { asyncConnection.close(); From 5230b0c5dea253e49a2666a86711aab791489887 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 09:56:29 +1100 Subject: [PATCH 1128/2152] Add logs when starting the shudown hook --- .../java/net/kencochrane/raven/connection/AsyncConnection.java | 1 + 1 file changed, 1 insertion(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index f16852600b5..5edcdd4dd3f 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -60,6 +60,7 @@ public void run() { try { // The current thread is managed by raven Raven.manageThread(); + logger.info("Automatic shutdown of the async connection"); AsyncConnection.this.close(); } catch (IOException e) { logger.error("An exception occurred while closing the connection.", e); From a56bea980bc911e2d35d97a87209bccdde27174c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 09:56:53 +1100 Subject: [PATCH 1129/2152] Check thread managemnt and connection close in the same test Both tests are relying on the same events and the connection.close should be exactly between a manageThread and a stopManagingThread --- .../raven/connection/AsyncConnectionTest.java | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index 837bbc6efc3..27992bc8aa1 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -34,25 +34,7 @@ public void verifyShutdownHookIsAdded() throws Exception { } @Test - public void verifyShutdownHookClosesConnection() throws Exception { - new NonStrictExpectations() {{ - mockRuntime.addShutdownHook((Thread) any); - result = new Delegate() { - public void addShutdownHook(Thread thread) { - thread.run(); - } - }; - }}; - - new AsyncConnection(mockConnection); - - new Verifications() {{ - mockConnection.close(); - }}; - } - - @Test - public void verifyShutdownHookSetRavenManagedThreadFlag( + public void verifyShutdownHookSetManagedByRavenAndCloseConnection( @Mocked({"manageThread", "stopManagingThread"}) Raven mockRaven) throws Exception { new NonStrictExpectations() {{ mockRuntime.addShutdownHook((Thread) any); @@ -65,8 +47,9 @@ public void addShutdownHook(Thread thread) { new AsyncConnection(mockConnection); - new Verifications() {{ + new VerificationsInOrder() {{ Raven.manageThread(); + mockConnection.close(); Raven.stopManagingThread(); }}; } From d23e6f639e76180088f9ee5c850391a883a6763f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 10:01:20 +1100 Subject: [PATCH 1130/2152] Ensure that exceptions during the close operation do not blow up --- .../raven/connection/AsyncConnection.java | 2 +- .../raven/connection/AsyncConnectionTest.java | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 5edcdd4dd3f..8332b53de49 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -62,7 +62,7 @@ public void run() { Raven.manageThread(); logger.info("Automatic shutdown of the async connection"); AsyncConnection.this.close(); - } catch (IOException e) { + } catch (Exception e) { logger.error("An exception occurred while closing the connection.", e); } finally { Raven.stopManagingThread(); diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index 27992bc8aa1..a086a6fb4d9 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -54,6 +54,27 @@ public void addShutdownHook(Thread thread) { }}; } + @Test + public void ensureFailingShutdownHookStopsBeingManaged( + @Mocked({"manageThread", "stopManagingThread"}) Raven mockRaven) throws Exception { + new NonStrictExpectations() {{ + mockRuntime.addShutdownHook((Thread) any); + result = new Delegate() { + public void addShutdownHook(Thread thread) { + thread.run(); + } + }; + mockConnection.close(); + result = new RuntimeException("Close operation failed"); + }}; + + new AsyncConnection(mockConnection); + + new VerificationsInOrder() {{ + Raven.stopManagingThread(); + }}; + } + @Test public void testCloseOperation() throws Exception { asyncConnection.close(); From e97451860e9be019fe1daf1061e5faf0671df25e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 10:06:55 +1100 Subject: [PATCH 1131/2152] Replace RAVEN_THREAD.method with static method calls --- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 6 +++--- .../kencochrane/raven/log4j/SentryAppenderFailuresTest.java | 4 ++-- .../java/net/kencochrane/raven/log4j2/SentryAppender.java | 6 +++--- .../raven/log4j2/SentryAppenderFailuresTest.java | 4 ++-- .../java/net/kencochrane/raven/logback/SentryAppender.java | 6 +++--- .../raven/logback/SentryAppenderFailuresTest.java | 4 ++-- .../net/kencochrane/raven/connection/AsyncConnection.java | 4 ++-- .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 6 +++--- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 077878e606b..4a2fe427aeb 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -135,18 +135,18 @@ protected void initRaven() { @Override protected void append(LoggingEvent loggingEvent) { // Do not log the event if the current thread is managed by raven - if (Raven.RAVEN_THREAD.get()) + if (Raven.isManagingThread()) return; try { - Raven.RAVEN_THREAD.set(true); + Raven.manageThread(); Event event = buildEvent(loggingEvent); raven.sendEvent(event); } catch (Exception e) { getErrorHandler().error("An exception occurred while creating a new event in Raven", e, ErrorCode.WRITE_FAILURE); } finally { - Raven.RAVEN_THREAD.remove(); + Raven.stopManagingThread(); } } diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java index d807e5592a6..3f5fe3bc123 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java @@ -65,7 +65,7 @@ public void testRavenFactoryFailureDoesNotPropagate() throws Exception { @Test public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { try { - Raven.RAVEN_THREAD.set(true); + Raven.manageThread(); sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); new Verifications() {{ @@ -74,7 +74,7 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { }}; assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } finally { - Raven.RAVEN_THREAD.remove(); + Raven.stopManagingThread(); } } } diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 96a3dbf9c75..65e5aa7fa5c 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -175,11 +175,11 @@ protected static List formatMessageParameters(Object[] parameters) { @Override public void append(LogEvent logEvent) { // Do not log the event if the current thread is managed by raven - if (Raven.RAVEN_THREAD.get()) + if (Raven.isManagingThread()) return; try { - Raven.RAVEN_THREAD.set(true); + Raven.manageThread(); if (raven == null) initRaven(); @@ -188,7 +188,7 @@ public void append(LogEvent logEvent) { } catch (Exception e) { error("An exception occurred while creating a new event in Raven", logEvent, e); } finally { - Raven.RAVEN_THREAD.remove(); + Raven.stopManagingThread(); } } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java index 88f3d249a36..3802e968ba0 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java @@ -62,7 +62,7 @@ public void testRavenFactoryFailureDoesNotPropagate() throws Exception { @Test public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { try { - Raven.RAVEN_THREAD.set(true); + Raven.manageThread(); sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); @@ -72,7 +72,7 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { }}; assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } finally { - Raven.RAVEN_THREAD.remove(); + Raven.stopManagingThread(); } } } diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index de7d035e4a2..4e1b6595523 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -125,11 +125,11 @@ protected static Event.Level formatLevel(Level level) { @Override protected void append(ILoggingEvent iLoggingEvent) { // Do not log the event if the current thread is managed by raven - if (Raven.RAVEN_THREAD.get()) + if (Raven.isManagingThread()) return; try { - Raven.RAVEN_THREAD.set(true); + Raven.manageThread(); if (raven == null) initRaven(); @@ -139,7 +139,7 @@ protected void append(ILoggingEvent iLoggingEvent) { } catch (Exception e) { addError("An exception occurred while creating a new event in Raven", e); } finally { - Raven.RAVEN_THREAD.remove(); + Raven.stopManagingThread(); } } diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java index 185b4c714a4..ed34acdf25c 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java @@ -75,7 +75,7 @@ public void testRavenFactoryFailureDoesNotPropagate() throws Exception { @Test public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { try { - Raven.RAVEN_THREAD.set(true); + Raven.manageThread(); final SentryAppender sentryAppender = new SentryAppender(mockRaven); sentryAppender.setContext(mockContext); sentryAppender.start(); @@ -88,7 +88,7 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { }}; assertThat(mockContext.getStatusManager().getCount(), is(0)); } finally { - Raven.RAVEN_THREAD.remove(); + Raven.stopManagingThread(); } } } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 8332b53de49..6c2a56bddfa 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -132,12 +132,12 @@ private EventSubmitter(Event event) { public void run() { try { // The current thread is managed by raven - Raven.RAVEN_THREAD.set(true); + Raven.manageThread(); actualConnection.send(event); } catch (Exception e) { logger.error("An exception occurred while sending the event to Sentry.", e); } finally { - Raven.RAVEN_THREAD.remove(); + Raven.stopManagingThread(); } } } diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 81b70ee7d96..6f5d89a85c5 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -113,11 +113,11 @@ protected void retrieveProperties() { @Override public void publish(LogRecord record) { // Do not log the event if the current thread is managed by raven - if (!isLoggable(record) || Raven.RAVEN_THREAD.get()) + if (!isLoggable(record) || Raven.isManagingThread()) return; try { - Raven.RAVEN_THREAD.set(true); + Raven.manageThread(); if (raven == null) initRaven(); Event event = buildEvent(record); @@ -125,7 +125,7 @@ public void publish(LogRecord record) { } catch (Exception e) { reportError("An exception occurred while creating a new event in Raven", e, ErrorManager.WRITE_FAILURE); } finally { - Raven.RAVEN_THREAD.remove(); + Raven.stopManagingThread(); } } From 65c739218065f76eb497bf40708b8967bbb4810d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 10:11:41 +1100 Subject: [PATCH 1132/2152] Make RAVEN_THREAD private and ensure that the related methods are always working --- .../java/net/kencochrane/raven/Raven.java | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 9e43dcf44e3..e71238f25ee 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -22,20 +22,20 @@ *

    */ public class Raven { + /** + * Version of this client, the major version is the current supported Sentry protocol, the minor version changes + * for each release of this project. + */ + public static final String NAME = ResourceBundle.getBundle("raven-build").getString("build.name"); /** * Indicates whether the current thread is managed by raven or not. */ - public static final ThreadLocal RAVEN_THREAD = new ThreadLocal() { + private static final ThreadLocal RAVEN_THREAD = new ThreadLocal() { @Override protected Boolean initialValue() { return false; } }; - /** - * Version of this client, the major version is the current supported Sentry protocol, the minor version changes - * for each release of this project. - */ - public static final String NAME = ResourceBundle.getBundle("raven-build").getString("build.name"); private static final Logger logger = LoggerFactory.getLogger(Raven.class); private final Set builderHelpers = new HashSet<>(); private Connection connection; @@ -47,9 +47,12 @@ protected Boolean initialValue() { *

    */ public static void manageThread() { - if (isManagingThread()) - logger.warn("Thread already managed by Raven"); - RAVEN_THREAD.set(true); + try { + if (isManagingThread()) + logger.warn("Thread already managed by Raven"); + } finally { + RAVEN_THREAD.set(true); + } } /** @@ -59,12 +62,15 @@ public static void manageThread() { *

    */ public static void stopManagingThread() { - if (!isManagingThread()) { - //Start managing the thread to send the warning only - manageThread(); - logger.warn("Thread not yet managed by Raven"); + try { + if (!isManagingThread()) { + //Start managing the thread only to send the warning + manageThread(); + logger.warn("Thread not yet managed by Raven"); + } + } finally { + RAVEN_THREAD.remove(); } - RAVEN_THREAD.remove(); } /** From b91e352d0736154646e8db1740c7b44aca9944f0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 10:13:08 +1100 Subject: [PATCH 1133/2152] Use same naming convention for every Raven Thread management method --- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 2 +- .../kencochrane/raven/log4j/SentryAppenderFailuresTest.java | 2 +- .../java/net/kencochrane/raven/log4j2/SentryAppender.java | 2 +- .../raven/log4j2/SentryAppenderFailuresTest.java | 2 +- .../java/net/kencochrane/raven/logback/SentryAppender.java | 2 +- .../raven/logback/SentryAppenderFailuresTest.java | 2 +- raven/src/main/java/net/kencochrane/raven/Raven.java | 4 ++-- .../net/kencochrane/raven/connection/AsyncConnection.java | 4 ++-- .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 2 +- .../kencochrane/raven/connection/AsyncConnectionTest.java | 6 +++--- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 4a2fe427aeb..465683fe831 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -139,7 +139,7 @@ protected void append(LoggingEvent loggingEvent) { return; try { - Raven.manageThread(); + Raven.startManagingThread(); Event event = buildEvent(loggingEvent); raven.sendEvent(event); } catch (Exception e) { diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java index 3f5fe3bc123..dc71d861f8d 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java @@ -65,7 +65,7 @@ public void testRavenFactoryFailureDoesNotPropagate() throws Exception { @Test public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { try { - Raven.manageThread(); + Raven.startManagingThread(); sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); new Verifications() {{ diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 65e5aa7fa5c..cc069b7fe05 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -179,7 +179,7 @@ public void append(LogEvent logEvent) { return; try { - Raven.manageThread(); + Raven.startManagingThread(); if (raven == null) initRaven(); diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java index 3802e968ba0..00ad1cb71dc 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java @@ -62,7 +62,7 @@ public void testRavenFactoryFailureDoesNotPropagate() throws Exception { @Test public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { try { - Raven.manageThread(); + Raven.startManagingThread(); sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 4e1b6595523..d2a7fbc2276 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -129,7 +129,7 @@ protected void append(ILoggingEvent iLoggingEvent) { return; try { - Raven.manageThread(); + Raven.startManagingThread(); if (raven == null) initRaven(); diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java index ed34acdf25c..772a1c67091 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java @@ -75,7 +75,7 @@ public void testRavenFactoryFailureDoesNotPropagate() throws Exception { @Test public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { try { - Raven.manageThread(); + Raven.startManagingThread(); final SentryAppender sentryAppender = new SentryAppender(mockRaven); sentryAppender.setContext(mockContext); sentryAppender.start(); diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index e71238f25ee..de2c2449ab9 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -46,7 +46,7 @@ protected Boolean initialValue() { * The logs generated by Threads managed by Raven will not send logs to Sentry. *

    */ - public static void manageThread() { + public static void startManagingThread() { try { if (isManagingThread()) logger.warn("Thread already managed by Raven"); @@ -65,7 +65,7 @@ public static void stopManagingThread() { try { if (!isManagingThread()) { //Start managing the thread only to send the warning - manageThread(); + startManagingThread(); logger.warn("Thread not yet managed by Raven"); } } finally { diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 6c2a56bddfa..d7fed360868 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -59,7 +59,7 @@ private void addShutdownHook() { public void run() { try { // The current thread is managed by raven - Raven.manageThread(); + Raven.startManagingThread(); logger.info("Automatic shutdown of the async connection"); AsyncConnection.this.close(); } catch (Exception e) { @@ -132,7 +132,7 @@ private EventSubmitter(Event event) { public void run() { try { // The current thread is managed by raven - Raven.manageThread(); + Raven.startManagingThread(); actualConnection.send(event); } catch (Exception e) { logger.error("An exception occurred while sending the event to Sentry.", e); diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 6f5d89a85c5..80b95b1f7d2 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -117,7 +117,7 @@ public void publish(LogRecord record) { return; try { - Raven.manageThread(); + Raven.startManagingThread(); if (raven == null) initRaven(); Event event = buildEvent(record); diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index a086a6fb4d9..4fe9708d7c7 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -35,7 +35,7 @@ public void verifyShutdownHookIsAdded() throws Exception { @Test public void verifyShutdownHookSetManagedByRavenAndCloseConnection( - @Mocked({"manageThread", "stopManagingThread"}) Raven mockRaven) throws Exception { + @Mocked({"startManagingThread", "stopManagingThread"}) Raven mockRaven) throws Exception { new NonStrictExpectations() {{ mockRuntime.addShutdownHook((Thread) any); result = new Delegate() { @@ -48,7 +48,7 @@ public void addShutdownHook(Thread thread) { new AsyncConnection(mockConnection); new VerificationsInOrder() {{ - Raven.manageThread(); + Raven.startManagingThread(); mockConnection.close(); Raven.stopManagingThread(); }}; @@ -56,7 +56,7 @@ public void addShutdownHook(Thread thread) { @Test public void ensureFailingShutdownHookStopsBeingManaged( - @Mocked({"manageThread", "stopManagingThread"}) Raven mockRaven) throws Exception { + @Mocked({"startManagingThread", "stopManagingThread"}) Raven mockRaven) throws Exception { new NonStrictExpectations() {{ mockRuntime.addShutdownHook((Thread) any); result = new Delegate() { From 67adca7c746f522ceec17e2cf4ac8f75c61be94e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 10:42:25 +1100 Subject: [PATCH 1134/2152] Use Tested rather than creating dummy implementations --- .../connection/AbstractConnectionTest.java | 48 +++++++------------ 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java index d2ecaa37340..e9bdaea6fe3 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java @@ -1,8 +1,9 @@ package net.kencochrane.raven.connection; import mockit.Injectable; -import mockit.Mock; -import mockit.MockUp; +import mockit.NonStrictExpectations; +import mockit.Tested; +import mockit.Verifications; import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import org.mockito.MockitoAnnotations; @@ -10,7 +11,6 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import java.io.IOException; import java.util.concurrent.locks.ReentrantLock; import static mockit.Deencapsulation.setField; @@ -19,8 +19,11 @@ import static org.mockito.Mockito.verify; public class AbstractConnectionTest { + @Injectable private final String publicKey = "9bcf4a8c-f353-4f25-9dda-76a873fff905"; + @Injectable private final String secretKey = "56a9d05e-9032-4fdd-8f67-867d526422f9"; + @Tested private AbstractConnection abstractConnection; //Spying with mockito as jMockit doesn't support mocks of ReentrantLock @Spy @@ -30,8 +33,6 @@ public class AbstractConnectionTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); setField(Raven.class, "NAME", "Raven-Java/Test"); - abstractConnection = new DummyAbstractConnection(publicKey, secretKey); - setField(abstractConnection, "lock", reentrantLock); } @Test @@ -46,43 +47,26 @@ public void testAuthHeader() throws Exception { @Test public void testSuccessfulSendCallsDoSend(@Injectable final Event mockEvent) throws Exception { - new MockUp() { - @SuppressWarnings("unused") - @Mock(invocations = 1) - protected void doSend(Event event) throws ConnectionException { - } - }; + setField(abstractConnection, "lock", reentrantLock); abstractConnection.send(mockEvent); + + new Verifications() {{ + abstractConnection.doSend(mockEvent); + }}; } @Test public void testExceptionOnSendStartLockDown(@Injectable final Event mockEvent) throws Exception { - new MockUp() { - @SuppressWarnings("unused") - @Mock - protected void doSend(Event event) throws ConnectionException { - throw new ConnectionException(); - } - }; + setField(abstractConnection, "lock", reentrantLock); + new NonStrictExpectations() {{ + abstractConnection.doSend((Event) any); + result = new ConnectionException(); + }}; abstractConnection.send(mockEvent); verify(reentrantLock).tryLock(); verify(reentrantLock).unlock(); } - - private static final class DummyAbstractConnection extends AbstractConnection { - public DummyAbstractConnection(String publicKey, String secretKey) { - super(publicKey, secretKey); - } - - @Override - protected void doSend(Event event) throws ConnectionException { - } - - @Override - public void close() throws IOException { - } - } } From 2fa72f4e3c2813b6dca46eee0a69269d36f4e4a3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 18:38:05 +1100 Subject: [PATCH 1135/2152] Remove unecessary VerificationsInOrder --- .../net/kencochrane/raven/connection/AsyncConnectionTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index 4fe9708d7c7..c5fb493860e 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -70,7 +70,7 @@ public void addShutdownHook(Thread thread) { new AsyncConnection(mockConnection); - new VerificationsInOrder() {{ + new Verifications() {{ Raven.stopManagingThread(); }}; } From 2bda90e2335d0cb6a6ce082e2e469ef024d987bf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 18:38:23 +1100 Subject: [PATCH 1136/2152] Ensure that the tested element is reset after each run --- .../kencochrane/raven/connection/AbstractConnectionTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java index e9bdaea6fe3..d2d320514ea 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java @@ -33,6 +33,8 @@ public class AbstractConnectionTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); setField(Raven.class, "NAME", "Raven-Java/Test"); + // Reset tested + abstractConnection = null; } @Test From 3544a8890453667cbe4f2a9ca372ff7a8cc0be9b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 18:40:03 +1100 Subject: [PATCH 1137/2152] Make the ExecutorService of AsyncConnection set on constructor --- .../net/kencochrane/raven/DefaultRavenFactory.java | 10 +++------- .../raven/connection/AsyncConnection.java | 14 ++++++++------ .../raven/connection/AsyncConnectionTest.java | 9 ++++----- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 1d47cac4091..9e6adbf0fb0 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -114,7 +114,6 @@ protected Connection createConnection(Dsn dsn) { * @return the asynchronous connection. */ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { - AsyncConnection asyncConnection = new AsyncConnection(connection); int maxThreads; if (dsn.getOptions().containsKey(MAX_THREADS_OPTION)) { @@ -137,13 +136,10 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { queue = new LinkedBlockingDeque<>(); } - asyncConnection.setExecutorService(new ThreadPoolExecutor( - maxThreads, maxThreads, - 0L, TimeUnit.MILLISECONDS, - queue, - new DaemonThreadFactory(priority))); + ExecutorService executorService = new ThreadPoolExecutor( + maxThreads, maxThreads, 0L, TimeUnit.MILLISECONDS, queue, new DaemonThreadFactory(priority)); - return asyncConnection; + return new AsyncConnection(connection, executorService); } /** diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index d7fed360868..1bde278986a 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -31,7 +31,7 @@ public class AsyncConnection implements Connection { /** * Executor service in charge of running the connection in separate threads. */ - private ExecutorService executorService = Executors.newSingleThreadExecutor(); + private final ExecutorService executorService; /** * Boolean used to check whether the connection is still open or not. */ @@ -44,9 +44,15 @@ public class AsyncConnection implements Connection { *

    * * @param actualConnection connection used to send the events. + * @param executorService executorService used to process events, if null, the executorService will automatically + * be set to {@code Executors.newSingleThreadExecutor()} */ - public AsyncConnection(Connection actualConnection) { + public AsyncConnection(Connection actualConnection, ExecutorService executorService) { this.actualConnection = actualConnection; + if (executorService == null) + this.executorService = Executors.newSingleThreadExecutor(); + else + this.executorService = executorService; addShutdownHook(); } @@ -113,10 +119,6 @@ public void close() throws IOException { } } - public void setExecutorService(ExecutorService executorService) { - this.executorService = executorService; - } - /** * Simple runnable using the {@link #send(net.kencochrane.raven.event.Event)} method of the * {@link #actualConnection}. diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index c5fb493860e..d754bded1cc 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -20,13 +20,12 @@ public class AsyncConnectionTest { @BeforeMethod public void setUp() throws Exception { - asyncConnection = new AsyncConnection(mockConnection); - asyncConnection.setExecutorService(mockExecutorService); + asyncConnection = new AsyncConnection(mockConnection, mockExecutorService); } @Test public void verifyShutdownHookIsAdded() throws Exception { - new AsyncConnection(mockConnection); + new AsyncConnection(mockConnection, mockExecutorService); new Verifications() {{ mockRuntime.addShutdownHook((Thread) any); @@ -45,7 +44,7 @@ public void addShutdownHook(Thread thread) { }; }}; - new AsyncConnection(mockConnection); + new AsyncConnection(mockConnection, mockExecutorService); new VerificationsInOrder() {{ Raven.startManagingThread(); @@ -68,7 +67,7 @@ public void addShutdownHook(Thread thread) { result = new RuntimeException("Close operation failed"); }}; - new AsyncConnection(mockConnection); + new AsyncConnection(mockConnection, mockExecutorService); new Verifications() {{ Raven.stopManagingThread(); From f032086e8e6ec67a2af4b23f155c0a93ca3d3bb9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 18:41:52 +1100 Subject: [PATCH 1138/2152] Use the @Tested annotation for AsyncConnectionTest --- .../net/kencochrane/raven/connection/AsyncConnectionTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index d754bded1cc..5ee24f044e2 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -10,6 +10,7 @@ import java.util.concurrent.TimeUnit; public class AsyncConnectionTest { + @Tested private AsyncConnection asyncConnection; @Injectable private Connection mockConnection; @@ -20,7 +21,8 @@ public class AsyncConnectionTest { @BeforeMethod public void setUp() throws Exception { - asyncConnection = new AsyncConnection(mockConnection, mockExecutorService); + // Reset Tested + asyncConnection = null; } @Test From 7df10f2a88ec3c88477a2a65adb5dbe9665cfe75 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 18:49:06 +1100 Subject: [PATCH 1139/2152] Remove need for UUID in unit test --- .../raven/connection/HttpConnectionTest.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index 3c72206360b..d4b135b7a11 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -19,14 +19,15 @@ import java.io.OutputStream; import java.net.URI; import java.net.URL; -import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; public class HttpConnectionTest { - private final String publicKey = UUID.randomUUID().toString(); - private final String secretKey = UUID.randomUUID().toString(); + @Injectable + private final String publicKey = "6cc48e8f-380c-44cc-986b-f566247a2af5"; + @Injectable + private final String secretKey = "e30cca23-3f97-470b-a8c2-e29b33dd25e0"; private HttpConnection httpConnection; @Injectable private HttpsURLConnection mockUrlConnection; @@ -130,7 +131,7 @@ public void testAuthHeaderSent(@Injectable final Event mockEvent) throws Excepti @Test(expectedExceptions = {ConnectionException.class}) public void testHttpErrorThrowsAnException(@Injectable final Event mockEvent) throws Exception { - final String httpErrorMessage = UUID.randomUUID().toString(); + final String httpErrorMessage = "93e3ddb1-c4f3-46c3-9900-529de83678b7"; new NonStrictExpectations() {{ mockUrlConnection.getOutputStream(); result = new IOException(); @@ -144,7 +145,7 @@ public void testHttpErrorThrowsAnException(@Injectable final Event mockEvent) th @Test public void testApiUrlCreation(@Injectable final URI sentryUri) throws Exception { final String uri = "http://host/sentry/"; - final String projectId = UUID.randomUUID().toString(); + final String projectId = "293b4958-71f8-40a9-b588-96f004f64463"; new Expectations() {{ sentryUri.toString(); result = "http://host/sentry/"; From 51e717819f98cb534fef3a8c2cad0335b97b9be3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 18:49:20 +1100 Subject: [PATCH 1140/2152] Simplify the check for the AuthHeader --- .../kencochrane/raven/connection/HttpConnectionTest.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index d4b135b7a11..e09d73a97d2 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -120,12 +120,7 @@ public void testAuthHeaderSent(@Injectable final Event mockEvent) throws Excepti new Verifications() {{ mockUrlConnection.setRequestProperty("User-Agent", Raven.NAME); - - String expectedAuthRequest = "Sentry sentry_version=5," - + "sentry_client=" + Raven.NAME + "," - + "sentry_key=" + publicKey + "," - + "sentry_secret=" + secretKey; - mockUrlConnection.setRequestProperty("X-Sentry-Auth", expectedAuthRequest); + mockUrlConnection.setRequestProperty("X-Sentry-Auth", httpConnection.getAuthHeader()); }}; } From 4d963903a5a3f85f48008d6fbc374676ca49fbfe Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 18:50:06 +1100 Subject: [PATCH 1141/2152] Remove redundant string --- .../net/kencochrane/raven/connection/HttpConnectionTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index e09d73a97d2..8b0d6062ce8 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -143,7 +143,7 @@ public void testApiUrlCreation(@Injectable final URI sentryUri) throws Exception final String projectId = "293b4958-71f8-40a9-b588-96f004f64463"; new Expectations() {{ sentryUri.toString(); - result = "http://host/sentry/"; + result = uri; }}; URL sentryApiUrl = HttpConnection.getSentryApiUrl(sentryUri, projectId); From 54885cd58d0f9108075f672c0d759b5298e9a8f8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 18:50:22 +1100 Subject: [PATCH 1142/2152] Use @Tested in HttpConnectionTest --- .../raven/connection/HttpConnectionTest.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index 8b0d6062ce8..df6241dea29 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -1,9 +1,6 @@ package net.kencochrane.raven.connection; -import mockit.Expectations; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Verifications; +import mockit.*; import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.marshaller.Marshaller; @@ -28,6 +25,7 @@ public class HttpConnectionTest { private final String publicKey = "6cc48e8f-380c-44cc-986b-f566247a2af5"; @Injectable private final String secretKey = "e30cca23-3f97-470b-a8c2-e29b33dd25e0"; + @Tested private HttpConnection httpConnection; @Injectable private HttpsURLConnection mockUrlConnection; @@ -42,6 +40,7 @@ public class HttpConnectionTest { @BeforeMethod public void setUp() throws Exception { + httpConnection = null; new NonStrictExpectations() {{ mockUrl.openConnection(); result = mockUrlConnection; @@ -49,9 +48,6 @@ public void setUp() throws Exception { result = mockOutputStream; mockUrlConnection.getInputStream(); result = mockInputStream; - - httpConnection = new HttpConnection(mockUrl, publicKey, secretKey); - httpConnection.setMarshaller(mockMarshaller); }}; } From 76fc1581c34126c1d689e242e43d9c261fe16eda Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 20:19:43 +1100 Subject: [PATCH 1143/2152] Use Tested for UDP connection tests --- .../raven/connection/UdpConnectionTest.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java index 53707a9c177..5da98341d0a 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java @@ -1,6 +1,7 @@ package net.kencochrane.raven.connection; import mockit.Injectable; +import mockit.Tested; import mockit.Verifications; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.marshaller.Marshaller; @@ -8,16 +9,25 @@ import org.testng.annotations.Test; import java.io.OutputStream; +import java.net.InetAddress; public class UdpConnectionTest { + @Injectable + private final String hostname = "127.0.0.1"; + @Injectable + private final int port = 1234; + @Injectable + private final String publicKey = "44850120-9d2a-451b-8e00-998bddaa2800"; + @Injectable + private final String secretKey = "1de38091-6e8c-42df-8298-cf7f8098617a"; + @Tested private UdpConnection udpConnection; @Injectable private Marshaller mockMarshaller; @BeforeMethod public void setUp() throws Exception { - udpConnection = new UdpConnection("", "", ""); - udpConnection.setMarshaller(mockMarshaller); + udpConnection = null; } @Test From a7804d53227f11eb2f246bb873636efa1c853543 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 21:14:16 +1100 Subject: [PATCH 1144/2152] Simplify message handling for Http IOExceptions --- .../kencochrane/raven/connection/HttpConnection.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 3f21449974e..4cd69543bbd 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -124,12 +124,12 @@ protected void doSend(Event event) { outputStream.close(); connection.getInputStream().close(); } catch (IOException e) { - if (connection.getErrorStream() != null) { - throw new ConnectionException(getErrorMessageFromStream(connection.getErrorStream()), e); - } else { - throw new ConnectionException("An exception occurred while submitting the event to the sentry server." - , e); - } + String errorMessage; + if (connection.getErrorStream() != null) + errorMessage = getErrorMessageFromStream(connection.getErrorStream()); + else + errorMessage = "An exception occurred while submitting the event to the sentry server."; + throw new ConnectionException(errorMessage, e); } finally { connection.disconnect(); } From 9601976f8a646f8554e733f871f54648cf0f2d74 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 21:15:32 +1100 Subject: [PATCH 1145/2152] Manage Exceptions from UDP message creation Exception that might be thrown by the ByteArrayOutputStream aren't related to the connection itself. For this reason it should be relayed as a RuntimeException that will be caught/managed in a different way. --- .../raven/connection/UdpConnection.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java index 7102a2061c0..cf8262bfbdc 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java @@ -48,13 +48,16 @@ public UdpConnection(String hostname, int port, String publicKey, String secretK @Override protected void doSend(Event event) { - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - writeHeader(baos); - marshaller.marshall(event, baos); - baos.close(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (OutputStream outputStream = baos) { + writeHeader(outputStream); + marshaller.marshall(event, outputStream); + } catch (IOException e) { + throw new RuntimeException("Got an exception while marshalling a message", e); + } + byte[] message = baos.toByteArray(); - byte[] message = baos.toByteArray(); + try { DatagramPacket packet = new DatagramPacket(message, message.length); socket.send(packet); } catch (IOException e) { From 1a62108248f02d0b76382bba868ffeac7723c09b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 21:17:28 +1100 Subject: [PATCH 1146/2152] Do not define a port during the tests, use the default one --- .../net/kencochrane/raven/connection/UdpConnectionTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java index 5da98341d0a..f1f30f76e9a 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java @@ -15,8 +15,6 @@ public class UdpConnectionTest { @Injectable private final String hostname = "127.0.0.1"; @Injectable - private final int port = 1234; - @Injectable private final String publicKey = "44850120-9d2a-451b-8e00-998bddaa2800"; @Injectable private final String secretKey = "1de38091-6e8c-42df-8298-cf7f8098617a"; From 906b73ed5c520bfa657e72d1207102ee314e3df1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 21:18:38 +1100 Subject: [PATCH 1147/2152] Add tests to check connection through UDP --- .../raven/connection/UdpConnectionTest.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java index f1f30f76e9a..7f6d076f4ae 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java @@ -1,15 +1,15 @@ package net.kencochrane.raven.connection; -import mockit.Injectable; -import mockit.Tested; -import mockit.Verifications; +import mockit.*; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.marshaller.Marshaller; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.OutputStream; -import java.net.InetAddress; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; + public class UdpConnectionTest { @Injectable @@ -22,12 +22,27 @@ public class UdpConnectionTest { private UdpConnection udpConnection; @Injectable private Marshaller mockMarshaller; + @Mocked + private DatagramSocket mockDatagramSocket; @BeforeMethod public void setUp() throws Exception { udpConnection = null; } + @Test + public void testConnectionWorkingWithProperHost(@Injectable("customHostname") final String mockHostname, + @Injectable("1234") final int mockPort) throws Exception { + new UdpConnection(mockHostname, mockPort, publicKey, secretKey); + + new Verifications() {{ + InetSocketAddress generatedAddress; + mockDatagramSocket.connect(generatedAddress = withCapture()); + assertThat(generatedAddress.getHostName(), is(mockHostname)); + assertThat(generatedAddress.getPort(), is(mockPort)); + }}; + } + @Test public void testContentMarshalled(@Injectable final Event event) throws Exception { udpConnection.send(event); From a8ad0cfb456e5777874089ce9ea257e588d90db7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 21:20:48 +1100 Subject: [PATCH 1148/2152] Check that the default UDP port is indeed 9001 --- .../raven/connection/UdpConnectionTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java index 7f6d076f4ae..143ffa0cd2c 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java @@ -43,6 +43,18 @@ public void testConnectionWorkingWithProperHost(@Injectable("customHostname") fi }}; } + @Test + public void testConnectionDefaultPortIsWorking(@Injectable("customHostname") final String mockHostname) + throws Exception { + new UdpConnection(mockHostname, publicKey, secretKey); + + new Verifications() {{ + InetSocketAddress generatedAddress; + mockDatagramSocket.connect(generatedAddress = withCapture()); + assertThat(generatedAddress.getPort(), is(UdpConnection.DEFAULT_UDP_PORT)); + }}; + } + @Test public void testContentMarshalled(@Injectable final Event event) throws Exception { udpConnection.send(event); From 38d4df6b6ba0f05df030eb43f1ed1724995f832c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 21:21:04 +1100 Subject: [PATCH 1149/2152] Import the missing assertion tools --- .../net/kencochrane/raven/connection/UdpConnectionTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java index 143ffa0cd2c..e69532fe142 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java @@ -10,6 +10,8 @@ import java.net.DatagramSocket; import java.net.InetSocketAddress; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; public class UdpConnectionTest { @Injectable From 54b9afd6c093cb317026cf7466e18fb3f01bf09c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 21:21:26 +1100 Subject: [PATCH 1150/2152] Ensure that the message sent by the UDP connector is properly formatted --- .../raven/connection/UdpConnectionTest.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java index e69532fe142..a005e79a30b 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java @@ -7,6 +7,7 @@ import org.testng.annotations.Test; import java.io.OutputStream; +import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; @@ -57,6 +58,27 @@ public void testConnectionDefaultPortIsWorking(@Injectable("customHostname") fin }}; } + @Test + public void udpMessageSentWorksAsExpected(@Injectable final Event event) throws Exception { + final String marshalledContent = "marshalledContent"; + new NonStrictExpectations() {{ + mockMarshaller.marshall(event, (OutputStream) any); + result = new Delegate() { + public void marshall(Event event, OutputStream os) throws Exception { + os.write(marshalledContent.getBytes("UTF-8")); + } + }; + }}; + udpConnection.send(event); + + new Verifications() {{ + DatagramPacket capturedPacket; + mockDatagramSocket.send(capturedPacket = withCapture()); + assertThat(new String(capturedPacket.getData(), "UTF-8"), + is(udpConnection.getAuthHeader() + "\n\n" + marshalledContent)); + }}; + } + @Test public void testContentMarshalled(@Injectable final Event event) throws Exception { udpConnection.send(event); From 509f6b6861d883184672c09c8c8b5c27dead4c32 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 21:22:00 +1100 Subject: [PATCH 1151/2152] Ensure that marshalling issues do not affect the flow of the entire application --- .../kencochrane/raven/connection/UdpConnectionTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java index a005e79a30b..a0862188151 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java @@ -79,6 +79,15 @@ public void marshall(Event event, OutputStream os) throws Exception { }}; } + @Test(expectedExceptions = RuntimeException.class) + public void marshallingIssuePreventSend(@Injectable final Event event){ + new NonStrictExpectations() {{ + mockMarshaller.marshall(event, (OutputStream) any); + result = new RuntimeException("Marshalling doesn't work"); + }}; + udpConnection.send(event); + } + @Test public void testContentMarshalled(@Injectable final Event event) throws Exception { udpConnection.send(event); From 9612d43cc5b715bc21acc82e8e55c8ec12312fc5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Mar 2014 22:09:47 +1100 Subject: [PATCH 1152/2152] Remove shutdown hooks when the connection is closed manually --- .../raven/connection/AsyncConnection.java | 42 ++++++++++++------- .../raven/connection/AsyncConnectionTest.java | 16 +++++++ 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 1bde278986a..102fadb418c 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -32,6 +32,10 @@ public class AsyncConnection implements Connection { * Executor service in charge of running the connection in separate threads. */ private final ExecutorService executorService; + /** + * Shutdown hook used to stop the async connection properly when the JVM quits. + */ + private final ShutDownHook shutDownHook = new ShutDownHook(); /** * Boolean used to check whether the connection is still open or not. */ @@ -61,20 +65,7 @@ public AsyncConnection(Connection actualConnection, ExecutorService executorServ */ private void addShutdownHook() { // JUL loggers are shutdown by an other shutdown hook, it's possible that nothing will get actually logged. - Runtime.getRuntime().addShutdownHook(new Thread() { - public void run() { - try { - // The current thread is managed by raven - Raven.startManagingThread(); - logger.info("Automatic shutdown of the async connection"); - AsyncConnection.this.close(); - } catch (Exception e) { - logger.error("An exception occurred while closing the connection.", e); - } finally { - Raven.stopManagingThread(); - } - } - }); + Runtime.getRuntime().addShutdownHook(shutDownHook); } /** @@ -100,6 +91,14 @@ public void send(Event event) { */ @Override public void close() throws IOException { + Runtime.getRuntime().removeShutdownHook(shutDownHook); + doClose(); + } + + /** + * @see #close() + */ + private void doClose() throws IOException { logger.info("Gracefully shutdown sentry threads."); closed = true; executorService.shutdown(); @@ -143,4 +142,19 @@ public void run() { } } } + + private final class ShutDownHook extends Thread { + public void run() { + try { + // The current thread is managed by raven + Raven.startManagingThread(); + logger.info("Automatic shutdown of the async connection"); + AsyncConnection.this.doClose(); + } catch (Exception e) { + logger.error("An exception occurred while closing the connection.", e); + } finally { + Raven.stopManagingThread(); + } + } + } } diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index 5ee24f044e2..1da6a5052e3 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -3,6 +3,7 @@ import mockit.*; import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -23,6 +24,21 @@ public class AsyncConnectionTest { public void setUp() throws Exception { // Reset Tested asyncConnection = null; + new NonStrictExpectations() {{ + mockExecutorService.awaitTermination(anyLong, (TimeUnit) any); + result = true; + }}; + } + + @AfterMethod + public void tearDown() throws Exception { + // Reset the expectation that has been already removed. + new NonStrictExpectations() {{ + mockExecutorService.awaitTermination(anyLong, (TimeUnit) any); + result = true; + }}; + // Ensure that the shutdown hooks are removed + asyncConnection.close(); } @Test From 13d521b6a4cbe1e934f31fdbad7f6c3ee24c79f8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 12 Mar 2014 04:48:25 +1100 Subject: [PATCH 1153/2152] Add documentation to "doClose" --- .../java/net/kencochrane/raven/connection/AsyncConnection.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 102fadb418c..aaae136569c 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -96,6 +96,8 @@ public void close() throws IOException { } /** + * Close the connection whether it's from the shutdown hook or not. + * * @see #close() */ private void doClose() throws IOException { From cbb21c346cfba4d3f1c634fa064bb8e0301c210e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 12 Mar 2014 05:43:05 +1100 Subject: [PATCH 1154/2152] Replace deprecated call to forEachInvocation --- .../raven/marshaller/json/ExceptionInterfaceBindingTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index 5bfda324212..609f2c21c1a 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -31,7 +31,7 @@ public void setUp() throws Exception { new NonStrictExpectations() {{ mockStackTraceInterfaceBinding.writeInterface(withInstanceOf(JsonGenerator.class), (StackTraceInterface) any); - forEachInvocation = new Delegate() { + result = new Delegate() { public void writeInterface(JsonGenerator jsonGenerator, StackTraceInterface stackTraceInterface) throws IOException { jsonGenerator.writeStartObject(); From 38e0e4f543896be839d057a99baebff4d7278788 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 12 Mar 2014 05:43:43 +1100 Subject: [PATCH 1155/2152] Update dependencies --- pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index c87bfe02cd9..e91206b0ed3 100644 --- a/pom.xml +++ b/pom.xml @@ -102,16 +102,16 @@ 7 - 1.7.5 - 16.0 - 2.3.0 + 1.7.6 + 16.0.1 + 2.3.2 3.0.1 - 1.0.13 + 1.1.1 1.2.17 2.0-beta9 1.9.5 - 1.6 - 6.8.7 + 1.7 + 6.8.8 1.3 From f55ce8f0015c673e95785d5f294caca471c7b2b6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 12 Mar 2014 05:43:56 +1100 Subject: [PATCH 1156/2152] Update to the RC1 of log4j2 --- pom.xml | 2 +- .../raven/log4j2/SentryAppender.java | 25 ++++++++----------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/pom.xml b/pom.xml index e91206b0ed3..0dc9184ca76 100644 --- a/pom.xml +++ b/pom.xml @@ -108,7 +108,7 @@ 3.0.1 1.1.1 1.2.17 - 2.0-beta9 + 2.0-rc1 1.9.5 1.7 6.8.8 diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index cc069b7fe05..e6c86600521 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -130,21 +130,16 @@ public static SentryAppender createAppender(@PluginAttribute("name") final Strin * @return log level used within raven. */ protected static Event.Level formatLevel(Level level) { - switch (level) { - case FATAL: - return Event.Level.FATAL; - case ERROR: - return Event.Level.ERROR; - case WARN: - return Event.Level.WARNING; - case INFO: - return Event.Level.INFO; - case DEBUG: - case TRACE: - return Event.Level.DEBUG; - default: - return null; - } + if (level.isAtLeastAsSpecificAs(Level.FATAL)) + return Event.Level.FATAL; + else if (level.isAtLeastAsSpecificAs(Level.ERROR)) + return Event.Level.ERROR; + else if (level.isAtLeastAsSpecificAs(Level.WARN)) + return Event.Level.WARNING; + else if (level.isAtLeastAsSpecificAs(Level.INFO)) + return Event.Level.INFO; + else + return Event.Level.DEBUG; } /** From 1e823e83451e080d1ebf3a084c3bb5e8f012832e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 12 Mar 2014 05:46:37 +1100 Subject: [PATCH 1157/2152] Add maven 3.0 as a prerequisite --- pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pom.xml b/pom.xml index 0dc9184ca76..f6ff24d6c43 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,10 @@ + + 3.0 + + raven raven-getsentry From 48087c5aa7ca0e376cc98e7f8ba0032db06b351f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 12 Mar 2014 08:28:46 +1100 Subject: [PATCH 1158/2152] Update AppEngineCode to use latest master changes --- .../appengine/connection/AppEngineAsyncConnection.java | 4 ++-- .../connection/AppEngineAsyncConnectionTest.java | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java index 0c4c1fb7d17..916fa9acba3 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java @@ -140,7 +140,7 @@ public void run() { setDoNotRetry(true); try { // The current thread is managed by raven - Raven.RAVEN_THREAD.set(true); + Raven.startManagingThread(); AppEngineAsyncConnection connection = APP_ENGINE_ASYNC_CONNECTIONS.get(connectionId); if (connection == null) { logger.warn("Couldn't find the AppEngineAsyncConnection identified by '{}'. " @@ -151,7 +151,7 @@ public void run() { } catch (Exception e) { logger.error("An exception occurred while sending the event to Sentry.", e); } finally { - Raven.RAVEN_THREAD.remove(); + Raven.stopManagingThread(); } } } diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java index 37aa4abbd89..985992d4dee 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java @@ -14,6 +14,8 @@ import java.util.*; import java.util.concurrent.TimeUnit; +import static mockit.Deencapsulation.getField; +import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -23,7 +25,7 @@ public class AppEngineAsyncConnectionTest { private Connection mockConnection; @Injectable private Queue mockQueue; - @Mocked(methods = "getDefaultQueue") + @Mocked("getDefaultQueue") private QueueFactory queueFactory; @Injectable("7b55a129-6975-4434-8edc-29ceefd38c95") private String mockConnectionId; @@ -35,7 +37,7 @@ private static DeferredTask extractDeferredTask(TaskOptions taskOptions) throws private static AppEngineAsyncConnection getTaskConnection(DeferredTask deferredTask) throws Exception { Map appEngineAsyncConnectionRegister - = Deencapsulation.getField(AppEngineAsyncConnection.class, "APP_ENGINE_ASYNC_CONNECTIONS"); + = getField(AppEngineAsyncConnection.class, "APP_ENGINE_ASYNC_CONNECTIONS"); return appEngineAsyncConnectionRegister.get(Deencapsulation.getField(deferredTask, "connectionId")); } @@ -54,7 +56,7 @@ public void testRegisterNewInstance( AppEngineAsyncConnection asyncConnection2 = new AppEngineAsyncConnection(mockConnectionId, mockConnection); Map appEngineAsyncConnectionRegister - = Deencapsulation.getField(AppEngineAsyncConnection.class, "APP_ENGINE_ASYNC_CONNECTIONS"); + = getField(AppEngineAsyncConnection.class, "APP_ENGINE_ASYNC_CONNECTIONS"); assertThat(appEngineAsyncConnectionRegister, hasEntry(mockConnectionId, asyncConnection2)); } @@ -64,7 +66,7 @@ public void testUnregisterInstance( new AppEngineAsyncConnection(mockConnectionId, mockConnection).close(); Map appEngineAsyncConnectionRegister - = Deencapsulation.getField(AppEngineAsyncConnection.class, "APP_ENGINE_ASYNC_CONNECTIONS"); + = getField(AppEngineAsyncConnection.class, "APP_ENGINE_ASYNC_CONNECTIONS"); assertThat(appEngineAsyncConnectionRegister, not(hasKey(mockConnectionId))); } From fb559859f7642dfb507c342247a37b0e070145ba Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 12 Mar 2014 08:29:33 +1100 Subject: [PATCH 1159/2152] Remove the use of Logger from the SentryAppender --- .../java/net/kencochrane/raven/logback/SentryAppender.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index d2a7fbc2276..7081726347d 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -16,8 +16,6 @@ import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.SentryException; import net.kencochrane.raven.event.interfaces.StackTraceInterface; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.*; @@ -35,7 +33,6 @@ public class SentryAppender extends AppenderBase { * Name of the {@link Event#extra} property containing the Thread name. */ public static final String THREAD_NAME = "Raven-Threadname"; - private static final Logger logger = LoggerFactory.getLogger(SentryAppender.class); /** * Current instance of {@link Raven}. * @@ -217,7 +214,7 @@ private Deque extractExceptionQueue(ILoggingEvent iLoggingEvent //Stack the exceptions to send them in the reverse order while (throwableProxy != null) { if (!circularityDetector.add(throwableProxy)) { - logger.warn("Exiting a circular exception!"); + addWarn("Exiting a circular exception!"); break; } From 5798859be3596cce5f9f5d034ade460038b5639e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 13 Mar 2014 21:29:07 +1100 Subject: [PATCH 1160/2152] Catch NoClassDefFoundError thrown when attempting to access JNDI --- raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java index 27d6b341d24..b59be58ca6f 100644 --- a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java @@ -81,7 +81,7 @@ public static String dsnLookup() { // Check that JNDI is available (not available on Android) by loading InitialContext Class.forName("javax.naming.InitialContext", false, Dsn.class.getClassLoader()); dsn = JndiLookup.jndiLookup(); - } catch (ClassNotFoundException e) { + } catch (ClassNotFoundException | NoClassDefFoundError e) { logger.debug("JNDI not available"); } From 393bc3c0642b2444486c5bfa12e1384a15770134 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 13 Mar 2014 21:30:01 +1100 Subject: [PATCH 1161/2152] Tags only work on pull queues, we have a push here. It is not possible to cleanup the tasks in the queue. We can live with those tasks dying --- .../connection/AppEngineAsyncConnection.java | 27 +++---------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java index 916fa9acba3..d0a20b21c32 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java @@ -3,7 +3,6 @@ import com.google.appengine.api.taskqueue.DeferredTask; import com.google.appengine.api.taskqueue.Queue; import com.google.appengine.api.taskqueue.QueueFactory; -import com.google.appengine.api.taskqueue.TaskHandle; import net.kencochrane.raven.Raven; import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.event.Event; @@ -12,9 +11,7 @@ import java.io.IOException; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.concurrent.TimeUnit; import static com.google.appengine.api.taskqueue.DeferredTaskContext.setDoNotRetry; import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withPayload; @@ -38,15 +35,6 @@ public class AppEngineAsyncConnection implements Connection { private static final Logger logger = LoggerFactory.getLogger(AppEngineAsyncConnection.class); private static final Map APP_ENGINE_ASYNC_CONNECTIONS = new HashMap<>(); - private static final String TASK_TAG = "RavenTask"; - /** - * Maximum number of tasks that can be leased at once when closing the connection. - */ - private static final long MAXIMUM_TASKS_LEASED = 1000L; - /** - * Maximum days of lease when removing tasks (7 days). - */ - private static final long MAXIMUM_TASK_LEASE_PERIOD_DAYS = 7; /** * Identifier of the async connection. */ @@ -88,7 +76,7 @@ public AppEngineAsyncConnection(String id, Connection actualConnection) { @Override public void send(Event event) { if (!closed) - queue.add(withPayload(new EventSubmitter(id, event)).tag(TASK_TAG)); + queue.add(withPayload(new EventSubmitter(id, event))); } /** @@ -101,17 +89,8 @@ public void send(Event event) { public void close() throws IOException { logger.info("Gracefully stopping sentry tasks."); closed = true; - try { - List tasks; - do { - tasks = queue.leaseTasksByTag(MAXIMUM_TASK_LEASE_PERIOD_DAYS, TimeUnit.DAYS, - MAXIMUM_TASKS_LEASED, TASK_TAG); - queue.deleteTask(tasks); - } while (!tasks.isEmpty()); - } finally { - actualConnection.close(); - APP_ENGINE_ASYNC_CONNECTIONS.remove(id); - } + actualConnection.close(); + APP_ENGINE_ASYNC_CONNECTIONS.remove(id); } /** From 517a8f00d83ccd8b9d9f6fa2d83204f9da4cdbeb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 13 Mar 2014 21:45:47 +1100 Subject: [PATCH 1162/2152] Use the app version in the connection identifier --- .../net/kencochrane/raven/appengine/AppEngineRavenFactory.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java index c98538b1afd..9fa86378f1f 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java @@ -1,5 +1,6 @@ package net.kencochrane.raven.appengine; +import com.google.appengine.api.utils.SystemProperty; import net.kencochrane.raven.DefaultRavenFactory; import net.kencochrane.raven.Raven; import net.kencochrane.raven.appengine.connection.AppEngineAsyncConnection; @@ -50,7 +51,7 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { if (dsn.getOptions().containsKey(QUEUE_NAME)) { connectionIdentifier = dsn.getOptions().get(CONNECTION_IDENTIFIER); } else { - connectionIdentifier = AppEngineRavenFactory.class.getCanonicalName() + dsn; + connectionIdentifier = AppEngineRavenFactory.class.getCanonicalName() + dsn + SystemProperty.version.get(); } AppEngineAsyncConnection asyncConnection = new AppEngineAsyncConnection(connectionIdentifier, connection); From c15aa9b32832d733e5cbb833c2814e0efdcf46b4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 13 Mar 2014 21:46:23 +1100 Subject: [PATCH 1163/2152] Add the GAE application version to the tags --- .../appengine/event/helper/AppEngineEventBuilderHelper.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java index 766cf5f7910..3a0e3ed4c65 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java @@ -1,5 +1,6 @@ package net.kencochrane.raven.appengine.event.helper; +import com.google.appengine.api.utils.SystemProperty; import com.google.apphosting.api.ApiProxy; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.helper.EventBuilderHelper; @@ -20,5 +21,7 @@ public void helpBuildingEvent(EventBuilder eventBuilder) { ApiProxy.Environment env = ApiProxy.getCurrentEnvironment(); // Set the hostname to the actual application hostname eventBuilder.setServerName((String) env.getAttributes().get(CURRENT_VERSION_HOSTNAME_PROPERTY)); + + eventBuilder.addTag("GAE Application Version", SystemProperty.applicationVersion.get()); } } From 3a96e9864442e85cff569e2d5f944af33fd05d8b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 01:12:12 +1100 Subject: [PATCH 1164/2152] Remove old tests for tags and pull queues --- .../AppEngineAsyncConnectionTest.java | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java index 985992d4dee..63b2d78a190 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java @@ -70,26 +70,6 @@ public void testUnregisterInstance( assertThat(appEngineAsyncConnectionRegister, not(hasKey(mockConnectionId))); } - @Test - public void testCloseOperation(@Injectable final List mockTaskHandleList) throws Exception { - new NonStrictExpectations() {{ - mockQueue.leaseTasksByTag(anyLong, (TimeUnit) any, anyLong, "RavenTask"); - result = mockTaskHandleList; - result = Collections.emptyList(); - mockTaskHandleList.isEmpty(); - result = false; - }}; - - asyncConnection.close(); - - new Verifications() {{ - mockConnection.close(); - mockQueue.leaseTasksByTag(anyLong, (TimeUnit) any, anyLong, "RavenTask"); - times = 2; - mockQueue.deleteTask(mockTaskHandleList); - }}; - } - @Test public void testSendEventQueued(@Injectable final Event mockEvent) throws Exception { new NonStrictExpectations() {{ @@ -103,7 +83,6 @@ public void testSendEventQueued(@Injectable final Event mockEvent) throws Except DeferredTask deferredTask; mockQueue.add(taskOptions = withCapture()); - assertThat(taskOptions.getTag(), is("RavenTask")); deferredTask = extractDeferredTask(taskOptions); assertThat(getField(deferredTask, "event"), Matchers.equalTo(mockEvent)); }}; From 3218ac108179b9e53744bc13ac9ebff36e48d667 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 09:56:21 +1100 Subject: [PATCH 1165/2152] Add test for Application Version tag in GAE --- .../AppEngineEventBuilderHelperTest.java | 53 +++++++++++++++++-- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java index a8c4db25ae9..0a8889cc719 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java @@ -1,13 +1,17 @@ package net.kencochrane.raven.appengine.event.helper; +import com.google.appengine.api.utils.SystemProperty; import com.google.apphosting.api.ApiProxy; import mockit.*; import net.kencochrane.raven.event.EventBuilder; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import java.util.Collections; +import java.util.*; +import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; public class AppEngineEventBuilderHelperTest { @@ -17,16 +21,29 @@ public class AppEngineEventBuilderHelperTest { private EventBuilder mockEventBuilder; @Mocked("getCurrentEnvironment") private ApiProxy mockApiProxy; + @Injectable + private ApiProxy.Environment mockEnvironment; + @Injectable + private SystemProperty mockApplicationVersion; + + @BeforeMethod + public void setUp() throws Exception { + setField(SystemProperty.class, "applicationVersion", mockApplicationVersion); + + new NonStrictExpectations() {{ + ApiProxy.getCurrentEnvironment(); + result = mockEnvironment; + mockEnvironment.getAttributes(); + result = Collections.emptyMap(); + }}; + } @Test public void ensureHostnameDefineByApiProxyEnvironment( - @Injectable("d7b8f251-ebe1-446f-8549-2b37982bd548") final String mockHostname, - @Injectable final ApiProxy.Environment mockEnvironment) { + @Injectable("d7b8f251-ebe1-446f-8549-2b37982bd548") final String mockHostname) { new NonStrictExpectations() {{ mockEnvironment.getAttributes(); result = Collections.singletonMap("com.google.appengine.runtime.default_version_hostname", mockHostname); - ApiProxy.getCurrentEnvironment(); - result = mockEnvironment; }}; eventBuilderHelper.helpBuildingEvent(mockEventBuilder); @@ -37,4 +54,30 @@ public void ensureHostnameDefineByApiProxyEnvironment( assertThat(hostname, is(mockHostname)); }}; } + + @Test + public void ensureApplicationVersionIsAddedAsTag( + @Injectable("dc485fa3-fc23-4e6c-b374-0d05d248e5a5") final String version) throws Exception { + new NonStrictExpectations() {{ + mockApplicationVersion.get(); + result = version; + }}; + + eventBuilderHelper.helpBuildingEvent(mockEventBuilder); + + new Verifications() {{ + List tagNames = new LinkedList<>(); + List tagValues = new LinkedList<>(); + mockEventBuilder.addTag(withCapture(tagNames), withCapture(tagValues)); + + Map tags = new HashMap<>(); + for (int i = 0; i < tagNames.size(); i++) { + String tagName = tagNames.get(i); + tags.put(tagName, tagValues.get(i)); + } + + assertThat(tags, hasEntry("GAE Application Version", version)); + }}; + } + } From 7a797cca7bb17abee47e5cd1983a3025a8a71de9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 09:57:24 +1100 Subject: [PATCH 1166/2152] Ensure that test methods are throwing Exception --- .../event/helper/AppEngineEventBuilderHelperTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java index 0a8889cc719..b5caeb65d43 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java @@ -40,7 +40,8 @@ public void setUp() throws Exception { @Test public void ensureHostnameDefineByApiProxyEnvironment( - @Injectable("d7b8f251-ebe1-446f-8549-2b37982bd548") final String mockHostname) { + @Injectable("d7b8f251-ebe1-446f-8549-2b37982bd548") final String mockHostname) + throws Exception { new NonStrictExpectations() {{ mockEnvironment.getAttributes(); result = Collections.singletonMap("com.google.appengine.runtime.default_version_hostname", mockHostname); From e4899995849fd65c636e61964d01754123f83ffe Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 09:57:45 +1100 Subject: [PATCH 1167/2152] Add the ApplicationId to the tags in GAE --- .../helper/AppEngineEventBuilderHelper.java | 1 + .../AppEngineEventBuilderHelperTest.java | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java index 3a0e3ed4c65..9b51cc2269f 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java @@ -23,5 +23,6 @@ public void helpBuildingEvent(EventBuilder eventBuilder) { eventBuilder.setServerName((String) env.getAttributes().get(CURRENT_VERSION_HOSTNAME_PROPERTY)); eventBuilder.addTag("GAE Application Version", SystemProperty.applicationVersion.get()); + eventBuilder.addTag("GAE Application Id", SystemProperty.applicationId.get()); } } diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java index b5caeb65d43..778ad2d1f99 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java @@ -24,10 +24,13 @@ public class AppEngineEventBuilderHelperTest { @Injectable private ApiProxy.Environment mockEnvironment; @Injectable + private SystemProperty mockApplicationId; + @Injectable private SystemProperty mockApplicationVersion; @BeforeMethod public void setUp() throws Exception { + setField(SystemProperty.class, "applicationId", mockApplicationId); setField(SystemProperty.class, "applicationVersion", mockApplicationVersion); new NonStrictExpectations() {{ @@ -81,4 +84,28 @@ public void ensureApplicationVersionIsAddedAsTag( }}; } + @Test + public void ensureApplicationIdIsAddedAsTag( + @Injectable("50a180eb-1484-4a07-9e44-b60d394cad18") final String applicationId) throws Exception { + new NonStrictExpectations() {{ + mockApplicationId.get(); + result = applicationId; + }}; + + eventBuilderHelper.helpBuildingEvent(mockEventBuilder); + + new Verifications() {{ + List tagNames = new LinkedList<>(); + List tagValues = new LinkedList<>(); + mockEventBuilder.addTag(withCapture(tagNames), withCapture(tagValues)); + + Map tags = new HashMap<>(); + for (int i = 0; i < tagNames.size(); i++) { + String tagName = tagNames.get(i); + tags.put(tagName, tagValues.get(i)); + } + + assertThat(tags, hasEntry("GAE Application Id", applicationId)); + }}; + } } From e03e446b5563ccf1fc0059f106b2b9f878662ac7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 09:58:04 +1100 Subject: [PATCH 1168/2152] Ensure that JNDI failures do not cause DSN lookup to fail --- .../net/kencochrane/raven/dsn/DsnTest.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java index ddc67a93a42..aa927b93339 100644 --- a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java @@ -2,10 +2,12 @@ import mockit.Expectations; import mockit.Mocked; +import mockit.NonStrictExpectations; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import javax.naming.Context; +import javax.naming.InitialContext; import java.lang.reflect.Field; import java.util.Collections; import java.util.Map; @@ -45,6 +47,26 @@ public void testDsnLookupWithNothingSet() throws Exception { assertThat(Dsn.dsnLookup(), is(nullValue())); } + @Test + public void testJndiLookupFailsWithException(@Mocked("jndiLookup") JndiLookup mockJndiLookup) throws Exception { + new NonStrictExpectations(){{ + JndiLookup.jndiLookup(); + result = new ClassNotFoundException("Couldn't find the JNDI classes"); + }}; + + assertThat(Dsn.dsnLookup(), is(nullValue())); + } + + @Test + public void testJndiLookupFailsWithError(@Mocked("jndiLookup") JndiLookup mockJndiLookup) throws Exception { + new NonStrictExpectations(){{ + JndiLookup.jndiLookup(); + result = new NoClassDefFoundError("Couldn't find the JNDI classes"); + }}; + + assertThat(Dsn.dsnLookup(), is(nullValue())); + } + @Test public void testDsnLookupWithJndi() throws Exception { final String dsn = UUID.randomUUID().toString(); From 8dbc067cc3f17558c3f7e4cd6bc1c5fb34a98af5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 10:49:04 +1100 Subject: [PATCH 1169/2152] Remove randomness in tests --- .../AppEngineAsyncConnectionTest.java | 6 ++-- .../SentryAppenderEventBuildingTest.java | 23 ++++++------- .../SentryAppenderEventBuildingTest.java | 26 +++++++-------- .../SentryAppenderEventBuildingTest.java | 30 ++++++++--------- .../java/net/kencochrane/raven/RavenTest.java | 4 +-- .../net/kencochrane/raven/dsn/DsnTest.java | 6 ++-- .../kencochrane/raven/event/EventTest.java | 5 +-- .../event/interfaces/HttpInterfaceTest.java | 32 +++++++++---------- .../event/interfaces/SentryExceptionTest.java | 4 +-- .../jul/SentryHandlerEventBuildingTest.java | 8 ++--- 10 files changed, 72 insertions(+), 72 deletions(-) diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java index 63b2d78a190..d9a72509e12 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java @@ -71,10 +71,8 @@ public void testUnregisterInstance( } @Test - public void testSendEventQueued(@Injectable final Event mockEvent) throws Exception { - new NonStrictExpectations() {{ - setField(mockEvent, "id", UUID.randomUUID()); - }}; + public void testSendEventQueued(@Injectable final Event mockEvent, @Injectable UUID mockUuid) throws Exception { + setField(mockEvent, "id", mockUuid); asyncConnection.send(mockEvent); diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java index 25f4110f427..b2b102185f6 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java @@ -51,9 +51,9 @@ private void assertNoErrorsInErrorHandler() throws Exception { @Test public void testSimpleMessageLogging() throws Exception { - final String loggerName = UUID.randomUUID().toString(); - final String message = UUID.randomUUID().toString(); - final String threadName = UUID.randomUUID().toString(); + final String loggerName = "cdc23028-9b1f-485d-90bf-853d2f5f52d6"; + final String message = "fae94de0-0df3-4f96-92d5-a3fb66e714f3"; + final String threadName = "78ecbf4d-aa61-4dd7-8ef4-b1c49232e8f4"; final Date date = new Date(1373883196416L); new Expectations() {{ mockLogger.getName(); @@ -100,7 +100,7 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th @Test public void testExceptionLogging() throws Exception { - final Exception exception = new Exception(UUID.randomUUID().toString()); + final Exception exception = new Exception("027a0db3-fb98-4377-bafb-fe5a49f067e8"); sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, exception)); @@ -118,8 +118,8 @@ public void testExceptionLogging() throws Exception { @Test public void testMdcAddedToExtra() throws Exception { - final String extraKey = UUID.randomUUID().toString(); - final String extraValue = UUID.randomUUID().toString(); + final String extraKey = "1aeb7253-6e0d-4902-86d6-7e4b36571cfd"; + final String extraValue = "b2e19866-08a2-4611-b72c-150fa6aa3394"; sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, null, null, null, Collections.singletonMap(extraKey, extraValue))); @@ -134,7 +134,8 @@ public void testMdcAddedToExtra() throws Exception { @Test public void testNdcAddedToExtra() throws Exception { - final String ndcEntries = Joiner.on(' ').join(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()); + final String ndcEntries = Joiner.on(' ').join("930580ba-f92f-4893-855b-ac24efa1a6c2", + "fa32ad74-a015-492a-991f-c6a0e04accaf", "be9dd914-3690-4781-97b2-fe14aedb4cbd"); sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, null, ndcEntries, null, null)); @@ -149,9 +150,9 @@ public void testNdcAddedToExtra() throws Exception { @Test public void testSourceUsedAsStacktrace(@Injectable final LocationInfo locationInfo) throws Exception { - final String className = UUID.randomUUID().toString(); - final String methodName = UUID.randomUUID().toString(); - final String fileName = UUID.randomUUID().toString(); + final String className = "8004ac1e-8bd3-4762-abe0-2d0d79ae4e40"; + final String methodName = "ce7cd195-9e6d-4315-b883-12951be3da6e"; + final String fileName = "1ab50f43-f11c-4439-a05c-d089281411fa"; final int line = 42; new NonStrictExpectations() {{ locationInfo.getClassName(); @@ -211,7 +212,7 @@ public void testCulpritWithSource(@Injectable final LocationInfo locationInfo) t @Test public void testCulpritWithoutSource() throws Exception { - final String loggerName = UUID.randomUUID().toString(); + final String loggerName = "2b27da1d-e03a-4292-9a81-78be5491a7e1"; new Expectations() {{ mockLogger.getName(); result = loggerName; diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java index e39672f34f5..53f1543fad9 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java @@ -45,9 +45,9 @@ private void assertNoErrorsInErrorHandler() throws Exception { @Test public void testSimpleMessageLogging() throws Exception { - final String loggerName = UUID.randomUUID().toString(); - final String message = UUID.randomUUID().toString(); - final String threadName = UUID.randomUUID().toString(); + final String loggerName = "0a05c9ff-45ef-45cf-9595-9307b0729a0d"; + final String message = "6ff10df4-2e27-43f5-b4e9-a957f8678176"; + final String threadName = "f891f3c4-c619-4441-9c47-f5c8564d3c0a"; final Date date = new Date(1373883196416L); sentryAppender.append(new Log4jLogEvent(loggerName, null, null, Level.INFO, new SimpleMessage(message), @@ -90,7 +90,7 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th @Test public void testExceptionLogging() throws Exception { - final Exception exception = new Exception(UUID.randomUUID().toString()); + final Exception exception = new Exception("d0d1b31f-e885-42e3-aac6-48c500f10ed1"); sentryAppender.append(new Log4jLogEvent(null, null, null, Level.ERROR, new SimpleMessage(""), exception)); @@ -130,7 +130,7 @@ public void testLogParametrisedMessage() throws Exception { @Test public void testMarkerAddedToTag() throws Exception { - final String markerName = UUID.randomUUID().toString(); + final String markerName = "c97e1fc0-9fff-41b3-8d0d-c24b54c670bb"; sentryAppender.append(new Log4jLogEvent(null, MarkerManager.getMarker(markerName), null, Level.INFO, new SimpleMessage(""), null)); @@ -145,8 +145,8 @@ public void testMarkerAddedToTag() throws Exception { @Test public void testMdcAddedToExtra() throws Exception { - final String extraKey = UUID.randomUUID().toString(); - final String extraValue = UUID.randomUUID().toString(); + final String extraKey = "a4ce2632-8d9c-471d-8b06-1744be2ae8e9"; + final String extraValue = "6dbeb494-197e-4f57-939a-613e2c16607d"; sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, Collections.singletonMap(extraKey, extraValue), null, null, null, 0)); @@ -163,9 +163,9 @@ public void testMdcAddedToExtra() throws Exception { @SuppressWarnings("unchecked") public void testNdcAddedToExtra() throws Exception { final ThreadContext.ContextStack contextStack = new DefaultThreadContextStack(true); - contextStack.push(UUID.randomUUID().toString()); - contextStack.push(UUID.randomUUID().toString()); - contextStack.push(UUID.randomUUID().toString()); + contextStack.push("444af01f-fb80-414f-b035-15bdb91cb8b2"); + contextStack.push("a1cb5e08-480a-4b32-b675-212f00c44e05"); + contextStack.push("0aa5db14-1579-46ef-aae2-350d974e7fb8"); sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, contextStack, null, null, 0)); @@ -180,8 +180,8 @@ public void testNdcAddedToExtra() throws Exception { @Test public void testSourceUsedAsStacktrace() throws Exception { - final StackTraceElement location = new StackTraceElement(UUID.randomUUID().toString(), - UUID.randomUUID().toString(), UUID.randomUUID().toString(), 42); + final StackTraceElement location = new StackTraceElement("7039c1f7-21e3-4134-8ced-524281633224", + "c68f3af9-1618-4d80-ad1b-ea0701568153", "f87a8821-1c70-44b8-81c3-271d454e4b08", 42); sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, null, null, location, 0)); @@ -214,7 +214,7 @@ public void testCulpritWithSource() throws Exception { @Test public void testCulpritWithoutSource() throws Exception { - final String loggerName = UUID.randomUUID().toString(); + final String loggerName = "150bbbfa-f729-460e-921b-a0fe1f7ab392 "; sentryAppender.append(new Log4jLogEvent(loggerName, null, null, Level.INFO, new SimpleMessage(""), null)); diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java index e716ba3a41f..faf5005b413 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java @@ -60,9 +60,9 @@ private void assertNoErrorsInStatusManager() throws Exception { @Test public void testSimpleMessageLogging() throws Exception { - final String message = UUID.randomUUID().toString(); - final String loggerName = UUID.randomUUID().toString(); - final String threadName = UUID.randomUUID().toString(); + final String message = "14a667c5-0de3-4b43-b62b-f9ccced7adf1"; + final String loggerName = "2cc053ad-8c13-44a6-849f-11a9bf8ba646"; + final String threadName = "a70e658d-f5fa-4707-b7ac-0d503429f1dd"; final Date date = new Date(1373883196416L); ILoggingEvent loggingEvent = new MockUpLoggingEvent(loggerName, null, Level.INFO, message, null, null, null, @@ -147,7 +147,7 @@ public void testLogParametrisedMessage() throws Exception { @Test public void testMarkerAddedToTag() throws Exception { - final String markerName = UUID.randomUUID().toString(); + final String markerName = "d33e3927-ea6c-4a5a-b66c-8dcb2052e812"; sentryAppender.append( new MockUpLoggingEvent(null, MarkerFactory.getMarker(markerName), Level.INFO, null, null, null) @@ -164,8 +164,8 @@ public void testMarkerAddedToTag() throws Exception { @Test public void testMdcAddedToExtra() throws Exception { - final String extraKey = UUID.randomUUID().toString(); - final String extraValue = UUID.randomUUID().toString(); + final String extraKey = "10e09b11-546f-4c57-99b2-cf3c627c8737"; + final String extraValue = "5f7a53b1-4354-4120-a368-78a615705540"; sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null, Collections.singletonMap(extraKey, extraValue), null, null, 0).getMockInstance()); @@ -181,8 +181,8 @@ public void testMdcAddedToExtra() throws Exception { @Test public void testContextPropertiesAddedToExtra() throws Exception { - final String extraKey = UUID.randomUUID().toString(); - final String extraValue = UUID.randomUUID().toString(); + final String extraKey = "0489bc59-b4ba-4890-9a60-58e65624fe8c"; + final String extraValue = "986adaa7-c0e4-4c09-9c5e-49edaf2e6d53"; sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null, null, null, null, 0, Collections.singletonMap(extraKey, extraValue)).getMockInstance()); @@ -198,10 +198,10 @@ public void testContextPropertiesAddedToExtra() throws Exception { @Test public void testMdcTakesPrecedenceOverContextProperties() throws Exception { - final String mdcKey = UUID.randomUUID().toString(); - final String mdcValue = UUID.randomUUID().toString(); + final String mdcKey = "0aab006e-0128-42d7-84d5-aa88329beb19"; + final String mdcValue = "8ba43697-7568-40e2-914b-4d2c3f12e70e"; final String contextKey = mdcKey; - final String contextValue = UUID.randomUUID().toString(); + final String contextValue = "66d123eb-7786-4f3d-86f1-a906039401d9"; sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null, Collections.singletonMap(mdcKey, mdcValue), null, null, 0, @@ -218,9 +218,9 @@ public void testMdcTakesPrecedenceOverContextProperties() throws Exception { @Test public void testSourceUsedAsStacktrace() throws Exception { - final StackTraceElement[] location = {new StackTraceElement(UUID.randomUUID().toString(), - UUID.randomUUID().toString(), - UUID.randomUUID().toString(), 42)}; + final StackTraceElement[] location = {new StackTraceElement("854de9b9-95ea-4dae-8e01-23b25c9bd271", + "49974348-1704-47cc-be5a-4e72f2e0db33", + "bf48ef03-657c-4924-844a-317743c4599b", 42)}; sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null, null, null, location, 0) .getMockInstance()); @@ -255,7 +255,7 @@ public void testCulpritWithSource() throws Exception { @Test public void testCulpritWithoutSource() throws Exception { - final String loggerName = UUID.randomUUID().toString(); + final String loggerName = "ee1c33e8-2f2e-4613-9c2c-2564624a9d4f"; sentryAppender.append(new MockUpLoggingEvent(loggerName, null, Level.INFO, null, null, null).getMockInstance()); diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index a2b877feb96..448a23a9d9b 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -39,7 +39,7 @@ public void testSendEvent() throws Exception { @Test public void testSendMessage() throws Exception { - final String message = UUID.randomUUID().toString(); + final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; raven.sendMessage(message); @@ -53,7 +53,7 @@ public void testSendMessage() throws Exception { @Test public void testSendException() throws Exception { - final String message = UUID.randomUUID().toString(); + final String message = "7b61ddb1-eb32-428d-bad9-a7d842605ba7"; final Exception exception = new Exception(message); raven.sendException(exception); diff --git a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java index aa927b93339..61b49985dea 100644 --- a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java @@ -69,7 +69,7 @@ public void testJndiLookupFailsWithError(@Mocked("jndiLookup") JndiLookup mockJn @Test public void testDsnLookupWithJndi() throws Exception { - final String dsn = UUID.randomUUID().toString(); + final String dsn = "6621980c-e27b-4dc9-9130-7fc5e9ea9750"; new Expectations() {{ mockContext.lookup("java:comp/env/sentry/dsn"); result = dsn; @@ -80,7 +80,7 @@ public void testDsnLookupWithJndi() throws Exception { @Test public void testDsnLookupWithSystemProperty() throws Exception { - String dsn = UUID.randomUUID().toString(); + String dsn = "aa9171a4-7e9b-4e3c-b3cc-fe537dc03527"; System.setProperty("SENTRY_DSN", dsn); assertThat(Dsn.dsnLookup(), is(dsn)); @@ -90,7 +90,7 @@ public void testDsnLookupWithSystemProperty() throws Exception { @Test public void testDsnLookupWithEnvironmentVariable() throws Exception { - String dsn = UUID.randomUUID().toString(); + String dsn = "759ed060-dd4f-4478-8a1a-3f23e044787c"; setEnv("SENTRY_DSN", dsn); assertThat(Dsn.dsnLookup(), is(dsn)); diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventTest.java index 7f694af4f60..12b0168e7be 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventTest.java @@ -19,13 +19,14 @@ public void ensureEventIdCantBeNull() throws Exception { @Test public void returnsCloneOfTimestamp(@Injectable final Date mockTimestamp, - @Injectable final Date mockCloneTimestamp) + @Injectable final Date mockCloneTimestamp, + @Injectable final UUID mockUuid) throws Exception { new NonStrictExpectations() {{ mockTimestamp.clone(); result = mockCloneTimestamp; }}; - final Event event = new Event(UUID.randomUUID()); + final Event event = new Event(mockUuid); event.setTimestamp(mockTimestamp); diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java index a5de91c8631..fb5c16d1f32 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java @@ -64,26 +64,26 @@ public void setUp() throws Exception { @Test public void testHttpServletCopied() throws Exception { - final String requestUrl = UUID.randomUUID().toString(); - final String method = UUID.randomUUID().toString(); - final String parameterName = UUID.randomUUID().toString(); - final String parameterValue = UUID.randomUUID().toString(); - final String queryString = UUID.randomUUID().toString(); - final String cookieName = UUID.randomUUID().toString(); - final String cookieValue = UUID.randomUUID().toString(); - final String remoteAddr = UUID.randomUUID().toString(); - final String serverName = UUID.randomUUID().toString(); + final String requestUrl = "713d97ff-bda1-4bbe-85bd-42a7bc203551"; + final String method = "afee226d-1c77-41f3-8711-cec1f611af25"; + final String parameterName = "dbb204d7-6332-43d6-bfac-3f112d5f290d"; + final String parameterValue = "00ec3c3e-5ded-4bca-a49b-f7bc9987a914"; + final String queryString = "31497680-12ce-41a6-8285-5de5d06968d3"; + final String cookieName = "451cd683-f7cd-4691-a73f-64829412b5bb"; + final String cookieValue = "5f9a9117-d806-4fd5-b472-a58529e913cd"; + final String remoteAddr = "718d7ac1-d41a-4aa7-84c5-3a877ed2f41c"; + final String serverName = "bf376b4e-a69c-4a2c-988e-5675096a028e"; final int serverPort = 123; - final String localAddr = UUID.randomUUID().toString(); - final String localName = UUID.randomUUID().toString(); + final String localAddr = "1d4a9df3-9a2f-46f4-a913-96fe4220bd8a"; + final String localName = "0698cd7f-5d8f-4ecd-8954-e25ac21e6161"; final int localPort = 321; - final String protocol = UUID.randomUUID().toString(); + final String protocol = "f4261066-8588-43d3-a71f-9e95fd3e0d65"; final boolean secure = true; final boolean asyncStarted = true; - final String authType = UUID.randomUUID().toString(); - final String remoteUser = UUID.randomUUID().toString(); - final String headerKey = UUID.randomUUID().toString(); - final String headerValue = UUID.randomUUID().toString(); + final String authType = "b4ec1983-06d1-4f0a-b467-435d2322d69f"; + final String remoteUser = "beae8915-1162-425e-afda-687146b3e3df"; + final String headerKey = "2c4a28c6-cef6-4847-92be-bf161ec4edc6"; + final String headerValue = "2327b4fe-c35f-4bbb-842a-a89c718f5f01"; new NonStrictExpectations() {{ mockHttpServletRequest.getRequestURL(); diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java index 068d6c23ba4..c72662a8afa 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java @@ -17,8 +17,8 @@ public class SentryExceptionTest { @Test public void ensureConversionToQueueKeepsOrder(@Injectable final Throwable mockCause) throws Exception { - final String exceptionMessage = UUID.randomUUID().toString(); - final String causeMessage = UUID.randomUUID().toString(); + final String exceptionMessage = "208ea34a-9c99-42d6-a399-59a4c85900dc"; + final String causeMessage = "46a1b2ee-629b-49eb-a2be-f5250c995ea4"; new NonStrictExpectations() {{ mockThrowable.getCause(); result = new Delegate() { diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java index 60b8fdaa20a..1c2b7da5cd2 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java @@ -43,8 +43,8 @@ private void assertNoErrorsInErrorManager() throws Exception { @Test public void testSimpleMessageLogging() throws Exception { - final String loggerName = UUID.randomUUID().toString(); - final String message = UUID.randomUUID().toString(); + final String loggerName = "e9cb78a9-aec8-4fcd-8580-42b428653061"; + final String message = "1feb7133-1bf5-4982-a30d-44883aa3de9c"; final Date date = new Date(1373883196416L); final long threadId = 12; @@ -88,7 +88,7 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th @Test public void testExceptionLogging() throws Exception { - final Exception exception = new Exception(UUID.randomUUID().toString()); + final Exception exception = new Exception("c2712792-e1ef-4824-a0e1-0e3e22907661"); sentryHandler.publish(newLogRecord(null, Level.SEVERE, null, null, exception)); @@ -123,7 +123,7 @@ public void testCulpritWithSource() throws Exception { @Test public void testCulpritWithoutSource() throws Exception { - final String loggerName = UUID.randomUUID().toString(); + final String loggerName = "0c929a2e-f2bc-4ebb-ad41-a29fb1591ffe"; sentryHandler.publish(newLogRecord(loggerName, Level.SEVERE, null, null, null)); From 6f6ae0e1e63f19206402ea7b7b62437c69f498dc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 10:59:05 +1100 Subject: [PATCH 1170/2152] Format code and imports --- .../appengine/connection/AppEngineAsyncConnectionTest.java | 7 ++++--- .../raven/log4j/SentryAppenderEventBuildingTest.java | 1 - .../java/net/kencochrane/raven/log4j/SentryAppenderIT.java | 2 +- .../raven/log4j2/SentryAppenderEventBuildingTest.java | 5 ++++- .../raven/logback/SentryAppenderEventBuildingTest.java | 1 - raven/src/test/java/net/kencochrane/raven/RavenTest.java | 2 -- .../kencochrane/raven/connection/UdpConnectionTest.java | 2 +- raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java | 6 ++---- .../raven/event/interfaces/HttpInterfaceTest.java | 1 - .../raven/event/interfaces/SentryExceptionTest.java | 1 - .../raven/jul/SentryHandlerEventBuildingTest.java | 1 - 11 files changed, 12 insertions(+), 17 deletions(-) diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java index d9a72509e12..7f5dbbbfd9b 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java @@ -1,7 +1,6 @@ package net.kencochrane.raven.appengine.connection; import com.google.appengine.api.taskqueue.*; -import com.google.appengine.api.taskqueue.Queue; import mockit.*; import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.event.Event; @@ -11,8 +10,10 @@ import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; -import java.util.*; -import java.util.concurrent.TimeUnit; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; import static mockit.Deencapsulation.getField; import static mockit.Deencapsulation.setField; diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java index b2b102185f6..83ff6c64321 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java @@ -22,7 +22,6 @@ import java.util.Collections; import java.util.Date; -import java.util.UUID; import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java index 0506c1ee89d..4145dce972d 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java @@ -14,7 +14,7 @@ public class SentryAppenderIT { private SentryStub sentryStub; @BeforeMethod - public void setUp() throws Exception{ + public void setUp() throws Exception { sentryStub = new SentryStub(); } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java index 53f1543fad9..afe0a1ef3ce 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java @@ -21,7 +21,10 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java index faf5005b413..0e625faf247 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java @@ -24,7 +24,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.Date; -import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index 448a23a9d9b..cbf9a750887 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -10,8 +10,6 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import java.util.UUID; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; diff --git a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java index a0862188151..cafdad35e0e 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java @@ -80,7 +80,7 @@ public void marshall(Event event, OutputStream os) throws Exception { } @Test(expectedExceptions = RuntimeException.class) - public void marshallingIssuePreventSend(@Injectable final Event event){ + public void marshallingIssuePreventSend(@Injectable final Event event) { new NonStrictExpectations() {{ mockMarshaller.marshall(event, (OutputStream) any); result = new RuntimeException("Marshalling doesn't work"); diff --git a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java index 61b49985dea..91deed1c7c6 100644 --- a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java @@ -7,11 +7,9 @@ import org.testng.annotations.Test; import javax.naming.Context; -import javax.naming.InitialContext; import java.lang.reflect.Field; import java.util.Collections; import java.util.Map; -import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -49,7 +47,7 @@ public void testDsnLookupWithNothingSet() throws Exception { @Test public void testJndiLookupFailsWithException(@Mocked("jndiLookup") JndiLookup mockJndiLookup) throws Exception { - new NonStrictExpectations(){{ + new NonStrictExpectations() {{ JndiLookup.jndiLookup(); result = new ClassNotFoundException("Couldn't find the JNDI classes"); }}; @@ -59,7 +57,7 @@ public void testJndiLookupFailsWithException(@Mocked("jndiLookup") JndiLookup mo @Test public void testJndiLookupFailsWithError(@Mocked("jndiLookup") JndiLookup mockJndiLookup) throws Exception { - new NonStrictExpectations(){{ + new NonStrictExpectations() {{ JndiLookup.jndiLookup(); result = new NoClassDefFoundError("Couldn't find the JNDI classes"); }}; diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java index fb5c16d1f32..0c22866adbf 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java @@ -9,7 +9,6 @@ import javax.servlet.http.HttpServletRequest; import java.util.Arrays; import java.util.Collections; -import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java index c72662a8afa..18cf4bdebbf 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java @@ -6,7 +6,6 @@ import org.testng.annotations.Test; import java.util.Deque; -import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java index 1c2b7da5cd2..d5f7901c2f7 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java @@ -13,7 +13,6 @@ import org.testng.annotations.Test; import java.util.Date; -import java.util.UUID; import java.util.logging.ErrorManager; import java.util.logging.Level; import java.util.logging.LogRecord; From 6654a1a07168794c4fd325d1bc137ae11e7a8358 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 10:59:24 +1100 Subject: [PATCH 1171/2152] Ensure that test methods throw exception --- .../raven/appengine/AppEngineRavenFactoryTest.java | 12 ++++++------ .../kencochrane/raven/log4j2/SentryAppenderIT.java | 2 +- .../raven/connection/UdpConnectionTest.java | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/AppEngineRavenFactoryTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/AppEngineRavenFactoryTest.java index a151f320cb9..bd89601da5f 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/AppEngineRavenFactoryTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/AppEngineRavenFactoryTest.java @@ -24,21 +24,21 @@ public class AppEngineRavenFactoryTest { private Dsn mockDsn; @Test - public void checkServiceLoaderProvidesFactory() { + public void checkServiceLoaderProvidesFactory() throws Exception { ServiceLoader ravenFactories = ServiceLoader.load(RavenFactory.class); assertThat(ravenFactories, Matchers.hasItem(instanceOf(AppEngineRavenFactory.class))); } @Test - public void asyncConnectionCreatedByAppEngineRavenFactoryIsForAppEngine() { + public void asyncConnectionCreatedByAppEngineRavenFactoryIsForAppEngine() throws Exception { Connection connection = appEngineRavenFactory.createAsyncConnection(mockDsn, mockConnection); assertThat(connection, is(instanceOf(AppEngineAsyncConnection.class))); } @Test - public void asyncConnectionWithoutConnectionIdGeneratesDefaultId() { + public void asyncConnectionWithoutConnectionIdGeneratesDefaultId() throws Exception { final String dnsString = "a1fe25d3-bc41-4040-8aa2-484e5aae87c5"; new NonStrictExpectations() {{ mockDsn.toString(); @@ -55,7 +55,7 @@ public void asyncConnectionWithoutConnectionIdGeneratesDefaultId() { @Test public void asyncConnectionWithConnectionIdUsesId( - @Injectable("543afd41-379d-41cb-8c99-8ce73e83a0cc") final String connectionId) { + @Injectable("543afd41-379d-41cb-8c99-8ce73e83a0cc") final String connectionId) throws Exception { new NonStrictExpectations() {{ mockDsn.getOptions(); result = Collections.singletonMap(AppEngineRavenFactory.CONNECTION_IDENTIFIER, connectionId); @@ -70,7 +70,7 @@ public void asyncConnectionWithConnectionIdUsesId( @Test public void asyncConnectionWithoutQueueNameKeepsDefaultQueue( - @Mocked final AppEngineAsyncConnection mockAppEngineAsyncConnection) { + @Mocked final AppEngineAsyncConnection mockAppEngineAsyncConnection) throws Exception { appEngineRavenFactory.createAsyncConnection(mockDsn, mockConnection); new Verifications() {{ @@ -82,7 +82,7 @@ public void asyncConnectionWithoutQueueNameKeepsDefaultQueue( @Test public void asyncConnectionWithQueueNameSetsQueue( @Mocked final AppEngineAsyncConnection mockAppEngineAsyncConnection, - @Injectable("queueName") final String mockQueueName) { + @Injectable("queueName") final String mockQueueName) throws Exception { new NonStrictExpectations() {{ mockDsn.getOptions(); result = Collections.singletonMap(AppEngineRavenFactory.QUEUE_NAME, mockQueueName); diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java index 523c5689923..b66e35f2286 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java @@ -15,7 +15,7 @@ public class SentryAppenderIT { private SentryStub sentryStub; @BeforeMethod - public void setUp() { + public void setUp() throws Exception { sentryStub = new SentryStub(); } diff --git a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java index cafdad35e0e..e3a34896916 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java @@ -80,7 +80,7 @@ public void marshall(Event event, OutputStream os) throws Exception { } @Test(expectedExceptions = RuntimeException.class) - public void marshallingIssuePreventSend(@Injectable final Event event) { + public void marshallingIssuePreventSend(@Injectable final Event event) throws Exception { new NonStrictExpectations() {{ mockMarshaller.marshall(event, (OutputStream) any); result = new RuntimeException("Marshalling doesn't work"); From 85c9d780d173c56a587db28b468e3bffb9a472bb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 11:19:23 +1100 Subject: [PATCH 1172/2152] Send errorHandler messages through a logger (to change the log level if necessary) --- .../raven/logback/MockUpStatusPrinter.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/MockUpStatusPrinter.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/MockUpStatusPrinter.java index 5d8ceed0414..2538f013bb4 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/MockUpStatusPrinter.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/MockUpStatusPrinter.java @@ -1,31 +1,30 @@ package net.kencochrane.raven.logback; -import ch.qos.logback.core.CoreConstants; import ch.qos.logback.core.status.Status; import ch.qos.logback.core.util.StatusPrinter; import mockit.Mock; import mockit.MockUp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class MockUpStatusPrinter extends MockUp { + private static final Logger logger = LoggerFactory.getLogger("ErrorHandler"); + @Mock public static void buildStr(StringBuilder sb, String indentation, Status s) { - sb.append("[RAVEN] ErrorHandler ") - .append("[").append(getLevel(s)).append("] ") - .append(s.getOrigin()).append(" - ") - .append(s.getMessage()) - .append(CoreConstants.LINE_SEPARATOR); - } - - private static String getLevel(Status s) { switch (s.getEffectiveLevel()) { case Status.INFO: - return "INFO"; + logger.info("{} - {}", s.getOrigin(), s.getMessage()); + return; case Status.WARN: - return "WARN"; + logger.warn("{} - {}", s.getOrigin(), s.getMessage()); + return; case Status.ERROR: - return "ERROR"; + logger.error("{} - {}", s.getOrigin(), s.getMessage()); + return; default: - return "UNKOWN"; + logger.debug("{} - {}", s.getOrigin(), s.getMessage()); + return; } } } From 4b18f16c44c80d85f37a1130fb3f7a0f89c7f6f3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 11:21:27 +1100 Subject: [PATCH 1173/2152] Disable logs during tests for logback --- raven-logback/src/test/resources/logback-test.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-logback/src/test/resources/logback-test.xml b/raven-logback/src/test/resources/logback-test.xml index 2019ec66924..bfd7282a648 100644 --- a/raven-logback/src/test/resources/logback-test.xml +++ b/raven-logback/src/test/resources/logback-test.xml @@ -5,7 +5,7 @@ - + From f4ec1f70a5ae6aae58a99b6f04b68640d2d11997 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 11:22:25 +1100 Subject: [PATCH 1174/2152] Disable logs during Raven tests --- raven/src/test/resources/logging-test.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/test/resources/logging-test.properties b/raven/src/test/resources/logging-test.properties index cbaf7ab647d..c1ed0e86734 100644 --- a/raven/src/test/resources/logging-test.properties +++ b/raven/src/test/resources/logging-test.properties @@ -1,5 +1,5 @@ handlers=java.util.logging.ConsoleHandler -level=ERROR +.level=OFF java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter java.util.logging.SimpleFormatter.format=[RAVEN] [%4$.4s] %3$s - %5$s%n From 4f93ccd004b23e7c99f483fdaf2e685638171cbe Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 11:24:13 +1100 Subject: [PATCH 1175/2152] Add log configuration for appengine, disable it during the tests --- raven-appengine/pom.xml | 5 +++++ raven-appengine/src/test/resources/logback-test.xml | 11 +++++++++++ 2 files changed, 16 insertions(+) create mode 100644 raven-appengine/src/test/resources/logback-test.xml diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index f09094617d7..1c604d90599 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -28,6 +28,11 @@ ${appengine-api.version} + + ch.qos.logback + logback-classic + test + com.googlecode.jmockit jmockit diff --git a/raven-appengine/src/test/resources/logback-test.xml b/raven-appengine/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..bfd7282a648 --- /dev/null +++ b/raven-appengine/src/test/resources/logback-test.xml @@ -0,0 +1,11 @@ + + + + [RAVEN] [%-5level] %logger{36} - %msg%n%nopex + + + + + + + From 58e41369295b5a04811dfaf8e7639cf653bf08d1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 11:29:05 +1100 Subject: [PATCH 1176/2152] Disable logs for getSentry tests --- raven-getsentry/src/test/resources/logback-test.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-getsentry/src/test/resources/logback-test.xml b/raven-getsentry/src/test/resources/logback-test.xml index 1c1939ce813..2d5d7c420f3 100644 --- a/raven-getsentry/src/test/resources/logback-test.xml +++ b/raven-getsentry/src/test/resources/logback-test.xml @@ -5,7 +5,7 @@ - + From 9f631ab244d7f8d18e8835d782c8a6f9add99d57 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 11:33:04 +1100 Subject: [PATCH 1177/2152] Log ErrorHandler messages with a logger Also disable the logs during the unit tests --- .../net/kencochrane/raven/log4j/MockUpErrorHandler.java | 6 ++++-- raven-log4j/src/test/resources/log4j-test.properties | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java index 98daf57ebd3..d9aa25fc526 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java @@ -4,15 +4,17 @@ import mockit.MockUp; import org.apache.log4j.spi.ErrorHandler; import org.apache.log4j.spi.LoggingEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class MockUpErrorHandler extends MockUp { + private static final Logger logger = LoggerFactory.getLogger("ErrorHandler"); private int errorCount = 0; @Mock public void error(String message) { errorCount++; - System.err.println("[RAVEN] ErrorHandler - " + message); - System.err.flush(); + logger.error(message); } @Mock diff --git a/raven-log4j/src/test/resources/log4j-test.properties b/raven-log4j/src/test/resources/log4j-test.properties index dbc9f995d93..2bd86f9037c 100644 --- a/raven-log4j/src/test/resources/log4j-test.properties +++ b/raven-log4j/src/test/resources/log4j-test.properties @@ -1,4 +1,4 @@ -log4j.rootLogger=ALL, ConsoleAppender +log4j.rootLogger=OFF, ConsoleAppender log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender log4j.appender.ConsoleAppender.layout=org.apache.log4j.EnhancedPatternLayout log4j.appender.ConsoleAppender.layout.ConversionPattern=[RAVEN] [%-5p] %c - %m%n%throwable{none} From 6d7ac530e81dcc9203801668bc03ee33d3356604 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 11:33:04 +1100 Subject: [PATCH 1178/2152] Log ErrorHandler messages with a logger Also disable the logs during the unit tests --- .../net/kencochrane/raven/log4j2/MockUpErrorHandler.java | 6 ++++-- raven-log4j2/src/test/resources/log4j2-test.xml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java index e5498111012..0bbde831069 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java @@ -4,15 +4,17 @@ import mockit.MockUp; import org.apache.logging.log4j.core.ErrorHandler; import org.apache.logging.log4j.core.LogEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class MockUpErrorHandler extends MockUp { + private static final Logger logger = LoggerFactory.getLogger("ErrorHandler"); private int errorCount = 0; @Mock public void error(String msg) { errorCount++; - System.err.println("[RAVEN] ErrorHandler - " + msg); - System.err.flush(); + logger.error(msg); } @Mock diff --git a/raven-log4j2/src/test/resources/log4j2-test.xml b/raven-log4j2/src/test/resources/log4j2-test.xml index 02629b80210..ad0b230214b 100644 --- a/raven-log4j2/src/test/resources/log4j2-test.xml +++ b/raven-log4j2/src/test/resources/log4j2-test.xml @@ -7,7 +7,7 @@ - + From 07f018242bb75f8b7eefdb3100152839f6640f84 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 12:02:52 +1100 Subject: [PATCH 1179/2152] Add SLF4j API as a dependency during tests --- raven-log4j/pom.xml | 5 +++++ raven-log4j2/pom.xml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 15e4ed1545a..0684c0aad30 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -68,6 +68,11 @@ slf4j-log4j12 test + + org.slf4j + slf4j-api + test + diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index d6295aa1c51..403644a5a47 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -72,6 +72,11 @@ log4j-slf4j-impl test + + org.slf4j + slf4j-api + test + From 7545ec37db8df6d0b3850f528fecaf9a3bd91f66 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 12:06:31 +1100 Subject: [PATCH 1180/2152] Specify dependency on slf4j api --- raven-appengine/pom.xml | 4 ++++ raven-getsentry/pom.xml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 1c604d90599..66a30d8f7cc 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -27,6 +27,10 @@ appengine-api-1.0-sdk ${appengine-api.version} + + org.slf4j + slf4j-api + ch.qos.logback diff --git a/raven-getsentry/pom.xml b/raven-getsentry/pom.xml index 0b02ff9ab62..1dfa5d7a0c2 100644 --- a/raven-getsentry/pom.xml +++ b/raven-getsentry/pom.xml @@ -19,6 +19,10 @@ ${project.groupId} raven + + org.slf4j + slf4j-api + ch.qos.logback From 177e120d67d344314f540c99669d7d11cd71f0fe Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 12:06:48 +1100 Subject: [PATCH 1181/2152] Bump GAE version to 1.9.0 --- raven-appengine/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 66a30d8f7cc..177b5354fbe 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -14,7 +14,7 @@ Raven module allowing to use GoogleApp Engine and its task queueing system. - 1.8.9 + 1.9.0 From 5cb83e1fba3e53a1cbe0a06ab9d01be35857d511 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 15 Mar 2014 12:13:29 +1100 Subject: [PATCH 1182/2152] Update plugin versions --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index b8cb39ceec1..9a571a3111e 100644 --- a/pom.xml +++ b/pom.xml @@ -237,7 +237,7 @@ org.apache.maven.plugins maven-release-plugin - 2.4.2 + 2.5 v@{project.version} true @@ -305,7 +305,7 @@ org.eclipse.jetty jetty-maven-plugin - 9.1.1.v20140108 + 9.1.3.v20140225 10 foo @@ -403,7 +403,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 2.11 + 2.12 src/checkstyle/checkstyle.xml true @@ -444,7 +444,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 2.11 + 2.12 src/checkstyle/checkstyle.xml @@ -492,7 +492,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.4 + 1.5 sign-artifacts From eb3bc3fce2fa490bb7b4e3bb933b6461d3135340 Mon Sep 17 00:00:00 2001 From: John Dennison Date: Fri, 10 Jan 2014 14:23:59 -0700 Subject: [PATCH 1183/2152] new mapped tags to appender --- .../raven/log4j/SentryAppender.java | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 077878e606b..87733273037 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -16,8 +16,10 @@ import org.apache.log4j.spi.LoggingEvent; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.Date; +import java.util.List; import java.util.Map; /** @@ -60,6 +62,14 @@ public class SentryAppender extends AppenderSkeleton { */ protected Map tags = Collections.emptyMap(); + /** + * List of tags to look for in log4j's MDC. These will be added as tags to be sent to sentry. + *

    + * Might be empty in which case no mapped tags are set. + *

    + */ + protected List mappedTags = Collections.emptyList(); + /** * Creates an instance of SentryAppender. */ @@ -187,9 +197,16 @@ protected Event buildEvent(LoggingEvent loggingEvent) { @SuppressWarnings("unchecked") Map properties = (Map) loggingEvent.getProperties(); - for (Map.Entry mdcEntry : properties.entrySet()) + for (Map.Entry mdcEntry : properties.entrySet()){ eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); - + //If MDC key is a mappedTag then add it to the event tag + if (this.mappedTags.contains(mdcEntry.getKey())){ + eventBuilder.addTag(mdcEntry.getKey(), mdcEntry.getValue().toString()); + } + } + + + for (Map.Entry tagEntry : tags.entrySet()) eventBuilder.addTag(tagEntry.getKey(), tagEntry.getValue()); @@ -213,6 +230,17 @@ public void setDsn(String dsn) { public void setTags(String tags) { this.tags = Splitter.on(",").withKeyValueSeparator(":").split(tags); } + + /** + * Set the mapped tags that will be used to search MDC and upgrade key pair to a tag sent along with the events. + * + * @param mappedTags A String of mappedTags. mappedTags are separated by commas(,). + */ + public void setMappedTags(String mappedTags) { + System.out.println("ADDING" + mappedTags); + this.mappedTags = Arrays.asList(mappedTags.split(",")); + } + @Override public void close() { From 246a659ec39903019d2db955e8845f6ab21d9105 Mon Sep 17 00:00:00 2001 From: John Dennison Date: Fri, 10 Jan 2014 14:24:55 -0700 Subject: [PATCH 1184/2152] new mapped tags parameter test --- .../SentryAppenderEventBuildingTest.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java index 0d9057c2411..96377a231fc 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java @@ -1,5 +1,7 @@ package net.kencochrane.raven.log4j; +import org.testng.annotations.Test; +import org.testng.annotations.BeforeMethod; import com.google.common.base.Joiner; import mockit.Expectations; import mockit.Injectable; @@ -35,7 +37,7 @@ public class SentryAppenderEventBuildingTest { @Injectable private Logger mockLogger = null; - @BeforeMethod + @BeforeMethod public void setUp() throws Exception { sentryAppender = new SentryAppender(mockRaven); mockUpErrorHandler = new MockUpErrorHandler(); @@ -130,6 +132,29 @@ public void testMdcAddedToExtra() throws Exception { }}; assertNoErrorsInErrorHandler(); } + + @Test + public void testMappedMdcAddedToTags() throws Exception { + final String mappedKey = "User"; + final String mappedValue = "Test"; + + sentryAppender.setMappedTags("User,foo"); + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, + null, null, null, Collections.singletonMap(mappedKey, mappedValue))); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getExtra(), Matchers.hasEntry(mappedKey, mappedValue)); + assertThat(event.getTags(), Matchers.hasEntry(mappedKey, mappedValue)); + + }}; + assertNoErrorsInErrorHandler(); + } + + + + @Test public void testNdcAddedToExtra() throws Exception { From 007275a1a761e6a39e34343c0d88eaffd9034a74 Mon Sep 17 00:00:00 2001 From: John Dennison Date: Fri, 10 Jan 2014 14:33:59 -0700 Subject: [PATCH 1185/2152] updated README with new mappedTags option --- raven-log4j/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index db035f0ea34..cf0ba87af15 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -80,6 +80,23 @@ public class MyClass { } ``` +### Mapped Tags +By default all MDC parameters are sent under the Additional Data Tab. By specifiy the mappedTags paramter in your properties file. You can specify MDC keys to send as tags in addition to including them in Additional Data. This allows them to be filtered within Sentry. + +```properties +log4j.appender.SentryAppender.mappedTags=User,OS +``` +```java + void logWithExtras() { + // MDC extras + MDC.put("User", "test user"); + MDC.put("OS", "Linux"); + + // This adds a message with extras and MDC keys declared in mappedTags as tags to Sentry + logger.info("This is a test"); + } +``` + ## Asynchronous logging It is not recommended to attempt to set up `SentryAppender` within an [AsyncAppender](https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/AsyncAppender.html). From b0e1133a3dd689786687632121b91328ff541194 Mon Sep 17 00:00:00 2001 From: John Dennison Date: Fri, 10 Jan 2014 14:36:44 -0700 Subject: [PATCH 1186/2152] removed redundant imports --- .../raven/log4j/SentryAppenderEventBuildingTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java index 96377a231fc..50fc58910c7 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java @@ -1,7 +1,5 @@ package net.kencochrane.raven.log4j; -import org.testng.annotations.Test; -import org.testng.annotations.BeforeMethod; import com.google.common.base.Joiner; import mockit.Expectations; import mockit.Injectable; @@ -37,7 +35,7 @@ public class SentryAppenderEventBuildingTest { @Injectable private Logger mockLogger = null; - @BeforeMethod + @BeforeMethod public void setUp() throws Exception { sentryAppender = new SentryAppender(mockRaven); mockUpErrorHandler = new MockUpErrorHandler(); From 147ad295f12c8876d57a989cbca2dd002262947e Mon Sep 17 00:00:00 2001 From: John Dennison Date: Fri, 10 Jan 2014 14:38:02 -0700 Subject: [PATCH 1187/2152] removed debug print from appender --- .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 1 - 1 file changed, 1 deletion(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 87733273037..1f72866f207 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -237,7 +237,6 @@ public void setTags(String tags) { * @param mappedTags A String of mappedTags. mappedTags are separated by commas(,). */ public void setMappedTags(String mappedTags) { - System.out.println("ADDING" + mappedTags); this.mappedTags = Arrays.asList(mappedTags.split(",")); } From 3b1301218161379a71c258918d51ea6cc0833b3a Mon Sep 17 00:00:00 2001 From: John Dennison Date: Fri, 10 Jan 2014 15:51:31 -0700 Subject: [PATCH 1188/2152] Fixed code style issues --- .../kencochrane/raven/log4j/SentryAppender.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 1f72866f207..cd6150ab7e9 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -197,16 +197,16 @@ protected Event buildEvent(LoggingEvent loggingEvent) { @SuppressWarnings("unchecked") Map properties = (Map) loggingEvent.getProperties(); - for (Map.Entry mdcEntry : properties.entrySet()){ + for (Map.Entry mdcEntry : properties.entrySet()) { eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); //If MDC key is a mappedTag then add it to the event tag - if (this.mappedTags.contains(mdcEntry.getKey())){ + if (this.mappedTags.contains(mdcEntry.getKey())) { eventBuilder.addTag(mdcEntry.getKey(), mdcEntry.getValue().toString()); } } - - - + + + for (Map.Entry tagEntry : tags.entrySet()) eventBuilder.addTag(tagEntry.getKey(), tagEntry.getValue()); @@ -230,7 +230,7 @@ public void setDsn(String dsn) { public void setTags(String tags) { this.tags = Splitter.on(",").withKeyValueSeparator(":").split(tags); } - + /** * Set the mapped tags that will be used to search MDC and upgrade key pair to a tag sent along with the events. * @@ -239,7 +239,7 @@ public void setTags(String tags) { public void setMappedTags(String mappedTags) { this.mappedTags = Arrays.asList(mappedTags.split(",")); } - + @Override public void close() { From fe36ddbdda3052d918a8d25f9693c16d1064a151 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 11 Jun 2014 05:40:20 +1000 Subject: [PATCH 1189/2152] Update dependencies and plugins --- pom.xml | 44 +++++++++++++++++++++++++++++++++++------ raven-appengine/pom.xml | 4 ++-- raven-getsentry/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 7 files changed, 45 insertions(+), 13 deletions(-) diff --git a/pom.xml b/pom.xml index 9a571a3111e..5a2eb156fef 100644 --- a/pom.xml +++ b/pom.xml @@ -107,15 +107,15 @@ 7 - 1.7.6 - 16.0.1 - 2.3.2 + 1.7.7 + 17.0 + 2.4.0 3.0.1 - 1.1.1 + 1.1.2 1.2.17 2.0-rc1 1.9.5 - 1.7 + 1.8 6.8.8 1.3 @@ -210,7 +210,7 @@ ${mockito.version}
    - com.googlecode.jmockit + org.jmockit jmockit ${jmockit.version} @@ -420,6 +420,38 @@ + + maven-clean-plugin + 2.5 + + + maven-compiler-plugin + 3.1 + + + maven-deploy-plugin + 2.8.1 + + + maven-install-plugin + 2.5.1 + + + maven-jar-plugin + 2.4 + + + maven-resources-plugin + 2.6 + + + maven-site-plugin + 3.3 + + + maven-war-plugin + 2.4 +
    diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 177b5354fbe..677d0683c0e 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -14,7 +14,7 @@ Raven module allowing to use GoogleApp Engine and its task queueing system. - 1.9.0 + 1.9.2 @@ -38,7 +38,7 @@ test - com.googlecode.jmockit + org.jmockit jmockit test diff --git a/raven-getsentry/pom.xml b/raven-getsentry/pom.xml index 1dfa5d7a0c2..9c953368a1d 100644 --- a/raven-getsentry/pom.xml +++ b/raven-getsentry/pom.xml @@ -30,7 +30,7 @@ test - com.googlecode.jmockit + org.jmockit jmockit test diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 0684c0aad30..2cb2b2cb2ec 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -30,7 +30,7 @@ - com.googlecode.jmockit + org.jmockit jmockit test diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 403644a5a47..e7baa2d0074 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -34,7 +34,7 @@ - com.googlecode.jmockit + org.jmockit jmockit test diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index d52951b656b..4a8d3853e3e 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -38,7 +38,7 @@ - com.googlecode.jmockit + org.jmockit jmockit test diff --git a/raven/pom.xml b/raven/pom.xml index ba1461aab4b..921698f2074 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -52,7 +52,7 @@ test - com.googlecode.jmockit + org.jmockit jmockit test From 735ad34c2535f03f32f3e31cd3435fb3d94d457b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 11 Jun 2014 06:18:45 +1000 Subject: [PATCH 1190/2152] Fix test to comply with the changes in JMockit issue 348 --- .../raven/connection/AsyncConnectionTest.java | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index 1da6a5052e3..4dcb5d4786e 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -22,27 +22,17 @@ public class AsyncConnectionTest { @BeforeMethod public void setUp() throws Exception { - // Reset Tested - asyncConnection = null; new NonStrictExpectations() {{ mockExecutorService.awaitTermination(anyLong, (TimeUnit) any); result = true; }}; } - @AfterMethod - public void tearDown() throws Exception { - // Reset the expectation that has been already removed. - new NonStrictExpectations() {{ - mockExecutorService.awaitTermination(anyLong, (TimeUnit) any); - result = true; - }}; - // Ensure that the shutdown hooks are removed - asyncConnection.close(); - } - @Test public void verifyShutdownHookIsAdded() throws Exception { + // Ensure that the shutdown hooks for the unused @Tested instance are removed + asyncConnection.close(); + new AsyncConnection(mockConnection, mockExecutorService); new Verifications() {{ @@ -53,6 +43,9 @@ public void verifyShutdownHookIsAdded() throws Exception { @Test public void verifyShutdownHookSetManagedByRavenAndCloseConnection( @Mocked({"startManagingThread", "stopManagingThread"}) Raven mockRaven) throws Exception { + // Ensure that the shutdown hooks for the unused @Tested instance are removed + asyncConnection.close(); + new NonStrictExpectations() {{ mockRuntime.addShutdownHook((Thread) any); result = new Delegate() { @@ -74,6 +67,9 @@ public void addShutdownHook(Thread thread) { @Test public void ensureFailingShutdownHookStopsBeingManaged( @Mocked({"startManagingThread", "stopManagingThread"}) Raven mockRaven) throws Exception { + // Ensure that the shutdown hooks for the unused @Tested instance are removed + asyncConnection.close(); + new NonStrictExpectations() {{ mockRuntime.addShutdownHook((Thread) any); result = new Delegate() { @@ -109,11 +105,14 @@ public void testSendEventQueued(@Injectable final Event mockEvent) throws Except new Verifications() {{ mockExecutorService.execute((Runnable) any); }}; + + // Ensure that the shutdown hooks for the used @Tested instance are removed + asyncConnection.close(); } @Test public void testQueuedEventExecuted(@Injectable final Event mockEvent) throws Exception { - new Expectations() {{ + new NonStrictExpectations() {{ mockExecutorService.execute((Runnable) any); result = new Delegate() { public void execute(Runnable runnable) { @@ -127,5 +126,8 @@ public void execute(Runnable runnable) { new Verifications() {{ mockConnection.send(mockEvent); }}; + + // Ensure that the shutdown hooks for the used @Tested instance are removed + asyncConnection.close(); } } From 21d23891c59e06905cfc0ee05e2e02abe143f9bb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 11 Jun 2014 06:21:57 +1000 Subject: [PATCH 1191/2152] Remove the getsentry module (not needed since the new certificate is available) --- README.md | 11 --- pom.xml | 1 - raven-getsentry/README.md | 45 --------- raven-getsentry/pom.xml | 62 ------------- .../getsentry/GetSentryRavenFactory.java | 52 ----------- .../connection/GetSentryHttpsConnection.java | 86 ------------------ .../net.kencochrane.raven.RavenFactory | 1 - .../src/main/resources/startcom/ca.crt | Bin 1997 -> 0 bytes .../getsentry/GetSentryRavenFactoryTest.java | 19 ---- .../GetSentryHttpsConnectionIT.java | 49 ---------- .../src/test/resources/logback-test.xml | 11 --- 11 files changed, 337 deletions(-) delete mode 100644 raven-getsentry/README.md delete mode 100644 raven-getsentry/pom.xml delete mode 100644 raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/GetSentryRavenFactory.java delete mode 100644 raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnection.java delete mode 100644 raven-getsentry/src/main/resources/META-INF/services/net.kencochrane.raven.RavenFactory delete mode 100644 raven-getsentry/src/main/resources/startcom/ca.crt delete mode 100644 raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/GetSentryRavenFactoryTest.java delete mode 100644 raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionIT.java delete mode 100644 raven-getsentry/src/test/resources/logback-test.xml diff --git a/README.md b/README.md index 371ab6e230f..5cfd0e8ebf0 100644 --- a/README.md +++ b/README.md @@ -20,17 +20,6 @@ Raven supports both HTTP(S) and UDP as transport protocols to the Sentry instance. -## Notes on GetSentry.com and Oracle JDK - -Due to GetSentry.com using a certificate provided by [StartCom](https://www.startcom.org/) and StartCom not being -in the list of the [CAcerts of Oracle JDK 6](http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/keytool.html#cacerts) -it isn't possible to establish an HTTPS connection to GetSentry.com out of the box. - -The said certificate [might be available out of the box with Java 8](https://forum.startcom.org/viewtopic.php?f=15&t=1815) -but in the mean time, you can use the [raven-getsentry](raven-getsentry) module which embeds the certificate and allows -SSL connections to GetSentry.com - - ## Sentry Protocol and Raven versions Since 2.0, the major version of raven matches the version of the Sentry protocol. diff --git a/pom.xml b/pom.xml index 5a2eb156fef..fb19ba67970 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,6 @@ raven - raven-getsentry raven-appengine raven-log4j raven-logback diff --git a/raven-getsentry/README.md b/raven-getsentry/README.md deleted file mode 100644 index c41c3b3c605..00000000000 --- a/raven-getsentry/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# GetSentry (module) -Module enabling the support of the GetSentry.com SSL certificate. - -GetSentry.com SSL certificate is provided by StartCom which isn't included in the list of the default CAs in -Oracle JDK 6 and 7. - -To work around that, this module embeds the certificate and uses it only for HTTPS connections to GetSentry.com - -__This module is not useful with Open JDK.__ - -## Installation - -### Maven -```xml - - net.kencochrane.raven - raven-getsentry - 4.1.2 - -``` - -### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1.2%7Cjar). - -### Manual dependency management -Relies on: - - - [raven dependencies](../raven) - -## Usage - -This module provides a new `RavenFactory` which supports the use of the 'getsentry://' scheme. -To use it, the DSN should look like this: - - getsentry://public:private@getsentry.com/1 - -The DSN supports the same options as those listed in the main README file. - -## Android - -As mentioned in the main README file, Android might require some additional configuration to use a custom `RavenFactory`. - -With the GetSentry module, the factory to register is `net.kencochrane.raven.getsentry.GetSentryRavenFactory`. - -Both factories can be registered at the same time without any problem. diff --git a/raven-getsentry/pom.xml b/raven-getsentry/pom.xml deleted file mode 100644 index 9c953368a1d..00000000000 --- a/raven-getsentry/pom.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - 4.0.0 - - - net.kencochrane.raven - raven-all - 5.0.0-SNAPSHOT - - - raven-getsentry - jar - - Raven-Java for getSentry - getSentry connector allowing to use the getSentry certificate. - - - - ${project.groupId} - raven - - - org.slf4j - slf4j-api - - - - ch.qos.logback - logback-classic - test - - - org.jmockit - jmockit - test - - - org.hamcrest - hamcrest-core - test - - - org.hamcrest - hamcrest-library - test - - - org.testng - testng - test - - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - - - diff --git a/raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/GetSentryRavenFactory.java b/raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/GetSentryRavenFactory.java deleted file mode 100644 index 47b29ade0f0..00000000000 --- a/raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/GetSentryRavenFactory.java +++ /dev/null @@ -1,52 +0,0 @@ -package net.kencochrane.raven.getsentry; - -import net.kencochrane.raven.DefaultRavenFactory; -import net.kencochrane.raven.connection.Connection; -import net.kencochrane.raven.dsn.Dsn; -import net.kencochrane.raven.getsentry.connection.GetSentryHttpsConnection; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Raven factory capable of handling 'getsentry' scheme, using the HTTPS connection to GetSentry.com. - */ -public class GetSentryRavenFactory extends DefaultRavenFactory { - private static final Logger logger = LoggerFactory.getLogger(GetSentryRavenFactory.class); - - @Override - protected Connection createConnection(Dsn dsn) { - String protocol = dsn.getProtocol(); - Connection connection; - - if (protocol.equalsIgnoreCase("getsentry")) { - logger.info("Using an HTTP connection to Sentry."); - connection = createHttpConnection(dsn); - - // Enable async unless its value is 'false'. - if (!Boolean.FALSE.toString().equalsIgnoreCase(dsn.getOptions().get(ASYNC_OPTION))) { - connection = createAsyncConnection(dsn, connection); - } - } else { - connection = super.createConnection(dsn); - } - - return connection; - } - - /** - * Creates an HTTPS connection to the GetSentry server. - * - * @param dsn Data Source Name of the Sentry server. - * @return a {@link GetSentryHttpsConnection} to the server. - */ - protected Connection createHttpConnection(Dsn dsn) { - GetSentryHttpsConnection httpConnection = new GetSentryHttpsConnection(dsn.getProjectId(), dsn.getPublicKey(), - dsn.getSecretKey()); - httpConnection.setMarshaller(createMarshaller(dsn)); - - // Set the HTTP timeout - if (dsn.getOptions().containsKey(TIMEOUT_OPTION)) - httpConnection.setTimeout(Integer.parseInt(dsn.getOptions().get(TIMEOUT_OPTION))); - return httpConnection; - } -} diff --git a/raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnection.java b/raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnection.java deleted file mode 100644 index 579f881407b..00000000000 --- a/raven-getsentry/src/main/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnection.java +++ /dev/null @@ -1,86 +0,0 @@ -package net.kencochrane.raven.getsentry.connection; - -import net.kencochrane.raven.connection.HttpConnection; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManagerFactory; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.KeyStore; -import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; - -/** - * Connection to GetSentry.com using the StartCom certificate. - */ -public class GetSentryHttpsConnection extends HttpConnection { - private static final Logger logger = LoggerFactory.getLogger(GetSentryHttpsConnection.class); - private static final String GETSENTRY_API_URL = "https://app.getsentry.com/api/%s/store/"; - private static final String CERTIFICATE_PATH = "/startcom/ca.crt"; - private final SSLSocketFactory startcomSslFactory; - - /** - * Creates an HTTP connection to the GetSentry server. - * - * @param projectId identifier of the project. - * @param publicKey public key of the current project. - * @param secretKey private key of the current project. - */ - public GetSentryHttpsConnection(String projectId, String publicKey, String secretKey) { - super(getSentryUrl(projectId), publicKey, secretKey); - try { - this.startcomSslFactory = getStartcomSslFactory(); - } catch (Exception e) { - throw new IllegalStateException("Couldn't create an SSL Factory for StartCom", e); - } - } - - /** - * Gets the URL to GetSentry for a given projectId. - * - * @param projectId Identifier of the project. - * @return The URL to getSentry for the given project. - */ - protected static URL getSentryUrl(String projectId) { - try { - return new URL(String.format(GETSENTRY_API_URL, projectId)); - } catch (MalformedURLException e) { - throw new RuntimeException("Couldn't create the URL for API of GetSentry", e); - } - } - - @Override - protected HttpsURLConnection getConnection() { - HttpsURLConnection connection = (HttpsURLConnection) super.getConnection(); - connection.setSSLSocketFactory(startcomSslFactory); - return connection; - } - - /** - * Create an SSLSocketFactory only able to handle certificates provided by StartCom. - * - * @return an SSL factory handling certificates from StartCom. - * @throws Exception - */ - private SSLSocketFactory getStartcomSslFactory() throws Exception { - logger.debug("Loading the certificate from '{}'", CERTIFICATE_PATH); - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - Certificate ca = cf.generateCertificate(GetSentryHttpsConnection.class.getResourceAsStream(CERTIFICATE_PATH)); - KeyStore ks = KeyStore.getInstance("jks"); - - ks.load(null, null); - ks.setCertificateEntry("ca", ca); - - TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); - tmf.init(ks); - - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, tmf.getTrustManagers(), null); - - return sslContext.getSocketFactory(); - } -} diff --git a/raven-getsentry/src/main/resources/META-INF/services/net.kencochrane.raven.RavenFactory b/raven-getsentry/src/main/resources/META-INF/services/net.kencochrane.raven.RavenFactory deleted file mode 100644 index 2e3dd3bbd57..00000000000 --- a/raven-getsentry/src/main/resources/META-INF/services/net.kencochrane.raven.RavenFactory +++ /dev/null @@ -1 +0,0 @@ -net.kencochrane.raven.getsentry.GetSentryRavenFactory diff --git a/raven-getsentry/src/main/resources/startcom/ca.crt b/raven-getsentry/src/main/resources/startcom/ca.crt deleted file mode 100644 index b60dea2484a21bf1e4bbe23e4d25380281637cf6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1997 zcmc(fX;c$e6vvs#f(b-0;RG95rcssRyv_aOHYse+7Iu;z3+eTyYIet zf3V)R9oAEh8%ZDlf&c(Bs5Bo#q+yf+02GQ1oabi;)2O7CWGV}ciuPl}PF91?V#MJx z440&M zzOsJ!Wy@XKc~!*6Syg*-P5xyTETy|e3~}cr-~460MMD3KsyKALR6G{gKX*u@ zJ|1eusta8X-U>VA&%Y&Xcrp0pq7PCVFH`gApW=nx+0h#Va(aR&u^)^M&xw#6>MAVk zYW?VGMvCaH@74`&JAQpE>YUZK_eN~kS^sWx%sso1CDN)(p2Bex+XZ`TeMe|ge@Cxp z2}hin2;9Y;JYGCD8Mio?vpVLx+m^$?d@OrBqMPM&K>LnBUEoBbC71TbaF{1h=N5Wd_joopviWQ-I;L$K&k4AFo6(i| ze*9!vQ{T=m^OH+!no@(!FX|2qeN(alFqM@r1a@r_@EoowJ=Y03nL4tMh_4NgATvp)ZANi->|=iD}& z6)OcvfDKTs2V;pJu@tr^e!VM;Oo2%NFbUgPEeA507`$V!tJRoEW|N(k+CAYMdMDwq zGg<_9h|(mTi;h`fg>~DVoCGJpzD6ITx4|Ai$pcGV#C4(59)uZ!)(v?$^mr19=>mO8tmQ7s_l4Z(Jl`KVtKr$S9?Zu{bViuLQ#t;b@ z#@+=0>%-gF!hwIg7-T-1_v-8j3?Yaa7yY^-rgMh?HNhRghOMd?R@#e3It!EY=#Rd4 zTpH+-+*9L0k?vg3G7$1xm^qx5U>miScTEX49j_eq*hdR3ozrU;7xAU%(ryJQ!;GG7 zmY|)k%j@;I`QP6B@cvJvcr98!W@DaBq}sIzhLI+^CxHB&)ZS3 zyfS2a@o>y8Wo{_x@vTbN%BRS(5o2dYm9SxGP%qgxh8`%+zAPz?!1@$oX2Yn5TVQe7 z`ux^)YtOV^3zr_vDl7T9&DTvjEBtFwtk(^CQdMC~@wc2T%O1zL1?G*Ll^J(TY2Fhu z!G+C70xwoD4Ts%Q6<<&V!dh)%+g^u{i;iv=EECB?OXTTDaTlB1%fo?P<5BHxT9O%L)|-<`HsToiY; z3k}R|b7?Je^dY*a>b3DjyB~CCU8#`B`(l1vKff2Lz)&*hJzBp5uxj3fd-Kff+SP(H zmuW!$p0jEj>cSI)OB{o4ck)M?A7wO`gI8#CX8f=+;HQF#fX$K=sN?5VQe^Ojs%74; kc?U%gDU+4lWBc>o{ta19uYuQ|9Nyxxk?|bcI ravenFactories = ServiceLoader.load(RavenFactory.class); - - assertThat(ravenFactories, Matchers.hasItem(instanceOf(GetSentryRavenFactory.class))); - } -} diff --git a/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionIT.java b/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionIT.java deleted file mode 100644 index 989949bc951..00000000000 --- a/raven-getsentry/src/test/java/net/kencochrane/raven/getsentry/connection/GetSentryHttpsConnectionIT.java +++ /dev/null @@ -1,49 +0,0 @@ -package net.kencochrane.raven.getsentry.connection; - -import mockit.Injectable; -import mockit.Tested; -import org.testng.annotations.Test; - -import javax.net.ssl.SSLHandshakeException; -import java.net.URL; -import java.net.URLConnection; - -import static mockit.Deencapsulation.setField; -import static org.testng.Assert.fail; - -public class GetSentryHttpsConnectionIT { - @Tested - private GetSentryHttpsConnection connection; - @Injectable("projectId") - private String projectId; - @Injectable("privateKey") - private String publicKey; - @Injectable("secretKey") - private String secretKey; - - @Test - public void ensureHttpsConnectionToGetSentryIsSecure() throws Exception { - connection.getConnection().connect(); - } - - @Test(expectedExceptions = SSLHandshakeException.class) - public void ensureHttpsConnectionToGoogleComIsNotSecure() throws Exception { - final URL url = new URL("https://www.google.com"); - setField(connection, "sentryUrl", url); - - connection.getConnection().connect(); - } - - /* - * Test disabled as it will fail when the CA is available in the default KeyStore. - * It is useful to be able to run it to ensure that it's indeed necessary to keep the entire module. - */ - @Test(expectedExceptions = SSLHandshakeException.class, enabled = false) - public void ensureHttpsConnectionToGetSentryRequiresCustomSslFactory() throws Exception { - final URLConnection httpsConnection = GetSentryHttpsConnection.getSentryUrl(projectId).openConnection(); - - httpsConnection.connect(); - - fail("Ensure that the StartCom certificate hasn't been added manually to the KeyStore or skip this test."); - } -} diff --git a/raven-getsentry/src/test/resources/logback-test.xml b/raven-getsentry/src/test/resources/logback-test.xml deleted file mode 100644 index 2d5d7c420f3..00000000000 --- a/raven-getsentry/src/test/resources/logback-test.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - [RAVEN] %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - From 1c18381d508b27effc1ea28d7cb9af90ed0a4348 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 11 Jun 2014 06:27:02 +1000 Subject: [PATCH 1192/2152] Update the repository to use the getsentry group --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fb19ba67970..c4598fa70b7 100644 --- a/pom.xml +++ b/pom.xml @@ -98,7 +98,7 @@ UTF-8 UTF-8 - kencochrane/raven-java + getsentry/raven-java github From d8c0a68fd93dc22bd26d06281473a34c528b4903 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 11 Jun 2014 06:34:02 +1000 Subject: [PATCH 1193/2152] Bump appengine version --- raven-appengine/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 677d0683c0e..9d5c9bf9206 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -14,7 +14,7 @@ Raven module allowing to use GoogleApp Engine and its task queueing system. - 1.9.2 + 1.9.6 From 2483676f302d34ab7c22a0a80108251fea23b9fa Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 11 Jun 2014 07:14:01 +1000 Subject: [PATCH 1194/2152] [maven-release-plugin] prepare release v5.0 --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index c4598fa70b7..865fbd75a6c 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 5.0.0-SNAPSHOT + 5.0 pom Raven-Java @@ -75,7 +75,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v5.0 https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 9d5c9bf9206..7b878b6c55c 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ net.kencochrane.raven raven-all - 5.0.0-SNAPSHOT + 5.0 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 2cb2b2cb2ec..e58d9f470d0 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.0-SNAPSHOT + 5.0 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index e7baa2d0074..5add81dfe94 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.0-SNAPSHOT + 5.0 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 4a8d3853e3e..99f9f774abb 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.0-SNAPSHOT + 5.0 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 921698f2074..1ce0476ed35 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.0-SNAPSHOT + 5.0 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index d8ffc90f88a..af18df18c93 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.0-SNAPSHOT + 5.0 sentry-stub From 8c6aa5f4097b13c7aea5ddfc2a5adf65705908c0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 11 Jun 2014 07:14:01 +1000 Subject: [PATCH 1195/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 865fbd75a6c..54835df97e9 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 5.0 + 5.0.1-SNAPSHOT pom Raven-Java @@ -75,7 +75,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v5.0 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 7b878b6c55c..94676294a04 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ net.kencochrane.raven raven-all - 5.0 + 5.0.1-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index e58d9f470d0..a6f50b6e8c1 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0 + 5.0.1-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 5add81dfe94..c9ebf5d20b8 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0 + 5.0.1-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 99f9f774abb..71012937d56 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0 + 5.0.1-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 1ce0476ed35..14e2d19990e 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0 + 5.0.1-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index af18df18c93..fc2ca631a7c 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0 + 5.0.1-SNAPSHOT sentry-stub From e65c2d3d7a9d31878fe51a73e4a5098e2399877f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 11 Jun 2014 07:25:59 +1000 Subject: [PATCH 1196/2152] Move to getsentry --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5cfd0e8ebf0..e4bb95ecef5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Raven -[![Build Status](https://secure.travis-ci.org/kencochrane/raven-java.png?branch=master)](https://travis-ci.org/kencochrane/raven-java) +[![Build Status](https://secure.travis-ci.org/getsentry/raven-java.png?branch=master)](https://travis-ci.org/getsentry/raven-java) Raven is the Java client for [Sentry](https://www.getsentry.com/). Raven relies on the most popular logging libraries to capture and convert logs From 622337ef84a843b43b7020aa24710424093a8c34 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 11 Jun 2014 07:26:31 +1000 Subject: [PATCH 1197/2152] Improve documentation --- README.md | 8 ++++---- raven-logback/README.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e4bb95ecef5..9fde3f0f3f2 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,14 @@ instance. ## Sentry Protocol and Raven versions -Since 2.0, the major version of raven matches the version of the Sentry protocol. +Since Sentry 2.0, the major version of raven matches the version of the Sentry protocol. | Raven version | Protocol version | Sentry version | | ------------- | ---------------- | -------------- | -| Raven 2.x | V2 | >= 2.0 | -| Raven 3.x | V3 | >= 5.1 | +| Raven 2.x(old)| V2 | >= 2.0 | +| Raven 3.x(old)| V3 | >= 5.1 | | Raven 4.x | V4 | >= 6.0 | -| Raven 5.x(dev)| V5 | >= 6.4 | +| Raven 5.x | V5 | >= 6.4 | Each release of Sentry supports the last two version of the protocol diff --git a/raven-logback/README.md b/raven-logback/README.md index be5590730bc..f604b9098b3 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -47,7 +47,7 @@ In the `logback.xml` file set: It's possible to add extra details to events captured by the logback module thanks to the [marker system](http://www.slf4j.org/faq.html#fatal) which will add a tag `logback-Marker`. -[The MDC system provided by Log4j 2](http://logback.qos.ch/manual/mdc.html) +[The MDC system provided by logback](http://logback.qos.ch/manual/mdc.html) allows to add extra information to the event. ### In practice From 6dd5912d522b2242502ce74287644207bc607a26 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 11 Jun 2014 07:28:30 +1000 Subject: [PATCH 1198/2152] Update the documented dependencies --- raven-log4j/README.md | 2 +- raven-log4j2/README.md | 6 +++--- raven-logback/README.md | 4 ++-- raven/README.md | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 9526a52a464..8c099057c88 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -22,7 +22,7 @@ Relies on: - [raven dependencies](../raven) - [log4j-1.2.17.jar](https://search.maven.org/#artifactdetails%7Clog4j%7Clog4j%7C1.2.17%7Cjar) - - [slf4j-log4j12-1.7.5.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-log4j12%7C1.7.5%7Cjar) + - [slf4j-log4j12-1.7.7.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-log4j12%7C1.7.7%7Cjar) is recommended as the implementation of slf4j (instead of slf4j-jdk14). ## Usage diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index 5f4868ec817..43ce3db76fe 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -21,9 +21,9 @@ Details in the [central Maven repository](https://search.maven.org/#artifactdeta Relies on: - [raven dependencies](../raven) - - [log4j-api-2.0-beta9.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7C2.0-beta9%7Cjar) - - [log4j-core-2.0-beta9.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7C2.0-beta9%7Cjar) - - [log4j-slf4j-impl-2.0-beta9.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7C2.0-beta9%7Cjar) + - [log4j-api-2.0-rc1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7C2.0-rc1%7Cjar) + - [log4j-core-2.0-rc1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7C2.0-rc1%7Cjar) + - [log4j-slf4j-impl-2.0-rc1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7C2.0-rc1%7Cjar) is recommended as the implementation of slf4j (instead of slf4j-jdk14). diff --git a/raven-logback/README.md b/raven-logback/README.md index f604b9098b3..a847d1f0cbb 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -21,8 +21,8 @@ Details in the [central Maven repository](https://search.maven.org/#artifactdeta Relies on: - [raven dependencies](../raven) - - [logback-core-1.0.13.jar](https://search.maven.org/#artifactdetails%7Cch.qos.logback%7Clogback-core%7C1.0.13%7Cjar) - - [logback-classic-1.0.13.jar](https://search.maven.org/#artifactdetails%7Cch.qos.logback%7Clogback-classic%7C1.0.13%7Cjar) + - [logback-core-1.1.2.jar](https://search.maven.org/#artifactdetails%7Cch.qos.logback%7Clogback-core%7C1.1.2%7Cjar) + - [logback-classic-1.1.2.jar](https://search.maven.org/#artifactdetails%7Cch.qos.logback%7Clogback-classic%7C1.1.2%7Cjar) will act as the implementation of slf4j (instead of slf4j-jdk14). ## Usage diff --git a/raven/README.md b/raven/README.md index a5aae03ceab..d15dcf4b53f 100644 --- a/raven/README.md +++ b/raven/README.md @@ -20,11 +20,11 @@ Details in the [central Maven repository](https://search.maven.org/#artifactdeta ### Manual dependency management Relies on: - - [raven-4.1.2.jar](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1.2%7Cjar) - - [guava-15.0.jar](https://search.maven.org/#artifactdetails%7Ccom.google.guava%7Cguava%7C15.0%7Cjar) - - [jackson-core-2.2.3.jar](https://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.2.3%7Cjar) - - [slf4j-api-1.7.5.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.5%7Cjar) - - [slf4j-jdk14-1.7.5.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-jdk14%7C1.7.5%7Cjar) + - [raven-5.0.jar](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C5.0%7Cjar) + - [guava-17.0.jar](https://search.maven.org/#artifactdetails%7Ccom.google.guava%7Cguava%7C17.0%7Cjar) + - [jackson-core-2.4.0.jar](https://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.4.0%7Cjar) + - [slf4j-api-1.7.7.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.7%7Cjar) + - [slf4j-jdk14-1.7.7.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-jdk14%7C1.7.7%7Cjar) is recommended as the implementation of slf4j to capture the log events generated by Raven (connection errors, ...) if `java.util.logging` is used. From cb35eae0e566c4cffdbe4cea3bb86eddd864126b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 11 Jun 2014 07:29:03 +1000 Subject: [PATCH 1199/2152] Bump version to 5.0 --- raven-appengine/README.md | 4 ++-- raven-log4j/README.md | 4 ++-- raven-log4j2/README.md | 4 ++-- raven-logback/README.md | 4 ++-- raven/README.md | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/raven-appengine/README.md b/raven-appengine/README.md index efc3a910294..61e0709cdfb 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -15,12 +15,12 @@ __This module is not useful outside of Google App Engine.__ net.kencochrane.raven raven-appengine - 5.0.0 + 5.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1.2%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C5.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 8c099057c88..63fb8eae560 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,12 +10,12 @@ for log4j to send the logged events to Sentry. net.kencochrane.raven raven-log4j - 4.1.2 + 5.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j%7C4.1.2%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j%7C5.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index 43ce3db76fe..1130eeedbc0 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,12 +10,12 @@ for Log4j 2 to send the logged events to Sentry. net.kencochrane.raven raven-log4j2 - 4.1.2 + 5.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j2%7C4.1.2%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j2%7C5.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-logback/README.md b/raven-logback/README.md index a847d1f0cbb..a8878364a90 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,12 +10,12 @@ for logback to send the logged events to Sentry. net.kencochrane.raven raven-logback - 4.1.2 + 5.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-logback%7C4.1.2%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-logback%7C5.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven/README.md b/raven/README.md index d15dcf4b53f..75e40fb9ea1 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,12 +10,12 @@ for `java.util.logging`. net.kencochrane.raven raven - 4.1.2 + 5.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C4.1.2%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C5.0%7Cjar). ### Manual dependency management Relies on: From 9783e1d80d80a0ca495e861b88ba5cc11ce9fd2d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 11 Jun 2014 07:31:15 +1000 Subject: [PATCH 1200/2152] Fix link to appengine artifact --- raven-appengine/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-appengine/README.md b/raven-appengine/README.md index 61e0709cdfb..51201bb91b8 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -20,7 +20,7 @@ __This module is not useful outside of Google App Engine.__ ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C5.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-appengine%7C5.0%7Cjar). ### Manual dependency management Relies on: From 58b0daac99182da74fe50b03d75abcc46f0a1d44 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 11 Jun 2014 07:38:05 +1000 Subject: [PATCH 1201/2152] Mention the GAE module in the main documentation --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9fde3f0f3f2..4a23f659798 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ manually with the main project [raven](raven). Raven supports both HTTP(S) and UDP as transport protocols to the Sentry instance. +Support for [Google App Engine](https://appengine.google.com/) is provided in [raven-appengine](raven-appengine) ## Sentry Protocol and Raven versions Since Sentry 2.0, the major version of raven matches the version of the Sentry protocol. From 33f22d35a8d32fb4731a9a839b352f5d96f65a08 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 10:03:03 +1000 Subject: [PATCH 1202/2152] Add a new flag to disable the graceful shutdown --- README.md | 17 ++++++++++++++ .../raven/DefaultRavenFactory.java | 8 ++++++- .../raven/connection/AsyncConnection.java | 6 +++-- .../raven/connection/AsyncConnectionTest.java | 23 +++++++++++++++---- 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4a23f659798..ab7351e6aee 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,23 @@ To disable the async mode, add `raven.async=false` to the DSN: http://public:private@host:port/1?raven.async=false +#### Graceful Shutdown (advanced) +In order to shutdown the asynchronous connection gracefully, a `ShutdownHook` +is created. +This could lead to memory leaks in an environment where the life cycle of +Raven doesn't match the life cycle of the JVM. + +An example would be in a JEE environment where the application using Raven +could be deployed and undeployed regularly. + +To avoid this behaviour, it is possible to disable the graceful shutdown. +This might lead to some log entries being lost if the log application +doesn't shut down the Raven instance nicely. + +The option to do so is `raven.async.gracefulshutdown`: + + http://public:private@host:port/1?raven.async.gracefulshutdown=false + #### Queue size (advanced) The default queue used to store the not yet processed events doesn't have a limit. diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 9e6adbf0fb0..3e1311b5e57 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -45,6 +45,10 @@ public class DefaultRavenFactory extends RavenFactory { * Option to send events asynchronously. */ public static final String ASYNC_OPTION = "raven.async"; + /** + * Option to disable the graceful shutdown. + */ + public static final String GRACEFUL_SHUTDOWN_OPTION = "raven.async.gracefulshutdown"; /** * Option for the number of threads assigned for the connection. */ @@ -139,7 +143,9 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { ExecutorService executorService = new ThreadPoolExecutor( maxThreads, maxThreads, 0L, TimeUnit.MILLISECONDS, queue, new DaemonThreadFactory(priority)); - return new AsyncConnection(connection, executorService); + boolean gracefulShutdown = !FALSE.equalsIgnoreCase(dsn.getOptions().get(GRACEFUL_SHUTDOWN_OPTION)); + + return new AsyncConnection(connection, executorService, gracefulShutdown); } /** diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index aaae136569c..5f35b3cf8dd 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -50,14 +50,16 @@ public class AsyncConnection implements Connection { * @param actualConnection connection used to send the events. * @param executorService executorService used to process events, if null, the executorService will automatically * be set to {@code Executors.newSingleThreadExecutor()} + * @param gracefulShutdown Indicates whether or not the shutdown operation should be managed by a ShutdownHook. */ - public AsyncConnection(Connection actualConnection, ExecutorService executorService) { + public AsyncConnection(Connection actualConnection, ExecutorService executorService, boolean gracefulShutdown) { this.actualConnection = actualConnection; if (executorService == null) this.executorService = Executors.newSingleThreadExecutor(); else this.executorService = executorService; - addShutdownHook(); + if (gracefulShutdown) + addShutdownHook(); } /** diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index 4dcb5d4786e..e240a163b71 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -17,6 +17,8 @@ public class AsyncConnectionTest { private Connection mockConnection; @Injectable private ExecutorService mockExecutorService; + @Injectable("false") + private boolean gracefulShutdown; @Mocked("addShutdownHook") private Runtime mockRuntime; @@ -29,17 +31,30 @@ public void setUp() throws Exception { } @Test - public void verifyShutdownHookIsAdded() throws Exception { + public void verifyShutdownHookIsAddedWhenGraceful() throws Exception { // Ensure that the shutdown hooks for the unused @Tested instance are removed asyncConnection.close(); - new AsyncConnection(mockConnection, mockExecutorService); + new AsyncConnection(mockConnection, mockExecutorService, true); new Verifications() {{ mockRuntime.addShutdownHook((Thread) any); }}; } + @Test + public void verifyShutdownHookNotAddedWhenNotGraceful() throws Exception { + // Ensure that the shutdown hooks for the unused @Tested instance are removed + asyncConnection.close(); + + new AsyncConnection(mockConnection, mockExecutorService, false); + + new Verifications() {{ + mockRuntime.addShutdownHook((Thread) any); + times = 0; + }}; + } + @Test public void verifyShutdownHookSetManagedByRavenAndCloseConnection( @Mocked({"startManagingThread", "stopManagingThread"}) Raven mockRaven) throws Exception { @@ -55,7 +70,7 @@ public void addShutdownHook(Thread thread) { }; }}; - new AsyncConnection(mockConnection, mockExecutorService); + new AsyncConnection(mockConnection, mockExecutorService, true); new VerificationsInOrder() {{ Raven.startManagingThread(); @@ -81,7 +96,7 @@ public void addShutdownHook(Thread thread) { result = new RuntimeException("Close operation failed"); }}; - new AsyncConnection(mockConnection, mockExecutorService); + new AsyncConnection(mockConnection, mockExecutorService, true); new Verifications() {{ Raven.stopManagingThread(); From bb868e0f15eed4edf30789d41341033828c46416 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 10:18:56 +1000 Subject: [PATCH 1203/2152] Declare the listener via a ServletContainerInitializer --- .../RavenServletContainerInitializer.java | 16 +++++++++ .../servlet/RavenServletRequestListener.java | 3 -- .../javax.servlet.ServletContainerInitializer | 1 + .../RavenServletContainerInitializerTest.java | 34 +++++++++++++++++++ 4 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 raven/src/main/java/net/kencochrane/raven/servlet/RavenServletContainerInitializer.java create mode 100644 raven/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer create mode 100644 raven/src/test/java/net/kencochrane/raven/servlet/RavenServletContainerInitializerTest.java diff --git a/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletContainerInitializer.java b/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletContainerInitializer.java new file mode 100644 index 00000000000..858a2702452 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletContainerInitializer.java @@ -0,0 +1,16 @@ +package net.kencochrane.raven.servlet; + +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import java.util.Set; + +/** + * Servlet container initializer used to add the {@link RavenServletRequestListener} to the {@link ServletContext}. + */ +public class RavenServletContainerInitializer implements ServletContainerInitializer { + @Override + public void onStartup(Set> c, ServletContext ctx) throws ServletException { + ctx.addListener(RavenServletRequestListener.class); + } +} diff --git a/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java b/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java index 367d729cf8e..1ca3dacb714 100644 --- a/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java +++ b/raven/src/main/java/net/kencochrane/raven/servlet/RavenServletRequestListener.java @@ -3,7 +3,6 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; -import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpServletRequest; /** @@ -11,8 +10,6 @@ * {@link net.kencochrane.raven.event.helper.HttpEventBuilderHelper} to provide details on the current HTTP session * in the event sent to Sentry. */ -//TODO: Consider Servlet < 3? -@WebListener public class RavenServletRequestListener implements ServletRequestListener { private static final ThreadLocal THREAD_REQUEST = new ThreadLocal<>(); diff --git a/raven/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/raven/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer new file mode 100644 index 00000000000..fea8bf2e132 --- /dev/null +++ b/raven/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer @@ -0,0 +1 @@ +net.kencochrane.raven.servlet.RavenServletContainerInitializer diff --git a/raven/src/test/java/net/kencochrane/raven/servlet/RavenServletContainerInitializerTest.java b/raven/src/test/java/net/kencochrane/raven/servlet/RavenServletContainerInitializerTest.java new file mode 100644 index 00000000000..14088395856 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/servlet/RavenServletContainerInitializerTest.java @@ -0,0 +1,34 @@ +package net.kencochrane.raven.servlet; + +import mockit.Injectable; +import mockit.Tested; +import mockit.Verifications; +import org.testng.annotations.Test; + +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import java.util.ServiceLoader; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.instanceOf; + +public class RavenServletContainerInitializerTest { + @Tested + private RavenServletContainerInitializer ravenServletContainerInitializer; + + @Test + public void testInitializerInjectedViaServiceLoader() throws Exception { + ServiceLoader serviceLoader = ServiceLoader.load(ServletContainerInitializer.class); + assertThat(serviceLoader, contains(instanceOf(RavenServletContainerInitializer.class))); + } + + @Test + public void testFilterAddedToServletContext(@Injectable final ServletContext mockServletContext) throws Exception{ + ravenServletContainerInitializer.onStartup(null, mockServletContext); + + new Verifications(){{ + mockServletContext.addListener(RavenServletRequestListener.class); + }}; + } +} From 93497016f6c04cfa292aa54d9326f2bf08b7066d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 10:46:14 +1000 Subject: [PATCH 1204/2152] Remove unecessary line --- raven/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/raven/README.md b/raven/README.md index 75e40fb9ea1..7240c28d0a4 100644 --- a/raven/README.md +++ b/raven/README.md @@ -20,7 +20,6 @@ Details in the [central Maven repository](https://search.maven.org/#artifactdeta ### Manual dependency management Relies on: - - [raven-5.0.jar](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C5.0%7Cjar) - [guava-17.0.jar](https://search.maven.org/#artifactdetails%7Ccom.google.guava%7Cguava%7C17.0%7Cjar) - [jackson-core-2.4.0.jar](https://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.4.0%7Cjar) - [slf4j-api-1.7.7.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.7%7Cjar) From 0988226404272490e946d6aba1f1b3b9c26e4c90 Mon Sep 17 00:00:00 2001 From: Marc O'Morain Date: Mon, 23 Jun 2014 10:52:07 +0100 Subject: [PATCH 1205/2152] Add a constructor to Dsn that take a URI Also improve the documentation --- .../java/net/kencochrane/raven/dsn/Dsn.java | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java index b59be58ca6f..6a91d75b925 100644 --- a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java @@ -40,22 +40,31 @@ public Dsn() { /** * Creates a DSN based on a String. * - * @param dsn dsn in a string form. - * @throws InvalidDsnException the given DSN isn't usable. + * @param dsn DSN in a string form. + * @throws InvalidDsnException the given DSN is not valid. */ public Dsn(String dsn) throws InvalidDsnException { + this(URI.create(dsn)); + } + + /** + * Creates a DSN based on a URI. + * + * @param dsn DSN in URI form. + * @throws InvalidDsnException the given DSN is not valid. + */ + public Dsn(URI dsn) throws InvalidDsnException { if (dsn == null) throw new InvalidDsnException("The sentry DSN must be provided and not be null"); options = new HashMap<>(); protocolSettings = new HashSet<>(); - URI dsnUri = URI.create(dsn); - extractProtocolInfo(dsnUri); - extractUserKeys(dsnUri); - extractHostInfo(dsnUri); - extractPathInfo(dsnUri); - extractOptions(dsnUri); + extractProtocolInfo(dsn); + extractUserKeys(dsn); + extractHostInfo(dsn); + extractPathInfo(dsn); + extractOptions(dsn); makeOptionsImmutable(); @@ -171,7 +180,7 @@ private void extractOptions(URI dsnUri) { } /** - * Makes protocol and dsn options immutable to allow an external usage. + * Makes protocol and dsn options immutable to allow external usage. */ private void makeOptionsImmutable() { // Make the options immutable From 2c89b6d4dd850a1ad6e528b31355a06eb7661d1e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 11:26:02 +1000 Subject: [PATCH 1206/2152] Add tests to ensure that DSN from URI is working --- .../net/kencochrane/raven/dsn/DsnTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java index 91deed1c7c6..3de30caa94f 100644 --- a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java @@ -8,6 +8,7 @@ import javax.naming.Context; import java.lang.reflect.Field; +import java.net.URI; import java.util.Collections; import java.util.Map; @@ -28,6 +29,11 @@ public void testEmptyDsnInvalid() throws Exception { new Dsn(""); } + @Test(expectedExceptions = InvalidDsnException.class) + public void testDsnFromInvalidUri() throws Exception { + new Dsn(URI.create("")); + } + @Test public void testSimpleDsnValid() throws Exception { Dsn dsn = new Dsn("http://publicKey:secretKey@host/9"); @@ -40,6 +46,18 @@ public void testSimpleDsnValid() throws Exception { assertThat(dsn.getProjectId(), is("9")); } + @Test + public void testSimpleDsnFromValidURI() throws Exception { + Dsn dsn = new Dsn(URI.create("http://publicKey:secretKey@host/9")); + + assertThat(dsn.getProtocol(), is("http")); + assertThat(dsn.getPublicKey(), is("publicKey")); + assertThat(dsn.getSecretKey(), is("secretKey")); + assertThat(dsn.getHost(), is("host")); + assertThat(dsn.getPath(), is("/")); + assertThat(dsn.getProjectId(), is("9")); + } + @Test public void testDsnLookupWithNothingSet() throws Exception { assertThat(Dsn.dsnLookup(), is(nullValue())); From 25431d53d0fc8bb526915ac0124b268aad4ee31d Mon Sep 17 00:00:00 2001 From: Marc O'Morain Date: Mon, 23 Jun 2014 10:52:07 +0100 Subject: [PATCH 1207/2152] Add @Override on the run method of ShutDownHook --- .../java/net/kencochrane/raven/connection/AsyncConnection.java | 1 + 1 file changed, 1 insertion(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 5f35b3cf8dd..71ca8e7c529 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -148,6 +148,7 @@ public void run() { } private final class ShutDownHook extends Thread { + @Override public void run() { try { // The current thread is managed by raven From 4a283afdeea6bb37cd3eb4478d31f705812c65b7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 12:32:18 +1000 Subject: [PATCH 1208/2152] Ensure that there is always a value for Dsn.toString --- raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java index 6a91d75b925..f9cef36cbb7 100644 --- a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java @@ -286,6 +286,8 @@ public int hashCode() { @Override public String toString() { - return getUri().toString(); + return "Dsn{" + + "uri=" + uri + + '}'; } } From 6557a5effdac721d3c54860b390957a3cd2f1a2a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 12:38:03 +1000 Subject: [PATCH 1209/2152] Add a toString method to Raven --- raven/src/main/java/net/kencochrane/raven/Raven.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index de2c2449ab9..8974e4d185a 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -169,4 +169,12 @@ public Connection getConnection() { public void setConnection(Connection connection) { this.connection = connection; } + + @Override + public String toString() { + return "Raven{" + + "name=" + NAME + + ", connection=" + connection + + '}'; + } } From d5f0fce508e2ebba5e60eba1af5a81f605769abe Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 12:39:26 +1000 Subject: [PATCH 1210/2152] Fix RavenFactoryTest to match the servlet test --- .../java/net/kencochrane/raven/DefaultRavenFactoryTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/DefaultRavenFactoryTest.java b/raven/src/test/java/net/kencochrane/raven/DefaultRavenFactoryTest.java index 4a69532abd1..10af02cb7b9 100644 --- a/raven/src/test/java/net/kencochrane/raven/DefaultRavenFactoryTest.java +++ b/raven/src/test/java/net/kencochrane/raven/DefaultRavenFactoryTest.java @@ -1,11 +1,11 @@ package net.kencochrane.raven; -import org.hamcrest.Matchers; import org.testng.annotations.Test; import java.util.ServiceLoader; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.instanceOf; public class DefaultRavenFactoryTest { @@ -13,6 +13,6 @@ public class DefaultRavenFactoryTest { public void checkServiceLoaderProvidesFactory() throws Exception { ServiceLoader ravenFactories = ServiceLoader.load(RavenFactory.class); - assertThat(ravenFactories, Matchers.hasItem(instanceOf(DefaultRavenFactory.class))); + assertThat(ravenFactories, contains(instanceOf(DefaultRavenFactory.class))); } } From ed9d43ce3a458203ba63d4ee6a3f7b203aa1b5df Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 12:40:02 +1000 Subject: [PATCH 1211/2152] Add a test to ensure that the ServiceLoader is used to load RavenFactories --- .../kencochrane/raven/RavenFactoryTest.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java b/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java new file mode 100644 index 00000000000..dcb2468d257 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java @@ -0,0 +1,45 @@ +package net.kencochrane.raven; + +import com.google.common.collect.Iterators; +import mockit.Injectable; +import mockit.Mocked; +import mockit.NonStrictExpectations; +import mockit.Tested; +import net.kencochrane.raven.dsn.Dsn; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.ServiceLoader; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class RavenFactoryTest { + @Tested + private RavenFactory ravenFactory; + @Mocked + private ServiceLoader mockServiceLoader; + + @BeforeMethod + public void setUp() throws Exception { + new NonStrictExpectations() {{ + ServiceLoader.load(RavenFactory.class); + result = mockServiceLoader; + }}; + } + + @Test + public void testGetFactoriesFromServiceLoader(@Injectable final Raven mockRaven, + @Injectable final Dsn mockDsn) throws Exception { + new NonStrictExpectations() {{ + mockServiceLoader.iterator(); + result = Iterators.singletonIterator(ravenFactory); + ravenFactory.createRavenInstance(mockDsn); + result = mockRaven; + }}; + + Raven raven = RavenFactory.ravenInstance(mockDsn); + + assertThat(raven, is(mockRaven)); + } +} From db00f4fbd9682364e114415626ff97c0d00cb4be Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 12:53:01 +1000 Subject: [PATCH 1212/2152] Add more toString methods --- .../main/java/net/kencochrane/raven/dsn/JndiLookup.java | 7 +++++++ .../java/net/kencochrane/raven/event/EventBuilder.java | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/dsn/JndiLookup.java b/raven/src/main/java/net/kencochrane/raven/dsn/JndiLookup.java index 5314dcab9e3..14545603c9d 100644 --- a/raven/src/main/java/net/kencochrane/raven/dsn/JndiLookup.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/JndiLookup.java @@ -44,4 +44,11 @@ public static String jndiLookup() { } return dsn; } + + @Override + public String toString() { + return "JndiLookup{" + + "JNDI_DSN_NAME='" + JNDI_DSN_NAME + '\'' + + '}'; + } } diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 38640e98b3f..51776ee6cbd 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -372,4 +372,12 @@ public String call() throws Exception { } } } + + @Override + public String toString() { + return "EventBuilder{" + + "event=" + event + + ", alreadyBuilt=" + alreadyBuilt + + '}'; + } } From 6a603b59dbe2cc7049c9221c25b9ea82887bb4ef Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 12:53:09 +1000 Subject: [PATCH 1213/2152] Set the buildname to a static value during tests --- .../net/kencochrane/raven/connection/AbstractConnectionTest.java | 1 - raven/src/test/resources/raven-build.properties | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 raven/src/test/resources/raven-build.properties diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java index d2d320514ea..e464cb650fc 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java @@ -32,7 +32,6 @@ public class AbstractConnectionTest { @BeforeMethod public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - setField(Raven.class, "NAME", "Raven-Java/Test"); // Reset tested abstractConnection = null; } diff --git a/raven/src/test/resources/raven-build.properties b/raven/src/test/resources/raven-build.properties new file mode 100644 index 00000000000..4b4cf051ab6 --- /dev/null +++ b/raven/src/test/resources/raven-build.properties @@ -0,0 +1 @@ +build.name=Raven-Java/Test From 15759707d4de986941f59d4e7523b8e8d28a9b0e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 12:56:12 +1000 Subject: [PATCH 1214/2152] Test that factories can be added manually --- .../net/kencochrane/raven/RavenFactoryTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java b/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java index dcb2468d257..2860e6276c5 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java @@ -42,4 +42,18 @@ public void testGetFactoriesFromServiceLoader(@Injectable final Raven mockRaven, assertThat(raven, is(mockRaven)); } + + @Test + public void testGetFactoriesManuallyAdded(@Injectable final Raven mockRaven, + @Injectable final Dsn mockDsn) throws Exception { + new NonStrictExpectations() {{ + RavenFactory.registerFactory(ravenFactory); + ravenFactory.createRavenInstance(mockDsn); + result = mockRaven; + }}; + + Raven raven = RavenFactory.ravenInstance(mockDsn); + + assertThat(raven, is(mockRaven)); + } } From 4e5ed0e7e7983f1b78861411a13692d92c2ff24d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 13:11:24 +1000 Subject: [PATCH 1215/2152] Check if the factory required name is used --- .../kencochrane/raven/RavenFactoryTest.java | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java b/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java index 2860e6276c5..be750c02f9e 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java @@ -25,6 +25,9 @@ public void setUp() throws Exception { new NonStrictExpectations() {{ ServiceLoader.load(RavenFactory.class); result = mockServiceLoader; + mockServiceLoader.iterator(); + result = Iterators.emptyIterator(); + }}; } @@ -45,7 +48,7 @@ public void testGetFactoriesFromServiceLoader(@Injectable final Raven mockRaven, @Test public void testGetFactoriesManuallyAdded(@Injectable final Raven mockRaven, - @Injectable final Dsn mockDsn) throws Exception { + @Injectable final Dsn mockDsn) throws Exception { new NonStrictExpectations() {{ RavenFactory.registerFactory(ravenFactory); ravenFactory.createRavenInstance(mockDsn); @@ -56,4 +59,32 @@ public void testGetFactoriesManuallyAdded(@Injectable final Raven mockRaven, assertThat(raven, is(mockRaven)); } + + @Test + public void testRavenInstanceForFactoryNameSucceedsIfFactoryFound(@Injectable final Raven mockRaven, + @Injectable final Dsn mockDsn) throws Exception { + String factoryName = ravenFactory.getClass().getName(); + new NonStrictExpectations() {{ + RavenFactory.registerFactory(ravenFactory); + ravenFactory.createRavenInstance(mockDsn); + result = mockRaven; + }}; + + Raven raven = RavenFactory.ravenInstance(mockDsn, factoryName); + + assertThat(raven, is(mockRaven)); + } + + @Test(expectedExceptions = IllegalStateException.class) + public void testRavenInstanceForFactoryNameFailsIfNoFactoryFound(@Injectable final Raven mockRaven, + @Injectable final Dsn mockDsn) throws Exception { + String factoryName = "invalidName"; + new NonStrictExpectations() {{ + RavenFactory.registerFactory(ravenFactory); + ravenFactory.createRavenInstance(mockDsn); + result = mockRaven; + }}; + + RavenFactory.ravenInstance(mockDsn, factoryName); + } } From 5cc2ac492b7d7b78a4689df24d873d59ca4af9d0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 14:11:12 +1000 Subject: [PATCH 1216/2152] Improve tests - Do not mock ServiceLoader entirely, just an injectable. - Inject the custom ServiceLoader in the RavenFactory class directly - Create a new Iterator every time the Iterator() method is called - Unregister manually registered factories - Do not register the factories in the expectations --- .../kencochrane/raven/RavenFactoryTest.java | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java b/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java index be750c02f9e..e498ebc91a4 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java @@ -1,42 +1,56 @@ package net.kencochrane.raven; import com.google.common.collect.Iterators; +import mockit.Delegate; import mockit.Injectable; -import mockit.Mocked; import mockit.NonStrictExpectations; import mockit.Tested; import net.kencochrane.raven.dsn.Dsn; +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import java.util.HashSet; +import java.util.Iterator; import java.util.ServiceLoader; +import static mockit.Deencapsulation.setField; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; public class RavenFactoryTest { @Tested private RavenFactory ravenFactory; - @Mocked + @Injectable private ServiceLoader mockServiceLoader; @BeforeMethod public void setUp() throws Exception { + setField(RavenFactory.class, "AUTO_REGISTERED_FACTORIES", mockServiceLoader); + new NonStrictExpectations() {{ - ServiceLoader.load(RavenFactory.class); - result = mockServiceLoader; mockServiceLoader.iterator(); result = Iterators.emptyIterator(); - }}; } + @AfterMethod + public void tearDown() throws Exception { + // Reset the registered factories + setField(RavenFactory.class, "MANUALLY_REGISTERED_FACTORIES", new HashSet<>()); + setField(RavenFactory.class, "AUTO_REGISTERED_FACTORIES", ServiceLoader.load(RavenFactory.class)); + } + @Test public void testGetFactoriesFromServiceLoader(@Injectable final Raven mockRaven, @Injectable final Dsn mockDsn) throws Exception { new NonStrictExpectations() {{ mockServiceLoader.iterator(); - result = Iterators.singletonIterator(ravenFactory); + result = new Delegate>() { + public Iterator iterator() { + return Iterators.singletonIterator(ravenFactory); + } + }; ravenFactory.createRavenInstance(mockDsn); result = mockRaven; }}; @@ -49,8 +63,8 @@ public void testGetFactoriesFromServiceLoader(@Injectable final Raven mockRaven, @Test public void testGetFactoriesManuallyAdded(@Injectable final Raven mockRaven, @Injectable final Dsn mockDsn) throws Exception { + RavenFactory.registerFactory(ravenFactory); new NonStrictExpectations() {{ - RavenFactory.registerFactory(ravenFactory); ravenFactory.createRavenInstance(mockDsn); result = mockRaven; }}; @@ -62,10 +76,10 @@ public void testGetFactoriesManuallyAdded(@Injectable final Raven mockRaven, @Test public void testRavenInstanceForFactoryNameSucceedsIfFactoryFound(@Injectable final Raven mockRaven, - @Injectable final Dsn mockDsn) throws Exception { + @Injectable final Dsn mockDsn) throws Exception { String factoryName = ravenFactory.getClass().getName(); + RavenFactory.registerFactory(ravenFactory); new NonStrictExpectations() {{ - RavenFactory.registerFactory(ravenFactory); ravenFactory.createRavenInstance(mockDsn); result = mockRaven; }}; @@ -77,10 +91,10 @@ public void testRavenInstanceForFactoryNameSucceedsIfFactoryFound(@Injectable fi @Test(expectedExceptions = IllegalStateException.class) public void testRavenInstanceForFactoryNameFailsIfNoFactoryFound(@Injectable final Raven mockRaven, - @Injectable final Dsn mockDsn) throws Exception { + @Injectable final Dsn mockDsn) throws Exception { String factoryName = "invalidName"; + RavenFactory.registerFactory(ravenFactory); new NonStrictExpectations() {{ - RavenFactory.registerFactory(ravenFactory); ravenFactory.createRavenInstance(mockDsn); result = mockRaven; }}; From 9c218cc44d13031176ccbbf0385a2c60498c606d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 14:36:37 +1000 Subject: [PATCH 1217/2152] Add a toString method to RavenFactory --- .../src/main/java/net/kencochrane/raven/RavenFactory.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index 9b068cba653..edc8cdd48dc 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -103,4 +103,11 @@ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { * @throws RuntimeException when an instance couldn't be created. */ public abstract Raven createRavenInstance(Dsn dsn); + + @Override + public String toString() { + return "RavenFactory{" + + "name='" + this.getClass().getName() + '\'' + + '}'; + } } From c629b41f4d255757d381f8b998a362ba1163cf2e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 14:36:54 +1000 Subject: [PATCH 1218/2152] Add a test to check that Raven creation failures are caught --- .../kencochrane/raven/RavenFactoryTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java b/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java index e498ebc91a4..b32c73b41ec 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java @@ -16,6 +16,7 @@ import static mockit.Deencapsulation.setField; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; public class RavenFactoryTest { @@ -101,4 +102,22 @@ public void testRavenInstanceForFactoryNameFailsIfNoFactoryFound(@Injectable fin RavenFactory.ravenInstance(mockDsn, factoryName); } + + @Test + public void testRavenInstantiationFailureCaught(@Injectable final Dsn mockDsn) throws Exception { + RavenFactory.registerFactory(ravenFactory); + Exception exception = null; + new NonStrictExpectations() {{ + ravenFactory.createRavenInstance(mockDsn); + result = new RuntimeException(); + }}; + + try { + RavenFactory.ravenInstance(mockDsn); + } catch (IllegalStateException e) { + exception = e; + } + + assertThat(exception, notNullValue()); + } } From adf079b8bbbce2f7dd774b4fa9774370c01a79d3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 14:37:20 +1000 Subject: [PATCH 1219/2152] Add a line to ensure that the consoleHandler will print everything that is logged --- raven/src/test/resources/logging-test.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/raven/src/test/resources/logging-test.properties b/raven/src/test/resources/logging-test.properties index c1ed0e86734..fc4bb62f3e2 100644 --- a/raven/src/test/resources/logging-test.properties +++ b/raven/src/test/resources/logging-test.properties @@ -2,4 +2,5 @@ handlers=java.util.logging.ConsoleHandler .level=OFF java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +java.util.logging.ConsoleHandler.level=ALL java.util.logging.SimpleFormatter.format=[RAVEN] [%4$.4s] %3$s - %5$s%n From 710646e7354cbe51bb45d25839eae366d0a386cc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 14:39:22 +1000 Subject: [PATCH 1220/2152] Fix typo --- raven/src/main/java/net/kencochrane/raven/Raven.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 8974e4d185a..5c7087716e7 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -144,7 +144,7 @@ public void sendException(Exception exception) { * @param builderHelper builder helper to remove. */ public void removeBuilderHelper(EventBuilderHelper builderHelper) { - logger.info("Removes '{}' to the list of builder helpers.", builderHelper); + logger.info("Removing '{}' from the list of builder helpers.", builderHelper); builderHelpers.remove(builderHelper); } From 86e1b92f69fde7f7e234df7fe6a469e8bd8a6a82 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 14:40:54 +1000 Subject: [PATCH 1221/2152] Add log level all to the integration tests --- raven/src/test/resources/logging-integration.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/raven/src/test/resources/logging-integration.properties b/raven/src/test/resources/logging-integration.properties index 7a14989f7c5..1e5b8ebb18f 100644 --- a/raven/src/test/resources/logging-integration.properties +++ b/raven/src/test/resources/logging-integration.properties @@ -5,6 +5,7 @@ net.kencochrane.raven.stub.handlers=java.util.logging.ConsoleHandler net.kencochrane.raven.stub.level=ALL java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +java.util.logging.ConsoleHandler.level=ALL java.util.logging.SimpleFormatter.format=[RAVEN] [%4$.4s] %3$s - %5$s%n net.kencochrane.raven.jul.SentryHandler.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false From 7f3105d6d292db95abca7f754b08148837a965ed Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 23:43:47 +1000 Subject: [PATCH 1222/2152] Replace the getConnection method with closeConnection --- .../kencochrane/raven/log4j/SentryAppender.java | 14 ++++++++------ .../raven/log4j/SentryAppenderCloseTest.java | 12 +++--------- .../kencochrane/raven/log4j2/SentryAppender.java | 11 ++++++----- .../raven/log4j2/SentryAppenderCloseTest.java | 8 +------- .../kencochrane/raven/logback/SentryAppender.java | 11 ++++++----- .../raven/logback/SentryAppenderCloseTest.java | 6 +----- .../src/main/java/net/kencochrane/raven/Raven.java | 12 ++++++++++-- .../net/kencochrane/raven/jul/SentryHandler.java | 7 +++++-- 8 files changed, 40 insertions(+), 41 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 465683fe831..391efaf5a03 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -216,16 +216,18 @@ public void setTags(String tags) { @Override public void close() { - if (this.closed) - return; - this.closed = true; - try { + Raven.startManagingThread(); + if (this.closed) + return; + this.closed = true; if (raven != null) - raven.getConnection().close(); - } catch (IOException e) { + raven.closeConnection(); + } catch (Exception e) { getErrorHandler().error("An exception occurred while closing the Raven connection", e, ErrorCode.CLOSE_FAILURE); + } finally { + Raven.stopManagingThread(); } } diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java index fc9c333bac0..248f9d5c9c4 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java @@ -15,8 +15,6 @@ public class SentryAppenderCloseTest { private MockUpErrorHandler mockUpErrorHandler; @Injectable private Raven mockRaven = null; - @Injectable - private Connection mockConnection = null; @Mocked("ravenInstance") private RavenFactory mockRavenFactory; @Mocked("dsnLookup") @@ -25,10 +23,6 @@ public class SentryAppenderCloseTest { @BeforeMethod public void setUp() throws Exception { mockUpErrorHandler = new MockUpErrorHandler(); - new NonStrictExpectations() {{ - mockRaven.getConnection(); - result = mockConnection; - }}; } private void assertNoErrorsInErrorHandler() throws Exception { @@ -44,7 +38,7 @@ public void testConnectionClosedWhenAppenderClosed() throws Exception { sentryAppender.close(); new Verifications() {{ - mockConnection.close(); + mockRaven.closeConnection(); }}; assertNoErrorsInErrorHandler(); } @@ -65,7 +59,7 @@ public void testClosedIfRavenInstanceNotProvided() throws Exception { sentryAppender.close(); new Verifications() {{ - mockConnection.close(); + mockRaven.closeConnection(); }}; assertNoErrorsInErrorHandler(); } @@ -107,7 +101,7 @@ public void testCloseDoNotFailWhenMultipleCalls() throws Exception { sentryAppender.close(); new Verifications() {{ - mockConnection.close(); + mockRaven.closeConnection(); times = 1; }}; assertNoErrorsInErrorHandler(); diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index e6c86600521..406bfdc26a0 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -20,7 +20,6 @@ import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.message.Message; -import java.io.IOException; import java.util.*; /** @@ -275,13 +274,15 @@ public void setTags(String tags) { @Override public void stop() { - super.stop(); - try { + Raven.startManagingThread(); + super.stop(); if (raven != null) - raven.getConnection().close(); - } catch (IOException e) { + raven.closeConnection(); + } catch (Exception e) { error("An exception occurred while closing the Raven connection", e); + } finally { + Raven.stopManagingThread(); } } } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java index 27494c93f7b..02c37d0afd0 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java @@ -14,17 +14,11 @@ public class SentryAppenderCloseTest { private MockUpErrorHandler mockUpErrorHandler; @Injectable - private Connection mockConnection = null; - @Injectable private Raven mockRaven = null; @BeforeMethod public void setUp() throws Exception { mockUpErrorHandler = new MockUpErrorHandler(); - new NonStrictExpectations() {{ - mockRaven.getConnection(); - result = mockConnection; - }}; } private void assertNoErrorsInErrorHandler() throws Exception { @@ -40,7 +34,7 @@ public void testConnectionClosedWhenAppenderStopped() throws Exception { sentryAppender.stop(); new Verifications() {{ - mockConnection.close(); + mockRaven.closeConnection(); }}; assertNoErrorsInErrorHandler(); } diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 7081726347d..10427ca4827 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -17,7 +17,6 @@ import net.kencochrane.raven.event.interfaces.SentryException; import net.kencochrane.raven.event.interfaces.StackTraceInterface; -import java.io.IOException; import java.util.*; /** @@ -288,13 +287,15 @@ public void setTags(String tags) { @Override public void stop() { - super.stop(); - try { + Raven.startManagingThread(); + super.stop(); if (raven != null) - raven.getConnection().close(); - } catch (IOException e) { + raven.closeConnection(); + } catch (Exception e) { addError("An exception occurred while closing the Raven connection", e); + } finally { + Raven.stopManagingThread(); } } } diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java index fad155d4c70..a8a429e417e 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java @@ -19,8 +19,6 @@ public class SentryAppenderCloseTest { private Raven mockRaven = null; @Injectable private Context mockContext = null; - @Injectable - private Connection mockConnection = null; @BeforeMethod public void setUp() throws Exception { @@ -33,8 +31,6 @@ public void setUp() throws Exception { mockContext.getStatusManager(); result = statusManager; - mockRaven.getConnection(); - result = mockConnection; }}; } @@ -50,7 +46,7 @@ public void testConnectionClosedWhenAppenderStopped() throws Exception { sentryAppender.stop(); new Verifications() {{ - mockConnection.close(); + mockRaven.closeConnection(); }}; assertNoErrorsInStatusManager(); } diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 5c7087716e7..bf9015de9e4 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -8,6 +8,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.ResourceBundle; @@ -162,8 +163,15 @@ public Set getBuilderHelpers() { return Collections.unmodifiableSet(builderHelpers); } - public Connection getConnection() { - return connection; + /** + * Closes the connection for the Raven instance. + */ + public void closeConnection() { + try { + connection.close(); + } catch (IOException e) { + throw new RuntimeException("Couldn't close the Raven connection", e); + } } public void setConnection(Connection connection) { diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 80b95b1f7d2..2987090ca82 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -197,10 +197,13 @@ public void flush() { @Override public void close() throws SecurityException { try { + Raven.startManagingThread(); if (raven != null) - raven.getConnection().close(); - } catch (IOException e) { + raven.closeConnection(); + } catch (Exception e) { reportError("An exception occurred while closing the Raven connection", e, ErrorManager.CLOSE_FAILURE); + } finally { + Raven.stopManagingThread(); } } } From 2bc2ef7b0817257682a6fa7745bf867dc5a1c9d3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 23:44:13 +1000 Subject: [PATCH 1223/2152] Test Thread management --- .../java/net/kencochrane/raven/RavenTest.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index cbf9a750887..7b61bf8d695 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -1,12 +1,14 @@ package net.kencochrane.raven; import mockit.Injectable; +import mockit.NonStrictExpectations; import mockit.Verifications; import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.helper.EventBuilderHelper; import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -26,6 +28,11 @@ public void setUp() throws Exception { raven.setConnection(mockConnection); } + @AfterMethod + public void tearDown() throws Exception { + Raven.stopManagingThread(); + } + @Test public void testSendEvent() throws Exception { raven.sendEvent(mockEvent); @@ -35,6 +42,19 @@ public void testSendEvent() throws Exception { }}; } + @Test + public void testSendEventFailingIsCaught() throws Exception { + new NonStrictExpectations(){{ + mockConnection.send((Event) any); + result = new RuntimeException(); + }}; + raven.sendEvent(mockEvent); + + new Verifications() {{ + mockConnection.send(mockEvent); + }}; + } + @Test public void testSendMessage() throws Exception { final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; @@ -104,4 +124,40 @@ public void testRunBuilderHelpers(@Injectable final EventBuilderHelper mockBuild mockBuilderHelper.helpBuildingEvent(mockEventBuilder); }}; } + + @Test + public void testThreadNotManagedByDefault() throws Exception { + assertThat(Raven.isManagingThread(), is(false)); + } + + @Test + public void testStartManagingThreadWorks() throws Exception { + assertThat(Raven.isManagingThread(), is(false)); + Raven.startManagingThread(); + assertThat(Raven.isManagingThread(), is(true)); + } + + @Test + public void testStartManagingAlreadyManagedThreadWorks() throws Exception { + Raven.startManagingThread(); + assertThat(Raven.isManagingThread(), is(true)); + Raven.startManagingThread(); + assertThat(Raven.isManagingThread(), is(true)); + } + + @Test + public void testStopManagingThreadWorks() throws Exception { + Raven.startManagingThread(); + assertThat(Raven.isManagingThread(), is(true)); + Raven.stopManagingThread(); + assertThat(Raven.isManagingThread(), is(false)); + } + + @Test + public void testStopManagingNonManagedThreadWorks() throws Exception { + Raven.stopManagingThread(); + assertThat(Raven.isManagingThread(), is(false)); + Raven.stopManagingThread(); + assertThat(Raven.isManagingThread(), is(false)); + } } From d5ffb8ddc3ee77c01cbf4b9b283d5e0a012453ac Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 28 Jun 2014 23:44:26 +1000 Subject: [PATCH 1224/2152] Avoid code repetition --- .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 2987090ca82..3db51161eed 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -103,9 +103,10 @@ protected static List formatMessageParameters(Object[] parameters) { */ protected void retrieveProperties() { LogManager manager = LogManager.getLogManager(); - dsn = manager.getProperty(SentryHandler.class.getName() + ".dsn"); - ravenFactory = manager.getProperty(SentryHandler.class.getName() + ".ravenFactory"); - String tagsProperty = manager.getProperty(SentryHandler.class.getName() + ".tags"); + String className = SentryHandler.class.getName(); + dsn = manager.getProperty(className + ".dsn"); + ravenFactory = manager.getProperty(className + ".ravenFactory"); + String tagsProperty = manager.getProperty(className + ".tags"); if (tagsProperty != null) tags = Splitter.on(",").withKeyValueSeparator(":").split(tagsProperty); } From 196a784397a9700980a27d287784c7b685525397 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 29 Jun 2014 00:21:15 +1000 Subject: [PATCH 1225/2152] Enable reentrant thread management This should avoid problems where an already locked thread could be unlocked by mistake --- .../java/net/kencochrane/raven/Raven.java | 13 +++++----- .../java/net/kencochrane/raven/RavenTest.java | 24 +++++++++++++++---- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index bf9015de9e4..b3fc13fdf67 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -13,6 +13,7 @@ import java.util.HashSet; import java.util.ResourceBundle; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; /** * Raven is a client for Sentry allowing to send an {@link Event} that will be processed and sent to a Sentry server. @@ -31,10 +32,10 @@ public class Raven { /** * Indicates whether the current thread is managed by raven or not. */ - private static final ThreadLocal RAVEN_THREAD = new ThreadLocal() { + private static final ThreadLocal RAVEN_THREAD = new ThreadLocal() { @Override - protected Boolean initialValue() { - return false; + protected AtomicInteger initialValue() { + return new AtomicInteger(); } }; private static final Logger logger = LoggerFactory.getLogger(Raven.class); @@ -52,7 +53,7 @@ public static void startManagingThread() { if (isManagingThread()) logger.warn("Thread already managed by Raven"); } finally { - RAVEN_THREAD.set(true); + RAVEN_THREAD.get().incrementAndGet(); } } @@ -70,7 +71,7 @@ public static void stopManagingThread() { logger.warn("Thread not yet managed by Raven"); } } finally { - RAVEN_THREAD.remove(); + RAVEN_THREAD.get().decrementAndGet(); } } @@ -80,7 +81,7 @@ public static void stopManagingThread() { * @return {@code true} if the thread is managed by Raven, {@code false} otherwise. */ public static boolean isManagingThread() { - return RAVEN_THREAD.get(); + return RAVEN_THREAD.get().get() > 0; } /** diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index 7b61bf8d695..7d8165f3ee3 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -12,6 +12,9 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import java.util.concurrent.atomic.AtomicInteger; + +import static mockit.Deencapsulation.getField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -30,7 +33,8 @@ public void setUp() throws Exception { @AfterMethod public void tearDown() throws Exception { - Raven.stopManagingThread(); + ThreadLocal ravenThread = getField(Raven.class, "RAVEN_THREAD"); + ravenThread.remove(); } @Test @@ -132,31 +136,43 @@ public void testThreadNotManagedByDefault() throws Exception { @Test public void testStartManagingThreadWorks() throws Exception { - assertThat(Raven.isManagingThread(), is(false)); Raven.startManagingThread(); + assertThat(Raven.isManagingThread(), is(true)); } @Test public void testStartManagingAlreadyManagedThreadWorks() throws Exception { Raven.startManagingThread(); - assertThat(Raven.isManagingThread(), is(true)); + Raven.startManagingThread(); + assertThat(Raven.isManagingThread(), is(true)); } @Test public void testStopManagingThreadWorks() throws Exception { Raven.startManagingThread(); - assertThat(Raven.isManagingThread(), is(true)); + Raven.stopManagingThread(); + assertThat(Raven.isManagingThread(), is(false)); } @Test public void testStopManagingNonManagedThreadWorks() throws Exception { Raven.stopManagingThread(); + assertThat(Raven.isManagingThread(), is(false)); + } + + @Test + public void testThreadManagedTwiceNeedsToBeUnmanagedTwice() throws Exception { + Raven.startManagingThread(); + Raven.startManagingThread(); + + Raven.stopManagingThread(); + assertThat(Raven.isManagingThread(), is(true)); Raven.stopManagingThread(); assertThat(Raven.isManagingThread(), is(false)); } From 307c9671e335eab3c75cfc84b77713df6d0f51a3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 29 Jun 2014 00:58:46 +1000 Subject: [PATCH 1226/2152] Use @Tested and initialise the mocks to null --- .../net/kencochrane/raven/RavenFactoryTest.java | 4 ++-- .../java/net/kencochrane/raven/RavenTest.java | 14 +++++--------- .../connection/AbstractConnectionTest.java | 5 +---- .../raven/connection/AsyncConnectionTest.java | 10 +++++----- .../raven/connection/HttpConnectionTest.java | 13 ++++++------- .../raven/connection/UdpConnectionTest.java | 11 +++-------- .../java/net/kencochrane/raven/dsn/DsnTest.java | 2 +- .../event/EventBuilderHostnameCacheTest.java | 6 +++--- .../raven/event/EventBuilderTest.java | 2 +- .../helper/HttpEventBuilderHelperTest.java | 17 +++++------------ .../event/interfaces/HttpInterfaceTest.java | 4 ++-- .../event/interfaces/SentryExceptionTest.java | 2 +- .../jul/SentryHandlerEventBuildingTest.java | 10 +++------- .../json/ExceptionInterfaceBindingTest.java | 10 +++++----- .../marshaller/json/JsonMarshallerTest.java | 12 ++++++------ .../json/MessageInterfaceBindingTest.java | 11 ++++------- .../json/StackTraceInterfaceBindingTest.java | 11 ++++------- .../RavenServletContainerInitializerTest.java | 2 +- 18 files changed, 58 insertions(+), 88 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java b/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java index b32c73b41ec..842ce83efdb 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java @@ -21,9 +21,9 @@ public class RavenFactoryTest { @Tested - private RavenFactory ravenFactory; + private RavenFactory ravenFactory = null; @Injectable - private ServiceLoader mockServiceLoader; + private ServiceLoader mockServiceLoader = null; @BeforeMethod public void setUp() throws Exception { diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index 7d8165f3ee3..3b76cbc9484 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -2,6 +2,7 @@ import mockit.Injectable; import mockit.NonStrictExpectations; +import mockit.Tested; import mockit.Verifications; import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.event.Event; @@ -19,17 +20,12 @@ import static org.hamcrest.Matchers.*; public class RavenTest { - private Raven raven; + @Tested + private Raven raven = null; @Injectable - private Connection mockConnection; + private Connection mockConnection = null; @Injectable - private Event mockEvent; - - @BeforeMethod - public void setUp() throws Exception { - raven = new Raven(); - raven.setConnection(mockConnection); - } + private Event mockEvent = null; @AfterMethod public void tearDown() throws Exception { diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java index e464cb650fc..25d26716a55 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java @@ -4,7 +4,6 @@ import mockit.NonStrictExpectations; import mockit.Tested; import mockit.Verifications; -import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import org.mockito.MockitoAnnotations; import org.mockito.Spy; @@ -24,7 +23,7 @@ public class AbstractConnectionTest { @Injectable private final String secretKey = "56a9d05e-9032-4fdd-8f67-867d526422f9"; @Tested - private AbstractConnection abstractConnection; + private AbstractConnection abstractConnection = null; //Spying with mockito as jMockit doesn't support mocks of ReentrantLock @Spy private ReentrantLock reentrantLock = new ReentrantLock(); @@ -32,8 +31,6 @@ public class AbstractConnectionTest { @BeforeMethod public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - // Reset tested - abstractConnection = null; } @Test diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index e240a163b71..c9dbf739c27 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -12,15 +12,15 @@ public class AsyncConnectionTest { @Tested - private AsyncConnection asyncConnection; + private AsyncConnection asyncConnection = null; @Injectable - private Connection mockConnection; + private Connection mockConnection = null; @Injectable - private ExecutorService mockExecutorService; + private ExecutorService mockExecutorService = null; @Injectable("false") - private boolean gracefulShutdown; + private boolean mockGracefulShutdown = false; @Mocked("addShutdownHook") - private Runtime mockRuntime; + private Runtime mockRuntime = null; @BeforeMethod public void setUp() throws Exception { diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index df6241dea29..0c0faf74692 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -26,21 +26,20 @@ public class HttpConnectionTest { @Injectable private final String secretKey = "e30cca23-3f97-470b-a8c2-e29b33dd25e0"; @Tested - private HttpConnection httpConnection; + private HttpConnection httpConnection = null; @Injectable - private HttpsURLConnection mockUrlConnection; + private HttpsURLConnection mockUrlConnection = null; @Injectable - private Marshaller mockMarshaller; + private Marshaller mockMarshaller = null; @Injectable - private URL mockUrl; + private URL mockUrl = null; @Injectable - private OutputStream mockOutputStream; + private OutputStream mockOutputStream = null; @Injectable - private InputStream mockInputStream; + private InputStream mockInputStream = null; @BeforeMethod public void setUp() throws Exception { - httpConnection = null; new NonStrictExpectations() {{ mockUrl.openConnection(); result = mockUrlConnection; diff --git a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java index e3a34896916..037c25211d9 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java @@ -22,16 +22,11 @@ public class UdpConnectionTest { @Injectable private final String secretKey = "1de38091-6e8c-42df-8298-cf7f8098617a"; @Tested - private UdpConnection udpConnection; + private UdpConnection udpConnection = null; @Injectable - private Marshaller mockMarshaller; + private Marshaller mockMarshaller = null; @Mocked - private DatagramSocket mockDatagramSocket; - - @BeforeMethod - public void setUp() throws Exception { - udpConnection = null; - } + private DatagramSocket mockDatagramSocket = null; @Test public void testConnectionWorkingWithProperHost(@Injectable("customHostname") final String mockHostname, diff --git a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java index 3de30caa94f..8a0c108f8dd 100644 --- a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java @@ -17,7 +17,7 @@ public class DsnTest { @Mocked - private Context mockContext; + private Context mockContext = null; @BeforeMethod public void setUp() throws Exception { diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java index 81147c0f3b1..81a24da4258 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java @@ -15,11 +15,11 @@ @Test(singleThreaded = true) public class EventBuilderHostnameCacheTest { @Injectable - private InetAddress mockLocalHost; + private InetAddress mockLocalHost = null; @Injectable("serverName") - private String mockLocalHostName; + private String mockLocalHostName = null; @Injectable - private InetAddress mockTimingOutLocalHost; + private InetAddress mockTimingOutLocalHost = null; private static void resetHostnameCache() { setField(getHostnameCache(), "expirationTimestamp", 0L); diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index bc5568ac52a..4efa42d9dd8 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -18,7 +18,7 @@ public class EventBuilderTest { @Injectable - private InetAddress mockLocalHost; + private InetAddress mockLocalHost = null; private static void resetHostnameCache() { setField(getHostnameCache(), "expirationTimestamp", 0l); diff --git a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java index 296c813a7f8..e27baf03e73 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java @@ -1,9 +1,6 @@ package net.kencochrane.raven.event.helper; -import mockit.Injectable; -import mockit.Mocked; -import mockit.NonStrictExpectations; -import mockit.Verifications; +import mockit.*; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.HttpInterface; import net.kencochrane.raven.event.interfaces.SentryInterface; @@ -14,16 +11,12 @@ import javax.servlet.http.HttpServletRequest; public class HttpEventBuilderHelperTest { - private HttpEventBuilderHelper httpEventBuilderHelper; + @Tested + private HttpEventBuilderHelper httpEventBuilderHelper = null; @Injectable - private EventBuilder mockEventBuilder; + private EventBuilder mockEventBuilder = null; @Mocked - private HttpInterface mockHttpInterface; - - @BeforeMethod - public void setUp() throws Exception { - httpEventBuilderHelper = new HttpEventBuilderHelper(); - } + private HttpInterface mockHttpInterface = null; @Test public void testNoRequest() throws Exception { diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java index 0c22866adbf..bb9eab93b2d 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/HttpInterfaceTest.java @@ -15,9 +15,9 @@ public class HttpInterfaceTest { @Injectable - private HttpServletRequest mockHttpServletRequest; + private HttpServletRequest mockHttpServletRequest = null; @Injectable - private Cookie mockCookie; + private Cookie mockCookie = null; @BeforeMethod public void setUp() throws Exception { diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java index 18cf4bdebbf..5493ecb72b3 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java @@ -12,7 +12,7 @@ public class SentryExceptionTest { @Injectable - private Throwable mockThrowable; + private Throwable mockThrowable = null; @Test public void ensureConversionToQueueKeepsOrder(@Injectable final Throwable mockCause) throws Exception { diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java index d5f7901c2f7..d1bf00bdb0c 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java @@ -1,6 +1,7 @@ package net.kencochrane.raven.jul; import mockit.Injectable; +import mockit.Tested; import mockit.Verifications; import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; @@ -21,18 +22,13 @@ import static org.hamcrest.Matchers.is; public class SentryHandlerEventBuildingTest { - private SentryHandler sentryHandler; + @Tested + private SentryHandler sentryHandler = null; @Injectable private ErrorManager errorManager = null; @Injectable private Raven mockRaven = null; - @BeforeMethod - public void setUp() throws Exception { - sentryHandler = new SentryHandler(mockRaven); - sentryHandler.setErrorManager(errorManager); - } - private void assertNoErrorsInErrorManager() throws Exception { new Verifications() {{ errorManager.error(anyString, (Exception) any, anyInt); diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index 609f2c21c1a..b07cd0b1609 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -4,6 +4,7 @@ import mockit.Delegate; import mockit.Injectable; import mockit.NonStrictExpectations; +import mockit.Tested; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.SentryException; import net.kencochrane.raven.event.interfaces.StackTraceInterface; @@ -19,16 +20,15 @@ import static org.hamcrest.Matchers.is; public class ExceptionInterfaceBindingTest { - private ExceptionInterfaceBinding interfaceBinding; + @Tested + private ExceptionInterfaceBinding interfaceBinding = null; @Injectable - private ExceptionInterface mockExceptionInterface; + private ExceptionInterface mockExceptionInterface = null; @Injectable - private InterfaceBinding mockStackTraceInterfaceBinding; + private InterfaceBinding mockStackTraceInterfaceBinding = null; @BeforeMethod public void setUp() throws Exception { - interfaceBinding = new ExceptionInterfaceBinding(mockStackTraceInterfaceBinding); - new NonStrictExpectations() {{ mockStackTraceInterfaceBinding.writeInterface(withInstanceOf(JsonGenerator.class), (StackTraceInterface) any); result = new Delegate() { diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index 239e04ed772..888a8b3eade 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -2,10 +2,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.google.common.base.Charsets; -import mockit.Delegate; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Verifications; +import mockit.*; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.interfaces.SentryInterface; import org.testng.annotations.BeforeMethod; @@ -24,14 +21,17 @@ import static org.hamcrest.Matchers.is; public class JsonMarshallerTest { - private final JsonMarshaller jsonMarshaller = new JsonMarshaller(); + @Tested + private JsonMarshaller jsonMarshaller = null; @Injectable - private Event mockEvent; + private Event mockEvent = null; @BeforeMethod public void setUp() throws Exception { + jsonMarshaller = new JsonMarshaller(); // Do not compress by default during the tests jsonMarshaller.setCompression(false); + new NonStrictExpectations() {{ mockEvent.getId(); result = UUID.fromString("00000000-0000-0000-0000-000000000000"); diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java index ee4f71eb32f..c893fcc1746 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java @@ -2,6 +2,7 @@ import mockit.Injectable; import mockit.NonStrictExpectations; +import mockit.Tested; import net.kencochrane.raven.event.interfaces.MessageInterface; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -14,14 +15,10 @@ import static org.hamcrest.Matchers.is; public class MessageInterfaceBindingTest { - private MessageInterfaceBinding interfaceBinding; + @Tested + private MessageInterfaceBinding interfaceBinding = null; @Injectable - private MessageInterface mockMessageInterface; - - @BeforeMethod - public void setUp() throws Exception { - interfaceBinding = new MessageInterfaceBinding(); - } + private MessageInterface mockMessageInterface = null; @Test public void testSimpleMessage() throws Exception { diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java index a986a982aed..bd38a6b93c8 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java @@ -2,6 +2,7 @@ import mockit.Injectable; import mockit.NonStrictExpectations; +import mockit.Tested; import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -11,14 +12,10 @@ import static org.hamcrest.Matchers.is; public class StackTraceInterfaceBindingTest { - private StackTraceInterfaceBinding interfaceBinding; + @Tested + private StackTraceInterfaceBinding interfaceBinding = null; @Injectable - private StackTraceInterface mockStackTraceInterface; - - @BeforeMethod - public void setUp() throws Exception { - interfaceBinding = new StackTraceInterfaceBinding(); - } + private StackTraceInterface mockStackTraceInterface = null; @Test public void testSingleStackFrame() throws Exception { diff --git a/raven/src/test/java/net/kencochrane/raven/servlet/RavenServletContainerInitializerTest.java b/raven/src/test/java/net/kencochrane/raven/servlet/RavenServletContainerInitializerTest.java index 14088395856..1164206054d 100644 --- a/raven/src/test/java/net/kencochrane/raven/servlet/RavenServletContainerInitializerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/servlet/RavenServletContainerInitializerTest.java @@ -15,7 +15,7 @@ public class RavenServletContainerInitializerTest { @Tested - private RavenServletContainerInitializer ravenServletContainerInitializer; + private RavenServletContainerInitializer ravenServletContainerInitializer = null; @Test public void testInitializerInjectedViaServiceLoader() throws Exception { From f62504300ae2f05a76ce0aea65eb99655ae4fb76 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 29 Jun 2014 02:02:14 +1000 Subject: [PATCH 1227/2152] Use @Tested when possible and force init to null --- .../connection/AppEngineAsyncConnectionTest.java | 11 ++++++----- .../event/helper/AppEngineEventBuilderHelperTest.java | 10 +++++----- .../raven/log4j/SentryAppenderCloseTest.java | 4 ++-- .../raven/log4j/SentryAppenderDsnTest.java | 8 +++++--- .../raven/log4j/SentryAppenderEventBuildingTest.java | 8 +++----- .../raven/log4j/SentryAppenderFailuresTest.java | 10 ++++------ .../raven/log4j2/SentryAppenderEventBuildingTest.java | 4 +++- .../raven/log4j2/SentryAppenderFailuresTest.java | 2 +- .../raven/logback/SentryAppenderDsnTest.java | 4 ++-- 9 files changed, 31 insertions(+), 30 deletions(-) diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java index 7f5dbbbfd9b..0ea4becf8f9 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java @@ -21,15 +21,16 @@ import static org.hamcrest.Matchers.*; public class AppEngineAsyncConnectionTest { - private AppEngineAsyncConnection asyncConnection; + @Tested + private AppEngineAsyncConnection asyncConnection = null; @Injectable - private Connection mockConnection; + private Connection mockConnection = null; @Injectable - private Queue mockQueue; + private Queue mockQueue = null; @Mocked("getDefaultQueue") - private QueueFactory queueFactory; + private QueueFactory queueFactory = null; @Injectable("7b55a129-6975-4434-8edc-29ceefd38c95") - private String mockConnectionId; + private String mockConnectionId = null; private static DeferredTask extractDeferredTask(TaskOptions taskOptions) throws Exception { ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(taskOptions.getPayload())); diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java index 778ad2d1f99..c3eeaa348e8 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java @@ -16,17 +16,17 @@ public class AppEngineEventBuilderHelperTest { @Tested - private AppEngineEventBuilderHelper eventBuilderHelper; + private AppEngineEventBuilderHelper eventBuilderHelper = null; @Injectable - private EventBuilder mockEventBuilder; + private EventBuilder mockEventBuilder = null; @Mocked("getCurrentEnvironment") private ApiProxy mockApiProxy; @Injectable - private ApiProxy.Environment mockEnvironment; + private ApiProxy.Environment mockEnvironment = null; @Injectable - private SystemProperty mockApplicationId; + private SystemProperty mockApplicationId = null; @Injectable - private SystemProperty mockApplicationVersion; + private SystemProperty mockApplicationVersion =null; @BeforeMethod public void setUp() throws Exception { diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java index 248f9d5c9c4..14d50860d87 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java @@ -16,9 +16,9 @@ public class SentryAppenderCloseTest { @Injectable private Raven mockRaven = null; @Mocked("ravenInstance") - private RavenFactory mockRavenFactory; + private RavenFactory mockRavenFactory = null; @Mocked("dsnLookup") - private Dsn mockDsn; + private Dsn mockDsn = null; @BeforeMethod public void setUp() throws Exception { diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java index 0235875096c..8d303b90e89 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java @@ -3,6 +3,7 @@ import mockit.Expectations; import mockit.Injectable; import mockit.Mocked; +import mockit.Tested; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; @@ -13,14 +14,15 @@ import static org.hamcrest.Matchers.is; public class SentryAppenderDsnTest { - private SentryAppender sentryAppender; + @Tested + private SentryAppender sentryAppender = null; private MockUpErrorHandler mockUpErrorHandler; @Injectable private Raven mockRaven = null; @Mocked("ravenInstance") - private RavenFactory mockRavenFactory; + private RavenFactory mockRavenFactory = null; @Mocked("dsnLookup") - private Dsn mockDsn; + private Dsn mockDsn = null; @BeforeMethod public void setUp() throws Exception { diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java index 83ff6c64321..f1dc0afe003 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java @@ -1,10 +1,7 @@ package net.kencochrane.raven.log4j; import com.google.common.base.Joiner; -import mockit.Expectations; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Verifications; +import mockit.*; import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; @@ -29,7 +26,8 @@ import static org.hamcrest.Matchers.is; public class SentryAppenderEventBuildingTest { - private SentryAppender sentryAppender; + @Tested + private SentryAppender sentryAppender = null; private MockUpErrorHandler mockUpErrorHandler; @Injectable private Raven mockRaven = null; diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java index dc71d861f8d..fe88378a294 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java @@ -1,9 +1,6 @@ package net.kencochrane.raven.log4j; -import mockit.Injectable; -import mockit.Mocked; -import mockit.NonStrictExpectations; -import mockit.Verifications; +import mockit.*; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; @@ -18,14 +15,15 @@ import static org.hamcrest.Matchers.is; public class SentryAppenderFailuresTest { - private SentryAppender sentryAppender; + @Tested + private SentryAppender sentryAppender = null; private MockUpErrorHandler mockUpErrorHandler; @Injectable private Raven mockRaven = null; @Injectable private Logger mockLogger = null; @Mocked("ravenInstance") - private RavenFactory mockRavenFactory; + private RavenFactory mockRavenFactory = null; @BeforeMethod public void setUp() throws Exception { diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java index afe0a1ef3ce..5284668e4f6 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java @@ -1,6 +1,7 @@ package net.kencochrane.raven.log4j2; import mockit.Injectable; +import mockit.Tested; import mockit.Verifications; import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; @@ -30,7 +31,8 @@ import static org.hamcrest.Matchers.*; public class SentryAppenderEventBuildingTest { - private SentryAppender sentryAppender; + @Tested + private SentryAppender sentryAppender = null; private MockUpErrorHandler mockUpErrorHandler; @Injectable private Raven mockRaven = null; diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java index 00ad1cb71dc..fd54bbf6fc7 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java @@ -23,7 +23,7 @@ public class SentryAppenderFailuresTest { @Injectable private Raven mockRaven = null; @Mocked("ravenInstance") - private RavenFactory mockRavenFactory; + private RavenFactory mockRavenFactory = null; @BeforeMethod public void setUp() throws Exception { diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java index 2e97445096a..d98bfa8ceef 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java @@ -23,9 +23,9 @@ public class SentryAppenderDsnTest { @Injectable private Context mockContext = null; @Mocked("ravenInstance") - private RavenFactory mockRavenFactory; + private RavenFactory mockRavenFactory = null; @Mocked("dsnLookup") - private Dsn mockDsn; + private Dsn mockDsn = null; @BeforeMethod public void setUp() throws Exception { From 14b4c836fe261df0c56de29b3f4e83741e80bc11 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 29 Jun 2014 02:03:23 +1000 Subject: [PATCH 1228/2152] Use @SuppressWarnings("unused") on unused @Mocked --- .../appengine/connection/AppEngineAsyncConnectionTest.java | 3 ++- .../event/helper/AppEngineEventBuilderHelperTest.java | 1 + .../kencochrane/raven/log4j/SentryAppenderCloseTest.java | 2 ++ .../net/kencochrane/raven/log4j/SentryAppenderDsnTest.java | 2 ++ .../raven/log4j/SentryAppenderFailuresTest.java | 1 + .../raven/log4j2/SentryAppenderFailuresTest.java | 1 + .../kencochrane/raven/logback/SentryAppenderDsnTest.java | 2 ++ .../raven/logback/SentryAppenderFailuresTest.java | 1 + .../kencochrane/raven/connection/AsyncConnectionTest.java | 7 +++++-- raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java | 6 ++++-- .../raven/event/EventBuilderHostnameCacheTest.java | 6 ++++-- .../raven/event/helper/HttpEventBuilderHelperTest.java | 1 + 12 files changed, 26 insertions(+), 7 deletions(-) diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java index 0ea4becf8f9..cedb90619ee 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java @@ -27,6 +27,7 @@ public class AppEngineAsyncConnectionTest { private Connection mockConnection = null; @Injectable private Queue mockQueue = null; + @SuppressWarnings("unused") @Mocked("getDefaultQueue") private QueueFactory queueFactory = null; @Injectable("7b55a129-6975-4434-8edc-29ceefd38c95") @@ -90,7 +91,7 @@ public void testSendEventQueued(@Injectable final Event mockEvent, @Injectable U @Test public void testQueuedEventSubmitted(@Injectable final Event mockEvent, - @Mocked("setDoNotRetry") DeferredTaskContext deferredTaskContext) + @SuppressWarnings("unused") @Mocked("setDoNotRetry") DeferredTaskContext deferredTaskContext) throws Exception { new NonStrictExpectations() {{ mockQueue.add((TaskOptions) any); diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java index c3eeaa348e8..1065d0b2b48 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java @@ -19,6 +19,7 @@ public class AppEngineEventBuilderHelperTest { private AppEngineEventBuilderHelper eventBuilderHelper = null; @Injectable private EventBuilder mockEventBuilder = null; + @SuppressWarnings("unused") @Mocked("getCurrentEnvironment") private ApiProxy mockApiProxy; @Injectable diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java index 14d50860d87..b3659d23152 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java @@ -15,8 +15,10 @@ public class SentryAppenderCloseTest { private MockUpErrorHandler mockUpErrorHandler; @Injectable private Raven mockRaven = null; + @SuppressWarnings("unused") @Mocked("ravenInstance") private RavenFactory mockRavenFactory = null; + @SuppressWarnings("unused") @Mocked("dsnLookup") private Dsn mockDsn = null; diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java index 8d303b90e89..a924d7457ce 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java @@ -19,8 +19,10 @@ public class SentryAppenderDsnTest { private MockUpErrorHandler mockUpErrorHandler; @Injectable private Raven mockRaven = null; + @SuppressWarnings("unused") @Mocked("ravenInstance") private RavenFactory mockRavenFactory = null; + @SuppressWarnings("unused") @Mocked("dsnLookup") private Dsn mockDsn = null; diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java index fe88378a294..2418f081d87 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java @@ -22,6 +22,7 @@ public class SentryAppenderFailuresTest { private Raven mockRaven = null; @Injectable private Logger mockLogger = null; + @SuppressWarnings("unused") @Mocked("ravenInstance") private RavenFactory mockRavenFactory = null; diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java index fd54bbf6fc7..2cebd0736e4 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java @@ -22,6 +22,7 @@ public class SentryAppenderFailuresTest { private MockUpErrorHandler mockUpErrorHandler; @Injectable private Raven mockRaven = null; + @SuppressWarnings("unused") @Mocked("ravenInstance") private RavenFactory mockRavenFactory = null; diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java index d98bfa8ceef..9ac0378528b 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java @@ -22,8 +22,10 @@ public class SentryAppenderDsnTest { private Raven mockRaven = null; @Injectable private Context mockContext = null; + @SuppressWarnings("unused") @Mocked("ravenInstance") private RavenFactory mockRavenFactory = null; + @SuppressWarnings("unused") @Mocked("dsnLookup") private Dsn mockDsn = null; diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java index 772a1c67091..c38321576bf 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java @@ -20,6 +20,7 @@ public class SentryAppenderFailuresTest { private Raven mockRaven = null; @Injectable private Context mockContext = null; + @SuppressWarnings("unused") @Mocked("ravenInstance") private RavenFactory mockRavenFactory; diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index c9dbf739c27..f11e024330a 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -19,6 +19,7 @@ public class AsyncConnectionTest { private ExecutorService mockExecutorService = null; @Injectable("false") private boolean mockGracefulShutdown = false; + @SuppressWarnings("unused") @Mocked("addShutdownHook") private Runtime mockRuntime = null; @@ -57,7 +58,8 @@ public void verifyShutdownHookNotAddedWhenNotGraceful() throws Exception { @Test public void verifyShutdownHookSetManagedByRavenAndCloseConnection( - @Mocked({"startManagingThread", "stopManagingThread"}) Raven mockRaven) throws Exception { + @SuppressWarnings("unused") @Mocked({"startManagingThread", "stopManagingThread"}) Raven mockRaven) + throws Exception { // Ensure that the shutdown hooks for the unused @Tested instance are removed asyncConnection.close(); @@ -81,7 +83,8 @@ public void addShutdownHook(Thread thread) { @Test public void ensureFailingShutdownHookStopsBeingManaged( - @Mocked({"startManagingThread", "stopManagingThread"}) Raven mockRaven) throws Exception { + @SuppressWarnings("unused") @Mocked({"startManagingThread", "stopManagingThread"}) Raven mockRaven) + throws Exception { // Ensure that the shutdown hooks for the unused @Tested instance are removed asyncConnection.close(); diff --git a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java index 8a0c108f8dd..b9e4e77fc04 100644 --- a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java @@ -64,7 +64,8 @@ public void testDsnLookupWithNothingSet() throws Exception { } @Test - public void testJndiLookupFailsWithException(@Mocked("jndiLookup") JndiLookup mockJndiLookup) throws Exception { + public void testJndiLookupFailsWithException( + @SuppressWarnings("unused") @Mocked("jndiLookup") JndiLookup mockJndiLookup) throws Exception { new NonStrictExpectations() {{ JndiLookup.jndiLookup(); result = new ClassNotFoundException("Couldn't find the JNDI classes"); @@ -74,7 +75,8 @@ public void testJndiLookupFailsWithException(@Mocked("jndiLookup") JndiLookup mo } @Test - public void testJndiLookupFailsWithError(@Mocked("jndiLookup") JndiLookup mockJndiLookup) throws Exception { + public void testJndiLookupFailsWithError( + @SuppressWarnings("unused") @Mocked("jndiLookup") JndiLookup mockJndiLookup) throws Exception { new NonStrictExpectations() {{ JndiLookup.jndiLookup(); result = new NoClassDefFoundError("Couldn't find the JNDI classes"); diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java index 81a24da4258..7fd8be1b7a0 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderHostnameCacheTest.java @@ -44,7 +44,8 @@ public void setUp() throws Exception { } @Test - public void successfulHostnameRetrievalIsCachedForFiveHours(@Mocked("currentTimeMillis") final System system) + public void successfulHostnameRetrievalIsCachedForFiveHours( + @SuppressWarnings("unused") @Mocked("currentTimeMillis") final System system) throws Exception { new NonStrictExpectations(InetAddress.class) {{ System.currentTimeMillis(); @@ -60,7 +61,8 @@ public void successfulHostnameRetrievalIsCachedForFiveHours(@Mocked("currentTime } @Test - public void unsuccessfulHostnameRetrievalIsCachedForOneSecond(@Mocked("currentTimeMillis") final System system) + public void unsuccessfulHostnameRetrievalIsCachedForOneSecond( + @SuppressWarnings("unused") @Mocked("currentTimeMillis") final System system) throws Exception { new NonStrictExpectations(InetAddress.class) {{ System.currentTimeMillis(); diff --git a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java index e27baf03e73..a019c62af82 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java @@ -15,6 +15,7 @@ public class HttpEventBuilderHelperTest { private HttpEventBuilderHelper httpEventBuilderHelper = null; @Injectable private EventBuilder mockEventBuilder = null; + @SuppressWarnings("unused") @Mocked private HttpInterface mockHttpInterface = null; From dafba88571813198d76289ac08179ed81ee758fe Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 29 Jun 2014 02:05:16 +1000 Subject: [PATCH 1229/2152] Fix imports and indentation --- .../appengine/connection/AppEngineAsyncConnectionTest.java | 2 +- .../event/helper/AppEngineEventBuilderHelperTest.java | 2 +- .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 1 - .../net/kencochrane/raven/log4j/SentryAppenderCloseTest.java | 1 - .../kencochrane/raven/logback/SentryAppenderCloseTest.java | 1 - .../net/kencochrane/raven/connection/AsyncConnection.java | 2 +- .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 1 - raven/src/test/java/net/kencochrane/raven/RavenTest.java | 3 +-- .../net/kencochrane/raven/connection/AsyncConnectionTest.java | 1 - .../net/kencochrane/raven/connection/UdpConnectionTest.java | 1 - .../src/test/java/net/kencochrane/raven/event/EventTest.java | 2 +- .../raven/event/helper/HttpEventBuilderHelperTest.java | 1 - .../kencochrane/raven/jul/SentryHandlerEventBuildingTest.java | 1 - .../raven/marshaller/json/MessageInterfaceBindingTest.java | 1 - .../raven/marshaller/json/StackTraceInterfaceBindingTest.java | 1 - .../raven/servlet/RavenServletContainerInitializerTest.java | 4 ++-- 16 files changed, 7 insertions(+), 18 deletions(-) diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java index cedb90619ee..61174496111 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java @@ -46,11 +46,11 @@ private static AppEngineAsyncConnection getTaskConnection(DeferredTask deferredT @BeforeMethod public void setUp() throws Exception { + asyncConnection = new AppEngineAsyncConnection(mockConnectionId, mockConnection); new NonStrictExpectations() {{ QueueFactory.getDefaultQueue(); result = mockQueue; }}; - asyncConnection = new AppEngineAsyncConnection(mockConnectionId, mockConnection); } @Test diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java index 1065d0b2b48..adecf7d1369 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java @@ -27,7 +27,7 @@ public class AppEngineEventBuilderHelperTest { @Injectable private SystemProperty mockApplicationId = null; @Injectable - private SystemProperty mockApplicationVersion =null; + private SystemProperty mockApplicationVersion = null; @BeforeMethod public void setUp() throws Exception { diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 391efaf5a03..614f45a8c66 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -15,7 +15,6 @@ import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; -import java.io.IOException; import java.util.Collections; import java.util.Date; import java.util.Map; diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java index b3659d23152..69501c49170 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java @@ -3,7 +3,6 @@ import mockit.*; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java index a8a429e417e..993d754c491 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java @@ -7,7 +7,6 @@ import mockit.NonStrictExpectations; import mockit.Verifications; import net.kencochrane.raven.Raven; -import net.kencochrane.raven.connection.Connection; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 71ca8e7c529..18ddeedb4a0 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -50,7 +50,7 @@ public class AsyncConnection implements Connection { * @param actualConnection connection used to send the events. * @param executorService executorService used to process events, if null, the executorService will automatically * be set to {@code Executors.newSingleThreadExecutor()} - * @param gracefulShutdown Indicates whether or not the shutdown operation should be managed by a ShutdownHook. + * @param gracefulShutdown Indicates whether or not the shutdown operation should be managed by a ShutdownHook. */ public AsyncConnection(Connection actualConnection, ExecutorService executorService, boolean gracefulShutdown) { this.actualConnection = actualConnection; diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 3db51161eed..177c7147a4a 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -10,7 +10,6 @@ import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; -import java.io.IOException; import java.text.MessageFormat; import java.util.*; import java.util.logging.*; diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index 3b76cbc9484..c7379d2dedf 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -10,7 +10,6 @@ import net.kencochrane.raven.event.helper.EventBuilderHelper; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.concurrent.atomic.AtomicInteger; @@ -44,7 +43,7 @@ public void testSendEvent() throws Exception { @Test public void testSendEventFailingIsCaught() throws Exception { - new NonStrictExpectations(){{ + new NonStrictExpectations() {{ mockConnection.send((Event) any); result = new RuntimeException(); }}; diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index f11e024330a..8a5fc35e4e9 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -3,7 +3,6 @@ import mockit.*; import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; -import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; diff --git a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java index 037c25211d9..23ba3bdf682 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java @@ -3,7 +3,6 @@ import mockit.*; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.marshaller.Marshaller; -import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.OutputStream; diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventTest.java index 12b0168e7be..41987105c46 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventTest.java @@ -14,7 +14,7 @@ public class EventTest { @Test(expectedExceptions = IllegalArgumentException.class) public void ensureEventIdCantBeNull() throws Exception { - final Event event = new Event(null); + new Event(null); } @Test diff --git a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java index a019c62af82..660879c8b5e 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java @@ -5,7 +5,6 @@ import net.kencochrane.raven.event.interfaces.HttpInterface; import net.kencochrane.raven.event.interfaces.SentryInterface; import net.kencochrane.raven.servlet.RavenServletRequestListener; -import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import javax.servlet.http.HttpServletRequest; diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java index d1bf00bdb0c..80ee9207267 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java @@ -9,7 +9,6 @@ import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.SentryException; import org.hamcrest.Matchers; -import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java index c893fcc1746..1fdd1531948 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java @@ -4,7 +4,6 @@ import mockit.NonStrictExpectations; import mockit.Tested; import net.kencochrane.raven.event.interfaces.MessageInterface; -import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.Arrays; diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java index bd38a6b93c8..6bc2bd71b10 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java @@ -4,7 +4,6 @@ import mockit.NonStrictExpectations; import mockit.Tested; import net.kencochrane.raven.event.interfaces.StackTraceInterface; -import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import static net.kencochrane.raven.marshaller.json.JsonTestTool.*; diff --git a/raven/src/test/java/net/kencochrane/raven/servlet/RavenServletContainerInitializerTest.java b/raven/src/test/java/net/kencochrane/raven/servlet/RavenServletContainerInitializerTest.java index 1164206054d..2768aa2b200 100644 --- a/raven/src/test/java/net/kencochrane/raven/servlet/RavenServletContainerInitializerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/servlet/RavenServletContainerInitializerTest.java @@ -24,10 +24,10 @@ public void testInitializerInjectedViaServiceLoader() throws Exception { } @Test - public void testFilterAddedToServletContext(@Injectable final ServletContext mockServletContext) throws Exception{ + public void testFilterAddedToServletContext(@Injectable final ServletContext mockServletContext) throws Exception { ravenServletContainerInitializer.onStartup(null, mockServletContext); - new Verifications(){{ + new Verifications() {{ mockServletContext.addListener(RavenServletRequestListener.class); }}; } From 72f12dd59b643061f01f04bda56a2d8743df9278 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 29 Jun 2014 02:05:50 +1000 Subject: [PATCH 1230/2152] Apply same tests from log4j to log4j2 --- .../raven/log4j2/SentryAppender.java | 2 + .../raven/log4j2/SentryAppenderCloseTest.java | 81 ++++++++++++++++++- .../raven/log4j2/SentryAppenderDsnTest.java | 34 +++++--- 3 files changed, 100 insertions(+), 17 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 406bfdc26a0..392c8900162 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -276,6 +276,8 @@ public void setTags(String tags) { public void stop() { try { Raven.startManagingThread(); + if (!isStarted()) + return; super.stop(); if (raven != null) raven.closeConnection(); diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java index 02c37d0afd0..85247ca1f44 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java @@ -1,10 +1,9 @@ package net.kencochrane.raven.log4j2; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Verifications; +import mockit.*; import net.kencochrane.raven.Raven; -import net.kencochrane.raven.connection.Connection; +import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -15,6 +14,12 @@ public class SentryAppenderCloseTest { private MockUpErrorHandler mockUpErrorHandler; @Injectable private Raven mockRaven = null; + @SuppressWarnings("unused") + @Mocked("ravenInstance") + private RavenFactory mockRavenFactory = null; + @SuppressWarnings("unused") + @Mocked("dsnLookup") + private Dsn mockDsn = null; @BeforeMethod public void setUp() throws Exception { @@ -38,4 +43,72 @@ public void testConnectionClosedWhenAppenderStopped() throws Exception { }}; assertNoErrorsInErrorHandler(); } + + @Test + public void testStopIfRavenInstanceNotProvided() throws Exception { + final String dsnUri = "protocol://public:private@host/1"; + final SentryAppender sentryAppender = new SentryAppender(); + sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + new Expectations() {{ + Dsn.dsnLookup(); + result = dsnUri; + RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); + result = mockRaven; + }}; + sentryAppender.start(); + sentryAppender.append(null); + + sentryAppender.stop(); + + new Verifications() {{ + mockRaven.closeConnection(); + }}; + //One error, because of the null event. + assertThat(mockUpErrorHandler.getErrorCount(), is(1)); + } + + @Test + public void testStopDoNotFailIfInitFailed() throws Exception { + // This checks that even if sentry wasn't setup correctly its appender can still be closed. + final SentryAppender sentryAppender = new SentryAppender(); + sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + new NonStrictExpectations() {{ + RavenFactory.ravenInstance((Dsn) any, anyString); + result = new UnsupportedOperationException(); + }}; + sentryAppender.start(); + sentryAppender.append(null); + + sentryAppender.stop(); + + //Two errors, one because of the exception, one because of the null event. + assertThat(mockUpErrorHandler.getErrorCount(), is(2)); + } + + @Test + public void testStopDoNotFailIfNoInit() + throws Exception { + final SentryAppender sentryAppender = new SentryAppender(); + sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + + sentryAppender.stop(); + + assertNoErrorsInErrorHandler(); + } + + @Test + public void testStopDoNotFailWhenMultipleCalls() throws Exception { + final SentryAppender sentryAppender = new SentryAppender(mockRaven); + sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.start(); + + sentryAppender.stop(); + sentryAppender.stop(); + + new Verifications() {{ + mockRaven.closeConnection(); + times = 1; + }}; + assertNoErrorsInErrorHandler(); + } } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java index 92e4e45fb9c..91f2766a345 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java @@ -3,34 +3,45 @@ import mockit.Expectations; import mockit.Injectable; import mockit.Mocked; +import mockit.Tested; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; public class SentryAppenderDsnTest { + @Tested + private SentryAppender sentryAppender = null; private MockUpErrorHandler mockUpErrorHandler = new MockUpErrorHandler(); @Injectable private Raven mockRaven = null; + @SuppressWarnings("unused") @Mocked("ravenInstance") - private RavenFactory mockRavenFactory; + private RavenFactory mockRavenFactory = null; + @SuppressWarnings("unused") @Mocked("dsnLookup") - private Dsn dsn; + private Dsn mockDsn = null; + + @BeforeMethod + public void setUp() throws Exception { + sentryAppender = new SentryAppender(); + sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + } private void assertNoErrorsInErrorHandler() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test - public void testLazyInitialisation() throws Exception { - final String dsnUri = "proto://private:public@host/1"; - final SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.setDsn(dsnUri); + public void testDsnDetected() throws Exception { + final String dsnUri = "protocol://public:private@host/1"; new Expectations() {{ + Dsn.dsnLookup(); + result = dsnUri; RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); result = mockRaven; }}; @@ -41,13 +52,10 @@ public void testLazyInitialisation() throws Exception { } @Test - public void testDsnAutoDetection() throws Exception { - final String dsnUri = "proto://private:public@host/1"; - final SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + public void testDsnProvided() throws Exception { + final String dsnUri = "protocol://public:private@host/2"; + sentryAppender.setDsn(dsnUri); new Expectations() {{ - Dsn.dsnLookup(); - result = dsnUri; RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); result = mockRaven; }}; From 0817e46492129d7399f5bf411f63471deab01751 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 29 Jun 2014 02:07:45 +1000 Subject: [PATCH 1231/2152] Fix style issues --- .../java/net/kencochrane/raven/RavenFactory.java | 6 +++--- .../kencochrane/raven/event/EventBuilder.java | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index edc8cdd48dc..bb8d48a2c46 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -106,8 +106,8 @@ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { @Override public String toString() { - return "RavenFactory{" + - "name='" + this.getClass().getName() + '\'' + - '}'; + return "RavenFactory{" + + "name='" + this.getClass().getName() + '\'' + + '}'; } } diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 51776ee6cbd..5c45bc5cd6b 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -291,6 +291,14 @@ public synchronized Event build() { return event; } + @Override + public String toString() { + return "EventBuilder{" + + "event=" + event + + ", alreadyBuilt=" + alreadyBuilt + + '}'; + } + /** * Time sensitive cache in charge of keeping track of the hostname. *

    @@ -372,12 +380,4 @@ public String call() throws Exception { } } } - - @Override - public String toString() { - return "EventBuilder{" + - "event=" + event + - ", alreadyBuilt=" + alreadyBuilt + - '}'; - } } From 93bdf94bd46c6e027ca79370b9529810014e1438 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 29 Jun 2014 03:22:25 +1000 Subject: [PATCH 1232/2152] Update logback tests to follow log4j test suite --- .../raven/logback/SentryAppender.java | 2 + .../logback/SentryAppenderCloseTest.java | 81 ++++++++++++++++++- .../raven/logback/SentryAppenderDsnTest.java | 8 +- .../SentryAppenderEventBuildingTest.java | 4 +- 4 files changed, 86 insertions(+), 9 deletions(-) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 10427ca4827..18f473109f7 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -289,6 +289,8 @@ public void setTags(String tags) { public void stop() { try { Raven.startManagingThread(); + if (!isStarted()) + return; super.stop(); if (raven != null) raven.closeConnection(); diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java index 993d754c491..34fd3309644 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderCloseTest.java @@ -3,10 +3,10 @@ import ch.qos.logback.core.BasicStatusManager; import ch.qos.logback.core.Context; import ch.qos.logback.core.status.OnConsoleStatusListener; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Verifications; +import mockit.*; import net.kencochrane.raven.Raven; +import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -18,6 +18,12 @@ public class SentryAppenderCloseTest { private Raven mockRaven = null; @Injectable private Context mockContext = null; + @SuppressWarnings("unused") + @Mocked("ravenInstance") + private RavenFactory mockRavenFactory = null; + @SuppressWarnings("unused") + @Mocked("dsnLookup") + private Dsn mockDsn = null; @BeforeMethod public void setUp() throws Exception { @@ -41,6 +47,7 @@ private void assertNoErrorsInStatusManager() throws Exception { public void testConnectionClosedWhenAppenderStopped() throws Exception { final SentryAppender sentryAppender = new SentryAppender(mockRaven); sentryAppender.setContext(mockContext); + sentryAppender.start(); sentryAppender.stop(); @@ -49,4 +56,72 @@ public void testConnectionClosedWhenAppenderStopped() throws Exception { }}; assertNoErrorsInStatusManager(); } + + @Test + public void testStopIfRavenInstanceNotProvided() throws Exception { + final String dsnUri = "protocol://public:private@host/1"; + final SentryAppender sentryAppender = new SentryAppender(); + sentryAppender.setContext(mockContext); + new Expectations() {{ + Dsn.dsnLookup(); + result = dsnUri; + RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); + result = mockRaven; + }}; + sentryAppender.start(); + sentryAppender.append(null); + + sentryAppender.stop(); + + new Verifications() {{ + mockRaven.closeConnection(); + }}; + //One error, because of the null event. + assertThat(mockContext.getStatusManager().getCount(), is(1)); + } + + @Test + public void testStopDoNotFailIfInitFailed() throws Exception { + // This checks that even if sentry wasn't setup correctly its appender can still be closed. + final SentryAppender sentryAppender = new SentryAppender(); + sentryAppender.setContext(mockContext); + new NonStrictExpectations() {{ + RavenFactory.ravenInstance((Dsn) any, anyString); + result = new UnsupportedOperationException(); + }}; + sentryAppender.start(); + sentryAppender.append(null); + + sentryAppender.stop(); + + //Two errors, one because of the exception, one because of the null event. + assertThat(mockContext.getStatusManager().getCount(), is(2)); + } + + @Test + public void testStopDoNotFailIfNoInit() + throws Exception { + final SentryAppender sentryAppender = new SentryAppender(); + sentryAppender.setContext(mockContext); + + sentryAppender.stop(); + + assertNoErrorsInStatusManager(); + } + + @Test + public void testStopDoNotFailWhenMultipleCalls() throws Exception { + final SentryAppender sentryAppender = new SentryAppender(mockRaven); + sentryAppender.setContext(mockContext); + sentryAppender.start(); + + sentryAppender.stop(); + sentryAppender.stop(); + + new Verifications() {{ + mockRaven.closeConnection(); + times = 1; + }}; + assertNoErrorsInStatusManager(); + } } diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java index 9ac0378528b..37f4c0d8e04 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderDsnTest.java @@ -3,10 +3,7 @@ import ch.qos.logback.core.BasicStatusManager; import ch.qos.logback.core.Context; import ch.qos.logback.core.status.OnConsoleStatusListener; -import mockit.Expectations; -import mockit.Injectable; -import mockit.Mocked; -import mockit.NonStrictExpectations; +import mockit.*; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; import net.kencochrane.raven.dsn.Dsn; @@ -17,7 +14,8 @@ import static org.hamcrest.Matchers.is; public class SentryAppenderDsnTest { - private SentryAppender sentryAppender; + @Tested + private SentryAppender sentryAppender = null; @Injectable private Raven mockRaven = null; @Injectable diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java index 0e625faf247..2a7d7fe80b8 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java @@ -7,6 +7,7 @@ import ch.qos.logback.core.status.OnConsoleStatusListener; import mockit.Injectable; import mockit.NonStrictExpectations; +import mockit.Tested; import mockit.Verifications; import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; @@ -29,7 +30,8 @@ import static org.hamcrest.Matchers.is; public class SentryAppenderEventBuildingTest { - private SentryAppender sentryAppender; + @Tested + private SentryAppender sentryAppender = null; @Injectable private Raven mockRaven = null; @Injectable From 0dfaf89934cefc36ef96f5f5439312515e787475 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 29 Jun 2014 21:17:24 +1000 Subject: [PATCH 1233/2152] Add current central version as a badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ab7351e6aee..d7bdad140b8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Raven -[![Build Status](https://secure.travis-ci.org/getsentry/raven-java.png?branch=master)](https://travis-ci.org/getsentry/raven-java) +[![Build Status](https://secure.travis-ci.org/getsentry/raven-java.png?branch=master)](https://travis-ci.org/getsentry/raven-java) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.kencochrane.raven/raven-all/badge.png)](https://maven-badges.herokuapp.com/maven-central/net.kencochrane.raven/raven-all) Raven is the Java client for [Sentry](https://www.getsentry.com/). Raven relies on the most popular logging libraries to capture and convert logs From 793830e4f2b7923575f47b0f617fa1ae4509b937 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 29 Jun 2014 21:23:51 +1000 Subject: [PATCH 1234/2152] Fix image links and use svg --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d7bdad140b8..016d59d5066 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Raven -[![Build Status](https://secure.travis-ci.org/getsentry/raven-java.png?branch=master)](https://travis-ci.org/getsentry/raven-java) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.kencochrane.raven/raven-all/badge.png)](https://maven-badges.herokuapp.com/maven-central/net.kencochrane.raven/raven-all) +[![Build Status](https://travis-ci.org/getsentry/raven-java.svg?branch=master)](https://travis-ci.org/getsentry/raven-java) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.kencochrane.raven/raven-all/badge.svg)](https://maven-badges.herokuapp.com/maven-central/net.kencochrane.raven/raven-all) Raven is the Java client for [Sentry](https://www.getsentry.com/). Raven relies on the most popular logging libraries to capture and convert logs From f0bd5eea5c65fcd0dc911f66fd104aaf58ce9941 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 05:32:32 +1000 Subject: [PATCH 1235/2152] Move Thread and Name management in a different package/class --- .../connection/AppEngineAsyncConnection.java | 6 +- .../raven/log4j/SentryAppender.java | 11 +-- .../log4j/SentryAppenderFailuresTest.java | 5 +- .../raven/log4j2/SentryAppender.java | 11 +-- .../log4j2/SentryAppenderFailuresTest.java | 6 +- .../raven/logback/SentryAppender.java | 12 +-- .../logback/SentryAppenderFailuresTest.java | 5 +- .../java/net/kencochrane/raven/Raven.java | 61 +------------ .../raven/connection/AbstractConnection.java | 4 +- .../raven/connection/AsyncConnection.java | 10 +-- .../raven/connection/HttpConnection.java | 4 +- .../raven/environment/RavenEnvironment.java | 86 +++++++++++++++++++ .../kencochrane/raven/jul/SentryHandler.java | 11 +-- .../raven/RavenEnvironmentTest.java | 57 ++++++++++++ .../java/net/kencochrane/raven/RavenTest.java | 48 ----------- .../raven/connection/AsyncConnectionTest.java | 7 +- .../raven/connection/HttpConnectionTest.java | 4 +- 17 files changed, 196 insertions(+), 152 deletions(-) create mode 100644 raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java create mode 100644 raven/src/test/java/net/kencochrane/raven/RavenEnvironmentTest.java diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java index d0a20b21c32..e46bd88e51f 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java @@ -3,7 +3,7 @@ import com.google.appengine.api.taskqueue.DeferredTask; import com.google.appengine.api.taskqueue.Queue; import com.google.appengine.api.taskqueue.QueueFactory; -import net.kencochrane.raven.Raven; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.event.Event; import org.slf4j.Logger; @@ -117,9 +117,9 @@ private EventSubmitter(String connectionId, Event event) { @Override public void run() { setDoNotRetry(true); + RavenEnvironment.startManagingThread(); try { // The current thread is managed by raven - Raven.startManagingThread(); AppEngineAsyncConnection connection = APP_ENGINE_ASYNC_CONNECTIONS.get(connectionId); if (connection == null) { logger.warn("Couldn't find the AppEngineAsyncConnection identified by '{}'. " @@ -130,7 +130,7 @@ public void run() { } catch (Exception e) { logger.error("An exception occurred while sending the event to Sentry.", e); } finally { - Raven.stopManagingThread(); + RavenEnvironment.stopManagingThread(); } } } diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 614f45a8c66..be596ef8b46 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -3,6 +3,7 @@ import com.google.common.base.Splitter; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.dsn.InvalidDsnException; import net.kencochrane.raven.event.Event; @@ -134,18 +135,18 @@ protected void initRaven() { @Override protected void append(LoggingEvent loggingEvent) { // Do not log the event if the current thread is managed by raven - if (Raven.isManagingThread()) + if (RavenEnvironment.isManagingThread()) return; + RavenEnvironment.startManagingThread(); try { - Raven.startManagingThread(); Event event = buildEvent(loggingEvent); raven.sendEvent(event); } catch (Exception e) { getErrorHandler().error("An exception occurred while creating a new event in Raven", e, ErrorCode.WRITE_FAILURE); } finally { - Raven.stopManagingThread(); + RavenEnvironment.stopManagingThread(); } } @@ -215,8 +216,8 @@ public void setTags(String tags) { @Override public void close() { + RavenEnvironment.startManagingThread(); try { - Raven.startManagingThread(); if (this.closed) return; this.closed = true; @@ -226,7 +227,7 @@ public void close() { getErrorHandler().error("An exception occurred while closing the Raven connection", e, ErrorCode.CLOSE_FAILURE); } finally { - Raven.stopManagingThread(); + RavenEnvironment.stopManagingThread(); } } diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java index 2418f081d87..73c70d7aff6 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java @@ -3,6 +3,7 @@ import mockit.*; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; import org.apache.log4j.Level; @@ -63,8 +64,8 @@ public void testRavenFactoryFailureDoesNotPropagate() throws Exception { @Test public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { + RavenEnvironment.startManagingThread(); try { - Raven.startManagingThread(); sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); new Verifications() {{ @@ -73,7 +74,7 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { }}; assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } finally { - Raven.stopManagingThread(); + RavenEnvironment.stopManagingThread(); } } } diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 392c8900162..d1917d1081a 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -3,6 +3,7 @@ import com.google.common.base.Splitter; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.dsn.InvalidDsnException; import net.kencochrane.raven.event.Event; @@ -169,11 +170,11 @@ protected static List formatMessageParameters(Object[] parameters) { @Override public void append(LogEvent logEvent) { // Do not log the event if the current thread is managed by raven - if (Raven.isManagingThread()) + if (RavenEnvironment.isManagingThread()) return; + RavenEnvironment.startManagingThread(); try { - Raven.startManagingThread(); if (raven == null) initRaven(); @@ -182,7 +183,7 @@ public void append(LogEvent logEvent) { } catch (Exception e) { error("An exception occurred while creating a new event in Raven", logEvent, e); } finally { - Raven.stopManagingThread(); + RavenEnvironment.stopManagingThread(); } } @@ -274,8 +275,8 @@ public void setTags(String tags) { @Override public void stop() { + RavenEnvironment.startManagingThread(); try { - Raven.startManagingThread(); if (!isStarted()) return; super.stop(); @@ -284,7 +285,7 @@ public void stop() { } catch (Exception e) { error("An exception occurred while closing the Raven connection", e); } finally { - Raven.stopManagingThread(); + RavenEnvironment.stopManagingThread(); } } } diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java index 2cebd0736e4..c8651ec3d5e 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java @@ -6,6 +6,7 @@ import mockit.Verifications; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; import org.apache.logging.log4j.Level; @@ -62,9 +63,8 @@ public void testRavenFactoryFailureDoesNotPropagate() throws Exception { @Test public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { + RavenEnvironment.startManagingThread(); try { - Raven.startManagingThread(); - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); new Verifications() {{ @@ -73,7 +73,7 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { }}; assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } finally { - Raven.stopManagingThread(); + RavenEnvironment.stopManagingThread(); } } } diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 18f473109f7..2083bf725dd 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -8,6 +8,7 @@ import com.google.common.base.Splitter; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.dsn.InvalidDsnException; import net.kencochrane.raven.event.Event; @@ -121,12 +122,11 @@ protected static Event.Level formatLevel(Level level) { @Override protected void append(ILoggingEvent iLoggingEvent) { // Do not log the event if the current thread is managed by raven - if (Raven.isManagingThread()) + if (RavenEnvironment.isManagingThread()) return; + RavenEnvironment.startManagingThread(); try { - Raven.startManagingThread(); - if (raven == null) initRaven(); @@ -135,7 +135,7 @@ protected void append(ILoggingEvent iLoggingEvent) { } catch (Exception e) { addError("An exception occurred while creating a new event in Raven", e); } finally { - Raven.stopManagingThread(); + RavenEnvironment.stopManagingThread(); } } @@ -287,8 +287,8 @@ public void setTags(String tags) { @Override public void stop() { + RavenEnvironment.startManagingThread(); try { - Raven.startManagingThread(); if (!isStarted()) return; super.stop(); @@ -297,7 +297,7 @@ public void stop() { } catch (Exception e) { addError("An exception occurred while closing the Raven connection", e); } finally { - Raven.stopManagingThread(); + RavenEnvironment.stopManagingThread(); } } } diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java index c38321576bf..95e37a050f7 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java @@ -7,6 +7,7 @@ import mockit.*; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.Event; import org.testng.annotations.BeforeMethod; @@ -75,8 +76,8 @@ public void testRavenFactoryFailureDoesNotPropagate() throws Exception { @Test public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { + RavenEnvironment.startManagingThread(); try { - Raven.startManagingThread(); final SentryAppender sentryAppender = new SentryAppender(mockRaven); sentryAppender.setContext(mockContext); sentryAppender.start(); @@ -89,7 +90,7 @@ public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { }}; assertThat(mockContext.getStatusManager().getCount(), is(0)); } finally { - Raven.stopManagingThread(); + RavenEnvironment.stopManagingThread(); } } } diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index b3fc13fdf67..78cdf56d108 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -5,15 +5,14 @@ import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.helper.EventBuilderHelper; import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.environment.RavenEnvironment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Collections; import java.util.HashSet; -import java.util.ResourceBundle; import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; /** * Raven is a client for Sentry allowing to send an {@link Event} that will be processed and sent to a Sentry server. @@ -24,66 +23,10 @@ *

    */ public class Raven { - /** - * Version of this client, the major version is the current supported Sentry protocol, the minor version changes - * for each release of this project. - */ - public static final String NAME = ResourceBundle.getBundle("raven-build").getString("build.name"); - /** - * Indicates whether the current thread is managed by raven or not. - */ - private static final ThreadLocal RAVEN_THREAD = new ThreadLocal() { - @Override - protected AtomicInteger initialValue() { - return new AtomicInteger(); - } - }; private static final Logger logger = LoggerFactory.getLogger(Raven.class); private final Set builderHelpers = new HashSet<>(); private Connection connection; - /** - * Sets the current thread as managed by Raven. - *

    - * The logs generated by Threads managed by Raven will not send logs to Sentry. - *

    - */ - public static void startManagingThread() { - try { - if (isManagingThread()) - logger.warn("Thread already managed by Raven"); - } finally { - RAVEN_THREAD.get().incrementAndGet(); - } - } - - /** - * Sets the current thread as not managed by Raven. - *

    - * The logs generated by Threads not managed by Raven will send logs to Sentry. - *

    - */ - public static void stopManagingThread() { - try { - if (!isManagingThread()) { - //Start managing the thread only to send the warning - startManagingThread(); - logger.warn("Thread not yet managed by Raven"); - } - } finally { - RAVEN_THREAD.get().decrementAndGet(); - } - } - - /** - * Checks whether the current thread is managed by Raven or not. - * - * @return {@code true} if the thread is managed by Raven, {@code false} otherwise. - */ - public static boolean isManagingThread() { - return RAVEN_THREAD.get().get() > 0; - } - /** * Runs the {@link EventBuilderHelper} against the {@link EventBuilder} to obtain additional information with a * MDC-like system. @@ -182,7 +125,7 @@ public void setConnection(Connection connection) { @Override public String toString() { return "Raven{" - + "name=" + NAME + + "name=" + RavenEnvironment.NAME + ", connection=" + connection + '}'; } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index b3d189094bf..30328487bbb 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -1,7 +1,7 @@ package net.kencochrane.raven.connection; -import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.environment.RavenEnvironment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,7 +53,7 @@ public abstract class AbstractConnection implements Connection { */ protected AbstractConnection(String publicKey, String secretKey) { authHeader = "Sentry sentry_version=" + SENTRY_PROTOCOL_VERSION + "," - + "sentry_client=" + Raven.NAME + "," + + "sentry_client=" + RavenEnvironment.NAME + "," + "sentry_key=" + publicKey + "," + "sentry_secret=" + secretKey; } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 18ddeedb4a0..2f8bf1f1674 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -1,6 +1,6 @@ package net.kencochrane.raven.connection; -import net.kencochrane.raven.Raven; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.event.Event; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -135,14 +135,14 @@ private EventSubmitter(Event event) { @Override public void run() { + RavenEnvironment.startManagingThread(); try { // The current thread is managed by raven - Raven.startManagingThread(); actualConnection.send(event); } catch (Exception e) { logger.error("An exception occurred while sending the event to Sentry.", e); } finally { - Raven.stopManagingThread(); + RavenEnvironment.stopManagingThread(); } } } @@ -150,15 +150,15 @@ public void run() { private final class ShutDownHook extends Thread { @Override public void run() { + RavenEnvironment.startManagingThread(); try { // The current thread is managed by raven - Raven.startManagingThread(); logger.info("Automatic shutdown of the async connection"); AsyncConnection.this.doClose(); } catch (Exception e) { logger.error("An exception occurred while closing the connection.", e); } finally { - Raven.stopManagingThread(); + RavenEnvironment.stopManagingThread(); } } } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 4cd69543bbd..121a16143c2 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -1,9 +1,9 @@ package net.kencochrane.raven.connection; import com.google.common.base.Charsets; -import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.marshaller.Marshaller; +import net.kencochrane.raven.environment.RavenEnvironment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -106,7 +106,7 @@ protected HttpURLConnection getConnection() { connection.setRequestMethod("POST"); connection.setDoOutput(true); connection.setConnectTimeout(timeout); - connection.setRequestProperty(USER_AGENT, Raven.NAME); + connection.setRequestProperty(USER_AGENT, RavenEnvironment.NAME); connection.setRequestProperty(SENTRY_AUTH, getAuthHeader()); return connection; } catch (IOException e) { diff --git a/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java b/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java new file mode 100644 index 00000000000..872b0532714 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java @@ -0,0 +1,86 @@ +package net.kencochrane.raven.environment; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ResourceBundle; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Manages environment information on Raven. + *

    + * Manages information related to Raven Runtime such as the name of the library or + * whether or not the thread is managed by Raven. + *

    + */ +public class RavenEnvironment { + /** + * Version of this client, the major version is the current supported Sentry protocol, the minor version changes + * for each release of this project. + */ + public static final String NAME = ResourceBundle.getBundle("raven-build").getString("build.name"); + private static final Logger logger = LoggerFactory.getLogger(RavenEnvironment.class); + + /** + * Indicates whether the current thread is managed by raven or not. + */ + private static final ThreadLocal RAVEN_THREAD = new ThreadLocal() { + @Override + protected AtomicInteger initialValue() { + return new AtomicInteger(); + } + }; + + /** + * Sets the current thread as managed by Raven. + *

    + * The logs generated by Threads managed by Raven will not send logs to Sentry. + *

    + *

    + * Recommended usage: + *

    {@code
    +     * RavenEnvironment.startManagingThread();
    +     * try {
    +     *     // Some code that shouldn't generate Sentry logs.
    +     * } finally {
    +     *     RavenEnvironment.stopManagingThread();
    +     * }
    +     * }
    + *

    + */ + public static void startManagingThread() { + try { + if (isManagingThread()) + logger.warn("Thread already managed by Raven"); + } finally { + RAVEN_THREAD.get().incrementAndGet(); + } + } + + /** + * Sets the current thread as not managed by Raven. + *

    + * The logs generated by Threads not managed by Raven will send logs to Sentry. + *

    + */ + public static void stopManagingThread() { + try { + if (!isManagingThread()) { + //Start managing the thread only to send the warning + startManagingThread(); + logger.warn("Thread not yet managed by Raven"); + } + } finally { + RAVEN_THREAD.get().decrementAndGet(); + } + } + + /** + * Checks whether the current thread is managed by Raven or not. + * + * @return {@code true} if the thread is managed by Raven, {@code false} otherwise. + */ + public static boolean isManagingThread() { + return RAVEN_THREAD.get().get() > 0; + } +} diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 177c7147a4a..ba3c8cfc2f1 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -3,6 +3,7 @@ import com.google.common.base.Splitter; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.dsn.InvalidDsnException; import net.kencochrane.raven.event.Event; @@ -113,11 +114,11 @@ protected void retrieveProperties() { @Override public void publish(LogRecord record) { // Do not log the event if the current thread is managed by raven - if (!isLoggable(record) || Raven.isManagingThread()) + if (!isLoggable(record) || RavenEnvironment.isManagingThread()) return; + RavenEnvironment.startManagingThread(); try { - Raven.startManagingThread(); if (raven == null) initRaven(); Event event = buildEvent(record); @@ -125,7 +126,7 @@ public void publish(LogRecord record) { } catch (Exception e) { reportError("An exception occurred while creating a new event in Raven", e, ErrorManager.WRITE_FAILURE); } finally { - Raven.stopManagingThread(); + RavenEnvironment.stopManagingThread(); } } @@ -196,14 +197,14 @@ public void flush() { @Override public void close() throws SecurityException { + RavenEnvironment.startManagingThread(); try { - Raven.startManagingThread(); if (raven != null) raven.closeConnection(); } catch (Exception e) { reportError("An exception occurred while closing the Raven connection", e, ErrorManager.CLOSE_FAILURE); } finally { - Raven.stopManagingThread(); + RavenEnvironment.stopManagingThread(); } } } diff --git a/raven/src/test/java/net/kencochrane/raven/RavenEnvironmentTest.java b/raven/src/test/java/net/kencochrane/raven/RavenEnvironmentTest.java new file mode 100644 index 00000000000..4259d5b0fbf --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/RavenEnvironmentTest.java @@ -0,0 +1,57 @@ +package net.kencochrane.raven; + +import net.kencochrane.raven.environment.RavenEnvironment; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class RavenEnvironmentTest { + @Test + public void testThreadNotManagedByDefault() throws Exception { + assertThat(RavenEnvironment.isManagingThread(), is(false)); + } + + @Test + public void testStartManagingThreadWorks() throws Exception { + RavenEnvironment.startManagingThread(); + + assertThat(RavenEnvironment.isManagingThread(), is(true)); + } + + @Test + public void testStartManagingAlreadyManagedThreadWorks() throws Exception { + RavenEnvironment.startManagingThread(); + + RavenEnvironment.startManagingThread(); + + assertThat(RavenEnvironment.isManagingThread(), is(true)); + } + + @Test + public void testStopManagingThreadWorks() throws Exception { + RavenEnvironment.startManagingThread(); + + RavenEnvironment.stopManagingThread(); + + assertThat(RavenEnvironment.isManagingThread(), is(false)); + } + + @Test + public void testStopManagingNonManagedThreadWorks() throws Exception { + RavenEnvironment.stopManagingThread(); + + assertThat(RavenEnvironment.isManagingThread(), is(false)); + } + + @Test + public void testThreadManagedTwiceNeedsToBeUnmanagedTwice() throws Exception { + RavenEnvironment.startManagingThread(); + RavenEnvironment.startManagingThread(); + + RavenEnvironment.stopManagingThread(); + assertThat(RavenEnvironment.isManagingThread(), is(true)); + RavenEnvironment.stopManagingThread(); + assertThat(RavenEnvironment.isManagingThread(), is(false)); + } +} diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index c7379d2dedf..57aa36edbe4 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -123,52 +123,4 @@ public void testRunBuilderHelpers(@Injectable final EventBuilderHelper mockBuild mockBuilderHelper.helpBuildingEvent(mockEventBuilder); }}; } - - @Test - public void testThreadNotManagedByDefault() throws Exception { - assertThat(Raven.isManagingThread(), is(false)); - } - - @Test - public void testStartManagingThreadWorks() throws Exception { - Raven.startManagingThread(); - - assertThat(Raven.isManagingThread(), is(true)); - } - - @Test - public void testStartManagingAlreadyManagedThreadWorks() throws Exception { - Raven.startManagingThread(); - - Raven.startManagingThread(); - - assertThat(Raven.isManagingThread(), is(true)); - } - - @Test - public void testStopManagingThreadWorks() throws Exception { - Raven.startManagingThread(); - - Raven.stopManagingThread(); - - assertThat(Raven.isManagingThread(), is(false)); - } - - @Test - public void testStopManagingNonManagedThreadWorks() throws Exception { - Raven.stopManagingThread(); - - assertThat(Raven.isManagingThread(), is(false)); - } - - @Test - public void testThreadManagedTwiceNeedsToBeUnmanagedTwice() throws Exception { - Raven.startManagingThread(); - Raven.startManagingThread(); - - Raven.stopManagingThread(); - assertThat(Raven.isManagingThread(), is(true)); - Raven.stopManagingThread(); - assertThat(Raven.isManagingThread(), is(false)); - } } diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index 8a5fc35e4e9..912e8a29629 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -2,6 +2,7 @@ import mockit.*; import net.kencochrane.raven.Raven; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.event.Event; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -74,9 +75,9 @@ public void addShutdownHook(Thread thread) { new AsyncConnection(mockConnection, mockExecutorService, true); new VerificationsInOrder() {{ - Raven.startManagingThread(); + RavenEnvironment.startManagingThread(); mockConnection.close(); - Raven.stopManagingThread(); + RavenEnvironment.stopManagingThread(); }}; } @@ -101,7 +102,7 @@ public void addShutdownHook(Thread thread) { new AsyncConnection(mockConnection, mockExecutorService, true); new Verifications() {{ - Raven.stopManagingThread(); + RavenEnvironment.stopManagingThread(); }}; } diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index 0c0faf74692..bcfe6a8c6f6 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -1,9 +1,9 @@ package net.kencochrane.raven.connection; import mockit.*; -import net.kencochrane.raven.Raven; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.marshaller.Marshaller; +import net.kencochrane.raven.environment.RavenEnvironment; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -114,7 +114,7 @@ public void testAuthHeaderSent(@Injectable final Event mockEvent) throws Excepti new Verifications() {{ - mockUrlConnection.setRequestProperty("User-Agent", Raven.NAME); + mockUrlConnection.setRequestProperty("User-Agent", RavenEnvironment.NAME); mockUrlConnection.setRequestProperty("X-Sentry-Auth", httpConnection.getAuthHeader()); }}; } From f27c53dce547e88bbee15d4a5e46eedc0458b2b0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 05:33:14 +1000 Subject: [PATCH 1236/2152] Add tests to ensure that closeConnection works --- .../java/net/kencochrane/raven/RavenTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index 57aa36edbe4..a1e34499d34 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -12,6 +12,7 @@ import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; +import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; import static mockit.Deencapsulation.getField; @@ -123,4 +124,23 @@ public void testRunBuilderHelpers(@Injectable final EventBuilderHelper mockBuild mockBuilderHelper.helpBuildingEvent(mockEventBuilder); }}; } + + @Test + public void testCloseConnectionSuccessful() throws Exception { + raven.closeConnection(); + + new Verifications(){{ + mockConnection.close(); + }}; + } + + @Test(expectedExceptions = RuntimeException.class) + public void testCloseConnectionFailed() throws Exception { + new NonStrictExpectations(){{ + mockConnection.close(); + result = new IOException(); + }}; + + raven.closeConnection(); + } } From 0d6daaaa73c0b68b143da30e21fa148687a71e38 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 05:33:43 +1000 Subject: [PATCH 1237/2152] Put the result of getThrown() in a local variable to use the return value --- .../java/net/kencochrane/raven/log4j2/SentryAppender.java | 4 ++-- .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index d1917d1081a..288d5a1ffdc 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -223,8 +223,8 @@ protected Event buildEvent(LogEvent event) { formatMessageParameters(eventMessage.getParameters()))); } - if (event.getThrown() != null) { - Throwable throwable = event.getThrown(); + Throwable throwable = event.getThrown(); + if (throwable != null) { eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); } else if (event.getSource() != null) { StackTraceElement[] stackTrace = {event.getSource()}; diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index ba3c8cfc2f1..496435e94f8 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -170,8 +170,9 @@ protected Event buildEvent(LogRecord record) { } eventBuilder.setMessage(message); - if (record.getThrown() != null) - eventBuilder.addSentryInterface(new ExceptionInterface(record.getThrown())); + Throwable throwable = record.getThrown(); + if (throwable != null) + eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); if (record.getSourceClassName() != null && record.getSourceMethodName() != null) { StackTraceElement fakeFrame = new StackTraceElement(record.getSourceClassName(), From 0c59786eb2571a02bb6417c0edf09bf506f4a66e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 05:34:25 +1000 Subject: [PATCH 1238/2152] Capitalise the I in ThreadId --- .../src/main/java/net/kencochrane/raven/jul/SentryHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 496435e94f8..74c5f1c4d54 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -22,7 +22,7 @@ public class SentryHandler extends Handler { /** * Name of the {@link Event#extra} property containing the Thread id. */ - public static final String THREAD_ID = "Raven-Threadid"; + public static final String THREAD_ID = "Raven-ThreadId"; /** * Current instance of {@link Raven}. * From 268f68096dc27dd33105160462617f45f918fb79 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 05:35:01 +1000 Subject: [PATCH 1239/2152] Add test for Dsn detection/creation with RavenFactory --- .../kencochrane/raven/RavenFactoryTest.java | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java b/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java index 842ce83efdb..16a02cb8cad 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java @@ -1,10 +1,7 @@ package net.kencochrane.raven; import com.google.common.collect.Iterators; -import mockit.Delegate; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Tested; +import mockit.*; import net.kencochrane.raven.dsn.Dsn; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; @@ -120,4 +117,43 @@ public void testRavenInstantiationFailureCaught(@Injectable final Dsn mockDsn) t assertThat(exception, notNullValue()); } + + @Test + public void testAutoDetectDsnIfNotProvided(@Injectable final Raven mockRaven, + @SuppressWarnings("unused") @Mocked final Dsn mockDsn) throws Exception { + final String dsn = "protocol://user:password@host:port/3"; + RavenFactory.registerFactory(ravenFactory); + new NonStrictExpectations() {{ + Dsn.dsnLookup(); + result = dsn; + + ravenFactory.createRavenInstance((Dsn) any); + result = mockRaven; + }}; + + Raven raven = RavenFactory.ravenInstance(); + + assertThat(raven, is(mockRaven)); + new Verifications() {{ + new Dsn(dsn); + }}; + } + + @Test + public void testCreateDsnIfStringProvided(@Injectable final Raven mockRaven, + @SuppressWarnings("unused") @Mocked final Dsn mockDsn) throws Exception { + final String dsn = "protocol://user:password@host:port/2"; + RavenFactory.registerFactory(ravenFactory); + new NonStrictExpectations() {{ + ravenFactory.createRavenInstance((Dsn) any); + result = mockRaven; + }}; + + Raven raven = RavenFactory.ravenInstance(dsn); + + assertThat(raven, is(mockRaven)); + new Verifications() {{ + new Dsn(dsn); + }}; + } } From 505775673e3eb374ace2d3a4d8a4adfe5eb9c294 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 05:35:31 +1000 Subject: [PATCH 1240/2152] Fix logger for the Dsn class --- raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java index f9cef36cbb7..4695fa92e30 100644 --- a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java @@ -18,7 +18,7 @@ public class Dsn { * Name of the environment and system variables containing the DSN. */ public static final String DSN_VARIABLE = "SENTRY_DSN"; - private static final Logger logger = LoggerFactory.getLogger(Raven.class); + private static final Logger logger = LoggerFactory.getLogger(Dsn.class); private String secretKey; private String publicKey; private String projectId; From 788e3bb47bcd729ff4f36941f84f1f76563c10c5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 05:47:21 +1000 Subject: [PATCH 1241/2152] Use the same pattern for every delegate --- .../AppEngineAsyncConnectionTest.java | 12 +++++++++--- .../kencochrane/raven/RavenFactoryTest.java | 1 + .../raven/connection/AsyncConnectionTest.java | 17 ++++++++++------- .../raven/connection/UdpConnectionTest.java | 10 ++++++++-- .../event/interfaces/SentryExceptionTest.java | 1 + .../json/ExceptionInterfaceBindingTest.java | 18 +++++++++++------- .../marshaller/json/JsonMarshallerTest.java | 1 + 7 files changed, 41 insertions(+), 19 deletions(-) diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java index 61174496111..86a1b8f1f0e 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java @@ -95,9 +95,15 @@ public void testQueuedEventSubmitted(@Injectable final Event mockEvent, throws Exception { new NonStrictExpectations() {{ mockQueue.add((TaskOptions) any); - result = new Delegate() { - void add(TaskOptions taskOptions) throws Exception { - extractDeferredTask(taskOptions).run(); + result = new Delegate() { + @SuppressWarnings("unused") + TaskHandle add(TaskOptions taskOptions) { + try { + extractDeferredTask(taskOptions).run(); + } catch (Exception e) { + throw new RuntimeException("Couldn't extract the task", e); + } + return null; } }; }}; diff --git a/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java b/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java index 16a02cb8cad..5d14064a415 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java @@ -45,6 +45,7 @@ public void testGetFactoriesFromServiceLoader(@Injectable final Raven mockRaven, new NonStrictExpectations() {{ mockServiceLoader.iterator(); result = new Delegate>() { + @SuppressWarnings("unused") public Iterator iterator() { return Iterators.singletonIterator(ravenFactory); } diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java index 912e8a29629..97a28544b8b 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AsyncConnectionTest.java @@ -66,8 +66,9 @@ public void verifyShutdownHookSetManagedByRavenAndCloseConnection( new NonStrictExpectations() {{ mockRuntime.addShutdownHook((Thread) any); result = new Delegate() { - public void addShutdownHook(Thread thread) { - thread.run(); + @SuppressWarnings("unused") + public void addShutdownHook(Thread hook) { + hook.run(); } }; }}; @@ -91,8 +92,9 @@ public void ensureFailingShutdownHookStopsBeingManaged( new NonStrictExpectations() {{ mockRuntime.addShutdownHook((Thread) any); result = new Delegate() { - public void addShutdownHook(Thread thread) { - thread.run(); + @SuppressWarnings("unused") + public void addShutdownHook(Thread hook) { + hook.run(); } }; mockConnection.close(); @@ -132,9 +134,10 @@ public void testSendEventQueued(@Injectable final Event mockEvent) throws Except public void testQueuedEventExecuted(@Injectable final Event mockEvent) throws Exception { new NonStrictExpectations() {{ mockExecutorService.execute((Runnable) any); - result = new Delegate() { - public void execute(Runnable runnable) { - runnable.run(); + result = new Delegate() { + @SuppressWarnings("unused") + public void execute(Runnable command) { + command.run(); } }; }}; diff --git a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java index 23ba3bdf682..225b329ae02 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java @@ -5,6 +5,7 @@ import net.kencochrane.raven.marshaller.Marshaller; import org.testng.annotations.Test; +import java.io.IOException; import java.io.OutputStream; import java.net.DatagramPacket; import java.net.DatagramSocket; @@ -58,8 +59,13 @@ public void udpMessageSentWorksAsExpected(@Injectable final Event event) throws new NonStrictExpectations() {{ mockMarshaller.marshall(event, (OutputStream) any); result = new Delegate() { - public void marshall(Event event, OutputStream os) throws Exception { - os.write(marshalledContent.getBytes("UTF-8")); + @SuppressWarnings("unused") + public void marshall(Event event, OutputStream destination) { + try { + destination.write(marshalledContent.getBytes("UTF-8")); + } catch (IOException e) { + throw new RuntimeException("Couldn't write to destination", e); + } } }; }}; diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java index 5493ecb72b3..88c2c8c20cd 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/SentryExceptionTest.java @@ -21,6 +21,7 @@ public void ensureConversionToQueueKeepsOrder(@Injectable final Throwable mockCa new NonStrictExpectations() {{ mockThrowable.getCause(); result = new Delegate() { + @SuppressWarnings("unused") public Throwable getCause() { return mockCause; } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index b07cd0b1609..bd6b1efe086 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -31,11 +31,12 @@ public class ExceptionInterfaceBindingTest { public void setUp() throws Exception { new NonStrictExpectations() {{ mockStackTraceInterfaceBinding.writeInterface(withInstanceOf(JsonGenerator.class), (StackTraceInterface) any); - result = new Delegate() { - public void writeInterface(JsonGenerator jsonGenerator, StackTraceInterface stackTraceInterface) + result = new Delegate() { + @SuppressWarnings("unused") + public void writeInterface(JsonGenerator generator, StackTraceInterface sentryInterface) throws IOException { - jsonGenerator.writeStartObject(); - jsonGenerator.writeEndObject(); + generator.writeStartObject(); + generator.writeEndObject(); } }; }}; @@ -48,7 +49,8 @@ public void testSimpleException() throws Exception { final Throwable throwable = new IllegalStateException(message); new NonStrictExpectations() {{ mockExceptionInterface.getExceptions(); - result = new Delegate() { + result = new Delegate>() { + @SuppressWarnings("unused") public Deque getExceptions() { return SentryException.extractExceptionQueue(throwable); } @@ -67,7 +69,8 @@ public void testClassInDefaultPackage() throws Exception { final Throwable throwable = new DefaultPackageException(); new NonStrictExpectations() {{ mockExceptionInterface.getExceptions(); - result = new Delegate() { + result = new Delegate>() { + @SuppressWarnings("unused") public Deque getExceptions() { return SentryException.extractExceptionQueue(throwable); } @@ -88,7 +91,8 @@ public void testChainedException() throws Exception { final Throwable throwable2 = new IllegalStateException(message2, throwable1); new NonStrictExpectations() {{ mockExceptionInterface.getExceptions(); - result = new Delegate() { + result = new Delegate>() { + @SuppressWarnings("unused") public Deque getExceptions() { return SentryException.extractExceptionQueue(throwable2); } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index 888a8b3eade..b7613de9b2c 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -243,6 +243,7 @@ public void testInterfaceBindingIsProperlyUsed( result = Collections.singletonMap("interfaceKey", mockSentryInterface); mockInterfaceBinding.writeInterface((JsonGenerator) any, mockSentryInterface); result = new Delegate() { + @SuppressWarnings("unused") public void writeInterface(JsonGenerator generator, SentryInterface sentryInterface) throws IOException { generator.writeNull(); } From 95ca9e3433c9dbdd14793a66157dcc95c381a1d0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 06:05:35 +1000 Subject: [PATCH 1242/2152] Move raven_thread reset to environment --- .../test/java/net/kencochrane/raven/RavenTest.java | 9 --------- .../{ => environment}/RavenEnvironmentTest.java | 13 +++++++++++-- 2 files changed, 11 insertions(+), 11 deletions(-) rename raven/src/test/java/net/kencochrane/raven/{ => environment}/RavenEnvironmentTest.java (81%) diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index a1e34499d34..5d3aa1ce30f 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -9,13 +9,10 @@ import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.helper.EventBuilderHelper; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; import java.io.IOException; -import java.util.concurrent.atomic.AtomicInteger; -import static mockit.Deencapsulation.getField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -27,12 +24,6 @@ public class RavenTest { @Injectable private Event mockEvent = null; - @AfterMethod - public void tearDown() throws Exception { - ThreadLocal ravenThread = getField(Raven.class, "RAVEN_THREAD"); - ravenThread.remove(); - } - @Test public void testSendEvent() throws Exception { raven.sendEvent(mockEvent); diff --git a/raven/src/test/java/net/kencochrane/raven/RavenEnvironmentTest.java b/raven/src/test/java/net/kencochrane/raven/environment/RavenEnvironmentTest.java similarity index 81% rename from raven/src/test/java/net/kencochrane/raven/RavenEnvironmentTest.java rename to raven/src/test/java/net/kencochrane/raven/environment/RavenEnvironmentTest.java index 4259d5b0fbf..7acef9464a8 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenEnvironmentTest.java +++ b/raven/src/test/java/net/kencochrane/raven/environment/RavenEnvironmentTest.java @@ -1,12 +1,21 @@ -package net.kencochrane.raven; +package net.kencochrane.raven.environment; -import net.kencochrane.raven.environment.RavenEnvironment; +import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; +import java.util.concurrent.atomic.AtomicInteger; + +import static mockit.Deencapsulation.getField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; public class RavenEnvironmentTest { + @AfterMethod + public void tearDown() throws Exception { + ThreadLocal ravenThread = getField(RavenEnvironment.class, "RAVEN_THREAD"); + ravenThread.remove(); + } + @Test public void testThreadNotManagedByDefault() throws Exception { assertThat(RavenEnvironment.isManagingThread(), is(false)); From 55ea0ab5c7eaf4e00935a68ba6cac3fdb914fcfe Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 06:07:27 +1000 Subject: [PATCH 1243/2152] Remove need for Reflection when working on Raven_thread --- .../kencochrane/raven/environment/RavenEnvironment.java | 7 +++---- .../raven/environment/RavenEnvironmentTest.java | 6 +----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java b/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java index 872b0532714..6ef31f95812 100644 --- a/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java +++ b/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java @@ -13,23 +13,22 @@ * whether or not the thread is managed by Raven. *

    */ -public class RavenEnvironment { +public final class RavenEnvironment { /** * Version of this client, the major version is the current supported Sentry protocol, the minor version changes * for each release of this project. */ public static final String NAME = ResourceBundle.getBundle("raven-build").getString("build.name"); - private static final Logger logger = LoggerFactory.getLogger(RavenEnvironment.class); - /** * Indicates whether the current thread is managed by raven or not. */ - private static final ThreadLocal RAVEN_THREAD = new ThreadLocal() { + protected static final ThreadLocal RAVEN_THREAD = new ThreadLocal() { @Override protected AtomicInteger initialValue() { return new AtomicInteger(); } }; + private static final Logger logger = LoggerFactory.getLogger(RavenEnvironment.class); /** * Sets the current thread as managed by Raven. diff --git a/raven/src/test/java/net/kencochrane/raven/environment/RavenEnvironmentTest.java b/raven/src/test/java/net/kencochrane/raven/environment/RavenEnvironmentTest.java index 7acef9464a8..fde716a9649 100644 --- a/raven/src/test/java/net/kencochrane/raven/environment/RavenEnvironmentTest.java +++ b/raven/src/test/java/net/kencochrane/raven/environment/RavenEnvironmentTest.java @@ -3,17 +3,13 @@ import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; -import java.util.concurrent.atomic.AtomicInteger; - -import static mockit.Deencapsulation.getField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; public class RavenEnvironmentTest { @AfterMethod public void tearDown() throws Exception { - ThreadLocal ravenThread = getField(RavenEnvironment.class, "RAVEN_THREAD"); - ravenThread.remove(); + RavenEnvironment.RAVEN_THREAD.remove(); } @Test From 0509cc132f2d8356c7d2848f0f5e848d9ac31d2a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 06:11:32 +1000 Subject: [PATCH 1244/2152] Fix imports and indentation --- raven/src/main/java/net/kencochrane/raven/Raven.java | 2 +- .../net/kencochrane/raven/connection/AbstractConnection.java | 2 +- .../java/net/kencochrane/raven/connection/HttpConnection.java | 2 +- raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java | 1 - .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 2 +- raven/src/test/java/net/kencochrane/raven/RavenTest.java | 4 ++-- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 78cdf56d108..79f4f4c21e4 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -1,11 +1,11 @@ package net.kencochrane.raven; import net.kencochrane.raven.connection.Connection; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.helper.EventBuilderHelper; import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.environment.RavenEnvironment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 30328487bbb..d550d89306a 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -1,7 +1,7 @@ package net.kencochrane.raven.connection; -import net.kencochrane.raven.event.Event; import net.kencochrane.raven.environment.RavenEnvironment; +import net.kencochrane.raven.event.Event; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 121a16143c2..42af12b7f2e 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -1,9 +1,9 @@ package net.kencochrane.raven.connection; import com.google.common.base.Charsets; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.marshaller.Marshaller; -import net.kencochrane.raven.environment.RavenEnvironment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java index 4695fa92e30..d211277b080 100644 --- a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java @@ -1,6 +1,5 @@ package net.kencochrane.raven.dsn; -import net.kencochrane.raven.Raven; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 74c5f1c4d54..3e0a63c6da3 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -3,9 +3,9 @@ import com.google.common.base.Splitter; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.dsn.InvalidDsnException; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index 5d3aa1ce30f..b9146b9d0ba 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -120,14 +120,14 @@ public void testRunBuilderHelpers(@Injectable final EventBuilderHelper mockBuild public void testCloseConnectionSuccessful() throws Exception { raven.closeConnection(); - new Verifications(){{ + new Verifications() {{ mockConnection.close(); }}; } @Test(expectedExceptions = RuntimeException.class) public void testCloseConnectionFailed() throws Exception { - new NonStrictExpectations(){{ + new NonStrictExpectations() {{ mockConnection.close(); result = new IOException(); }}; From 3f61fac5fc459ed952600e0d074630bc61dc00e7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 06:11:46 +1000 Subject: [PATCH 1245/2152] Ensure that the constructor isn't available --- .../net/kencochrane/raven/environment/RavenEnvironment.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java b/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java index 6ef31f95812..624a0afac03 100644 --- a/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java +++ b/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java @@ -30,6 +30,9 @@ protected AtomicInteger initialValue() { }; private static final Logger logger = LoggerFactory.getLogger(RavenEnvironment.class); + private RavenEnvironment(){ + } + /** * Sets the current thread as managed by Raven. *

    From 9363eb531f364b085b74cdda0e8ad47d1f9d2291 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 06:37:12 +1000 Subject: [PATCH 1246/2152] Fix indentation --- .../connection/AppEngineAsyncConnection.java | 2 +- .../raven/log4j/SentryAppender.java | 2 +- .../log4j/SentryAppenderFailuresTest.java | 2 +- .../raven/log4j2/SentryAppender.java | 2 +- .../log4j2/SentryAppenderFailuresTest.java | 2 +- .../raven/logback/SentryAppender.java | 2 +- .../logback/SentryAppenderFailuresTest.java | 2 +- .../raven/environment/RavenEnvironment.java | 2 +- .../raven/connection/HttpConnectionTest.java | 2 +- .../json/jsonmarshallertest/testChecksum.json | 22 +++++----- .../json/jsonmarshallertest/testCulprit.json | 22 +++++----- .../json/jsonmarshallertest/testEventId.json | 22 +++++----- .../jsonmarshallertest/testExtraArray.json | 36 ++++++++--------- .../jsonmarshallertest/testExtraBoolean.json | 26 ++++++------ .../testExtraCustomValue.json | 26 ++++++------ .../jsonmarshallertest/testExtraIterable.json | 36 ++++++++--------- .../json/jsonmarshallertest/testExtraMap.json | 30 +++++++------- .../jsonmarshallertest/testExtraNull.json | 26 ++++++------ .../testExtraNullKeyMap.json | 30 +++++++------- .../jsonmarshallertest/testExtraNumber.json | 26 ++++++------ .../testExtraObjectKeyMap.json | 30 +++++++------- .../testExtraRecursiveArray.json | 40 +++++++++---------- .../testExtraRecursiveMap.json | 40 +++++++++---------- .../jsonmarshallertest/testExtraString.json | 26 ++++++------ .../testInterfaceBinding.json | 24 +++++------ .../jsonmarshallertest/testLevelDebug.json | 22 +++++----- .../jsonmarshallertest/testLevelError.json | 22 +++++----- .../jsonmarshallertest/testLevelFatal.json | 22 +++++----- .../jsonmarshallertest/testLevelInfo.json | 22 +++++----- .../jsonmarshallertest/testLevelWarning.json | 22 +++++----- .../json/jsonmarshallertest/testLogger.json | 22 +++++----- .../json/jsonmarshallertest/testMessage.json | 22 +++++----- .../json/jsonmarshallertest/testPlatform.json | 22 +++++----- .../jsonmarshallertest/testServerName.json | 22 +++++----- .../json/jsonmarshallertest/testTags.json | 26 ++++++------ .../jsonmarshallertest/testTimestamp.json | 22 +++++----- sentry-stub/src/main/webapp/WEB-INF/web.xml | 4 +- 37 files changed, 365 insertions(+), 365 deletions(-) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java index e46bd88e51f..b26d69c57c6 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java @@ -3,8 +3,8 @@ import com.google.appengine.api.taskqueue.DeferredTask; import com.google.appengine.api.taskqueue.Queue; import com.google.appengine.api.taskqueue.QueueFactory; -import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.connection.Connection; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.event.Event; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index be596ef8b46..00dc98cc0bb 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -3,9 +3,9 @@ import com.google.common.base.Splitter; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.dsn.InvalidDsnException; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java index 73c70d7aff6..7020d0df01a 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java @@ -3,8 +3,8 @@ import mockit.*; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.dsn.Dsn; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.event.Event; import org.apache.log4j.Level; import org.apache.log4j.Logger; diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 288d5a1ffdc..9c4056bf763 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -3,9 +3,9 @@ import com.google.common.base.Splitter; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.dsn.InvalidDsnException; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java index c8651ec3d5e..80c823729ae 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java @@ -6,8 +6,8 @@ import mockit.Verifications; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.dsn.Dsn; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.event.Event; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.impl.Log4jLogEvent; diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 2083bf725dd..4f243e97ca0 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -8,9 +8,9 @@ import com.google.common.base.Splitter; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.dsn.InvalidDsnException; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java index 95e37a050f7..89ceb38d3aa 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderFailuresTest.java @@ -7,8 +7,8 @@ import mockit.*; import net.kencochrane.raven.Raven; import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.dsn.Dsn; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.event.Event; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; diff --git a/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java b/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java index 624a0afac03..10198c225a6 100644 --- a/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java +++ b/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java @@ -30,7 +30,7 @@ protected AtomicInteger initialValue() { }; private static final Logger logger = LoggerFactory.getLogger(RavenEnvironment.class); - private RavenEnvironment(){ + private RavenEnvironment() { } /** diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index bcfe6a8c6f6..a217abeafbb 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -1,9 +1,9 @@ package net.kencochrane.raven.connection; import mockit.*; +import net.kencochrane.raven.environment.RavenEnvironment; import net.kencochrane.raven.event.Event; import net.kencochrane.raven.marshaller.Marshaller; -import net.kencochrane.raven.environment.RavenEnvironment; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testChecksum.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testChecksum.json index e49263b8ea5..e4be16572bd 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testChecksum.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testChecksum.json @@ -1,13 +1,13 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": {}, - "checksum": "1234567890abcdef" + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": "1234567890abcdef" } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testCulprit.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testCulprit.json index 95fa489f9c5..5c719e1de26 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testCulprit.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testCulprit.json @@ -1,13 +1,13 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": "culprit", - "tags": {}, - "server_name": null, - "extra": {}, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": "culprit", + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testEventId.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testEventId.json index 12dc219542a..1fb34a3c6bd 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testEventId.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testEventId.json @@ -1,13 +1,13 @@ { - "event_id": "3b71fba5413e4022ae985f0b80a155a5", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": {}, - "checksum": null + "event_id": "3b71fba5413e4022ae985f0b80a155a5", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json index 2cab8733672..e0c701b5f31 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraArray.json @@ -1,20 +1,20 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": { - "key": [ - "string", - 1, - null, - true - ] - }, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": [ + "string", + 1, + null, + true + ] + }, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json index c42ccc0a567..8befd9a619e 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json @@ -1,15 +1,15 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": { - "key": true - }, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": true + }, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json index 2cda2e99a79..ddceaf8e8a1 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json @@ -1,15 +1,15 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": { - "key": "test" - }, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": "test" + }, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json index 67afbe0df4b..4d3c6538357 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json @@ -1,20 +1,20 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": { - "key": [ - true, - null, - 1, - "string" - ] - }, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": [ + true, + null, + 1, + "string" + ] + }, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraMap.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraMap.json index 1792e5e9f4f..a44468edad5 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraMap.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraMap.json @@ -1,17 +1,17 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": { - "key": { - "key": "value" - } - }, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": { + "key": "value" + } + }, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNull.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNull.json index 5990313ad72..a5af736fcc3 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNull.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNull.json @@ -1,15 +1,15 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": { - "key": null - }, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": null + }, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json index 0fb3d9f6d5c..620794c1546 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json @@ -1,17 +1,17 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": { - "key": { - "null": "value" - } - }, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": { + "null": "value" + } + }, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json index 85f9bc51b13..32991b5b053 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json @@ -1,15 +1,15 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": { - "key": 1 - }, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": 1 + }, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json index 5d4822a345e..b023cf303c5 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json @@ -1,17 +1,17 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": { - "key": { - "true": "value" - } - }, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": { + "true": "value" + } + }, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json index f8cdb89b7b6..8ee7c96265b 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json @@ -1,22 +1,22 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": { - "key": [ - [ - "string", - 1, - null, - true - ] - ] - }, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": [ + [ + "string", + 1, + null, + true + ] + ] + }, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json index 9549e8d72a7..4c85a476057 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json @@ -1,22 +1,22 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": { - "key": { - "key": [ - "string", - 1, - true, - null - ] - } - }, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": { + "key": [ + "string", + 1, + true, + null + ] + } + }, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraString.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraString.json index e07330cc8f5..900d1453d83 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraString.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraString.json @@ -1,15 +1,15 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": { - "key": "string" - }, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": { + "key": "string" + }, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json index 9eccdebd662..0aea80f867d 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json @@ -1,14 +1,14 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": {}, - "checksum": null, - "interfaceKey": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null, + "interfaceKey": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json index b957c2bc557..2b2b69683a8 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json @@ -1,13 +1,13 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": "debug", - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": {}, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": "debug", + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelError.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelError.json index a80f1726bd0..4a07c54da30 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelError.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelError.json @@ -1,13 +1,13 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": "error", - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": {}, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": "error", + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json index 9ed6d5373b5..7db81be2c7d 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json @@ -1,13 +1,13 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": "fatal", - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": {}, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": "fatal", + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json index 138a4ac379f..ea907c1841e 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json @@ -1,13 +1,13 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": "info", - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": {}, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": "info", + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json index 386ff2cdcf3..c39e0c25afe 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json @@ -1,13 +1,13 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": "warning", - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": {}, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": "warning", + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLogger.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLogger.json index b8e66a11fde..30513fdadcd 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLogger.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLogger.json @@ -1,13 +1,13 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": "logger", - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": {}, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": "logger", + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testMessage.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testMessage.json index e3c46802090..4afed43c89d 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testMessage.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testMessage.json @@ -1,13 +1,13 @@ { - "event_id": "00000000000000000000000000000000", - "message": "message", - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": {}, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": "message", + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testPlatform.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testPlatform.json index d19e5bafc71..f62d84f56d5 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testPlatform.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testPlatform.json @@ -1,13 +1,13 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": "platform", - "culprit": null, - "tags": {}, - "server_name": null, - "extra": {}, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": "platform", + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testServerName.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testServerName.json index 6eee76a9e55..a18c76a34d3 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testServerName.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testServerName.json @@ -1,13 +1,13 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": "serverName", - "extra": {}, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": "serverName", + "extra": {}, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTags.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTags.json index 65ffa6b56ae..5a9d0c17444 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTags.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTags.json @@ -1,15 +1,15 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": { - "tagName": "tagValue" - }, - "server_name": null, - "extra": {}, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": { + "tagName": "tagValue" + }, + "server_name": null, + "extra": {}, + "checksum": null } diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTimestamp.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTimestamp.json index d48f27557dd..1936fb233e0 100644 --- a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTimestamp.json +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTimestamp.json @@ -1,13 +1,13 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "2013-11-24T04:11:35", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "tags": {}, - "server_name": null, - "extra": {}, - "checksum": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "2013-11-24T04:11:35", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "extra": {}, + "checksum": null } diff --git a/sentry-stub/src/main/webapp/WEB-INF/web.xml b/sentry-stub/src/main/webapp/WEB-INF/web.xml index b9436cdd146..52a30ef50f5 100644 --- a/sentry-stub/src/main/webapp/WEB-INF/web.xml +++ b/sentry-stub/src/main/webapp/WEB-INF/web.xml @@ -1,5 +1,5 @@ - From 17368fb402ab3c31658852a68d51f63956f62a0d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 06:41:58 +1000 Subject: [PATCH 1247/2152] Upgrade to log4j2-rc2 --- pom.xml | 2 +- .../net/kencochrane/raven/log4j2/SentryAppender.java | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 54835df97e9..815b14a5d17 100644 --- a/pom.xml +++ b/pom.xml @@ -112,7 +112,7 @@ 3.0.1 1.1.2 1.2.17 - 2.0-rc1 + 2.0-rc2 1.9.5 1.8 6.8.8 diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 9c4056bf763..db463d6707c 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -130,13 +130,13 @@ public static SentryAppender createAppender(@PluginAttribute("name") final Strin * @return log level used within raven. */ protected static Event.Level formatLevel(Level level) { - if (level.isAtLeastAsSpecificAs(Level.FATAL)) + if (level.isMoreSpecificThan(Level.FATAL)) return Event.Level.FATAL; - else if (level.isAtLeastAsSpecificAs(Level.ERROR)) + else if (level.isMoreSpecificThan(Level.ERROR)) return Event.Level.ERROR; - else if (level.isAtLeastAsSpecificAs(Level.WARN)) + else if (level.isMoreSpecificThan(Level.WARN)) return Event.Level.WARNING; - else if (level.isAtLeastAsSpecificAs(Level.INFO)) + else if (level.isMoreSpecificThan(Level.INFO)) return Event.Level.INFO; else return Event.Level.DEBUG; @@ -212,7 +212,7 @@ protected void initRaven() { protected Event buildEvent(LogEvent event) { Message eventMessage = event.getMessage(); EventBuilder eventBuilder = new EventBuilder() - .setTimestamp(new Date(event.getMillis())) + .setTimestamp(new Date(event.getTimeMillis())) .setMessage(eventMessage.getFormattedMessage()) .setLogger(event.getLoggerName()) .setLevel(formatLevel(event.getLevel())) From b5e7d8915ae8ec6f13f6247570681003d374f11b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 06:42:21 +1000 Subject: [PATCH 1248/2152] Upgrade Jackson to 2.4.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 815b14a5d17..d8348a6d292 100644 --- a/pom.xml +++ b/pom.xml @@ -108,7 +108,7 @@ 1.7.7 17.0 - 2.4.0 + 2.4.1 3.0.1 1.1.2 1.2.17 From 99f35fdd79ee8688b9208d95b43d9870cfdcf316 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 07:09:20 +1000 Subject: [PATCH 1249/2152] Add the nexus staging plugin to make releases easier to manage --- pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pom.xml b/pom.xml index d8348a6d292..10366896486 100644 --- a/pom.xml +++ b/pom.xml @@ -534,6 +534,17 @@ + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.2 + true + + sonatype-nexus-staging + https://oss.sonatype.org/ + false + + From 971b5a0b72ef10d6a67e804b86484e49486a2079 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 07:17:42 +1000 Subject: [PATCH 1250/2152] Avoid ampersands in the javadoc --- .../kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java index 94da6e59a3b..6a505177a99 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java @@ -20,7 +20,7 @@ *

      *
    • JSON Stream (nothing to do)
    • *
    • Base 64'd JSON streams (base64 decoded)
    • - *
    • Base 64'd & deflated JSON streams (base64 decoded and inflated)
    • + *
    • Base 64'd and deflated JSON streams (base64 decoded and inflated)
    • *
    *

    */ From 99e9725e435337505b598fc07073943591c4699b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 07:31:46 +1000 Subject: [PATCH 1251/2152] Remove closing

    in the javadoc (HTML not XHTML) --- .../kencochrane/raven/appengine/AppEngineRavenFactory.java | 2 -- .../appengine/connection/AppEngineAsyncConnection.java | 5 ----- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 3 --- .../java/net/kencochrane/raven/log4j2/SentryAppender.java | 5 ----- .../java/net/kencochrane/raven/logback/SentryAppender.java | 5 ----- .../java/net/kencochrane/raven/DefaultRavenFactory.java | 3 --- raven/src/main/java/net/kencochrane/raven/Raven.java | 3 --- .../src/main/java/net/kencochrane/raven/RavenFactory.java | 2 -- .../kencochrane/raven/connection/AbstractConnection.java | 2 -- .../net/kencochrane/raven/connection/AsyncConnection.java | 4 ---- .../kencochrane/raven/connection/ConnectionException.java | 1 - .../net/kencochrane/raven/connection/HttpConnection.java | 1 - raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java | 1 - .../net/kencochrane/raven/dsn/InvalidDsnException.java | 1 - .../main/java/net/kencochrane/raven/dsn/JndiLookup.java | 1 - .../kencochrane/raven/environment/RavenEnvironment.java | 4 ---- raven/src/main/java/net/kencochrane/raven/event/Event.java | 5 ----- .../java/net/kencochrane/raven/event/EventBuilder.java | 7 ------- .../kencochrane/raven/event/helper/EventBuilderHelper.java | 1 - .../raven/event/helper/HttpEventBuilderHelper.java | 1 - .../raven/event/interfaces/MessageInterface.java | 4 ---- .../raven/event/interfaces/SentryException.java | 3 --- .../raven/event/interfaces/StackTraceInterface.java | 1 - .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 3 --- .../java/net/kencochrane/raven/marshaller/Marshaller.java | 1 - .../raven/marshaller/json/ExceptionInterfaceBinding.java | 1 - .../kencochrane/raven/marshaller/json/JsonMarshaller.java | 1 - raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java | 2 -- .../marshaller/json/ExceptionInterfaceBindingTest.java | 1 - .../raven/sentrystub/SentryUdpContextListener.java | 1 - .../kencochrane/raven/sentrystub/auth/AuthValidator.java | 4 ---- .../raven/sentrystub/unmarshaller/JsonDecoder.java | 2 -- 32 files changed, 81 deletions(-) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java index 9fa86378f1f..89d63fe2201 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java @@ -21,11 +21,9 @@ public class AppEngineRavenFactory extends DefaultRavenFactory { *

    * It is important to set a different connection identifier for each opened connection to keep the uniqueness * of connection ID. - *

    *

    * If the connection identifier is not specified, the system will define a connection identifier itself, but its * uniqueness within an instance isn't guaranteed. - *

    * {@see AppEngineAsyncConnection} */ public static final String CONNECTION_IDENTIFIER = "raven.async.gae.connectionid"; diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java index b26d69c57c6..7872109b456 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java @@ -21,7 +21,6 @@ *

    * Instead of synchronously sending each event to a connection, use a the task queue system to establish the connection * and submit the event. - *

    *

    * Google App Engine serialises the tasks before queuing them, to keep a link between the task and the * {@link AppEngineAsyncConnection} associated, a register of the instances of {@code AppEngineAsyncConnection} is @@ -30,7 +29,6 @@ * is removed from the register when it has been closed with {@link #close()}.
    * The register works based on identifier defined by the user. There is no ID conflict handling, the user is expected * to manage the uniqueness of those ID. - *

    */ public class AppEngineAsyncConnection implements Connection { private static final Logger logger = LoggerFactory.getLogger(AppEngineAsyncConnection.class); @@ -56,7 +54,6 @@ public class AppEngineAsyncConnection implements Connection { * Creates a connection which will rely on an executor to send events. *

    * Will propagate the {@link #close()} operation. - *

    * * @param id Identifier of the connection shared across all the instances of the application. * @param actualConnection Connection used to send the events. @@ -71,7 +68,6 @@ public AppEngineAsyncConnection(String id, Connection actualConnection) { * {@inheritDoc} *

    * The event will be added to a queue and will be handled by a separate {@code Thread} later on. - *

    */ @Override public void send(Event event) { @@ -83,7 +79,6 @@ public void send(Event event) { * {@inheritDoc}. *

    * Closing the {@link AppEngineAsyncConnection} will gracefully remove every task created earlier from the queue. - *

    */ @Override public void close() throws IOException { diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 00dc98cc0bb..71f5dc468a0 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -42,21 +42,18 @@ public class SentryAppender extends AppenderSkeleton { * DSN property of the appender. *

    * Might be null in which case the DSN should be detected automatically. - *

    */ protected String dsn; /** * Name of the {@link RavenFactory} being used. *

    * Might be null in which case the factory should be defined automatically. - *

    */ protected String ravenFactory; /** * Additional tags to be sent to sentry. *

    * Might be empty in which case no tags are sent. - *

    */ protected Map tags = Collections.emptyMap(); diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index db463d6707c..406817e584a 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -54,21 +54,18 @@ public class SentryAppender extends AbstractAppender { * DSN property of the appender. *

    * Might be null in which case the DSN should be detected automatically. - *

    */ protected String dsn; /** * Name of the {@link RavenFactory} being used. *

    * Might be null in which case the factory should be defined automatically. - *

    */ protected String ravenFactory; /** * Additional tags to be sent to sentry. *

    * Might be empty in which case no tags are sent. - *

    */ protected Map tags = Collections.emptyMap(); @@ -146,7 +143,6 @@ else if (level.isMoreSpecificThan(Level.INFO)) * Extracts message parameters into a List of Strings. *

    * null parameters are kept as null. - *

    * * @param parameters parameters provided to the logging system. * @return the parameters formatted as Strings in a List. @@ -163,7 +159,6 @@ protected static List formatMessageParameters(Object[] parameters) { *

    * The raven instance is set in this method instead of {@link #start()} in order to avoid substitute loggers * being generated during the instantiation of {@link Raven}.
    - *

    * * @param logEvent The LogEvent. */ diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 4f243e97ca0..e2b5fe70b55 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -43,21 +43,18 @@ public class SentryAppender extends AppenderBase { * DSN property of the appender. *

    * Might be null in which case the DSN should be detected automatically. - *

    */ protected String dsn; /** * Name of the {@link RavenFactory} being used. *

    * Might be null in which case the factory should be defined automatically. - *

    */ protected String ravenFactory; /** * Additional tags to be sent to sentry. *

    * Might be empty in which case no tags are sent. - *

    */ protected Map tags = Collections.emptyMap(); @@ -80,7 +77,6 @@ public SentryAppender(Raven raven) { * Extracts message parameters into a List of Strings. *

    * null parameters are kept as null. - *

    * * @param parameters parameters provided to the logging system. * @return the parameters formatted as Strings in a List. @@ -117,7 +113,6 @@ protected static Event.Level formatLevel(Level level) { * The raven instance is started in this method instead of {@link #start()} in order to avoid substitute loggers * being generated during the instantiation of {@link Raven}.
    * More on www.slf4j.org/codes.html#substituteLogger - *

    */ @Override protected void append(ILoggingEvent iLoggingEvent) { diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 3e1311b5e57..7ae63699c7f 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -25,7 +25,6 @@ * Default implementation of {@link RavenFactory}. *

    * In most cases this is the implementation to use or extend for additional features. - *

    */ public class DefaultRavenFactory extends RavenFactory { //TODO: Add support for tags set by default @@ -216,7 +215,6 @@ protected Marshaller createMarshaller(Dsn dsn) { *

    * Those packages will be used with the {@link StackTraceInterface} to hide frames that aren't a part of * the main application. - *

    * * @return the list of "not in-app" packages. */ @@ -235,7 +233,6 @@ protected Collection getNotInAppFrames() { *

    * Those (usually) low priority threads will allow to send event details to sentry concurrently without slowing * down the main application. - *

    */ @SuppressWarnings("PMD.AvoidThreadGroup") protected static final class DaemonThreadFactory implements ThreadFactory { diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 79f4f4c21e4..269e8e98e1d 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -20,7 +20,6 @@ * It is recommended to create an instance of Raven through * {@link RavenFactory#createRavenInstance(net.kencochrane.raven.dsn.Dsn)}, this will use the best factory available to * create a sensible instance of Raven. - *

    */ public class Raven { private static final Logger logger = LoggerFactory.getLogger(Raven.class); @@ -56,7 +55,6 @@ public void sendEvent(Event event) { * Sends a message to the Sentry server. *

    * The message will be logged at the {@link Event.Level#INFO} level. - *

    * * @param message message to send to Sentry. */ @@ -71,7 +69,6 @@ public void sendMessage(String message) { * Sends an exception to the Sentry server. *

    * The Exception will be logged at the {@link Event.Level#ERROR} level. - *

    * * @param exception exception to send to Sentry. */ diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index bb8d48a2c46..c7c9c353a2c 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -13,7 +13,6 @@ * Factory in charge of creating {@link Raven} instances. *

    * The factories register themselves through the {@link ServiceLoader} system. - *

    */ public abstract class RavenFactory { private static final ServiceLoader AUTO_REGISTERED_FACTORIES = ServiceLoader.load(RavenFactory.class); @@ -26,7 +25,6 @@ public abstract class RavenFactory { * Usually RavenFactories are automatically detected with the {@link ServiceLoader} system, but some systems * such as Android do not provide a fully working ServiceLoader.
    * If the factory isn't detected automatically, it's possible to add it through this method. - *

    * * @param ravenFactory ravenFactory to support. */ diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index d550d89306a..e48eb011a5a 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -14,7 +14,6 @@ * Provide the basic tools to submit events to the server (authentication header, dsn).
    * To avoid spamming the network if and when Sentry is down, automatically lock the connection each time a * {@link ConnectionException} is caught. - *

    */ public abstract class AbstractConnection implements Connection { /** @@ -40,7 +39,6 @@ public abstract class AbstractConnection implements Connection { * Base duration for a lockdown. *

    * On each attempt the time is doubled until it reaches {@link #maxWaitingTime}. - *

    */ private long baseWaitingTime = DEFAULT_BASE_WAITING_TIME; private long waitingTime = baseWaitingTime; diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 2f8bf1f1674..c0aef47baab 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -16,7 +16,6 @@ *

    * Instead of synchronously sending each event to a connection, use a ThreadPool to establish the connection * and submit the event. - *

    */ public class AsyncConnection implements Connection { private static final Logger logger = LoggerFactory.getLogger(AsyncConnection.class); @@ -45,7 +44,6 @@ public class AsyncConnection implements Connection { * Creates a connection which will rely on an executor to send events. *

    * Will propagate the {@link #close()} operation. - *

    * * @param actualConnection connection used to send the events. * @param executorService executorService used to process events, if null, the executorService will automatically @@ -74,7 +72,6 @@ private void addShutdownHook() { * {@inheritDoc} *

    * The event will be added to a queue and will be handled by a separate {@code Thread} later on. - *

    */ @Override public void send(Event event) { @@ -89,7 +86,6 @@ public void send(Event event) { * timeout of {@link #SHUTDOWN_TIMEOUT}, allowing the current events to be submitted while new events will * be rejected.
    * If the shutdown times out, the {@code executorService} will be forced to shutdown. - *

    */ @Override public void close() throws IOException { diff --git a/raven/src/main/java/net/kencochrane/raven/connection/ConnectionException.java b/raven/src/main/java/net/kencochrane/raven/connection/ConnectionException.java index 41d9ee0fdf6..d2b0cd96b8e 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/ConnectionException.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/ConnectionException.java @@ -4,7 +4,6 @@ * Exception thrown by a {@link net.kencochrane.raven.connection.Connection} if something went wrong temporarily. *

    * This allows connections to know when to back off for a while. - *

    */ public class ConnectionException extends RuntimeException { //CHECKSTYLE.OFF: JavadocMethod diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 42af12b7f2e..b9451285625 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -21,7 +21,6 @@ * Basic connection to a Sentry server, using HTTP and HTTPS. *

    * It is possible to enable the "naive mode" to allow a connection over SSL using a certificate with a wildcard. - *

    */ public class HttpConnection extends AbstractConnection { private static final Logger logger = LoggerFactory.getLogger(HttpConnection.class); diff --git a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java index d211277b080..dad0db54285 100644 --- a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java @@ -191,7 +191,6 @@ private void makeOptionsImmutable() { * Validates internally the DSN, and check for mandatory elements. *

    * Mandatory elements are the {@link #host}, {@link #publicKey}, {@link #secretKey} and {@link #projectId}. - *

    */ private void validate() { List missingElements = new LinkedList<>(); diff --git a/raven/src/main/java/net/kencochrane/raven/dsn/InvalidDsnException.java b/raven/src/main/java/net/kencochrane/raven/dsn/InvalidDsnException.java index 3e538985e56..5a0cad746c4 100644 --- a/raven/src/main/java/net/kencochrane/raven/dsn/InvalidDsnException.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/InvalidDsnException.java @@ -5,7 +5,6 @@ *

    * The invalidity of the DSN can either be due on the content of the DSN (invalid or missing parameters) or the sentry * server issuing an authentication error. - *

    */ public class InvalidDsnException extends RuntimeException { //CHECKSTYLE.OFF: JavadocMethod diff --git a/raven/src/main/java/net/kencochrane/raven/dsn/JndiLookup.java b/raven/src/main/java/net/kencochrane/raven/dsn/JndiLookup.java index 14545603c9d..8dda41fcfec 100644 --- a/raven/src/main/java/net/kencochrane/raven/dsn/JndiLookup.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/JndiLookup.java @@ -13,7 +13,6 @@ *

    * Android does not support JNDI making the automatic lookup through JNDI illegal and fatal to the application. * Having the lookup in a separate class allows the classloader to load {@link Dsn} without exceptions. - *

    */ final class JndiLookup { /** diff --git a/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java b/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java index 10198c225a6..42096a5b0bb 100644 --- a/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java +++ b/raven/src/main/java/net/kencochrane/raven/environment/RavenEnvironment.java @@ -11,7 +11,6 @@ *

    * Manages information related to Raven Runtime such as the name of the library or * whether or not the thread is managed by Raven. - *

    */ public final class RavenEnvironment { /** @@ -37,7 +36,6 @@ private RavenEnvironment() { * Sets the current thread as managed by Raven. *

    * The logs generated by Threads managed by Raven will not send logs to Sentry. - *

    *

    * Recommended usage: *

    {@code
    @@ -48,7 +46,6 @@ private RavenEnvironment() {
          *     RavenEnvironment.stopManagingThread();
          * }
          * }
    - *

    */ public static void startManagingThread() { try { @@ -63,7 +60,6 @@ public static void startManagingThread() { * Sets the current thread as not managed by Raven. *

    * The logs generated by Threads not managed by Raven will send logs to Sentry. - *

    */ public static void stopManagingThread() { try { diff --git a/raven/src/main/java/net/kencochrane/raven/event/Event.java b/raven/src/main/java/net/kencochrane/raven/event/Event.java index 1d9d3d0738b..0406a7ad64d 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/Event.java +++ b/raven/src/main/java/net/kencochrane/raven/event/Event.java @@ -13,7 +13,6 @@ *

    * For security purposes, an event should be created from an {@link EventBuilder} only, and be completely immutable * once it has been fully generated. - *

    *

    * Notes to developers: *

      @@ -26,7 +25,6 @@ * There is one exception, the {@link #extra} section can't be transformed to be completely immutable. * *
    - *

    */ public class Event implements Serializable { /** @@ -61,7 +59,6 @@ public class Event implements Serializable { * A map or list of tags for this event. *

    * Automatically created with a Map that is made unmodifiable by the {@link EventBuilder}. - *

    */ private Map tags = new HashMap<>(); /** @@ -72,7 +69,6 @@ public class Event implements Serializable { * A map or list of additional properties for this event. *

    * Automatically created with a Map that is made unmodifiable by the {@link EventBuilder}. - *

    */ private Map extra = new HashMap<>(); /** @@ -83,7 +79,6 @@ public class Event implements Serializable { * Additional interfaces for other information and metadata. *

    * Automatically created with a Map that is made unmodifiable by the {@link EventBuilder}. - *

    */ private Map sentryInterfaces = new HashMap<>(); diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 5c45bc5cd6b..e876a411113 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -41,7 +41,6 @@ public class EventBuilder { * Creates a new EventBuilder to prepare a new {@link Event}. *

    * Automatically generates the id of the new event. - *

    */ public EventBuilder() { this(UUID.randomUUID()); @@ -198,7 +197,6 @@ public EventBuilder setCulprit(String culprit) { * Adds a tag to an event. *

    * This allows to set a tag value in different contexts. - *

    * * @param tagKey name of the tag. * @param tagValue value of the tag. @@ -246,7 +244,6 @@ public EventBuilder generateChecksum(String contentToChecksum) { * Sets the checksum for the current event. *

    * It's recommended to rely instead on the checksum system provided by Sentry. - *

    * * @param checksum checksum for the event. * @return the current {@code EventBuilder} for chained calls. @@ -261,7 +258,6 @@ public EventBuilder setChecksum(String checksum) { *

    * If a {@code SentryInterface} with the same interface name has already been added, the new one will replace * the old one. - *

    * * @param sentryInterface sentry interface to add to the event. * @return the current {@code EventBuilder} for chained calls. @@ -275,7 +271,6 @@ public EventBuilder addSentryInterface(SentryInterface sentryInterface) { * Finalises the {@link Event} and returns it. *

    * This operations will automatically set the missing values and make the mutable values immutable. - *

    * * @return an immutable event. */ @@ -307,7 +302,6 @@ public String toString() { * hostname for a period defined during the construction.
    * For performance purposes, the operation of retrieving the hostname will automatically fail after a period of time * defined by {@link #GET_HOSTNAME_TIMEOUT} without result. - *

    */ private static final class HostnameCache { /** @@ -341,7 +335,6 @@ private HostnameCache(long cacheDuration) { * Gets the hostname of the current machine. *

    * Gets the value from the cache if possible otherwise calls {@link #updateCache()}. - *

    * * @return the hostname of the current machine. */ diff --git a/raven/src/main/java/net/kencochrane/raven/event/helper/EventBuilderHelper.java b/raven/src/main/java/net/kencochrane/raven/event/helper/EventBuilderHelper.java index 9330c3e085a..17d2a2521eb 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/helper/EventBuilderHelper.java +++ b/raven/src/main/java/net/kencochrane/raven/event/helper/EventBuilderHelper.java @@ -12,7 +12,6 @@ public interface EventBuilderHelper { *

    * EventBuilderHelper are supposed to only add details to the Event before it's built. Calling the * {@link EventBuilder#build()} method from the helper will prevent the event from being built properly. - *

    * * @param eventBuilder event builder to enhance before the event is built. */ diff --git a/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java b/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java index 47a6c97558a..8f121c0b594 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java +++ b/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java @@ -11,7 +11,6 @@ *

    * The {@link HttpServletRequest} is retrieved from a {@link ThreadLocal} storage. This means that this builder must * be called from the thread in which the HTTP request has been handled. - *

    */ public class HttpEventBuilderHelper implements EventBuilderHelper { @Override diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java index 4a5cfa0381b..e0471f1fb71 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java @@ -12,18 +12,15 @@ * Sentry's ability to regroup event with the same messages is based on the content of the message, meaning that an * {@link net.kencochrane.raven.event.Event} with the message "User1 failed to provide an email address" * won't be grouped with an Event with the message "User2 failed to provide an email address". - *

    *

    * To allow this kind of grouping, sentry supports the message interface which will provide both the pattern of the * message and the parameters. In this example the pattern could be:
    * {} failed to provide an email address
    * And the parameters would be User1 in the first Event and User2 in the second Event.
    * This way, Sentry will be able to put the two events in the same category. - *

    *

    * Note: Sentry won't attempt to format the message, this is why the formatted message should be set through * {@link net.kencochrane.raven.event.EventBuilder#setMessage(String)} in any case. - *

    */ public class MessageInterface implements SentryInterface { /** @@ -38,7 +35,6 @@ public class MessageInterface implements SentryInterface { *

    * While it's technically possible to create a non parametrised message with {@code MessageInterface}, it's * recommended to use {@link net.kencochrane.raven.event.EventBuilder#setMessage(String)} instead. - *

    * * @param message message to add to the event. * @deprecated Use {@link net.kencochrane.raven.event.EventBuilder#setMessage(String)} instead. diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java index 895c87bb746..81e538ea94c 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java @@ -23,7 +23,6 @@ public final class SentryException { *

    * The {@code childExceptionStackTrace} parameter is used to define the common frames with the child exception * (Exception caused by {@code throwable}). - *

    * * @param throwable Java exception to send to Sentry. * @param childExceptionStackTrace StackTrace of the exception caused by {@code throwable}. @@ -58,7 +57,6 @@ public SentryException(String exceptionMessage, * Transforms a {@link Throwable} into a Queue of {@link SentryException}. *

    * Exceptions are stored in the queue from the most recent one to the oldest one. - *

    * * @param throwable throwable to transform in a queue of exceptions. * @return a queue of exception with StackTrace. @@ -90,7 +88,6 @@ public String getExceptionClassName() { * Gets the exception package name. *

    * If there is no package, the value will be {@link #DEFAULT_PACKAGE_NAME}. - *

    * * @return the exception package name or {@link #DEFAULT_PACKAGE_NAME} if it isn't defined. */ diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java index d636ec27197..9f097f84da6 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/StackTraceInterface.java @@ -27,7 +27,6 @@ public StackTraceInterface(StackTraceElement[] stackTrace) { *

    * With the help of the enclosing StackTrace, figure out which frames are in common with the parent exception * to potentially hide them later in Sentry. - *

    * * @param stackTrace StackTrace to provide to Sentry. * @param enclosingStackTrace StackTrace of the enclosing exception, to determine how many Stack frames diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 3e0a63c6da3..ec775724e3f 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -33,14 +33,12 @@ public class SentryHandler extends Handler { * DSN property of the appender. *

    * Might be null in which case the DSN should be detected automatically. - *

    */ protected String dsn; /** * Name of the {@link RavenFactory} being used. *

    * Might be null in which case the factory should be defined automatically. - *

    */ protected String ravenFactory; /** @@ -86,7 +84,6 @@ else if (level.intValue() >= Level.ALL.intValue()) * Extracts message parameters into a List of Strings. *

    * null parameters are kept as null. - *

    * * @param parameters parameters provided to the logging system. * @return the parameters formatted as Strings in a List. diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java index 7a20c92d9f9..095fb1ea92d 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/Marshaller.java @@ -14,7 +14,6 @@ public interface Marshaller { *

    * The marshaller should not close the given stream, use {@link UncloseableOutputStream} to prevent automatic calls * to {@link OutputStream#close()}. - *

    * * @param event event to serialise. * @param destination destination stream. diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java index 613a5f22671..19e8906373d 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBinding.java @@ -24,7 +24,6 @@ public class ExceptionInterfaceBinding implements InterfaceBinding * Exceptions may contain StackTraces, this means that the system should also be able to send a * {@link StackTraceInterface} on the JSON stream. - *

    * * @param stackTraceInterfaceBinding InterfaceBinding allowing to send a {@link StackTraceInterface} on the JSON * stream. diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 5789b364caf..414e5d768f7 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -24,7 +24,6 @@ *

    * The content can also be compressed with {@link DeflaterOutputStream} in which case the binary result is encoded * in base 64. - *

    */ public class JsonMarshaller implements Marshaller { /** diff --git a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java index b9e4e77fc04..486270727ce 100644 --- a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java @@ -120,7 +120,6 @@ public void testDsnLookupWithEnvironmentVariable() throws Exception { * Sets an environment variable during Unit-Test. *

    * DO NOT USE THIS METHOD OUTSIDE OF THE UNIT TESTS! - *

    * * @param key name of the environment variable. * @param value value for the variable. @@ -145,7 +144,6 @@ private void setEnv(String key, String value) throws Exception { * Removes an environment variable during Unit-Test. *

    * DO NOT USE THIS METHOD OUTSIDE OF THE UNIT TESTS! - *

    * * @param key name of the environment variable. * @throws Exception if anything goes wrong. diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index bd6b1efe086..9b816b65916 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -112,7 +112,6 @@ public Deque getExceptions() { * Obviously we can't use an Exception which is really defined in the default package within those tests * (can't import it), so instead set the name of the class to remove the package name.
    * {@code Deencapsulation.setField(Object) DefaultPackageException.class, "name", "DefaultPackageClass")} - *

    */ class DefaultPackageException extends Exception { } diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java index b0fd878a5bc..3e9971fc311 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java @@ -23,7 +23,6 @@ * ContextListener stating an UDP socket when the servlet container starts. *

    * This listener allows a {@link DatagramSocket} to be started to listen to UDP requests. - *

    */ @WebListener public class SentryUdpContextListener implements ServletContextListener { diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java index 0522175184f..10baabc9c36 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java @@ -9,7 +9,6 @@ *

    * The validation of a header goes from the validation of the content to the authorisation check for the given public * and secret keys. - *

    */ public class AuthValidator { private static final Logger logger = Logger.getLogger(AuthValidator.class.getCanonicalName()); @@ -79,7 +78,6 @@ public void validateSentryAuth(Map authParameters, String projec * Validates the version of the protocol given in the Auth header. *

    * The only supported versions are listed in {@link #SENTRY_PROTOCOL_VERSIONS}. - *

    * * @param authSentryVersion version of the Sentry protocol given in the auth header. * @param invalidAuthException exception thrown if the auth header is invalid. @@ -94,7 +92,6 @@ private void validateVersion(String authSentryVersion, InvalidAuthException inva * Validates the public and secret user keys provided in the Auth Header. *

    * Valid keys are listed in {@link #publicKeySecretKey}. - *

    * * @param publicKey public key used to identify a user. * @param secretKey secret key used as a password. @@ -138,7 +135,6 @@ private void validateProject(String publicKey, String projectId, InvalidAuthExce * Validates the client part of the header. *

    * The client should always be provided. - *

    * * @param client string identifying a client type (such as Java/3.0) * @param invalidAuthException exception thrown if the auth header is invalid. diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java index 6a505177a99..409cd877196 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java @@ -22,7 +22,6 @@ *
  • Base 64'd JSON streams (base64 decoded)
  • *
  • Base 64'd and deflated JSON streams (base64 decoded and inflated)
  • * - *

    */ public class JsonDecoder { private static Logger logger = Logger.getLogger(JsonDecoder.class.getCanonicalName()); @@ -31,7 +30,6 @@ public class JsonDecoder { * Attempts to read the content of the stream and determine if it's compressed, encoded or simple JSON. *

    * This method isn't efficient but it isn't really a problem as this part of the project is not about performances. - *

    * * @param originalStream origin stream of information that can be compressed or encoded in base64. * @return a Stream containing pure JSON. From 849e72f3f2b45260ad9fa44973c0f4f0c0c8d271 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 07:31:46 +1000 Subject: [PATCH 1252/2152] Replace self closing
    in the javadoc (HTML not XHTML) --- .../appengine/connection/AppEngineAsyncConnection.java | 4 ++-- .../java/net/kencochrane/raven/log4j2/SentryAppender.java | 2 +- .../java/net/kencochrane/raven/logback/SentryAppender.java | 2 +- raven/src/main/java/net/kencochrane/raven/RavenFactory.java | 2 +- .../kencochrane/raven/connection/AbstractConnection.java | 2 +- .../net/kencochrane/raven/connection/AsyncConnection.java | 2 +- raven/src/main/java/net/kencochrane/raven/event/Event.java | 2 +- .../main/java/net/kencochrane/raven/event/EventBuilder.java | 2 +- .../raven/event/interfaces/MessageInterface.java | 6 +++--- .../marshaller/json/ExceptionInterfaceBindingTest.java | 2 +- 10 files changed, 13 insertions(+), 13 deletions(-) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java index 7872109b456..812437f885f 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java @@ -24,9 +24,9 @@ *

    * Google App Engine serialises the tasks before queuing them, to keep a link between the task and the * {@link AppEngineAsyncConnection} associated, a register of the instances of {@code AppEngineAsyncConnection} is - * kept in {@link #APP_ENGINE_ASYNC_CONNECTIONS}.
    + * kept in {@link #APP_ENGINE_ASYNC_CONNECTIONS}.
    * This register is populated when a new instance of {@code AppEngineAsyncConnection} is created and the connection - * is removed from the register when it has been closed with {@link #close()}.
    + * is removed from the register when it has been closed with {@link #close()}.
    * The register works based on identifier defined by the user. There is no ID conflict handling, the user is expected * to manage the uniqueness of those ID. */ diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 406817e584a..5e54b4fe02d 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -158,7 +158,7 @@ protected static List formatMessageParameters(Object[] parameters) { * {@inheritDoc} *

    * The raven instance is set in this method instead of {@link #start()} in order to avoid substitute loggers - * being generated during the instantiation of {@link Raven}.
    + * being generated during the instantiation of {@link Raven}.
    * * @param logEvent The LogEvent. */ diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index e2b5fe70b55..04cfbbf63fa 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -111,7 +111,7 @@ protected static Event.Level formatLevel(Level level) { * {@inheritDoc} *

    * The raven instance is started in this method instead of {@link #start()} in order to avoid substitute loggers - * being generated during the instantiation of {@link Raven}.
    + * being generated during the instantiation of {@link Raven}.
    * More on www.slf4j.org/codes.html#substituteLogger */ @Override diff --git a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java index c7c9c353a2c..eb4e095483e 100644 --- a/raven/src/main/java/net/kencochrane/raven/RavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/RavenFactory.java @@ -23,7 +23,7 @@ public abstract class RavenFactory { * Manually adds a RavenFactory to the system. *

    * Usually RavenFactories are automatically detected with the {@link ServiceLoader} system, but some systems - * such as Android do not provide a fully working ServiceLoader.
    + * such as Android do not provide a fully working ServiceLoader.
    * If the factory isn't detected automatically, it's possible to add it through this method. * * @param ravenFactory ravenFactory to support. diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index e48eb011a5a..2cd87fe53cb 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -11,7 +11,7 @@ /** * Abstract connection to a Sentry server. *

    - * Provide the basic tools to submit events to the server (authentication header, dsn).
    + * Provide the basic tools to submit events to the server (authentication header, dsn).
    * To avoid spamming the network if and when Sentry is down, automatically lock the connection each time a * {@link ConnectionException} is caught. */ diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index c0aef47baab..e0d4241a269 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -84,7 +84,7 @@ public void send(Event event) { *

    * Closing the {@link AsyncConnection} will attempt a graceful shutdown of the {@link #executorService} with a * timeout of {@link #SHUTDOWN_TIMEOUT}, allowing the current events to be submitted while new events will - * be rejected.
    + * be rejected.
    * If the shutdown times out, the {@code executorService} will be forced to shutdown. */ @Override diff --git a/raven/src/main/java/net/kencochrane/raven/event/Event.java b/raven/src/main/java/net/kencochrane/raven/event/Event.java index 0406a7ad64d..038c87dfa52 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/Event.java +++ b/raven/src/main/java/net/kencochrane/raven/event/Event.java @@ -21,7 +21,7 @@ * *

  • * A proper immutable Object should only contain immutable Objects and primitives, this must be ensured before - * publishing the Event.
    + * publishing the Event.
    * There is one exception, the {@link #extra} section can't be transformed to be completely immutable. *
  • * diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index e876a411113..99896a7bd40 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -299,7 +299,7 @@ public String toString() { *

    * The {@code InetAddress.getLocalHost().getCanonicalHostName()} call can be quite expensive and could be called * for the creation of each {@link Event}. This system will prevent unnecessary costs by keeping track of the - * hostname for a period defined during the construction.
    + * hostname for a period defined during the construction.
    * For performance purposes, the operation of retrieving the hostname will automatically fail after a period of time * defined by {@link #GET_HOSTNAME_TIMEOUT} without result. */ diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java index e0471f1fb71..b47326267ec 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java @@ -14,9 +14,9 @@ * won't be grouped with an Event with the message "User2 failed to provide an email address". *

    * To allow this kind of grouping, sentry supports the message interface which will provide both the pattern of the - * message and the parameters. In this example the pattern could be:
    - * {} failed to provide an email address
    - * And the parameters would be User1 in the first Event and User2 in the second Event.
    + * message and the parameters. In this example the pattern could be:
    + * {} failed to provide an email address
    + * And the parameters would be User1 in the first Event and User2 in the second Event.
    * This way, Sentry will be able to put the two events in the same category. *

    * Note: Sentry won't attempt to format the message, this is why the formatted message should be set through diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index 9b816b65916..f2c6e1ef4fb 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -110,7 +110,7 @@ public Deque getExceptions() { * Exception used to test exceptions defined in the default package. *

    * Obviously we can't use an Exception which is really defined in the default package within those tests - * (can't import it), so instead set the name of the class to remove the package name.
    + * (can't import it), so instead set the name of the class to remove the package name.
    * {@code Deencapsulation.setField(Object) DefaultPackageException.class, "name", "DefaultPackageClass")} */ class DefaultPackageException extends Exception { From 281410aaf15ea3edcd4344b46b8de2a0d7ce57e2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 07:31:46 +1000 Subject: [PATCH 1253/2152] Remove closing in the javadoc (HTML not XHTML) --- raven/src/main/java/net/kencochrane/raven/event/Event.java | 2 -- .../raven/sentrystub/unmarshaller/JsonDecoder.java | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/Event.java b/raven/src/main/java/net/kencochrane/raven/event/Event.java index 038c87dfa52..bb9aa1cb732 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/Event.java +++ b/raven/src/main/java/net/kencochrane/raven/event/Event.java @@ -18,12 +18,10 @@ *

      *
    • * In order to ensure that a Event can't be modified externally, the setters should have a package visibility. - *
    • *
    • * A proper immutable Object should only contain immutable Objects and primitives, this must be ensured before * publishing the Event.
      * There is one exception, the {@link #extra} section can't be transformed to be completely immutable. - *
    • *
    */ public class Event implements Serializable { diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java index 409cd877196..2f8b44cc721 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/unmarshaller/JsonDecoder.java @@ -18,9 +18,9 @@ *

    * The supported stream formats are: *

      - *
    • JSON Stream (nothing to do)
    • - *
    • Base 64'd JSON streams (base64 decoded)
    • - *
    • Base 64'd and deflated JSON streams (base64 decoded and inflated)
    • + *
    • JSON Stream (nothing to do) + *
    • Base 64'd JSON streams (base64 decoded) + *
    • Base 64'd and deflated JSON streams (base64 decoded and inflated) *
    */ public class JsonDecoder { From 188fa7215bb204f288823b6c46773113f0da40bc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 2 Jul 2014 07:40:22 +1000 Subject: [PATCH 1254/2152] Fix See tag --- .../net/kencochrane/raven/appengine/AppEngineRavenFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java index 89d63fe2201..cd21d930461 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java @@ -24,7 +24,7 @@ public class AppEngineRavenFactory extends DefaultRavenFactory { *

    * If the connection identifier is not specified, the system will define a connection identifier itself, but its * uniqueness within an instance isn't guaranteed. - * {@see AppEngineAsyncConnection} + * @see AppEngineAsyncConnection */ public static final String CONNECTION_IDENTIFIER = "raven.async.gae.connectionid"; From 96e31e516fa0979eb4d5000b853d691a0b36538f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 5 Jul 2014 10:03:43 +1000 Subject: [PATCH 1255/2152] Move unchecked operation in simpler methods --- .../raven/marshaller/json/JsonMarshaller.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 414e5d768f7..05d74129f76 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -84,7 +84,7 @@ public class JsonMarshaller implements Marshaller { private static final String ISO_8601_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; private static final Logger logger = LoggerFactory.getLogger(JsonMarshaller.class); private final JsonFactory jsonFactory = new JsonFactory(); - private final Map, InterfaceBinding> interfaceBindings = new HashMap<>(); + private final Map, InterfaceBinding> interfaceBindings = new HashMap<>(); /** * Enables disables the compression of JSON. */ @@ -125,7 +125,6 @@ private void writeContent(JsonGenerator generator, Event event) throws IOExcepti generator.writeEndObject(); } - @SuppressWarnings("unchecked") private void writeInterfaces(JsonGenerator generator, Map sentryInterfaces) throws IOException { for (Map.Entry interfaceEntry : sentryInterfaces.entrySet()) { @@ -133,7 +132,7 @@ private void writeInterfaces(JsonGenerator generator, Map InterfaceBinding getInterfaceBinding(T sentryInterface) { + // Reduces the @SuppressWarnings to a oneliner + return (InterfaceBinding) interfaceBindings.get(sentryInterface.getClass()); + } + private void writeExtras(JsonGenerator generator, Map extras) throws IOException { generator.writeObjectFieldStart(EXTRA); for (Map.Entry extra : extras.entrySet()) { From dbd3c1e67f511a7e7fac8a1baf697dfea685ae80 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 5 Jul 2014 10:28:05 +1000 Subject: [PATCH 1256/2152] Reduce the number of DateFormat being created Also stop relying on a String for the UTC Timezone --- .../raven/marshaller/json/JsonMarshaller.java | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 05d74129f76..3f6805c1924 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -14,6 +14,7 @@ import java.io.OutputStreamWriter; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.time.ZoneOffset; import java.util.*; import java.util.zip.DeflaterOutputStream; @@ -81,7 +82,15 @@ public class JsonMarshaller implements Marshaller { /** * Date format for ISO 8601. */ - private static final String ISO_8601_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; + private static final ThreadLocal ISO_FORMAT = new ThreadLocal() { + @Override + protected DateFormat initialValue() { + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + dateFormat.setTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC)); + return dateFormat; + } + }; + private static final Logger logger = LoggerFactory.getLogger(JsonMarshaller.class); private final JsonFactory jsonFactory = new JsonFactory(); private final Map, InterfaceBinding> interfaceBindings = new HashMap<>(); @@ -111,7 +120,7 @@ private void writeContent(JsonGenerator generator, Event event) throws IOExcepti generator.writeStringField(EVENT_ID, formatId(event.getId())); generator.writeStringField(MESSAGE, formatMessage(event.getMessage())); - generator.writeStringField(TIMESTAMP, formatTimestamp(event.getTimestamp())); + generator.writeStringField(TIMESTAMP, ISO_FORMAT.get().format(event.getTimestamp())); generator.writeStringField(LEVEL, formatLevel(event.getLevel())); generator.writeStringField(LOGGER, event.getLogger()); generator.writeStringField(PLATFORM, event.getPlatform()); @@ -249,18 +258,6 @@ private String formatLevel(Event.Level level) { } } - /** - * Formats a timestamp in the ISO-8601 format without timezone. - * - * @param timestamp date to format. - * @return timestamp as a formatted String. - */ - private String formatTimestamp(Date timestamp) { - DateFormat isoFormat = new SimpleDateFormat(ISO_8601_FORMAT); - isoFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - return isoFormat.format(timestamp); - } - /** * Add an interface binding to send a type of {@link SentryInterface} through a JSON stream. * From b30a933bf1942a30043fcb00f1b609dcfbe130c6 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 5 Jul 2014 10:33:51 +1000 Subject: [PATCH 1257/2152] Document the call to generator.writeObject without codecs --- .../net/kencochrane/raven/marshaller/json/JsonMarshaller.java | 1 + 1 file changed, 1 insertion(+) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 3f6805c1924..2b9b1af092b 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -189,6 +189,7 @@ private void safelyWriteObject(JsonGenerator generator, Object value) throws IOE generator.writeNull(); } else { try { + /** @see com.fasterxml.jackson.core.JsonGenerator#_writeSimpleObject(Object) */ generator.writeObject(value); } catch (IllegalStateException e) { logger.debug("Couldn't marshal '{}' of type '{}', had to be converted into a String", From af981c0d9274e15678f4cc7349b671262531aa57 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 5 Jul 2014 10:34:17 +1000 Subject: [PATCH 1258/2152] Increase the logs from warn to error when the loglevel can't be parsed --- .../net/kencochrane/raven/marshaller/json/JsonMarshaller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 2b9b1af092b..e4104ccdee1 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -253,7 +253,7 @@ private String formatLevel(Event.Level level) { case ERROR: return "error"; default: - logger.warn("The level '{}' isn't supported, this should NEVER happen, contact Raven developers", + logger.error("The level '{}' isn't supported, this should NEVER happen, contact Raven developers", level.name()); return null; } From 5c41c3ceacada846d06171340b87a3b6bca84153 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 5 Jul 2014 10:55:53 +1000 Subject: [PATCH 1259/2152] Back to String based timezones (java 8 only) --- .../net/kencochrane/raven/marshaller/json/JsonMarshaller.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index e4104ccdee1..819418d8dfc 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -14,7 +14,6 @@ import java.io.OutputStreamWriter; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.time.ZoneOffset; import java.util.*; import java.util.zip.DeflaterOutputStream; @@ -86,7 +85,7 @@ public class JsonMarshaller implements Marshaller { @Override protected DateFormat initialValue() { DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - dateFormat.setTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC)); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); return dateFormat; } }; From 1a14ce73a515041271e5f04d6a5acf86d2f2b7d1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 5 Jul 2014 10:56:10 +1000 Subject: [PATCH 1260/2152] Use generics when possible --- .../src/test/java/net/kencochrane/raven/RavenFactoryTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java b/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java index 5d14064a415..f80bc2dfed4 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenFactoryTest.java @@ -20,7 +20,7 @@ public class RavenFactoryTest { @Tested private RavenFactory ravenFactory = null; @Injectable - private ServiceLoader mockServiceLoader = null; + private ServiceLoader mockServiceLoader = null; @BeforeMethod public void setUp() throws Exception { @@ -28,7 +28,7 @@ public void setUp() throws Exception { new NonStrictExpectations() {{ mockServiceLoader.iterator(); - result = Iterators.emptyIterator(); + result = Iterators.emptyIterator(); }}; } From c5fad2b6dd686c14143fcc7065c8511b570c9c76 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 5 Jul 2014 10:56:25 +1000 Subject: [PATCH 1261/2152] Replace hacky test with mocks --- .../net/kencochrane/raven/dsn/DsnTest.java | 58 ++----------------- 1 file changed, 6 insertions(+), 52 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java index 486270727ce..4fb2dc61b40 100644 --- a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java @@ -107,60 +107,14 @@ public void testDsnLookupWithSystemProperty() throws Exception { } @Test - public void testDsnLookupWithEnvironmentVariable() throws Exception { - String dsn = "759ed060-dd4f-4478-8a1a-3f23e044787c"; - setEnv("SENTRY_DSN", dsn); + public void testDsnLookupWithEnvironmentVariable(@Mocked("getenv") final System system) throws Exception { + final String dsn = "759ed060-dd4f-4478-8a1a-3f23e044787c"; + new NonStrictExpectations(){{ + System.getenv("SENTRY_DSN"); + result = dsn; + }}; assertThat(Dsn.dsnLookup(), is(dsn)); - - removeEnv("SENTRY_DSN"); - } - - /** - * Sets an environment variable during Unit-Test. - *

    - * DO NOT USE THIS METHOD OUTSIDE OF THE UNIT TESTS! - * - * @param key name of the environment variable. - * @param value value for the variable. - * @throws Exception if anything goes wrong. - */ - @SuppressWarnings("unchecked") - private void setEnv(String key, String value) throws Exception { - Class[] classes = Collections.class.getDeclaredClasses(); - Map env = System.getenv(); - for (Class cl : classes) { - if (env.getClass().equals(cl)) { - Field field = cl.getDeclaredField("m"); - field.setAccessible(true); - Map map = (Map) field.get(env); - map.put(key, value); - break; - } - } - } - - /** - * Removes an environment variable during Unit-Test. - *

    - * DO NOT USE THIS METHOD OUTSIDE OF THE UNIT TESTS! - * - * @param key name of the environment variable. - * @throws Exception if anything goes wrong. - */ - @SuppressWarnings("unchecked") - private void removeEnv(String key) throws Exception { - Class[] classes = Collections.class.getDeclaredClasses(); - Map env = System.getenv(); - for (Class cl : classes) { - if (env.getClass().equals(cl)) { - Field field = cl.getDeclaredField("m"); - field.setAccessible(true); - Map map = (Map) field.get(env); - map.remove(key); - break; - } - } } @Test(expectedExceptions = InvalidDsnException.class) From 62e5044a5bfce7363ee17b1e0723451f6d19ced4 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 6 Jul 2014 13:16:44 +1000 Subject: [PATCH 1262/2152] Add a script to auto deploy master snapshots after a green build --- .travis.yml | 10 ++++++++-- .travis/addServer.py | 45 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 .travis/addServer.py diff --git a/.travis.yml b/.travis.yml index 097ffb4c572..3732aebabd1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,11 @@ language: java script: mvn verify jdk: - - oraclejdk7 - - oraclejdk8 +- oraclejdk7 +- oraclejdk8 +env: + global: + - SONATYPE_USERNAME=JAZGbCvS + - secure: x6UWj3ICsut4u3HS0pFt3b5vxqT9gM+zFn9+3YgM01G9NPiM3yXtGCUsq3T3exajfsmH2ax2fd6vRDWv7wUIsyVCf9tJ84TRJuDFNU0Zkqo7doC6EaY2bgTrjREBpfWhDiIXtsHpU44yM/NTx9aTfW9Tm+GO4+lxymm6kb3IPpE= +after_success: + - "[[ $TRAVIS_BRANCH == \"master\" ]] && { python .travis/addServer.py; mvn clean deploy --settings ~/.m2/mySettings.xml; };" diff --git a/.travis/addServer.py b/.travis/addServer.py new file mode 100644 index 00000000000..672abf3ee01 --- /dev/null +++ b/.travis/addServer.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +import sys +import os +import os.path +import xml.dom.minidom + +if os.environ["TRAVIS_SECURE_ENV_VARS"] == "false": + print "no secure env vars available, skipping deployment" + sys.exit() + +homedir = os.path.expanduser("~") + +m2 = xml.dom.minidom.parse(homedir + '/.m2/settings.xml') +settings = m2.getElementsByTagName("settings")[0] + +serversNodes = settings.getElementsByTagName("servers") +if not serversNodes: + serversNode = m2.createElement("servers") + settings.appendChild(serversNode) +else: + serversNode = serversNodes[0] + +sonatypeServerNode = m2.createElement("server") +sonatypeServerId = m2.createElement("id") +sonatypeServerUser = m2.createElement("username") +sonatypeServerPass = m2.createElement("password") + +idNode = m2.createTextNode("sonatype-nexus-snapshots") +userNode = m2.createTextNode(os.environ["SONATYPE_USERNAME"]) +passNode = m2.createTextNode(os.environ["SONATYPE_PASSWORD"]) + +sonatypeServerId.appendChild(idNode) +sonatypeServerUser.appendChild(userNode) +sonatypeServerPass.appendChild(passNode) + +sonatypeServerNode.appendChild(sonatypeServerId) +sonatypeServerNode.appendChild(sonatypeServerUser) +sonatypeServerNode.appendChild(sonatypeServerPass) + +serversNode.appendChild(sonatypeServerNode) + +m2Str = m2.toxml() +f = open(homedir + '/.m2/mySettings.xml', 'w') +f.write(m2Str) +f.close() From 445d1348c6c57f4299e56364f20d3e8d07df3251 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 6 Jul 2014 13:22:05 +1000 Subject: [PATCH 1263/2152] Enable travis cache on m2 --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 3732aebabd1..b6f6f99d792 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,9 @@ script: mvn verify jdk: - oraclejdk7 - oraclejdk8 +cache: + directories: + - $HOME/.m2 env: global: - SONATYPE_USERNAME=JAZGbCvS From 629d7686a32ff411ed99153ccb73a085cc8c4bbc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 6 Jul 2014 13:50:25 +1000 Subject: [PATCH 1264/2152] Enable auto deploy on other release branches --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b6f6f99d792..8193633195a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,4 +11,4 @@ env: - SONATYPE_USERNAME=JAZGbCvS - secure: x6UWj3ICsut4u3HS0pFt3b5vxqT9gM+zFn9+3YgM01G9NPiM3yXtGCUsq3T3exajfsmH2ax2fd6vRDWv7wUIsyVCf9tJ84TRJuDFNU0Zkqo7doC6EaY2bgTrjREBpfWhDiIXtsHpU44yM/NTx9aTfW9Tm+GO4+lxymm6kb3IPpE= after_success: - - "[[ $TRAVIS_BRANCH == \"master\" ]] && { python .travis/addServer.py; mvn clean deploy --settings ~/.m2/mySettings.xml; };" + - "{ [[ $TRAVIS_BRANCH == 'master' ]] || [[ $TRAVIS_BRANCH == raven-*.x ]]; } && { python .travis/addServer.py; mvn clean deploy --settings $HOME/.m2/mySettings.xml; };" From ff1c574c444ef27041135160f718d7aabe3bce08 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 6 Jul 2014 13:55:14 +1000 Subject: [PATCH 1265/2152] Fix indentation --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8193633195a..46c53626781 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: java script: mvn verify jdk: -- oraclejdk7 -- oraclejdk8 + - oraclejdk7 + - oraclejdk8 cache: directories: - $HOME/.m2 From bf8943388642b308cdb63dd9cafe36a8ec0e8958 Mon Sep 17 00:00:00 2001 From: Ivan Dyedov Date: Sat, 12 Jul 2014 16:24:02 -0400 Subject: [PATCH 1266/2152] fix README for java.util.logging configuration: level needs a dot prefix: ".level" --- raven/README.md | 2 +- raven/src/test/resources/logging-integration.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/README.md b/raven/README.md index 7240c28d0a4..378a33123dd 100644 --- a/raven/README.md +++ b/raven/README.md @@ -33,7 +33,7 @@ Relies on: In the `logging.properties` file set: ```properties -level=WARN +.level=WARN handlers=net.kencochrane.raven.jul.SentryHandler net.kencochrane.raven.jul.SentryHandler.dsn=https://publicKey:secretKey@host:port/1?options net.kencochrane.raven.jul.SentryHandler.tags=tag1:value1,tag2:value2 diff --git a/raven/src/test/resources/logging-integration.properties b/raven/src/test/resources/logging-integration.properties index 1e5b8ebb18f..988a4e3275b 100644 --- a/raven/src/test/resources/logging-integration.properties +++ b/raven/src/test/resources/logging-integration.properties @@ -1,5 +1,5 @@ handlers=java.util.logging.ConsoleHandler, net.kencochrane.raven.jul.SentryHandler -level=ALL +.level=ALL # Ignore raven stub logs net.kencochrane.raven.stub.handlers=java.util.logging.ConsoleHandler net.kencochrane.raven.stub.level=ALL From 5061075f350fc692f04328fcedbed31530908bae Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 13 Jul 2014 21:51:43 +1000 Subject: [PATCH 1267/2152] Fix indentation of the .m2 folder --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 46c53626781..8d2a5343711 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ jdk: - oraclejdk8 cache: directories: - - $HOME/.m2 + - $HOME/.m2 env: global: - SONATYPE_USERNAME=JAZGbCvS From b4827e328c979c39421effd03c25b09f78e273c5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 27 Jul 2014 10:29:08 +1000 Subject: [PATCH 1268/2152] Fix test ensuring that the date is set by default --- .../test/java/net/kencochrane/raven/event/EventBuilderTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index 4efa42d9dd8..11d80cb9c9a 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -99,7 +99,6 @@ public void builtEventWithoutTimestampHasDefaultTimestamp(@Injectable final Date result = mockTimestamp; }}; final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setTimestamp(mockTimestamp); final Event event = eventBuilder.build(); From 8a500129a24f8752fef3471a3d1647def170943d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 2 Aug 2014 07:52:50 +1000 Subject: [PATCH 1269/2152] Upgrade log4j2 to 2.0 final --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 10366896486..eb8f8cf0f38 100644 --- a/pom.xml +++ b/pom.xml @@ -112,7 +112,7 @@ 3.0.1 1.1.2 1.2.17 - 2.0-rc2 + 2.0 1.9.5 1.8 6.8.8 From 46f549e1f7378583b551d97addaa9d33d6dccfd1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 2 Aug 2014 07:57:16 +1000 Subject: [PATCH 1270/2152] Upgrade GAE version --- raven-appengine/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 94676294a04..27d180a7dfc 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -14,7 +14,7 @@ Raven module allowing to use GoogleApp Engine and its task queueing system. - 1.9.6 + 1.9.7 From 59cae3ada92ddf4e8a700f5a5c4e01db176528fc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 2 Aug 2014 07:58:58 +1000 Subject: [PATCH 1271/2152] Upgrade to jmockit 1.10 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index eb8f8cf0f38..35ab1f2a4d8 100644 --- a/pom.xml +++ b/pom.xml @@ -114,7 +114,7 @@ 1.2.17 2.0 1.9.5 - 1.8 + 1.10 6.8.8 1.3 From 19c815c3db0250378ada61fcad0111f7da30832c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 2 Aug 2014 08:03:38 +1000 Subject: [PATCH 1272/2152] Upgrade the requirement of maven to 3.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 35ab1f2a4d8..89c008d60c1 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ - 3.0 + 3.2 From 3bd8b04ac93556f401d57bde931f316c8321be57 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 2 Aug 2014 08:14:45 +1000 Subject: [PATCH 1273/2152] Move non shared dependencies to the poms using them In particular everything directly related to logback,log4j,log4j2,JUL --- pom.xml | 43 ----------------------------------------- raven-appengine/pom.xml | 2 ++ raven-log4j/pom.xml | 7 ++++++- raven-log4j2/pom.xml | 7 +++++++ raven-logback/pom.xml | 6 ++++++ raven/pom.xml | 1 + 6 files changed, 22 insertions(+), 44 deletions(-) diff --git a/pom.xml b/pom.xml index 89c008d60c1..f75db5ef4cf 100644 --- a/pom.xml +++ b/pom.xml @@ -110,9 +110,6 @@ 17.0 2.4.1 3.0.1 - 1.1.2 - 1.2.17 - 2.0 1.9.5 1.10 6.8.8 @@ -138,16 +135,6 @@ slf4j-api ${slf4j.version} - - org.slf4j - slf4j-log4j12 - ${slf4j.version} - - - org.slf4j - slf4j-jdk14 - ${slf4j.version} - com.google.guava guava @@ -173,36 +160,6 @@ javax.servlet-api ${javax.servlet-api.version} - - log4j - log4j - ${log4j.version} - - - ch.qos.logback - logback-core - ${logback.version} - - - ch.qos.logback - logback-classic - ${logback.version} - - - org.apache.logging.log4j - log4j-api - ${log4j2.version} - - - org.apache.logging.log4j - log4j-core - ${log4j2.version} - - - org.apache.logging.log4j - log4j-slf4j-impl - ${log4j2.version} - org.mockito mockito-core diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 27d180a7dfc..4b00de05948 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -15,6 +15,7 @@ 1.9.7 + 1.1.2 @@ -35,6 +36,7 @@ ch.qos.logback logback-classic + ${logback.version} test diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index a6f50b6e8c1..90b6b9213ae 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -14,6 +14,10 @@ Raven-Java for log4j log4j appender allowing to send logs to the Raven-Java client. + + 1.2.17 + + ${project.groupId} @@ -22,6 +26,7 @@ log4j log4j + ${log4j.version} com.google.guava @@ -66,7 +71,7 @@ org.slf4j slf4j-log4j12 - test + ${slf4j.version} org.slf4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index c9ebf5d20b8..0d6982c4012 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -14,6 +14,10 @@ Raven-Java for log4j2 log4j2 appender allowing to send logs to the Raven-Java client. + + 2.0 + + ${project.groupId} @@ -22,10 +26,12 @@ org.apache.logging.log4j log4j-api + ${log4j2.version} org.apache.logging.log4j log4j-core + ${log4j2.version} com.google.guava @@ -70,6 +76,7 @@ org.apache.logging.log4j log4j-slf4j-impl + ${log4j2.version} test diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 71012937d56..db94185e57a 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -14,6 +14,10 @@ Raven-Java for Logback Logback appender allowing to send logs to the Raven-Java client. + + 1.1.2 + + ${project.groupId} @@ -26,10 +30,12 @@ ch.qos.logback logback-core + ${logback.version} ch.qos.logback logback-classic + ${logback.version} com.google.guava diff --git a/raven/pom.xml b/raven/pom.xml index 14e2d19990e..463f971da5e 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -38,6 +38,7 @@ org.slf4j slf4j-jdk14 + ${slf4j.version} test From 20a84b1320cd89870d8168ba75f53ac7210be6a5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 2 Aug 2014 08:18:52 +1000 Subject: [PATCH 1274/2152] Move servlet dependency to raven itself (and the stub) --- pom.xml | 6 ------ raven/pom.xml | 5 +++++ sentry-stub/pom.xml | 5 +++++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index f75db5ef4cf..e29a2a6fef6 100644 --- a/pom.xml +++ b/pom.xml @@ -109,7 +109,6 @@ 1.7.7 17.0 2.4.1 - 3.0.1 1.9.5 1.10 6.8.8 @@ -155,11 +154,6 @@ jackson-databind ${jackson.version} - - javax.servlet - javax.servlet-api - ${javax.servlet-api.version} - org.mockito mockito-core diff --git a/raven/pom.xml b/raven/pom.xml index 463f971da5e..d7433211a61 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -14,6 +14,10 @@ Raven-Java client Sentry client written in Java. + + 3.0.1 + + org.slf4j @@ -32,6 +36,7 @@ javax.servlet-api provided true + ${javax.servlet-api.version} diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index fc2ca631a7c..f2805183063 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -14,6 +14,10 @@ Sentry stub Stub for tests against a Sentry server. + + 3.0.1 + + com.google.guava @@ -34,6 +38,7 @@ javax.servlet javax.servlet-api + ${javax.servlet-api.version} provided From 4df7dba5ecb8269e0a669f0749d5aaaa665dc635 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 2 Aug 2014 10:03:56 +1000 Subject: [PATCH 1275/2152] Add pitest to the project as a new profile --- pom.xml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/pom.xml b/pom.xml index e29a2a6fef6..a693d2b861a 100644 --- a/pom.xml +++ b/pom.xml @@ -499,5 +499,31 @@ + + + pitest + + + junit + junit + 4.11 + test + + + + + + org.pitest + pitest-maven + 1.0.0 + + + *Test + + + + + + From c336143dd39a3f19a5cf0e0d214c527739b36468 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 3 Aug 2014 09:30:42 +1000 Subject: [PATCH 1276/2152] Add tests to ensure that eventBuilderHelpers are called properly --- raven/src/test/java/net/kencochrane/raven/RavenTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/RavenTest.java b/raven/src/test/java/net/kencochrane/raven/RavenTest.java index b9146b9d0ba..eec0f91a0f2 100644 --- a/raven/src/test/java/net/kencochrane/raven/RavenTest.java +++ b/raven/src/test/java/net/kencochrane/raven/RavenTest.java @@ -23,6 +23,8 @@ public class RavenTest { private Connection mockConnection = null; @Injectable private Event mockEvent = null; + @Injectable + private EventBuilderHelper mockEventBuilderHelper = null; @Test public void testSendEvent() throws Exception { @@ -39,6 +41,7 @@ public void testSendEventFailingIsCaught() throws Exception { mockConnection.send((Event) any); result = new RuntimeException(); }}; + raven.sendEvent(mockEvent); new Verifications() {{ @@ -49,11 +52,13 @@ public void testSendEventFailingIsCaught() throws Exception { @Test public void testSendMessage() throws Exception { final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; + raven.addBuilderHelper(mockEventBuilderHelper); raven.sendMessage(message); new Verifications() {{ Event event; + mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); mockConnection.send(event = withCapture()); assertThat(event.getLevel(), equalTo(Event.Level.INFO)); assertThat(event.getMessage(), equalTo(message)); @@ -64,11 +69,13 @@ public void testSendMessage() throws Exception { public void testSendException() throws Exception { final String message = "7b61ddb1-eb32-428d-bad9-a7d842605ba7"; final Exception exception = new Exception(message); + raven.addBuilderHelper(mockEventBuilderHelper); raven.sendException(exception); new Verifications() {{ Event event; + mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); mockConnection.send(event = withCapture()); assertThat(event.getLevel(), equalTo(Event.Level.ERROR)); assertThat(event.getMessage(), equalTo(message)); From 457567a9dcf014cc2779de286389fe7d70a7b05d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 3 Aug 2014 09:54:52 +1000 Subject: [PATCH 1277/2152] Do not log sentry stub info in sentry... --- .../test/java/net/kencochrane/raven/stub/SentryStub.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/stub/SentryStub.java b/raven/src/test/java/net/kencochrane/raven/stub/SentryStub.java index 2dd9bb2375d..b45d31686db 100644 --- a/raven/src/test/java/net/kencochrane/raven/stub/SentryStub.java +++ b/raven/src/test/java/net/kencochrane/raven/stub/SentryStub.java @@ -1,6 +1,7 @@ package net.kencochrane.raven.stub; import com.fasterxml.jackson.databind.ObjectMapper; +import net.kencochrane.raven.environment.RavenEnvironment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +36,7 @@ public SentryStub(URL url) { } public int getEventCount() { + RavenEnvironment.startManagingThread(); try { HttpURLConnection connection = connnectTo("count"); connection.setRequestMethod("GET"); @@ -42,10 +44,13 @@ public int getEventCount() { } catch (Exception e) { logger.error("Couldn't get the number of events created.", e); return -1; + } finally { + RavenEnvironment.stopManagingThread(); } } public void removeEvents() { + RavenEnvironment.startManagingThread(); try { HttpURLConnection connection = connnectTo("cleanup"); connection.setRequestMethod("DELETE"); @@ -54,6 +59,8 @@ public void removeEvents() { connection.getInputStream().close(); } catch (Exception e) { logger.error("Couldn't remove stub events.", e); + } finally { + RavenEnvironment.stopManagingThread(); } } From b22cbb7fc71c6f2626da9d6acc6cb1da5cab34b8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 3 Aug 2014 09:56:57 +1000 Subject: [PATCH 1278/2152] Set the scope of slf4j-log4j12 to test only --- raven-log4j/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 90b6b9213ae..c1b9ab74fe7 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -72,6 +72,7 @@ org.slf4j slf4j-log4j12 ${slf4j.version} + test org.slf4j From 4f52b411434f861f1688e98a78c937f28fa52f4e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 3 Aug 2014 10:28:45 +1000 Subject: [PATCH 1279/2152] Add properties to make the execution of the mutation tests easier --- pom.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a693d2b861a..8c694f4b042 100644 --- a/pom.xml +++ b/pom.xml @@ -502,11 +502,15 @@ pitest + + 1.8 + 4.11 + junit junit - 4.11 + ${junit.version} test From e5031bf80e40b4a443a9faa246fb8de57777c44e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 3 Aug 2014 10:29:20 +1000 Subject: [PATCH 1280/2152] Add tests to ensure that the lockdown is working properly and doubling as expected --- .../connection/AbstractConnectionTest.java | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java index 25d26716a55..77cd17064a3 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java @@ -1,9 +1,6 @@ package net.kencochrane.raven.connection; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Tested; -import mockit.Verifications; +import mockit.*; import net.kencochrane.raven.event.Event; import org.mockito.MockitoAnnotations; import org.mockito.Spy; @@ -12,6 +9,7 @@ import java.util.concurrent.locks.ReentrantLock; +import static mockit.Deencapsulation.getField; import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -27,6 +25,9 @@ public class AbstractConnectionTest { //Spying with mockito as jMockit doesn't support mocks of ReentrantLock @Spy private ReentrantLock reentrantLock = new ReentrantLock(); + //Disable thread sleep during the tests + @Mocked("sleep") + private Thread mockThread = null; @BeforeMethod public void setUp() throws Exception { @@ -67,4 +68,37 @@ public void testExceptionOnSendStartLockDown(@Injectable final Event mockEvent) verify(reentrantLock).tryLock(); verify(reentrantLock).unlock(); } + + @Test + public void testLockDownDoublesTheWaitingTime(@Injectable final Event mockEvent) throws Exception { + new NonStrictExpectations() {{ + abstractConnection.doSend((Event) any); + result = new ConnectionException(); + }}; + + abstractConnection.send(mockEvent); + + long waitingTimeAfter = getField(abstractConnection, "waitingTime"); + assertThat(waitingTimeAfter, is(AbstractConnection.DEFAULT_BASE_WAITING_TIME * 2)); + new Verifications() {{ + Thread.sleep(AbstractConnection.DEFAULT_BASE_WAITING_TIME); + }}; + } + + @Test + public void testLockDownDoesntDoubleItAtMax(@Injectable final Event mockEvent) throws Exception { + setField(abstractConnection, "waitingTime", AbstractConnection.DEFAULT_MAX_WAITING_TIME); + new NonStrictExpectations() {{ + abstractConnection.doSend((Event) any); + result = new ConnectionException(); + }}; + + abstractConnection.send(mockEvent); + + long waitingTimeAfter = getField(abstractConnection, "waitingTime"); + assertThat(waitingTimeAfter, is(AbstractConnection.DEFAULT_MAX_WAITING_TIME)); + new Verifications() {{ + Thread.sleep(AbstractConnection.DEFAULT_MAX_WAITING_TIME); + }}; + } } From d8a44739c7b651ed2fa8370f259da232bb33b58b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 17 Aug 2014 11:09:43 +1000 Subject: [PATCH 1281/2152] Fix documentation/import/indentation --- .../kencochrane/raven/appengine/AppEngineRavenFactory.java | 1 + .../main/java/net/kencochrane/raven/event/EventBuilder.java | 3 ++- raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java | 5 +---- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java index cd21d930461..c11b0e08e76 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java @@ -24,6 +24,7 @@ public class AppEngineRavenFactory extends DefaultRavenFactory { *

    * If the connection identifier is not specified, the system will define a connection identifier itself, but its * uniqueness within an instance isn't guaranteed. + * * @see AppEngineAsyncConnection */ public static final String CONNECTION_IDENTIFIER = "raven.async.gae.connectionid"; diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 99896a7bd40..b93e1804b39 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -300,7 +300,8 @@ public String toString() { * The {@code InetAddress.getLocalHost().getCanonicalHostName()} call can be quite expensive and could be called * for the creation of each {@link Event}. This system will prevent unnecessary costs by keeping track of the * hostname for a period defined during the construction.
    - * For performance purposes, the operation of retrieving the hostname will automatically fail after a period of time + * For performance purposes, the operation of retrieving the hostname will automatically fail after a period of + * time * defined by {@link #GET_HOSTNAME_TIMEOUT} without result. */ private static final class HostnameCache { diff --git a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java index 4fb2dc61b40..75d9ead6abc 100644 --- a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java @@ -7,10 +7,7 @@ import org.testng.annotations.Test; import javax.naming.Context; -import java.lang.reflect.Field; import java.net.URI; -import java.util.Collections; -import java.util.Map; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -109,7 +106,7 @@ public void testDsnLookupWithSystemProperty() throws Exception { @Test public void testDsnLookupWithEnvironmentVariable(@Mocked("getenv") final System system) throws Exception { final String dsn = "759ed060-dd4f-4478-8a1a-3f23e044787c"; - new NonStrictExpectations(){{ + new NonStrictExpectations() {{ System.getenv("SENTRY_DSN"); result = dsn; }}; From abffe52e2cb665d6d832d65617b2f2e55af88f76 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 17 Aug 2014 11:52:45 +1000 Subject: [PATCH 1282/2152] Add test coverage for the ServletRequestListener --- .../RavenServletRequestListenerTest.java | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/servlet/RavenServletRequestListenerTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/servlet/RavenServletRequestListenerTest.java b/raven/src/test/java/net/kencochrane/raven/servlet/RavenServletRequestListenerTest.java new file mode 100644 index 00000000000..ac22f1bfb6b --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/servlet/RavenServletRequestListenerTest.java @@ -0,0 +1,92 @@ +package net.kencochrane.raven.servlet; + +import mockit.Injectable; +import mockit.NonStrictExpectations; +import mockit.Tested; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestEvent; +import javax.servlet.http.HttpServletRequest; + +import static mockit.Deencapsulation.getField; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; + +public class RavenServletRequestListenerTest { + @Tested + private RavenServletRequestListener ravenServletRequestListener = null; + @Injectable + private ServletRequestEvent mockServletRequestEvent = null; + + @AfterMethod + public void tearDown() throws Exception { + // Reset the threadLocal value + ((ThreadLocal) getField(RavenServletRequestListener.class, "THREAD_REQUEST")).remove(); + } + + @Test + public void requestListenerEmptyByDefault() throws Exception { + assertThat(RavenServletRequestListener.getServletRequest(), is(nullValue())); + } + + @Test + public void requestListenerContainsTheCurrentRequest(@Injectable final HttpServletRequest mockHttpServletRequest) + throws Exception { + new NonStrictExpectations() {{ + mockServletRequestEvent.getServletRequest(); + result = mockHttpServletRequest; + }}; + + ravenServletRequestListener.requestInitialized(mockServletRequestEvent); + + assertThat(RavenServletRequestListener.getServletRequest(), is(mockHttpServletRequest)); + } + + @Test + public void requestListenerDoesntWorkWithNonHttpRequests(@Injectable final ServletRequest mockServletRequest) + throws Exception { + new NonStrictExpectations() {{ + mockServletRequestEvent.getServletRequest(); + result = mockServletRequest; + }}; + + ravenServletRequestListener.requestInitialized(mockServletRequestEvent); + + assertThat(RavenServletRequestListener.getServletRequest(), is(nullValue())); + } + + @Test + public void requestListenerDestroyRemovesTheCurrentRequest(@Injectable final HttpServletRequest mockHttpServletRequest) + throws Exception { + new NonStrictExpectations() {{ + mockServletRequestEvent.getServletRequest(); + result = mockHttpServletRequest; + }}; + ravenServletRequestListener.requestInitialized(mockServletRequestEvent); + + ravenServletRequestListener.requestDestroyed(mockServletRequestEvent); + + assertThat(RavenServletRequestListener.getServletRequest(), is(nullValue())); + } + + @Test + public void requestListenerSpecificToLocalThread(@Injectable final HttpServletRequest mockHttpServletRequest) + throws Exception { + new NonStrictExpectations() {{ + mockServletRequestEvent.getServletRequest(); + result = mockHttpServletRequest; + }}; + + new Thread(){ + @Override + public void run() { + ravenServletRequestListener.requestInitialized(mockServletRequestEvent); + } + }.start(); + + assertThat(RavenServletRequestListener.getServletRequest(), is(nullValue())); + } +} \ No newline at end of file From db27a7561bb4d5a0088b8cab99126095db526b58 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 17 Aug 2014 12:04:02 +1000 Subject: [PATCH 1283/2152] Add test coverage for the UncloseableOutputStream --- .../UncloseableOutputStreamTest.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/marshaller/UncloseableOutputStreamTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/UncloseableOutputStreamTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/UncloseableOutputStreamTest.java new file mode 100644 index 00000000000..faab73e200d --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/UncloseableOutputStreamTest.java @@ -0,0 +1,70 @@ +package net.kencochrane.raven.marshaller; + +import mockit.Injectable; +import mockit.Tested; +import mockit.Verifications; +import org.testng.annotations.Test; + +import java.io.OutputStream; + +public class UncloseableOutputStreamTest { + @Tested + private Marshaller.UncloseableOutputStream uncloseableOutputStream = null; + @Injectable + private OutputStream mockOutputStream = null; + + @Test + public void testWriteSingleByte() throws Exception { + final int i = 12; + + uncloseableOutputStream.write(i); + + new Verifications() {{ + mockOutputStream.write(i); + }}; + } + + @Test + public void testWriteByteArray() throws Exception { + final byte[] array = new byte[0]; + + uncloseableOutputStream.write(array); + + new Verifications() {{ + mockOutputStream.write(array); + }}; + } + + @Test + public void testWritePartOfByteArray() throws Exception { + final byte[] array = new byte[0]; + final int off = 93; + final int len = 42; + + uncloseableOutputStream.write(array, off, len); + + new Verifications() {{ + mockOutputStream.write(array, off, len); + }}; + } + + @Test + public void testFlush() throws Exception { + uncloseableOutputStream.flush(); + + new Verifications() {{ + mockOutputStream.flush(); + }}; + + } + + @Test + public void testClose() throws Exception { + uncloseableOutputStream.close(); + + new Verifications() {{ + mockOutputStream.close(); + times = 0; + }}; + } +} \ No newline at end of file From fb9f5b869c59854542fb9292c7f3dd0b11c4fa2c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 17 Aug 2014 12:04:30 +1000 Subject: [PATCH 1284/2152] Fix indentation --- .../raven/servlet/RavenServletRequestListenerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/servlet/RavenServletRequestListenerTest.java b/raven/src/test/java/net/kencochrane/raven/servlet/RavenServletRequestListenerTest.java index ac22f1bfb6b..604e0b82e63 100644 --- a/raven/src/test/java/net/kencochrane/raven/servlet/RavenServletRequestListenerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/servlet/RavenServletRequestListenerTest.java @@ -80,7 +80,7 @@ public void requestListenerSpecificToLocalThread(@Injectable final HttpServletRe result = mockHttpServletRequest; }}; - new Thread(){ + new Thread() { @Override public void run() { ravenServletRequestListener.requestInitialized(mockServletRequestEvent); From 20584f6b4ff478d99ff0597700f45e5ef6ec9588 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 17 Aug 2014 06:18:02 +0100 Subject: [PATCH 1285/2152] Add test coverage for the MessageInterface class --- .../interfaces/MessageInterfaceTest.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/event/interfaces/MessageInterfaceTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/MessageInterfaceTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/MessageInterfaceTest.java new file mode 100644 index 00000000000..aed2dd7ab90 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/MessageInterfaceTest.java @@ -0,0 +1,47 @@ +package net.kencochrane.raven.event.interfaces; + +import org.testng.annotations.Test; + +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class MessageInterfaceTest { + + @Test + public void testStandaloneMessage() throws Exception { + final String message = "3d301be3-2990-4735-9dbf-d1b610c14a84"; + + final MessageInterface messageInterface = new MessageInterface(message); + + assertThat(messageInterface.getMessage(), is(message)); + assertThat(messageInterface.getParameters(), is(empty())); + assertThat(messageInterface.getInterfaceName(), is(MessageInterface.MESSAGE_INTERFACE)); + } + + @Test + public void testListParameters() throws Exception { + final String message = "b88145c2-8c46-49fc-81cc-8982f288e5c2"; + final List parameters = Collections.singletonList("e703048a-0084-4306-a04e-04eaca572046"); + + final MessageInterface messageInterface = new MessageInterface(message, parameters); + + assertThat(messageInterface.getMessage(), is(message)); + assertThat(messageInterface.getParameters(), equalTo(parameters)); + assertThat(messageInterface.getInterfaceName(), is(MessageInterface.MESSAGE_INTERFACE)); + } + + @Test + public void testVarargsParameters() throws Exception { + final String message = "b3b31d87-de49-47fb-8f83-e3be45e7a611"; + final String parameter = "9113953f-3306-4aeb-8d3a-319b1ea83683"; + + final MessageInterface messageInterface = new MessageInterface(message, parameter); + + assertThat(messageInterface.getMessage(), is(message)); + assertThat(messageInterface.getParameters(), equalTo(Collections.singletonList(parameter))); + assertThat(messageInterface.getInterfaceName(), is(MessageInterface.MESSAGE_INTERFACE)); + } +} From 143659b3c8f3e04c24a56d71e70d79d4ea9c9a8c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 17 Aug 2014 10:55:29 +0100 Subject: [PATCH 1286/2152] Ignore equals/toString/hashCode during test coverage/mutation testing Not useful for now --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index 8c694f4b042..b76f86d38f2 100644 --- a/pom.xml +++ b/pom.xml @@ -524,6 +524,11 @@ *Test + + equals() + toString() + hashCode() + From 3d210fb1746fc6a8d341587469bdf36ba88603cb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 19 Aug 2014 11:41:33 +0200 Subject: [PATCH 1287/2152] Ensure that the message interface is properly used during the JUL test --- .../raven/jul/SentryHandlerEventBuildingTest.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java index 80ee9207267..2a790f2c64b 100644 --- a/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/jul/SentryHandlerEventBuildingTest.java @@ -7,18 +7,23 @@ import net.kencochrane.raven.event.Event; import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; +import net.kencochrane.raven.event.interfaces.MessageInterface; import net.kencochrane.raven.event.interfaces.SentryException; +import net.kencochrane.raven.event.interfaces.SentryInterface; import org.hamcrest.Matchers; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.util.Arrays; import java.util.Date; +import java.util.Map; +import java.util.UUID; import java.util.logging.ErrorManager; import java.util.logging.Level; import java.util.logging.LogRecord; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; public class SentryHandlerEventBuildingTest { @Tested @@ -39,16 +44,21 @@ private void assertNoErrorsInErrorManager() throws Exception { public void testSimpleMessageLogging() throws Exception { final String loggerName = "e9cb78a9-aec8-4fcd-8580-42b428653061"; final String message = "1feb7133-1bf5-4982-a30d-44883aa3de9c"; + final Object[] arguments = {"341ecbc9-3d0a-4799-9ff9-bdd18bb2b399", "acbb0393-57a8-4fff-a8b7-bb391867628c"}; final Date date = new Date(1373883196416L); final long threadId = 12; - sentryHandler.publish(newLogRecord(loggerName, Level.INFO, message, null, null, null, threadId, date.getTime())); + sentryHandler.publish(newLogRecord(loggerName, Level.INFO, message, arguments, null, null, threadId, date.getTime())); new Verifications() {{ Event event; mockRaven.runBuilderHelpers((EventBuilder) any); mockRaven.sendEvent(event = withCapture()); assertThat(event.getMessage(), is(message)); + Map sentryInterfaces = event.getSentryInterfaces(); + assertThat(sentryInterfaces, hasKey(MessageInterface.MESSAGE_INTERFACE)); + MessageInterface messageInterface = (MessageInterface) sentryInterfaces.get(MessageInterface.MESSAGE_INTERFACE); + assertThat(messageInterface.getParameters(), contains(arguments)); assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryHandler.THREAD_ID, (int) threadId)); assertThat(event.getTimestamp(), is(date)); From 48f417ae66d42b3615aed7efc645e93cbbfe6e92 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 21 Aug 2014 20:10:41 +0200 Subject: [PATCH 1288/2152] Ensure that other threads using the connection are paused during a lockdown --- .../raven/connection/AbstractConnection.java | 51 +++++++++++++++---- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 2cd87fe53cb..0ab11cfb24a 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -6,6 +6,9 @@ import org.slf4j.LoggerFactory; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** @@ -29,7 +32,9 @@ public abstract class AbstractConnection implements Connection { */ public static final long DEFAULT_BASE_WAITING_TIME = TimeUnit.MILLISECONDS.toMillis(10); private static final Logger logger = LoggerFactory.getLogger(AbstractConnection.class); - private final ReentrantLock lock = new ReentrantLock(); + private final AtomicBoolean lockdown = new AtomicBoolean(); + private final Lock lock = new ReentrantLock(); + private final Condition condition = lock.newCondition(); private final String authHeader; /** * Maximum duration for a lockdown. @@ -68,23 +73,40 @@ protected String getAuthHeader() { @Override public final void send(Event event) { try { - if (!lock.isLocked()) { - doSend(event); - waitingTime = baseWaitingTime; - } + waitIfLockedDown(); + + doSend(event); + waitingTime = baseWaitingTime; } catch (ConnectionException e) { - lock.tryLock(); logger.warn("An exception due to the connection occurred, a lockdown will be initiated.", e); - } finally { - if (lock.isHeldByCurrentThread()) - lockDown(); + lockDown(); + } + } + + /** + * Pauses the current thread if there is a lockdown. + */ + private void waitIfLockedDown() { + while (lockdown.get()) { + lock.lock(); + try { + if (lockdown.get()) + condition.await(); + } catch (InterruptedException e) { + logger.warn("An exception occurred during the lockdown.", e); + } finally { + lock.unlock(); + } } } /** - * Initiates a lockdown for {@link #waitingTime}ms and release the lock once the lockdown ends. + * Initiates a lockdown for {@link #waitingTime}ms and resume the paused threads once the lockdown ends. */ private void lockDown() { + if (!lockdown.compareAndSet(false, true)) + return; + try { logger.warn("Lockdown started for {}ms.", waitingTime); Thread.sleep(waitingTime); @@ -95,7 +117,14 @@ private void lockDown() { } catch (Exception e) { logger.warn("An exception occurred during the lockdown.", e); } finally { - lock.unlock(); + lockdown.set(false); + + lock.lock(); + try { + condition.signalAll(); + } finally { + lock.unlock(); + } logger.warn("Lockdown ended."); } } From c08464377bb7e3c80855626256c81788e414d4c8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Thu, 21 Aug 2014 20:31:27 +0200 Subject: [PATCH 1289/2152] Replace ReentrantLock with Lock Remove now useless mockito --- pom.xml | 6 ---- raven/pom.xml | 6 ---- .../connection/AbstractConnectionTest.java | 32 +++++++++---------- 3 files changed, 15 insertions(+), 29 deletions(-) diff --git a/pom.xml b/pom.xml index b76f86d38f2..c178541f557 100644 --- a/pom.xml +++ b/pom.xml @@ -109,7 +109,6 @@ 1.7.7 17.0 2.4.1 - 1.9.5 1.10 6.8.8 1.3 @@ -154,11 +153,6 @@ jackson-databind ${jackson.version} - - org.mockito - mockito-core - ${mockito.version} - org.jmockit jmockit diff --git a/raven/pom.xml b/raven/pom.xml index d7433211a61..8df175cefe2 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -51,12 +51,6 @@ jackson-databind test - - - org.mockito - mockito-core - test - org.jmockit jmockit diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java index 77cd17064a3..78d177c68c7 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java @@ -2,18 +2,15 @@ import mockit.*; import net.kencochrane.raven.event.Event; -import org.mockito.MockitoAnnotations; -import org.mockito.Spy; -import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; import static mockit.Deencapsulation.getField; import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.verify; public class AbstractConnectionTest { @Injectable @@ -22,18 +19,14 @@ public class AbstractConnectionTest { private final String secretKey = "56a9d05e-9032-4fdd-8f67-867d526422f9"; @Tested private AbstractConnection abstractConnection = null; - //Spying with mockito as jMockit doesn't support mocks of ReentrantLock - @Spy - private ReentrantLock reentrantLock = new ReentrantLock(); + @Injectable + private Lock mockLock = null; + @Injectable + private Condition mockCondition = null; //Disable thread sleep during the tests @Mocked("sleep") private Thread mockThread = null; - @BeforeMethod - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - } - @Test public void testAuthHeader() throws Exception { String authHeader = abstractConnection.getAuthHeader(); @@ -46,7 +39,8 @@ public void testAuthHeader() throws Exception { @Test public void testSuccessfulSendCallsDoSend(@Injectable final Event mockEvent) throws Exception { - setField(abstractConnection, "lock", reentrantLock); + setField(abstractConnection, "lock", mockLock); + setField(abstractConnection, "condition", mockCondition); abstractConnection.send(mockEvent); @@ -57,7 +51,8 @@ public void testSuccessfulSendCallsDoSend(@Injectable final Event mockEvent) thr @Test public void testExceptionOnSendStartLockDown(@Injectable final Event mockEvent) throws Exception { - setField(abstractConnection, "lock", reentrantLock); + setField(abstractConnection, "lock", mockLock); + setField(abstractConnection, "condition", mockCondition); new NonStrictExpectations() {{ abstractConnection.doSend((Event) any); result = new ConnectionException(); @@ -65,8 +60,11 @@ public void testExceptionOnSendStartLockDown(@Injectable final Event mockEvent) abstractConnection.send(mockEvent); - verify(reentrantLock).tryLock(); - verify(reentrantLock).unlock(); + new Verifications() {{ + mockLock.lock(); + mockCondition.signalAll(); + mockLock.unlock(); + }}; } @Test From 1fbd2bc78c53bf016511b68078e58ae24d8af88e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 22 Aug 2014 12:05:30 +0200 Subject: [PATCH 1290/2152] Enable the DiscardOlderPolicy This policy will make sure that we keep only the recent events and avoid keeping too old objects in memory. --- README.md | 4 ++-- .../main/java/net/kencochrane/raven/DefaultRavenFactory.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 016d59d5066..c9f83545fef 100644 --- a/README.md +++ b/README.md @@ -167,8 +167,8 @@ It is possible to set a maximum with the option `raven.async.queuesize`: http://public:private@host:port/1?raven.async.queuesize=100 -This means that if the connection to the Sentry server is down, only the first -100 events will be stored and be processed as soon as the server is back up. +This means that if the connection to the Sentry server is down, only the 100 +most recent events will be stored and processed as soon as the server is back up. #### Threads count (advanced) By default the thread pool used by the async connection contains one thread per diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 7ae63699c7f..db79a284225 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -140,7 +140,8 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { } ExecutorService executorService = new ThreadPoolExecutor( - maxThreads, maxThreads, 0L, TimeUnit.MILLISECONDS, queue, new DaemonThreadFactory(priority)); + maxThreads, maxThreads, 0L, TimeUnit.MILLISECONDS, queue, + new DaemonThreadFactory(priority), new ThreadPoolExecutor.DiscardOldestPolicy()); boolean gracefulShutdown = !FALSE.equalsIgnoreCase(dsn.getOptions().get(GRACEFUL_SHUTDOWN_OPTION)); From d65f1819d238635d4ccc4c5b0a28b63796cec0eb Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Aug 2014 17:11:42 +0200 Subject: [PATCH 1291/2152] Bump jackson, appenginge and log4j2 versions --- pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index c178541f557..fc28b3b8b21 100644 --- a/pom.xml +++ b/pom.xml @@ -108,7 +108,7 @@ 1.7.7 17.0 - 2.4.1 + 2.4.2 1.10 6.8.8 1.3 diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 4b00de05948..5f80b2093c8 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -14,7 +14,7 @@ Raven module allowing to use GoogleApp Engine and its task queueing system. - 1.9.7 + 1.9.9 1.1.2 diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 0d6982c4012..f7de385c66b 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -15,7 +15,7 @@ log4j2 appender allowing to send logs to the Raven-Java client. - 2.0 + 2.0.2 From 079bcfdfb03d412f19c31a0fefbf9c9fad025665 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Aug 2014 17:17:45 +0200 Subject: [PATCH 1292/2152] [maven-release-plugin] prepare release v5.0.1 --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index fc28b3b8b21..f2106157d90 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 5.0.1-SNAPSHOT + 5.0.1 pom Raven-Java @@ -75,7 +75,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v5.0.1 https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 5f80b2093c8..2ed4e93fdd6 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ net.kencochrane.raven raven-all - 5.0.1-SNAPSHOT + 5.0.1 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index c1b9ab74fe7..f80615d220e 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.1-SNAPSHOT + 5.0.1 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index f7de385c66b..5a713d68f6d 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.1-SNAPSHOT + 5.0.1 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index db94185e57a..cab96225bd3 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.1-SNAPSHOT + 5.0.1 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 8df175cefe2..a721a7d673f 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.1-SNAPSHOT + 5.0.1 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index f2805183063..feea08c45ba 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.1-SNAPSHOT + 5.0.1 sentry-stub From 6ab3c483757565aff494066e3b78e3d6a08de2bf Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Aug 2014 17:17:45 +0200 Subject: [PATCH 1293/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index f2106157d90..ba371bf1c18 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 5.0.1 + 5.0.2-SNAPSHOT pom Raven-Java @@ -75,7 +75,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v5.0.1 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 2ed4e93fdd6..23d69b07a81 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ net.kencochrane.raven raven-all - 5.0.1 + 5.0.2-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index f80615d220e..eff99e1c2db 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.1 + 5.0.2-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 5a713d68f6d..7cb7374ebb5 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.1 + 5.0.2-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index cab96225bd3..ae6007878be 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.1 + 5.0.2-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index a721a7d673f..d864b5fdde9 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.1 + 5.0.2-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index feea08c45ba..a6e59499e32 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.1 + 5.0.2-SNAPSHOT sentry-stub From 8cfca5e52a7028685ebdff1b71d9879e081eb02b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 24 Aug 2014 17:26:19 +0200 Subject: [PATCH 1294/2152] Enable auto-release --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ba371bf1c18..488dc3df4e2 100644 --- a/pom.xml +++ b/pom.xml @@ -487,7 +487,7 @@ sonatype-nexus-staging https://oss.sonatype.org/ - false + true From d8e74bf5c5031f3a1d51ee9243b390f9037d3f91 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 29 Aug 2014 19:43:45 +0200 Subject: [PATCH 1295/2152] Ensure that extra tags aren't sent as extras as well Use a Set rather than a list for the Extra tags Add documentation and improve test coverage --- .../raven/log4j/SentryAppender.java | 30 ++++---- .../SentryAppenderEventBuildingTest.java | 69 ++++++++++++------- 2 files changed, 59 insertions(+), 40 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 4b4f5eb77d9..b5a71440dcd 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -16,11 +16,7 @@ import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Appender for log4j in charge of sending the logged events to a Sentry server. @@ -58,6 +54,13 @@ public class SentryAppender extends AppenderSkeleton { * Might be empty in which case no tags are sent. */ protected Map tags = Collections.emptyMap(); + /** + * List of tags to look for in the MDC. These will be added as tags to be sent to sentry. + *

    + * Might be empty in which case no mapped tags are set. + *

    + */ + private Set extraTags = Collections.emptySet(); /** * List of tags to look for in log4j's MDC. These will be added as tags to be sent to sentry. @@ -195,15 +198,13 @@ protected Event buildEvent(LoggingEvent loggingEvent) { @SuppressWarnings("unchecked") Map properties = (Map) loggingEvent.getProperties(); for (Map.Entry mdcEntry : properties.entrySet()) { - eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); - //If MDC key is a mappedTag then add it to the event tag - if (this.mappedTags.contains(mdcEntry.getKey())) { + if (extraTags.contains(mdcEntry.getKey())) { eventBuilder.addTag(mdcEntry.getKey(), mdcEntry.getValue().toString()); + } else { + eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); } } - - for (Map.Entry tagEntry : tags.entrySet()) eventBuilder.addTag(tagEntry.getKey(), tagEntry.getValue()); @@ -229,15 +230,14 @@ public void setTags(String tags) { } /** - * Set the mapped tags that will be used to search MDC and upgrade key pair to a tag sent along with the events. + * Set the mapped extras that will be used to search MDC and upgrade key pair to a tag sent along with the events. * - * @param mappedTags A String of mappedTags. mappedTags are separated by commas(,). + * @param extraTags A String of mappedTags. mappedTags are separated by commas(,). */ - public void setMappedTags(String mappedTags) { - this.mappedTags = Arrays.asList(mappedTags.split(",")); + public void setExtraTags(String extraTags) { + this.extraTags = new HashSet<>(Arrays.asList(extraTags.split(","))); } - @Override public void close() { RavenEnvironment.startManagingThread(); diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java index 8933911b616..f7de54dad82 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java +++ b/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java @@ -19,11 +19,12 @@ import java.util.Collections; import java.util.Date; +import java.util.HashMap; +import java.util.Map; import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.arrayWithSize; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; public class SentryAppenderEventBuildingTest { @Tested @@ -33,12 +34,14 @@ public class SentryAppenderEventBuildingTest { private Raven mockRaven = null; @Injectable private Logger mockLogger = null; + private String mockExtraTag = "a8e0ad33-3c11-4899-b8c7-c99926c6d7b8"; @BeforeMethod public void setUp() throws Exception { sentryAppender = new SentryAppender(mockRaven); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.setExtraTags(mockExtraTag); sentryAppender.activateOptions(); } @@ -128,29 +131,6 @@ public void testMdcAddedToExtra() throws Exception { }}; assertNoErrorsInErrorHandler(); } - - @Test - public void testMappedMdcAddedToTags() throws Exception { - final String mappedKey = "User"; - final String mappedValue = "Test"; - - sentryAppender.setMappedTags("User,foo"); - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, - null, null, null, Collections.singletonMap(mappedKey, mappedValue))); - - new Verifications() {{ - Event event; - mockRaven.sendEvent(event = withCapture()); - assertThat(event.getExtra(), Matchers.hasEntry(mappedKey, mappedValue)); - assertThat(event.getTags(), Matchers.hasEntry(mappedKey, mappedValue)); - - }}; - assertNoErrorsInErrorHandler(); - } - - - - @Test public void testNdcAddedToExtra() throws Exception { @@ -247,4 +227,43 @@ public void testCulpritWithoutSource() throws Exception { }}; assertNoErrorsInErrorHandler(); } + + @Test + public void testExtraTagObtainedFromMdc() throws Exception { + Map properties = new HashMap<>(); + properties.put(mockExtraTag, "ac84f38a-3889-41ed-9519-201402688abb"); + properties.put("other_property", "10ebc4f6-a915-46d0-bb60-75bc9bd71371"); + + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, null, null, null, properties)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getTags().entrySet(), hasSize(1)); + assertThat(event.getTags(), hasEntry(mockExtraTag, "ac84f38a-3889-41ed-9519-201402688abb")); + assertThat(event.getExtra(), not(hasKey(mockExtraTag))); + assertThat(event.getExtra(), Matchers.hasEntry("other_property", "10ebc4f6-a915-46d0-bb60-75bc9bd71371")); + }}; + assertNoErrorsInErrorHandler(); + } + + @Test + public void testExtraTagObtainedFromMdcConvertedToString(@Injectable final Object extraTagValue) throws Exception { + Map properties = Collections.singletonMap(mockExtraTag, extraTagValue); + new NonStrictExpectations() {{ + extraTagValue.toString(); + result = "3c8981b4-01ad-47ec-8a3a-77a0bbcb42e2"; + }}; + + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, null, null, null, properties)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getTags().entrySet(), hasSize(1)); + assertThat(event.getTags(), hasEntry(mockExtraTag, "3c8981b4-01ad-47ec-8a3a-77a0bbcb42e2")); + assertThat(event.getExtra(), not(hasKey(mockExtraTag))); + }}; + assertNoErrorsInErrorHandler(); + } } From 4702b30e136dbfcbd6ba3db2199e9e967350f23c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 29 Aug 2014 19:43:45 +0200 Subject: [PATCH 1296/2152] Add extra tags to log4j2 --- .../raven/log4j2/SentryAppender.java | 26 ++++++++++++++++++- .../SentryAppenderEventBuildingTest.java | 26 ++++++++++++++++--- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 5e54b4fe02d..759cb4c3e78 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -68,6 +68,13 @@ public class SentryAppender extends AbstractAppender { * Might be empty in which case no tags are sent. */ protected Map tags = Collections.emptyMap(); + /** + * Set of tags to look for in the MDC. These will be added as tags to be sent to sentry. + *

    + * Might be empty in which case no mapped tags are set. + *

    + */ + private Set extraTags = Collections.emptySet(); /** * Creates an instance of SentryAppender. @@ -97,6 +104,7 @@ private SentryAppender(String name, Filter filter) { * @param dsn Data Source Name to access the Sentry server. * @param ravenFactory Name of the factory to use to build the {@link Raven} instance. * @param tags Tags to add to each event. + * @param extraTags Tags to search through the MDC. * @param filter The filter, if any, to use. * @return The SentryAppender. */ @@ -105,6 +113,7 @@ public static SentryAppender createAppender(@PluginAttribute("name") final Strin @PluginAttribute("dsn") final String dsn, @PluginAttribute("ravenFactory") final String ravenFactory, @PluginAttribute("tags") final String tags, + @PluginAttribute("extraTags") final String extraTags, @PluginElement("filters") final Filter filter) { if (name == null) { @@ -116,6 +125,8 @@ public static SentryAppender createAppender(@PluginAttribute("name") final Strin sentryAppender.setDsn(dsn); if (tags != null) sentryAppender.setTags(tags); + if (extraTags != null) + sentryAppender.setExtraTags(extraTags); sentryAppender.setRavenFactory(ravenFactory); return sentryAppender; } @@ -237,7 +248,11 @@ protected Event buildEvent(LogEvent event) { if (event.getContextMap() != null) { for (Map.Entry mdcEntry : event.getContextMap().entrySet()) { - eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); + if (extraTags.contains(mdcEntry.getKey())) { + eventBuilder.addTag(mdcEntry.getKey(), mdcEntry.getValue()); + } else { + eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); + } } } @@ -268,6 +283,15 @@ public void setTags(String tags) { this.tags = Splitter.on(",").withKeyValueSeparator(":").split(tags); } + /** + * Set the mapped extras that will be used to search MDC and upgrade key pair to a tag sent along with the events. + * + * @param extraTags A String of mappedTags. mappedTags are separated by commas(,). + */ + public void setExtraTags(String extraTags) { + this.extraTags = new HashSet<>(Arrays.asList(extraTags.split(","))); + } + @Override public void stop() { RavenEnvironment.startManagingThread(); diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java index 5284668e4f6..09c151a2c0f 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java +++ b/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java @@ -22,10 +22,7 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.List; +import java.util.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -36,12 +33,14 @@ public class SentryAppenderEventBuildingTest { private MockUpErrorHandler mockUpErrorHandler; @Injectable private Raven mockRaven = null; + private String mockExtraTag = "d421627f-7a25-4d43-8210-140dfe73ff10"; @BeforeMethod public void setUp() throws Exception { sentryAppender = new SentryAppender(mockRaven); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.setExtraTags(mockExtraTag); } private void assertNoErrorsInErrorHandler() throws Exception { @@ -230,4 +229,23 @@ public void testCulpritWithoutSource() throws Exception { }}; assertNoErrorsInErrorHandler(); } + + @Test + public void testExtraTagObtainedFromMdc() throws Exception { + Map mdc = new HashMap<>(); + mdc.put(mockExtraTag, "565940d2-f4a4-42f6-9496-42e3c7c85c43"); + mdc.put("other_property", "395856e8-fa1d-474f-8fa9-c062b4886527"); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, mdc, null, null, null, 0)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getTags().entrySet(), hasSize(1)); + assertThat(event.getTags(), hasEntry(mockExtraTag, "565940d2-f4a4-42f6-9496-42e3c7c85c43")); + assertThat(event.getExtra(), not(hasKey(mockExtraTag))); + assertThat(event.getExtra(), Matchers.hasEntry("other_property", "395856e8-fa1d-474f-8fa9-c062b4886527")); + }}; + assertNoErrorsInErrorHandler(); + } } From 139b7374db8e9a1f757fd97f72c2fff96ae4788b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 29 Aug 2014 19:43:45 +0200 Subject: [PATCH 1297/2152] Add extra tags to logback --- .../raven/logback/SentryAppender.java | 19 ++++++++++++- .../SentryAppenderEventBuildingTest.java | 28 ++++++++++++++++--- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 04cfbbf63fa..2298fe4bf3f 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -57,6 +57,10 @@ public class SentryAppender extends AppenderBase { * Might be empty in which case no tags are sent. */ protected Map tags = Collections.emptyMap(); + /** + * Extras to use as tags. + */ + protected Set extraTags = Collections.emptySet(); /** * Creates an instance of SentryAppender. @@ -186,7 +190,11 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { } for (Map.Entry mdcEntry : iLoggingEvent.getMDCPropertyMap().entrySet()) { - eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); + if (extraTags.contains(mdcEntry.getKey())) { + eventBuilder.addTag(mdcEntry.getKey(), mdcEntry.getValue()); + } else { + eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); + } } if (iLoggingEvent.getMarker() != null) @@ -280,6 +288,15 @@ public void setTags(String tags) { this.tags = Splitter.on(",").withKeyValueSeparator(":").split(tags); } + /** + * Set the mapped extras that will be used to search MDC and upgrade key pair to a tag sent along with the events. + * + * @param extraTags A String of mappedTags. mappedTags are separated by commas(,). + */ + public void setExtraTags(String extraTags) { + this.extraTags = new HashSet<>(Arrays.asList(extraTags.split(","))); + } + @Override public void stop() { RavenEnvironment.startManagingThread(); diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java index 2a7d7fe80b8..c2632acbd0a 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java @@ -22,12 +22,10 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; +import java.util.*; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; public class SentryAppenderEventBuildingTest { @Tested @@ -36,12 +34,14 @@ public class SentryAppenderEventBuildingTest { private Raven mockRaven = null; @Injectable private Context mockContext = null; + private String mockExtraTag = "60f42409-c029-447d-816a-fb2722913c93"; @BeforeMethod public void setUp() throws Exception { new MockUpStatusPrinter(); sentryAppender = new SentryAppender(mockRaven); sentryAppender.setContext(mockContext); + sentryAppender.setExtraTags(mockExtraTag); sentryAppender.initRaven(); new NonStrictExpectations() {{ @@ -268,4 +268,24 @@ public void testCulpritWithoutSource() throws Exception { }}; assertNoErrorsInStatusManager(); } + + @Test + public void testExtraTagObtainedFromMdc() throws Exception { + Map mdcPropertyMap = new HashMap<>(); + mdcPropertyMap.put(mockExtraTag, "47008f35-50c8-4e40-94ca-c8c1a3ddb729"); + mdcPropertyMap.put("other_property", "cb9c92a1-0182-4e9c-866f-b06b271cd196"); + + sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null, mdcPropertyMap, null, + null, 0).getMockInstance()); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getTags().entrySet(), hasSize(1)); + assertThat(event.getTags(), hasEntry(mockExtraTag, "47008f35-50c8-4e40-94ca-c8c1a3ddb729")); + assertThat(event.getExtra(), not(hasKey(mockExtraTag))); + assertThat(event.getExtra(), Matchers.hasEntry("other_property", "cb9c92a1-0182-4e9c-866f-b06b271cd196")); + }}; + assertNoErrorsInStatusManager(); + } } From f03bd40716e0adbe328d262e219a0bbacfcb93c7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 16 Sep 2014 18:20:02 +0200 Subject: [PATCH 1298/2152] Add upport of MDC and extra tags to JUL (with SLF4J support of MDC) --- .../kencochrane/raven/jul/SentryHandler.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index ec775724e3f..e96f420b44e 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -10,6 +10,7 @@ import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.ExceptionInterface; import net.kencochrane.raven.event.interfaces.MessageInterface; +import org.slf4j.MDC; import java.text.MessageFormat; import java.util.*; @@ -53,6 +54,14 @@ public SentryHandler() { retrieveProperties(); } + /** + * Set of tags to look for in the MDC. These will be added as tags to be sent to sentry. + *

    + * Might be empty in which case no mapped tags are set. + *

    + */ + private Set extraTags = Collections.emptySet(); + /** * Creates an instance of SentryHandler. * @@ -106,6 +115,9 @@ protected void retrieveProperties() { String tagsProperty = manager.getProperty(className + ".tags"); if (tagsProperty != null) tags = Splitter.on(",").withKeyValueSeparator(":").split(tagsProperty); + String extraTagsProperty = manager.getProperty(className + ".extraTags"); + if (extraTagsProperty != null) + extraTags = new HashSet<>(Arrays.asList(extraTagsProperty.split(","))); } @Override @@ -179,6 +191,17 @@ protected Event buildEvent(LogRecord record) { eventBuilder.setCulprit(record.getLoggerName()); } + Map mdc = MDC.getMDCAdapter().getCopyOfContextMap(); + if (mdc != null) { + for (Map.Entry mdcEntry : mdc.entrySet()) { + if (extraTags.contains(mdcEntry.getKey())) { + eventBuilder.addTag(mdcEntry.getKey(), mdcEntry.getValue()); + } else { + eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); + } + } + } + for (Map.Entry tagEntry : tags.entrySet()) { eventBuilder.addTag(tagEntry.getKey(), tagEntry.getValue()); } From 1061cafe3165601bea5c13bdd0a20cd167e6b84d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 28 Sep 2014 11:25:58 +1000 Subject: [PATCH 1299/2152] Add the User interface --- .../raven/event/interfaces/UserInterface.java | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/event/interfaces/UserInterface.java diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/UserInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/UserInterface.java new file mode 100644 index 00000000000..a315fa585f8 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/UserInterface.java @@ -0,0 +1,77 @@ +package net.kencochrane.raven.event.interfaces; + +/** + * The User interface for Sentry allows to send details about the User currently using the application. + */ +public class UserInterface implements SentryInterface { + /** + * Name of the user interface in Sentry. + */ + public static final String USER_INTERFACE = "sentry.interfaces.User"; + private final String id; + private final String username; + private final String ipAddress; + private final String email; + + /** + * Creates a user. + * + * @param id Id of the user in the system, as a String. + * @param username Name of the user. + * @param ipAddress IP address used to connect to the application. + * @param email user email address. + */ + public UserInterface(String id, String username, String ipAddress, String email) { + this.id = id; + this.username = username; + this.ipAddress = ipAddress; + this.email = email; + } + + @Override + public String getInterfaceName() { + return USER_INTERFACE; + } + + public String getId() { + return id; + } + + public String getUsername() { + return username; + } + + public String getIpAddress() { + return ipAddress; + } + + public String getEmail() { + return email; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + UserInterface that = (UserInterface) o; + + return !(id != null ? !id.equals(that.id) : that.id != null); + + } + + @Override + public int hashCode() { + return id != null ? id.hashCode() : 0; + } + + @Override + public String toString() { + return "UserInterface{" + + "id='" + id + '\'' + + ", username='" + username + '\'' + + ", ipAddress='" + ipAddress + '\'' + + ", email='" + email + '\'' + + '}'; + } +} From 85ea587191392baa297a7f43448b881849d66b82 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 28 Sep 2014 11:32:20 +1000 Subject: [PATCH 1300/2152] Add tests for the new User interface --- .../event/interfaces/UserInterfaceTest.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/event/interfaces/UserInterfaceTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/event/interfaces/UserInterfaceTest.java b/raven/src/test/java/net/kencochrane/raven/event/interfaces/UserInterfaceTest.java new file mode 100644 index 00000000000..42946880ade --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/event/interfaces/UserInterfaceTest.java @@ -0,0 +1,25 @@ +package net.kencochrane.raven.event.interfaces; + +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class UserInterfaceTest { + @Test + public void testListParameters() throws Exception { + final String id = "a8750a8d-1f67-41d3-a83d-0f04c08a2760"; + final String username = "dfdf5b9e-09e2-4993-89c3-68962b43370a"; + final String ipAddress = "584ed94b-e771-44eb-b722-74db19485efc"; + final String email = "e9c743ad-3b46-4c7e-8d4a-16688c496fc3"; + + final UserInterface userInterface = new UserInterface(id, username, ipAddress, email); + + assertThat(userInterface.getId(), is(id)); + assertThat(userInterface.getUsername(), is(username)); + assertThat(userInterface.getIpAddress(), is(ipAddress)); + assertThat(userInterface.getEmail(), is(email)); + assertThat(userInterface.getInterfaceName(), is(UserInterface.USER_INTERFACE)); + } +} + From 339da30d9f5389204a4da6e1a9dda829b071f499 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 28 Sep 2014 11:41:06 +1000 Subject: [PATCH 1301/2152] Ensure that Sentry interfaces are replaced if they already exist in the object --- .../raven/event/EventBuilderTest.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index 11d80cb9c9a..3f935066233 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -407,6 +407,27 @@ public void builtEventWithSentryInterfacesHasProperSentryInterfaces( assertThat(event.getSentryInterfaces(), hasEntry(mockSentryInterfaceName, mockSentryInterface)); assertThat(event.getSentryInterfaces().entrySet(), hasSize(1)); } + @Test + public void builtEventReplacesSentryInterfacesWithSameName( + @Injectable("sentryInterfaceName") final String mockSentryInterfaceName, + @Injectable final SentryInterface mockSentryInterface, + @Injectable final SentryInterface mockSentryInterface2) + throws Exception { + new NonStrictExpectations() {{ + mockSentryInterface.getInterfaceName(); + result = mockSentryInterfaceName; + mockSentryInterface2.getInterfaceName(); + result = mockSentryInterfaceName; + }}; + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.addSentryInterface(mockSentryInterface); + eventBuilder.addSentryInterface(mockSentryInterface2); + + final Event event = eventBuilder.build(); + + assertThat(event.getSentryInterfaces(), hasEntry(mockSentryInterfaceName, mockSentryInterface2)); + assertThat(event.getSentryInterfaces().entrySet(), hasSize(1)); + } @Test(expectedExceptions = IllegalStateException.class) public void buildingTheEventTwiceFails() throws Exception { From abb1bee7125f0e54a4dd77b3b16270c21fe42737 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 28 Sep 2014 11:43:30 +1000 Subject: [PATCH 1302/2152] Add the option of not replacing a Sentry interface if it already exists --- .../kencochrane/raven/event/EventBuilder.java | 18 ++++++- .../raven/event/EventBuilderTest.java | 48 ++++++++++++++++++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index b93e1804b39..494e1db8513 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -263,7 +263,23 @@ public EventBuilder setChecksum(String checksum) { * @return the current {@code EventBuilder} for chained calls. */ public EventBuilder addSentryInterface(SentryInterface sentryInterface) { - event.getSentryInterfaces().put(sentryInterface.getInterfaceName(), sentryInterface); + return addSentryInterface(sentryInterface, true); + } + + /** + * Adds a {@link SentryInterface} to the event. + *

    + * Checks whether or not the entry already exists, and replaces it only if {@code replace} is true. + * + * @param sentryInterface sentry interface to add to the event. + * @param replace If true and a Sentry Interface with the same name has already been added it will be + * replaced. + * If false the statement will be ignored. + * @return the current {@code EventBuilder} for chained calls. + */ + public EventBuilder addSentryInterface(SentryInterface sentryInterface, boolean replace) { + if (replace || !event.getSentryInterfaces().containsKey(sentryInterface.getInterfaceName())) + event.getSentryInterfaces().put(sentryInterface.getInterfaceName(), sentryInterface); return this; } diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index 3f935066233..b3842ad3c7f 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -407,8 +407,9 @@ public void builtEventWithSentryInterfacesHasProperSentryInterfaces( assertThat(event.getSentryInterfaces(), hasEntry(mockSentryInterfaceName, mockSentryInterface)); assertThat(event.getSentryInterfaces().entrySet(), hasSize(1)); } + @Test - public void builtEventReplacesSentryInterfacesWithSameName( + public void builtEventReplacesSentryInterfacesWithSameNameByDefault( @Injectable("sentryInterfaceName") final String mockSentryInterfaceName, @Injectable final SentryInterface mockSentryInterface, @Injectable final SentryInterface mockSentryInterface2) @@ -429,6 +430,51 @@ public void builtEventReplacesSentryInterfacesWithSameName( assertThat(event.getSentryInterfaces().entrySet(), hasSize(1)); } + + @Test + public void builtEventReplacesSentryInterfacesWithSameNameIfReplacementEnabled( + @Injectable("sentryInterfaceName") final String mockSentryInterfaceName, + @Injectable final SentryInterface mockSentryInterface, + @Injectable final SentryInterface mockSentryInterface2) + throws Exception { + new NonStrictExpectations() {{ + mockSentryInterface.getInterfaceName(); + result = mockSentryInterfaceName; + mockSentryInterface2.getInterfaceName(); + result = mockSentryInterfaceName; + }}; + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.addSentryInterface(mockSentryInterface); + eventBuilder.addSentryInterface(mockSentryInterface2, true); + + final Event event = eventBuilder.build(); + + assertThat(event.getSentryInterfaces(), hasEntry(mockSentryInterfaceName, mockSentryInterface2)); + assertThat(event.getSentryInterfaces().entrySet(), hasSize(1)); + } + + @Test + public void builtEventKeepsSentryInterfacesWithSameNameIfReplacementDisabled( + @Injectable("sentryInterfaceName") final String mockSentryInterfaceName, + @Injectable final SentryInterface mockSentryInterface, + @Injectable final SentryInterface mockSentryInterface2) + throws Exception { + new NonStrictExpectations() {{ + mockSentryInterface.getInterfaceName(); + result = mockSentryInterfaceName; + mockSentryInterface2.getInterfaceName(); + result = mockSentryInterfaceName; + }}; + final EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.addSentryInterface(mockSentryInterface); + eventBuilder.addSentryInterface(mockSentryInterface2, false); + + final Event event = eventBuilder.build(); + + assertThat(event.getSentryInterfaces(), hasEntry(mockSentryInterfaceName, mockSentryInterface)); + assertThat(event.getSentryInterfaces().entrySet(), hasSize(1)); + } + @Test(expectedExceptions = IllegalStateException.class) public void buildingTheEventTwiceFails() throws Exception { final EventBuilder eventBuilder = new EventBuilder(); From 79aaeadf4ca1a2c3bde26bfb5a87861fe394a5a3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 28 Sep 2014 11:58:46 +1000 Subject: [PATCH 1303/2152] Create a UserInterface when building with HttpEventBuilderHelper --- .../event/helper/HttpEventBuilderHelper.java | 20 +++++++- .../helper/HttpEventBuilderHelperTest.java | 50 ++++++++++++++++++- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java b/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java index 8f121c0b594..1e6720a0de7 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java +++ b/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java @@ -2,6 +2,7 @@ import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.HttpInterface; +import net.kencochrane.raven.event.interfaces.UserInterface; import net.kencochrane.raven.servlet.RavenServletRequestListener; import javax.servlet.http.HttpServletRequest; @@ -16,8 +17,23 @@ public class HttpEventBuilderHelper implements EventBuilderHelper { @Override public void helpBuildingEvent(EventBuilder eventBuilder) { HttpServletRequest servletRequest = RavenServletRequestListener.getServletRequest(); - if (servletRequest != null) { - eventBuilder.addSentryInterface(new HttpInterface(servletRequest)); + if (servletRequest == null) + return; + + addHttpInterface(eventBuilder, servletRequest); + addUserInterface(eventBuilder, servletRequest); + } + + private void addHttpInterface(EventBuilder eventBuilder, HttpServletRequest servletRequest) { + eventBuilder.addSentryInterface(new HttpInterface(servletRequest), false); + } + + private void addUserInterface(EventBuilder eventBuilder, HttpServletRequest servletRequest) { + String username = null; + if (servletRequest.getUserPrincipal() != null) { + username = servletRequest.getUserPrincipal().getName(); } + + eventBuilder.addSentryInterface(new UserInterface(null, username, servletRequest.getRemoteAddr(), null), false); } } diff --git a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java index 660879c8b5e..0682e925228 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java @@ -4,10 +4,12 @@ import net.kencochrane.raven.event.EventBuilder; import net.kencochrane.raven.event.interfaces.HttpInterface; import net.kencochrane.raven.event.interfaces.SentryInterface; +import net.kencochrane.raven.event.interfaces.UserInterface; import net.kencochrane.raven.servlet.RavenServletRequestListener; import org.testng.annotations.Test; import javax.servlet.http.HttpServletRequest; +import java.security.Principal; public class HttpEventBuilderHelperTest { @Tested @@ -29,13 +31,14 @@ public void testNoRequest() throws Exception { new Verifications() {{ new HttpInterface(withInstanceOf(HttpServletRequest.class)); times = 0; - mockEventBuilder.addSentryInterface(withInstanceOf(SentryInterface.class)); + mockEventBuilder.addSentryInterface(withInstanceOf(SentryInterface.class), anyBoolean); times = 0; }}; } @Test public void testWithRequest(@Injectable final HttpServletRequest mockHttpServletRequest) throws Exception { + new NonStrictExpectations(RavenServletRequestListener.class) {{ RavenServletRequestListener.getServletRequest(); result = mockHttpServletRequest; @@ -45,7 +48,50 @@ public void testWithRequest(@Injectable final HttpServletRequest mockHttpServlet new Verifications() {{ new HttpInterface(mockHttpServletRequest); - mockEventBuilder.addSentryInterface(this.withNotNull()); + new UserInterface(null, null, null, null); + mockEventBuilder.addSentryInterface(this.withNotNull(), false); + mockEventBuilder.addSentryInterface(this.withNotNull(), false); + }}; + } + + @Test + public void testWithUserPrincipal(@Injectable final HttpServletRequest mockHttpServletRequest, + @Injectable final Principal mockUserPrincipal, + @Injectable("93ad24e4-cad1-4214-af8e-2e48e76b02de") final String mockUsername) + throws Exception { + new NonStrictExpectations(RavenServletRequestListener.class) {{ + RavenServletRequestListener.getServletRequest(); + result = mockHttpServletRequest; + mockHttpServletRequest.getUserPrincipal(); + result = mockUserPrincipal; + mockUserPrincipal.getName(); + result = mockUsername; + }}; + + httpEventBuilderHelper.helpBuildingEvent(mockEventBuilder); + + new Verifications() {{ + new UserInterface(null, mockUsername, null, null); + mockEventBuilder.addSentryInterface(this.withNotNull(), false); + }}; + } + + @Test + public void testWithIpAddress(@Injectable final HttpServletRequest mockHttpServletRequest, + @Injectable("d90e92cc-1929-4f9e-a44c-3062a8b00c70") final String mockIpAddress) + throws Exception { + new NonStrictExpectations(RavenServletRequestListener.class) {{ + RavenServletRequestListener.getServletRequest(); + result = mockHttpServletRequest; + mockHttpServletRequest.getRemoteAddr(); + result = mockIpAddress; + }}; + + httpEventBuilderHelper.helpBuildingEvent(mockEventBuilder); + + new Verifications() {{ + new UserInterface(null, null, mockIpAddress, null); + mockEventBuilder.addSentryInterface(this.withNotNull(), false); }}; } } From 48dd71bde582a3649f7c0c9c5d0e1ff77c62c11d Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 28 Sep 2014 12:32:59 +1000 Subject: [PATCH 1304/2152] Improve the API for the event builder, use "with" as a key word --- .../helper/AppEngineEventBuilderHelper.java | 6 +- .../AppEngineEventBuilderHelperTest.java | 6 +- .../raven/log4j/SentryAppender.java | 24 +- .../raven/log4j2/SentryAppender.java | 28 +-- .../raven/logback/SentryAppender.java | 28 +-- .../java/net/kencochrane/raven/Raven.java | 10 +- .../kencochrane/raven/event/EventBuilder.java | 217 ++++++++++++++++-- .../event/helper/HttpEventBuilderHelper.java | 4 +- .../event/interfaces/MessageInterface.java | 6 +- .../kencochrane/raven/jul/SentryHandler.java | 20 +- .../raven/event/EventBuilderTest.java | 38 +-- .../helper/HttpEventBuilderHelperTest.java | 10 +- 12 files changed, 290 insertions(+), 107 deletions(-) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java index 9b51cc2269f..ecb9bba7643 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java @@ -20,9 +20,9 @@ public class AppEngineEventBuilderHelper implements EventBuilderHelper { public void helpBuildingEvent(EventBuilder eventBuilder) { ApiProxy.Environment env = ApiProxy.getCurrentEnvironment(); // Set the hostname to the actual application hostname - eventBuilder.setServerName((String) env.getAttributes().get(CURRENT_VERSION_HOSTNAME_PROPERTY)); + eventBuilder.withServerName((String) env.getAttributes().get(CURRENT_VERSION_HOSTNAME_PROPERTY)); - eventBuilder.addTag("GAE Application Version", SystemProperty.applicationVersion.get()); - eventBuilder.addTag("GAE Application Id", SystemProperty.applicationId.get()); + eventBuilder.withTag("GAE Application Version", SystemProperty.applicationVersion.get()); + eventBuilder.withTag("GAE Application Id", SystemProperty.applicationId.get()); } } diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java index adecf7d1369..0e6a28d6d20 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java @@ -55,7 +55,7 @@ public void ensureHostnameDefineByApiProxyEnvironment( new Verifications() {{ String hostname; - mockEventBuilder.setServerName(hostname = withCapture()); + mockEventBuilder.withServerName(hostname = withCapture()); assertThat(hostname, is(mockHostname)); }}; } @@ -73,7 +73,7 @@ public void ensureApplicationVersionIsAddedAsTag( new Verifications() {{ List tagNames = new LinkedList<>(); List tagValues = new LinkedList<>(); - mockEventBuilder.addTag(withCapture(tagNames), withCapture(tagValues)); + mockEventBuilder.withTag(withCapture(tagNames), withCapture(tagValues)); Map tags = new HashMap<>(); for (int i = 0; i < tagNames.size(); i++) { @@ -98,7 +98,7 @@ public void ensureApplicationIdIsAddedAsTag( new Verifications() {{ List tagNames = new LinkedList<>(); List tagValues = new LinkedList<>(); - mockEventBuilder.addTag(withCapture(tagNames), withCapture(tagValues)); + mockEventBuilder.withTag(withCapture(tagNames), withCapture(tagValues)); Map tags = new HashMap<>(); for (int i = 0; i < tagNames.size(); i++) { diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 71f5dc468a0..28d677a8219 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -155,40 +155,40 @@ protected void append(LoggingEvent loggingEvent) { */ protected Event buildEvent(LoggingEvent loggingEvent) { EventBuilder eventBuilder = new EventBuilder() - .setTimestamp(new Date(loggingEvent.getTimeStamp())) - .setMessage(loggingEvent.getRenderedMessage()) - .setLogger(loggingEvent.getLoggerName()) - .setLevel(formatLevel(loggingEvent.getLevel())) - .addExtra(THREAD_NAME, loggingEvent.getThreadName()); + .withTimestamp(new Date(loggingEvent.getTimeStamp())) + .withMessage(loggingEvent.getRenderedMessage()) + .withLogger(loggingEvent.getLoggerName()) + .withLevel(formatLevel(loggingEvent.getLevel())) + .withExtra(THREAD_NAME, loggingEvent.getThreadName()); if (loggingEvent.getThrowableInformation() != null) { Throwable throwable = loggingEvent.getThrowableInformation().getThrowable(); - eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); + eventBuilder.withSentryInterface(new ExceptionInterface(throwable)); } else if (loggingEvent.getLocationInformation().fullInfo != null) { LocationInfo location = loggingEvent.getLocationInformation(); if (!LocationInfo.NA.equals(location.getFileName()) && !LocationInfo.NA.equals(location.getLineNumber())) { StackTraceElement[] stackTrace = {asStackTraceElement(location)}; - eventBuilder.addSentryInterface(new StackTraceInterface(stackTrace)); + eventBuilder.withSentryInterface(new StackTraceInterface(stackTrace)); } } // Set culprit if (loggingEvent.getLocationInformation().fullInfo != null) { - eventBuilder.setCulprit(asStackTraceElement(loggingEvent.getLocationInformation())); + eventBuilder.withCulprit(asStackTraceElement(loggingEvent.getLocationInformation())); } else { - eventBuilder.setCulprit(loggingEvent.getLoggerName()); + eventBuilder.withCulprit(loggingEvent.getLoggerName()); } if (loggingEvent.getNDC() != null) - eventBuilder.addExtra(LOG4J_NDC, loggingEvent.getNDC()); + eventBuilder.withExtra(LOG4J_NDC, loggingEvent.getNDC()); @SuppressWarnings("unchecked") Map properties = (Map) loggingEvent.getProperties(); for (Map.Entry mdcEntry : properties.entrySet()) - eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); + eventBuilder.withExtra(mdcEntry.getKey(), mdcEntry.getValue()); for (Map.Entry tagEntry : tags.entrySet()) - eventBuilder.addTag(tagEntry.getKey(), tagEntry.getValue()); + eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); raven.runBuilderHelpers(eventBuilder); return eventBuilder.build(); diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index 5e54b4fe02d..ac080293efb 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -207,45 +207,45 @@ protected void initRaven() { protected Event buildEvent(LogEvent event) { Message eventMessage = event.getMessage(); EventBuilder eventBuilder = new EventBuilder() - .setTimestamp(new Date(event.getTimeMillis())) - .setMessage(eventMessage.getFormattedMessage()) - .setLogger(event.getLoggerName()) - .setLevel(formatLevel(event.getLevel())) - .addExtra(THREAD_NAME, event.getThreadName()); + .withTimestamp(new Date(event.getTimeMillis())) + .withMessage(eventMessage.getFormattedMessage()) + .withLogger(event.getLoggerName()) + .withLevel(formatLevel(event.getLevel())) + .withExtra(THREAD_NAME, event.getThreadName()); if (!eventMessage.getFormattedMessage().equals(eventMessage.getFormat())) { - eventBuilder.addSentryInterface(new MessageInterface(eventMessage.getFormat(), + eventBuilder.withSentryInterface(new MessageInterface(eventMessage.getFormat(), formatMessageParameters(eventMessage.getParameters()))); } Throwable throwable = event.getThrown(); if (throwable != null) { - eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); + eventBuilder.withSentryInterface(new ExceptionInterface(throwable)); } else if (event.getSource() != null) { StackTraceElement[] stackTrace = {event.getSource()}; - eventBuilder.addSentryInterface(new StackTraceInterface(stackTrace)); + eventBuilder.withSentryInterface(new StackTraceInterface(stackTrace)); } if (event.getSource() != null) { - eventBuilder.setCulprit(event.getSource()); + eventBuilder.withCulprit(event.getSource()); } else { - eventBuilder.setCulprit(event.getLoggerName()); + eventBuilder.withCulprit(event.getLoggerName()); } if (event.getContextStack() != null) - eventBuilder.addExtra(LOG4J_NDC, event.getContextStack().asList()); + eventBuilder.withExtra(LOG4J_NDC, event.getContextStack().asList()); if (event.getContextMap() != null) { for (Map.Entry mdcEntry : event.getContextMap().entrySet()) { - eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); + eventBuilder.withExtra(mdcEntry.getKey(), mdcEntry.getValue()); } } if (event.getMarker() != null) - eventBuilder.addTag(LOG4J_MARKER, event.getMarker().getName()); + eventBuilder.withTag(LOG4J_MARKER, event.getMarker().getName()); for (Map.Entry tagEntry : tags.entrySet()) - eventBuilder.addTag(tagEntry.getKey(), tagEntry.getValue()); + eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); raven.runBuilderHelpers(eventBuilder); return eventBuilder.build(); diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 04cfbbf63fa..a3ce34a64ce 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -158,42 +158,42 @@ protected void initRaven() { */ protected Event buildEvent(ILoggingEvent iLoggingEvent) { EventBuilder eventBuilder = new EventBuilder() - .setTimestamp(new Date(iLoggingEvent.getTimeStamp())) - .setMessage(iLoggingEvent.getFormattedMessage()) - .setLogger(iLoggingEvent.getLoggerName()) - .setLevel(formatLevel(iLoggingEvent.getLevel())) - .addExtra(THREAD_NAME, iLoggingEvent.getThreadName()); + .withTimestamp(new Date(iLoggingEvent.getTimeStamp())) + .withMessage(iLoggingEvent.getFormattedMessage()) + .withLogger(iLoggingEvent.getLoggerName()) + .withLevel(formatLevel(iLoggingEvent.getLevel())) + .withExtra(THREAD_NAME, iLoggingEvent.getThreadName()); if (iLoggingEvent.getArgumentArray() != null) { - eventBuilder.addSentryInterface(new MessageInterface(iLoggingEvent.getMessage(), + eventBuilder.withSentryInterface(new MessageInterface(iLoggingEvent.getMessage(), formatMessageParameters(iLoggingEvent.getArgumentArray()))); } if (iLoggingEvent.getThrowableProxy() != null) { - eventBuilder.addSentryInterface(new ExceptionInterface(extractExceptionQueue(iLoggingEvent))); + eventBuilder.withSentryInterface(new ExceptionInterface(extractExceptionQueue(iLoggingEvent))); } else if (iLoggingEvent.getCallerData().length > 0) { - eventBuilder.addSentryInterface(new StackTraceInterface(iLoggingEvent.getCallerData())); + eventBuilder.withSentryInterface(new StackTraceInterface(iLoggingEvent.getCallerData())); } if (iLoggingEvent.getCallerData().length > 0) { - eventBuilder.setCulprit(iLoggingEvent.getCallerData()[0]); + eventBuilder.withCulprit(iLoggingEvent.getCallerData()[0]); } else { - eventBuilder.setCulprit(iLoggingEvent.getLoggerName()); + eventBuilder.withCulprit(iLoggingEvent.getLoggerName()); } for (Map.Entry contextEntry : iLoggingEvent.getLoggerContextVO().getPropertyMap().entrySet()) { - eventBuilder.addExtra(contextEntry.getKey(), contextEntry.getValue()); + eventBuilder.withExtra(contextEntry.getKey(), contextEntry.getValue()); } for (Map.Entry mdcEntry : iLoggingEvent.getMDCPropertyMap().entrySet()) { - eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); + eventBuilder.withExtra(mdcEntry.getKey(), mdcEntry.getValue()); } if (iLoggingEvent.getMarker() != null) - eventBuilder.addTag(LOGBACK_MARKER, iLoggingEvent.getMarker().getName()); + eventBuilder.withTag(LOGBACK_MARKER, iLoggingEvent.getMarker().getName()); for (Map.Entry tagEntry : tags.entrySet()) - eventBuilder.addTag(tagEntry.getKey(), tagEntry.getValue()); + eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); raven.runBuilderHelpers(eventBuilder); return eventBuilder.build(); diff --git a/raven/src/main/java/net/kencochrane/raven/Raven.java b/raven/src/main/java/net/kencochrane/raven/Raven.java index 269e8e98e1d..0871a390dc0 100644 --- a/raven/src/main/java/net/kencochrane/raven/Raven.java +++ b/raven/src/main/java/net/kencochrane/raven/Raven.java @@ -59,8 +59,8 @@ public void sendEvent(Event event) { * @param message message to send to Sentry. */ public void sendMessage(String message) { - EventBuilder eventBuilder = new EventBuilder().setMessage(message) - .setLevel(Event.Level.INFO); + EventBuilder eventBuilder = new EventBuilder().withMessage(message) + .withLevel(Event.Level.INFO); runBuilderHelpers(eventBuilder); sendEvent(eventBuilder.build()); } @@ -73,9 +73,9 @@ public void sendMessage(String message) { * @param exception exception to send to Sentry. */ public void sendException(Exception exception) { - EventBuilder eventBuilder = new EventBuilder().setMessage(exception.getMessage()) - .setLevel(Event.Level.ERROR) - .addSentryInterface(new ExceptionInterface(exception)); + EventBuilder eventBuilder = new EventBuilder().withMessage(exception.getMessage()) + .withLevel(Event.Level.ERROR) + .withSentryInterface(new ExceptionInterface(exception)); runBuilderHelpers(eventBuilder); sendEvent(eventBuilder.build()); } diff --git a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java index 494e1db8513..28ee43231c3 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java +++ b/raven/src/main/java/net/kencochrane/raven/event/EventBuilder.java @@ -109,7 +109,7 @@ private static void makeImmutable(Event event) { * @param message message of the event. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder setMessage(String message) { + public EventBuilder withMessage(String message) { event.setMessage(message); return this; } @@ -120,7 +120,7 @@ public EventBuilder setMessage(String message) { * @param timestamp timestamp of the event. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder setTimestamp(Date timestamp) { + public EventBuilder withTimestamp(Date timestamp) { event.setTimestamp(timestamp); return this; } @@ -131,7 +131,7 @@ public EventBuilder setTimestamp(Date timestamp) { * @param level log level of the event. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder setLevel(Event.Level level) { + public EventBuilder withLevel(Event.Level level) { event.setLevel(level); return this; } @@ -142,7 +142,7 @@ public EventBuilder setLevel(Event.Level level) { * @param logger logger of the event. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder setLogger(String logger) { + public EventBuilder withLogger(String logger) { event.setLogger(logger); return this; } @@ -153,7 +153,7 @@ public EventBuilder setLogger(String logger) { * @param platform platform of the event. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder setPlatform(String platform) { + public EventBuilder withPlatform(String platform) { event.setPlatform(platform); return this; } @@ -164,7 +164,7 @@ public EventBuilder setPlatform(String platform) { * @param frame stack frame during which the event was captured. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder setCulprit(StackTraceElement frame) { + public EventBuilder withCulprit(StackTraceElement frame) { StringBuilder sb = new StringBuilder(); sb.append(frame.getClassName()) @@ -179,7 +179,7 @@ public EventBuilder setCulprit(StackTraceElement frame) { sb.append(")"); } - return setCulprit(sb.toString()); + return withCulprit(sb.toString()); } /** @@ -188,7 +188,7 @@ public EventBuilder setCulprit(StackTraceElement frame) { * @param culprit culprit. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder setCulprit(String culprit) { + public EventBuilder withCulprit(String culprit) { event.setCulprit(culprit); return this; } @@ -202,7 +202,7 @@ public EventBuilder setCulprit(String culprit) { * @param tagValue value of the tag. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder addTag(String tagKey, String tagValue) { + public EventBuilder withTag(String tagKey, String tagValue) { event.getTags().put(tagKey, tagValue); return this; } @@ -213,7 +213,7 @@ public EventBuilder addTag(String tagKey, String tagValue) { * @param serverName name of the server responsible for the event. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder setServerName(String serverName) { + public EventBuilder withServerName(String serverName) { event.setServerName(serverName); return this; } @@ -225,7 +225,7 @@ public EventBuilder setServerName(String serverName) { * @param extraValue value of the extra property. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder addExtra(String extraName, Object extraValue) { + public EventBuilder withExtra(String extraName, Object extraValue) { event.getExtra().put(extraName, extraValue); return this; } @@ -236,8 +236,8 @@ public EventBuilder addExtra(String extraName, Object extraValue) { * @param contentToChecksum content to checksum. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder generateChecksum(String contentToChecksum) { - return setChecksum(calculateChecksum(contentToChecksum)); + public EventBuilder withChecksumFor(String contentToChecksum) { + return withChecksum(calculateChecksum(contentToChecksum)); } /** @@ -248,7 +248,7 @@ public EventBuilder generateChecksum(String contentToChecksum) { * @param checksum checksum for the event. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder setChecksum(String checksum) { + public EventBuilder withChecksum(String checksum) { event.setChecksum(checksum); return this; } @@ -262,8 +262,8 @@ public EventBuilder setChecksum(String checksum) { * @param sentryInterface sentry interface to add to the event. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder addSentryInterface(SentryInterface sentryInterface) { - return addSentryInterface(sentryInterface, true); + public EventBuilder withSentryInterface(SentryInterface sentryInterface) { + return withSentryInterface(sentryInterface, true); } /** @@ -277,7 +277,7 @@ public EventBuilder addSentryInterface(SentryInterface sentryInterface) { * If false the statement will be ignored. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder addSentryInterface(SentryInterface sentryInterface, boolean replace) { + public EventBuilder withSentryInterface(SentryInterface sentryInterface, boolean replace) { if (replace || !event.getSentryInterfaces().containsKey(sentryInterface.getInterfaceName())) event.getSentryInterfaces().put(sentryInterface.getInterfaceName(), sentryInterface); return this; @@ -310,6 +310,189 @@ public String toString() { + '}'; } + + /** + * Sets the message in the event. + * + * @param message message of the event. + * @return the current {@code EventBuilder} for chained calls. + * @deprecated Use {@link #withMessage(String)} instead. + */ + @Deprecated + public EventBuilder setMessage(String message) { + return withMessage(message); + } + + /** + * Sets the timestamp in the event. + * + * @param timestamp timestamp of the event. + * @return the current {@code EventBuilder} for chained calls. + * @deprecated Use {@link #withTimestamp(Date)} instead. + */ + @Deprecated + public EventBuilder setTimestamp(Date timestamp) { + return withTimestamp(timestamp); + } + + /** + * Sets the log level in the event. + * + * @param level log level of the event. + * @return the current {@code EventBuilder} for chained calls. + * @deprecated Use {@link #withLevel(Event.Level)} instead. + */ + @Deprecated + public EventBuilder setLevel(Event.Level level) { + return withLevel(level); + } + + /** + * Sets the logger in the event. + * + * @param logger logger of the event. + * @return the current {@code EventBuilder} for chained calls. + * @deprecated Use {@link #withLogger(String)} instead. + */ + @Deprecated + public EventBuilder setLogger(String logger) { + return withLogger(logger); + } + + /** + * Sets the platform in the event. + * + * @param platform platform of the event. + * @return the current {@code EventBuilder} for chained calls. + * @deprecated Use {@link #withPlatform(String)} instead. + */ + @Deprecated + public EventBuilder setPlatform(String platform) { + return withPlatform(platform); + } + + /** + * Sets the culprit in the event based on a {@link StackTraceElement}. + * + * @param frame stack frame during which the event was captured. + * @return the current {@code EventBuilder} for chained calls. + * @deprecated Use {@link #withCulprit(StackTraceElement)} instead. + */ + @Deprecated + public EventBuilder setCulprit(StackTraceElement frame) { + return withCulprit(frame); + } + + /** + * Sets the culprit in the event. + * + * @param culprit culprit. + * @return the current {@code EventBuilder} for chained calls. + * @deprecated Use {@link #withCulprit(String)} instead. + */ + @Deprecated + public EventBuilder setCulprit(String culprit) { + return withCulprit(culprit); + } + + /** + * Adds a tag to an event. + *

    + * This allows to set a tag value in different contexts. + * + * @param tagKey name of the tag. + * @param tagValue value of the tag. + * @return the current {@code EventBuilder} for chained calls. + * @deprecated use {@link #withTag(String, String)} instead. + */ + @Deprecated + public EventBuilder addTag(String tagKey, String tagValue) { + return withTag(tagKey, tagValue); + } + + /** + * Sets the serverName in the event. + * + * @param serverName name of the server responsible for the event. + * @return the current {@code EventBuilder} for chained calls. + * @deprecated use {@link #withServerName(String)} instead. + */ + @Deprecated + public EventBuilder setServerName(String serverName) { + return withServerName(serverName); + } + + /** + * Adds an extra property to the event. + * + * @param extraName name of the extra property. + * @param extraValue value of the extra property. + * @return the current {@code EventBuilder} for chained calls. + * @deprecated use {@link #withExtra(String, Object)} instead. + */ + @Deprecated + public EventBuilder addExtra(String extraName, Object extraValue) { + return withExtra(extraName, extraValue); + } + + /** + * Generates a checksum from a given content and set it to the current event. + * + * @param contentToChecksum content to checksum. + * @return the current {@code EventBuilder} for chained calls. + * @deprecated use {@link #withChecksumFor(String)} instead. + */ + @Deprecated + public EventBuilder generateChecksum(String contentToChecksum) { + return withChecksumFor(contentToChecksum); + } + + /** + * Sets the checksum for the current event. + *

    + * It's recommended to rely instead on the checksum system provided by Sentry. + * + * @param checksum checksum for the event. + * @return the current {@code EventBuilder} for chained calls. + * @deprecated use {@link #withChecksum(String)} instead. + */ + @Deprecated + public EventBuilder setChecksum(String checksum) { + return withChecksum(checksum); + } + + /** + * Adds a {@link SentryInterface} to the event. + *

    + * If a {@code SentryInterface} with the same interface name has already been added, the new one will replace + * the old one. + * + * @param sentryInterface sentry interface to add to the event. + * @return the current {@code EventBuilder} for chained calls. + * @deprecated use {@link #withSentryInterface(SentryInterface)} instead. + */ + @Deprecated + public EventBuilder addSentryInterface(SentryInterface sentryInterface) { + return withSentryInterface(sentryInterface); + } + + /** + * Adds a {@link SentryInterface} to the event. + *

    + * Checks whether or not the entry already exists, and replaces it only if {@code replace} is true. + * + * @param sentryInterface sentry interface to add to the event. + * @param replace If true and a Sentry Interface with the same name has already been added it will be + * replaced. + * If false the statement will be ignored. + * @return the current {@code EventBuilder} for chained calls. + * @deprecated use {@link #withSentryInterface(SentryInterface, boolean)} instead. + */ + @Deprecated + public EventBuilder addSentryInterface(SentryInterface sentryInterface, boolean replace) { + return withSentryInterface(sentryInterface, replace); + } + /** * Time sensitive cache in charge of keeping track of the hostname. *

    diff --git a/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java b/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java index 1e6720a0de7..29579111d2e 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java +++ b/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java @@ -25,7 +25,7 @@ public void helpBuildingEvent(EventBuilder eventBuilder) { } private void addHttpInterface(EventBuilder eventBuilder, HttpServletRequest servletRequest) { - eventBuilder.addSentryInterface(new HttpInterface(servletRequest), false); + eventBuilder.withSentryInterface(new HttpInterface(servletRequest), false); } private void addUserInterface(EventBuilder eventBuilder, HttpServletRequest servletRequest) { @@ -34,6 +34,6 @@ private void addUserInterface(EventBuilder eventBuilder, HttpServletRequest serv username = servletRequest.getUserPrincipal().getName(); } - eventBuilder.addSentryInterface(new UserInterface(null, username, servletRequest.getRemoteAddr(), null), false); + eventBuilder.withSentryInterface(new UserInterface(null, username, servletRequest.getRemoteAddr(), null), false); } } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java index b47326267ec..89571fb5c5c 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/MessageInterface.java @@ -20,7 +20,7 @@ * This way, Sentry will be able to put the two events in the same category. *

    * Note: Sentry won't attempt to format the message, this is why the formatted message should be set through - * {@link net.kencochrane.raven.event.EventBuilder#setMessage(String)} in any case. + * {@link net.kencochrane.raven.event.EventBuilder#withMessage(String)} in any case. */ public class MessageInterface implements SentryInterface { /** @@ -34,10 +34,10 @@ public class MessageInterface implements SentryInterface { * Creates a non parametrised message. *

    * While it's technically possible to create a non parametrised message with {@code MessageInterface}, it's - * recommended to use {@link net.kencochrane.raven.event.EventBuilder#setMessage(String)} instead. + * recommended to use {@link net.kencochrane.raven.event.EventBuilder#withMessage(String)} instead. * * @param message message to add to the event. - * @deprecated Use {@link net.kencochrane.raven.event.EventBuilder#setMessage(String)} instead. + * @deprecated Use {@link net.kencochrane.raven.event.EventBuilder#withMessage(String)} instead. */ @Deprecated public MessageInterface(String message) { diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index ec775724e3f..44ed9020ebe 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -152,9 +152,9 @@ protected void initRaven() { */ protected Event buildEvent(LogRecord record) { EventBuilder eventBuilder = new EventBuilder() - .setLevel(getLevel(record.getLevel())) - .setTimestamp(new Date(record.getMillis())) - .setLogger(record.getLoggerName()); + .withLevel(getLevel(record.getLevel())) + .withTimestamp(new Date(record.getMillis())) + .withLogger(record.getLoggerName()); String message = record.getMessage(); if (record.getResourceBundle() != null && record.getResourceBundle().containsKey(record.getMessage())) { @@ -162,28 +162,28 @@ protected Event buildEvent(LogRecord record) { } if (record.getParameters() != null) { List parameters = formatMessageParameters(record.getParameters()); - eventBuilder.addSentryInterface(new MessageInterface(message, parameters)); + eventBuilder.withSentryInterface(new MessageInterface(message, parameters)); message = MessageFormat.format(message, record.getParameters()); } - eventBuilder.setMessage(message); + eventBuilder.withMessage(message); Throwable throwable = record.getThrown(); if (throwable != null) - eventBuilder.addSentryInterface(new ExceptionInterface(throwable)); + eventBuilder.withSentryInterface(new ExceptionInterface(throwable)); if (record.getSourceClassName() != null && record.getSourceMethodName() != null) { StackTraceElement fakeFrame = new StackTraceElement(record.getSourceClassName(), record.getSourceMethodName(), null, -1); - eventBuilder.setCulprit(fakeFrame); + eventBuilder.withCulprit(fakeFrame); } else { - eventBuilder.setCulprit(record.getLoggerName()); + eventBuilder.withCulprit(record.getLoggerName()); } for (Map.Entry tagEntry : tags.entrySet()) { - eventBuilder.addTag(tagEntry.getKey(), tagEntry.getValue()); + eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); } - eventBuilder.addExtra(THREAD_ID, record.getThreadID()); + eventBuilder.withExtra(THREAD_ID, record.getThreadID()); raven.runBuilderHelpers(eventBuilder); return eventBuilder.build(); diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java index b3842ad3c7f..2e2312c8fd5 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventBuilderTest.java @@ -82,7 +82,7 @@ public void builtEventWithMessageHasProperMessage( @Injectable("message") final String mockMessage) throws Exception { final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setMessage(mockMessage); + eventBuilder.withMessage(mockMessage); final Event event = eventBuilder.build(); @@ -113,7 +113,7 @@ public void builtEventWithTimestampHasProperTimestamp(@Injectable final Date moc result = mockTimestamp; }}; final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setTimestamp(mockTimestamp); + eventBuilder.withTimestamp(mockTimestamp); final Event event = eventBuilder.build(); @@ -133,7 +133,7 @@ public void builtEventWithoutLevelHasNullLevel() throws Exception { public void builtEventWithLevelHasProperLevel(@Injectable final Event.Level mockLevel) throws Exception { final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setLevel(mockLevel); + eventBuilder.withLevel(mockLevel); final Event event = eventBuilder.build(); @@ -153,7 +153,7 @@ public void builtEventWithoutLoggerHasNullLogger() throws Exception { public void builtEventWithLoggerHasProperLogger(@Injectable("logger") final String mockLogger) throws Exception { final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setLogger(mockLogger); + eventBuilder.withLogger(mockLogger); final Event event = eventBuilder.build(); @@ -173,7 +173,7 @@ public void builtEventWithoutPlatformHasDefaultPlatform() throws Exception { public void builtEventWithPlatformHasProperPlatform(@Injectable("platform") final String mockPlatform) throws Exception { final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setPlatform(mockPlatform); + eventBuilder.withPlatform(mockPlatform); final Event event = eventBuilder.build(); @@ -193,7 +193,7 @@ public void builtEventWithoutCulpritHasNullCulprit() throws Exception { public void builtEventWithCulpritHasProperCulprit(@Injectable("culprit") final String mockCulprit) throws Exception { final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setCulprit(mockCulprit); + eventBuilder.withCulprit(mockCulprit); final Event event = eventBuilder.build(); @@ -215,7 +215,7 @@ public void builtEventWithStackFrameAsCulpritHasProperCulprit(StackTraceElement String expectedCulprit) throws Exception { final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setCulprit(mockStackFrame); + eventBuilder.withCulprit(mockStackFrame); final Event event = eventBuilder.build(); @@ -244,7 +244,7 @@ public void builtEventWithTagsHasProperTags(@Injectable("tagKey") final String m @Injectable("tagValue") final String mockTagValue) throws Exception { final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.addTag(mockTagKey, mockTagValue); + eventBuilder.withTag(mockTagKey, mockTagValue); final Event event = eventBuilder.build(); @@ -294,7 +294,7 @@ public void builtEventWithServerNameUsesProvidedServerName(@Injectable("serverNa throws Exception { resetHostnameCache(); final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setServerName(mockServerName); + eventBuilder.withServerName(mockServerName); final Event event = eventBuilder.build(); @@ -323,7 +323,7 @@ public void builtEventWithExtrasHasProperExtras(@Injectable("extraKey") final St @Injectable("extraValue") final String mockExtraValue) throws Exception { final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.addExtra(mockExtraKey, mockExtraValue); + eventBuilder.withExtra(mockExtraKey, mockExtraValue); final Event event = eventBuilder.build(); @@ -345,7 +345,7 @@ public void builtEventWithChecksumHasProperChecksum( @Injectable("checksum") final String mockChecksum) throws Exception { final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.setChecksum(mockChecksum); + eventBuilder.withChecksum(mockChecksum); final Event event = eventBuilder.build(); @@ -365,7 +365,7 @@ public Object[][] checksumProvider() { public void builtEventWithGeneratedChecksumHasCRC32Checksum(String string, String expectedChecksum) throws Exception { final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.generateChecksum(string); + eventBuilder.withChecksumFor(string); final Event event = eventBuilder.build(); @@ -400,7 +400,7 @@ public void builtEventWithSentryInterfacesHasProperSentryInterfaces( result = mockSentryInterfaceName; }}; final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.addSentryInterface(mockSentryInterface); + eventBuilder.withSentryInterface(mockSentryInterface); final Event event = eventBuilder.build(); @@ -421,8 +421,8 @@ public void builtEventReplacesSentryInterfacesWithSameNameByDefault( result = mockSentryInterfaceName; }}; final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.addSentryInterface(mockSentryInterface); - eventBuilder.addSentryInterface(mockSentryInterface2); + eventBuilder.withSentryInterface(mockSentryInterface); + eventBuilder.withSentryInterface(mockSentryInterface2); final Event event = eventBuilder.build(); @@ -444,8 +444,8 @@ public void builtEventReplacesSentryInterfacesWithSameNameIfReplacementEnabled( result = mockSentryInterfaceName; }}; final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.addSentryInterface(mockSentryInterface); - eventBuilder.addSentryInterface(mockSentryInterface2, true); + eventBuilder.withSentryInterface(mockSentryInterface); + eventBuilder.withSentryInterface(mockSentryInterface2, true); final Event event = eventBuilder.build(); @@ -466,8 +466,8 @@ public void builtEventKeepsSentryInterfacesWithSameNameIfReplacementDisabled( result = mockSentryInterfaceName; }}; final EventBuilder eventBuilder = new EventBuilder(); - eventBuilder.addSentryInterface(mockSentryInterface); - eventBuilder.addSentryInterface(mockSentryInterface2, false); + eventBuilder.withSentryInterface(mockSentryInterface); + eventBuilder.withSentryInterface(mockSentryInterface2, false); final Event event = eventBuilder.build(); diff --git a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java index 0682e925228..b3bb3cf7274 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelperTest.java @@ -31,7 +31,7 @@ public void testNoRequest() throws Exception { new Verifications() {{ new HttpInterface(withInstanceOf(HttpServletRequest.class)); times = 0; - mockEventBuilder.addSentryInterface(withInstanceOf(SentryInterface.class), anyBoolean); + mockEventBuilder.withSentryInterface(withInstanceOf(SentryInterface.class), anyBoolean); times = 0; }}; } @@ -49,8 +49,8 @@ public void testWithRequest(@Injectable final HttpServletRequest mockHttpServlet new Verifications() {{ new HttpInterface(mockHttpServletRequest); new UserInterface(null, null, null, null); - mockEventBuilder.addSentryInterface(this.withNotNull(), false); - mockEventBuilder.addSentryInterface(this.withNotNull(), false); + mockEventBuilder.withSentryInterface(this.withNotNull(), false); + mockEventBuilder.withSentryInterface(this.withNotNull(), false); }}; } @@ -72,7 +72,7 @@ public void testWithUserPrincipal(@Injectable final HttpServletRequest mockHttpS new Verifications() {{ new UserInterface(null, mockUsername, null, null); - mockEventBuilder.addSentryInterface(this.withNotNull(), false); + mockEventBuilder.withSentryInterface(this.withNotNull(), false); }}; } @@ -91,7 +91,7 @@ public void testWithIpAddress(@Injectable final HttpServletRequest mockHttpServl new Verifications() {{ new UserInterface(null, null, mockIpAddress, null); - mockEventBuilder.addSentryInterface(this.withNotNull(), false); + mockEventBuilder.withSentryInterface(this.withNotNull(), false); }}; } } From 57c0240987f9f9c1d46e40d0af07d267815b4c10 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 28 Sep 2014 12:33:34 +1000 Subject: [PATCH 1305/2152] Fix indentation and style --- .../raven/event/helper/HttpEventBuilderHelper.java | 3 ++- .../raven/event/interfaces/UserInterface.java | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java b/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java index 29579111d2e..293bc5822cd 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java +++ b/raven/src/main/java/net/kencochrane/raven/event/helper/HttpEventBuilderHelper.java @@ -34,6 +34,7 @@ private void addUserInterface(EventBuilder eventBuilder, HttpServletRequest serv username = servletRequest.getUserPrincipal().getName(); } - eventBuilder.withSentryInterface(new UserInterface(null, username, servletRequest.getRemoteAddr(), null), false); + UserInterface userInterface = new UserInterface(null, username, servletRequest.getRemoteAddr(), null); + eventBuilder.withSentryInterface(userInterface, false); } } diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/UserInterface.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/UserInterface.java index a315fa585f8..ba0aea8272b 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/UserInterface.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/UserInterface.java @@ -67,11 +67,11 @@ public int hashCode() { @Override public String toString() { - return "UserInterface{" + - "id='" + id + '\'' + - ", username='" + username + '\'' + - ", ipAddress='" + ipAddress + '\'' + - ", email='" + email + '\'' + - '}'; + return "UserInterface{" + + "id='" + id + '\'' + + ", username='" + username + '\'' + + ", ipAddress='" + ipAddress + '\'' + + ", email='" + email + '\'' + + '}'; } } From 0890895501a6acb6a1aa39102d2775d390694e59 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 28 Sep 2014 12:36:35 +1000 Subject: [PATCH 1306/2152] Update readme file --- raven/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/raven/README.md b/raven/README.md index 378a33123dd..742cc6a3d96 100644 --- a/raven/README.md +++ b/raven/README.md @@ -155,9 +155,9 @@ public class MyClass { void logSimpleMessage() { // This adds a simple message to the logs EventBuilder eventBuilder = new EventBuilder() - .setMessage("This is a test") - .setLevel(Event.Level.INFO) - .setLogger(MyClass.class.getName()); + .withMessage("This is a test") + .withLevel(Event.Level.INFO) + .withLogger(MyClass.class.getName()); raven.runBuilderHelpers(eventBuilder); // Optional raven.sendEvent(eventBuilder.build()); } @@ -168,10 +168,10 @@ public class MyClass { } catch (Exception e) { // This adds an exception to the logs EventBuilder eventBuilder = new EventBuilder() - .setMessage("Exception caught") - .setLevel(Event.Level.ERROR) - .setLogger(MyClass.class.getName()) - .addSentryInterface(new ExceptionInterface(e)); + .withMessage("Exception caught") + .withLevel(Event.Level.ERROR) + .withLogger(MyClass.class.getName()) + .withSentryInterface(new ExceptionInterface(e)); raven.runBuilderHelpers(eventBuilder); // Optional raven.sendEvent(eventBuilder.build()); } From f26ffa3f046c2f8733b88d3e6c1e2eb9e76a37f1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 28 Sep 2014 15:44:17 +1000 Subject: [PATCH 1307/2152] Bump guava to 18.0 --- pom.xml | 2 +- raven/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 488dc3df4e2..4431a072bc8 100644 --- a/pom.xml +++ b/pom.xml @@ -107,7 +107,7 @@ 1.7.7 - 17.0 + 18.0 2.4.2 1.10 6.8.8 diff --git a/raven/README.md b/raven/README.md index 742cc6a3d96..a0590302ef3 100644 --- a/raven/README.md +++ b/raven/README.md @@ -20,7 +20,7 @@ Details in the [central Maven repository](https://search.maven.org/#artifactdeta ### Manual dependency management Relies on: - - [guava-17.0.jar](https://search.maven.org/#artifactdetails%7Ccom.google.guava%7Cguava%7C17.0%7Cjar) + - [guava-18.0.jar](https://search.maven.org/#artifactdetails%7Ccom.google.guava%7Cguava%7C18.0%7Cjar) - [jackson-core-2.4.0.jar](https://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.4.0%7Cjar) - [slf4j-api-1.7.7.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.7%7Cjar) - [slf4j-jdk14-1.7.7.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-jdk14%7C1.7.7%7Cjar) From fe6f5a936a7592b28e4a566e44a5eb62834cf93b Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 28 Sep 2014 15:46:22 +1000 Subject: [PATCH 1308/2152] Bump Jmockit to 1.12 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4431a072bc8..a948f5b3e1c 100644 --- a/pom.xml +++ b/pom.xml @@ -109,7 +109,7 @@ 1.7.7 18.0 2.4.2 - 1.10 + 1.12 6.8.8 1.3 From ace66aba24ee40bc9b7859240083b0dbaa4994d7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 28 Sep 2014 15:46:32 +1000 Subject: [PATCH 1309/2152] Bump appengine to 1.9.12 --- raven-appengine/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 23d69b07a81..2bba6c9f81c 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -14,7 +14,7 @@ Raven module allowing to use GoogleApp Engine and its task queueing system. - 1.9.9 + 1.9.12 1.1.2 From 40a3dd99ef6fa7c8579083032520fc76dc472f8e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 28 Sep 2014 16:00:32 +1000 Subject: [PATCH 1310/2152] Bump various plugins --- pom.xml | 42 +++++++++++++++++++++--------------------- raven/pom.xml | 2 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/pom.xml b/pom.xml index a948f5b3e1c..0c34158180f 100644 --- a/pom.xml +++ b/pom.xml @@ -181,7 +181,7 @@ org.apache.maven.plugins maven-release-plugin - 2.5 + 2.5.1 v@{project.version} true @@ -198,7 +198,7 @@ com.github.github site-maven-plugin - 0.9 + 0.10 Creating site for ${project.artifactId} ${project.version} ${project.distributionManagement.site.url} @@ -217,7 +217,7 @@ org.apache.maven.plugins maven-site-plugin - 3.3 + 3.4 true @@ -225,7 +225,7 @@ org.codehaus.mojo animal-sniffer-maven-plugin - 1.10 + 1.11 org.codehaus.mojo.signature @@ -249,7 +249,7 @@ org.eclipse.jetty jetty-maven-plugin - 9.1.3.v20140225 + 9.2.3.v20140905 10 foo @@ -281,12 +281,12 @@ org.apache.maven.plugins maven-surefire-plugin - 2.16 + 2.17 org.apache.maven.plugins maven-failsafe-plugin - 2.16 + 2.17 integration-tests @@ -300,7 +300,7 @@ org.apache.maven.plugins maven-dependency-plugin - 2.8 + 2.9 analyze @@ -347,7 +347,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 2.12 + 2.13 src/checkstyle/checkstyle.xml true @@ -366,7 +366,7 @@ maven-clean-plugin - 2.5 + 2.6 maven-compiler-plugin @@ -374,15 +374,15 @@ maven-deploy-plugin - 2.8.1 + 2.8.2 maven-install-plugin - 2.5.1 + 2.5.2 maven-jar-plugin - 2.4 + 2.5 maven-resources-plugin @@ -390,7 +390,7 @@ maven-site-plugin - 3.3 + 3.4 maven-war-plugin @@ -410,17 +410,17 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.9.1 + 2.10 org.apache.maven.plugins maven-surefire-report-plugin - 2.16 + 2.17 org.apache.maven.plugins maven-checkstyle-plugin - 2.12 + 2.13 src/checkstyle/checkstyle.xml @@ -442,7 +442,7 @@ org.apache.maven.plugins maven-source-plugin - 2.2.1 + 2.3 attach-sources @@ -455,7 +455,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.9.1 + 2.10 attach-javadocs @@ -482,7 +482,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.2 + 1.6.4 true sonatype-nexus-staging @@ -513,7 +513,7 @@ org.pitest pitest-maven - 1.0.0 + 1.1.0 *Test diff --git a/raven/pom.xml b/raven/pom.xml index d864b5fdde9..589b91bea56 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -90,7 +90,7 @@ org.codehaus.mojo buildnumber-maven-plugin - 1.2 + 1.3 From ff308090564cb1ba60facd5015f0500f68b7cf0c Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Fri, 3 Oct 2014 08:25:17 +1000 Subject: [PATCH 1311/2152] Revert to jmockit 1.10 for now --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0c34158180f..4b37ab31ba0 100644 --- a/pom.xml +++ b/pom.xml @@ -109,7 +109,7 @@ 1.7.7 18.0 2.4.2 - 1.12 + 1.10 6.8.8 1.3 From 0fbd6277676a5c63defc53abc69529690134db21 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 4 Oct 2014 00:54:09 +1000 Subject: [PATCH 1312/2152] Add interface binding for UserInterface --- .../raven/DefaultRavenFactory.java | 6 ++--- .../marshaller/json/UserInterfaceBinding.java | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 raven/src/main/java/net/kencochrane/raven/marshaller/json/UserInterfaceBinding.java diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index db79a284225..abac32ebe7b 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -6,10 +6,7 @@ import net.kencochrane.raven.connection.UdpConnection; import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.helper.HttpEventBuilderHelper; -import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.HttpInterface; -import net.kencochrane.raven.event.interfaces.MessageInterface; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import net.kencochrane.raven.event.interfaces.*; import net.kencochrane.raven.marshaller.Marshaller; import net.kencochrane.raven.marshaller.json.*; import org.slf4j.Logger; @@ -200,6 +197,7 @@ protected Marshaller createMarshaller(Dsn dsn) { marshaller.addInterfaceBinding(StackTraceInterface.class, stackTraceBinding); marshaller.addInterfaceBinding(ExceptionInterface.class, new ExceptionInterfaceBinding(stackTraceBinding)); marshaller.addInterfaceBinding(MessageInterface.class, new MessageInterfaceBinding()); + marshaller.addInterfaceBinding(UserInterface.class, new UserInterfaceBinding()); HttpInterfaceBinding httpBinding = new HttpInterfaceBinding(); //TODO: Add a way to clean the HttpRequest //httpBinding. diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/UserInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/UserInterfaceBinding.java new file mode 100644 index 00000000000..34ffa386b27 --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/UserInterfaceBinding.java @@ -0,0 +1,26 @@ +package net.kencochrane.raven.marshaller.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import net.kencochrane.raven.event.interfaces.UserInterface; + +import java.io.IOException; + +/** + * Binding allowing to transform a {@link UserInterface} into a JSON stream. + */ +public class UserInterfaceBinding implements InterfaceBinding { + private static final String ID = "id"; + private static final String USERNAME = "username"; + private static final String EMAIL = "email"; + private static final String IP_ADDRESS = "ip_address"; + + @Override + public void writeInterface(JsonGenerator generator, UserInterface userInterface) throws IOException { + generator.writeStartObject(); + generator.writeStringField(ID, userInterface.getId()); + generator.writeStringField(USERNAME, userInterface.getUsername()); + generator.writeStringField(EMAIL, userInterface.getEmail()); + generator.writeStringField(IP_ADDRESS, userInterface.getIpAddress()); + generator.writeEndObject(); + } +} From 17b91d3386ae3338778e8c623980da989b13023e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 4 Oct 2014 01:25:49 +1000 Subject: [PATCH 1313/2152] Add tests for UserINterface --- .../json/UserInterfaceBindingTest.java | 42 +++++++++++++++++++ .../raven/marshaller/json/User1.json | 6 +++ 2 files changed, 48 insertions(+) create mode 100644 raven/src/test/java/net/kencochrane/raven/marshaller/json/UserInterfaceBindingTest.java create mode 100644 raven/src/test/resources/net/kencochrane/raven/marshaller/json/User1.json diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/UserInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/UserInterfaceBindingTest.java new file mode 100644 index 00000000000..677da29926e --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/UserInterfaceBindingTest.java @@ -0,0 +1,42 @@ +package net.kencochrane.raven.marshaller.json; + +import mockit.Injectable; +import mockit.NonStrictExpectations; +import mockit.Tested; +import net.kencochrane.raven.event.interfaces.UserInterface; +import org.testng.annotations.Test; + +import static net.kencochrane.raven.marshaller.json.JsonTestTool.jsonResource; +import static net.kencochrane.raven.marshaller.json.JsonTestTool.newJsonGenerator; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class UserInterfaceBindingTest { + @Tested + private UserInterfaceBinding userInterfaceBinding; + @Injectable + private UserInterface mockUserInterface = null; + + @Test + public void testSimpleMessage() throws Exception { + final JsonTestTool.JsonGeneratorTool generatorTool = newJsonGenerator(); + final String id = "970e9df6-6e0b-4a27-b2ee-0faf0f368354"; + final String username = "3eaa555a-e813-4778-9852-7c1880bf0fd7"; + final String email = "9bcade34-a58c-4616-9de7-bc8b456c96de"; + final String ipAddress = "9a1a658b-6f74-43ae-9e45-0f89f4c5fcb4"; + new NonStrictExpectations() {{ + mockUserInterface.getId(); + result = id; + mockUserInterface.getUsername(); + result = username; + mockUserInterface.getEmail(); + result = email; + mockUserInterface.getIpAddress(); + result = ipAddress; + }}; + + userInterfaceBinding.writeInterface(generatorTool.generator(), mockUserInterface); + + assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/User1.json"))); + } +} diff --git a/raven/src/test/resources/net/kencochrane/raven/marshaller/json/User1.json b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/User1.json new file mode 100644 index 00000000000..94795cbe238 --- /dev/null +++ b/raven/src/test/resources/net/kencochrane/raven/marshaller/json/User1.json @@ -0,0 +1,6 @@ +{ + "id": "970e9df6-6e0b-4a27-b2ee-0faf0f368354", + "username": "3eaa555a-e813-4778-9852-7c1880bf0fd7", + "email": "9bcade34-a58c-4616-9de7-bc8b456c96de", + "ip_address": "9a1a658b-6f74-43ae-9e45-0f89f4c5fcb4" +} From 425d6d16818c4b75151deff33b3d2f42aaea6f1e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 4 Oct 2014 10:21:22 +1000 Subject: [PATCH 1314/2152] Refactor the Json tools --- .../json/ExceptionInterfaceBindingTest.java | 20 ++--- ...nTestTool.java => JsonComparisonUtil.java} | 64 ++++++++------- .../marshaller/json/JsonMarshallerTest.java | 80 +++++++++---------- .../json/MessageInterfaceBindingTest.java | 8 +- .../json/StackTraceInterfaceBindingTest.java | 20 ++--- .../json/UserInterfaceBindingTest.java | 13 +-- 6 files changed, 107 insertions(+), 98 deletions(-) rename raven/src/test/java/net/kencochrane/raven/marshaller/json/{JsonTestTool.java => JsonComparisonUtil.java} (50%) diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java index f2c6e1ef4fb..06fd77800f0 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/ExceptionInterfaceBindingTest.java @@ -15,7 +15,7 @@ import java.util.Deque; import static mockit.Deencapsulation.setField; -import static net.kencochrane.raven.marshaller.json.JsonTestTool.*; +import static net.kencochrane.raven.marshaller.json.JsonComparisonUtil.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -44,7 +44,7 @@ public void writeInterface(JsonGenerator generator, StackTraceInterface sentryIn @Test public void testSimpleException() throws Exception { - final JsonGeneratorTool generatorTool = newJsonGenerator(); + final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final String message = "6e65f60d-9f22-495a-9556-7a61eeea2a14"; final Throwable throwable = new IllegalStateException(message); new NonStrictExpectations() {{ @@ -57,15 +57,15 @@ public Deque getExceptions() { }; }}; - interfaceBinding.writeInterface(generatorTool.generator(), mockExceptionInterface); + interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockExceptionInterface); - assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/Exception1.json"))); + assertThat(jsonGeneratorParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/Exception1.json"))); } @Test public void testClassInDefaultPackage() throws Exception { setField((Object) DefaultPackageException.class, "name", DefaultPackageException.class.getSimpleName()); - final JsonGeneratorTool generatorTool = newJsonGenerator(); + final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final Throwable throwable = new DefaultPackageException(); new NonStrictExpectations() {{ mockExceptionInterface.getExceptions(); @@ -77,14 +77,14 @@ public Deque getExceptions() { }; }}; - interfaceBinding.writeInterface(generatorTool.generator(), mockExceptionInterface); + interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockExceptionInterface); - assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/Exception2.json"))); + assertThat(jsonGeneratorParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/Exception2.json"))); } @Test public void testChainedException() throws Exception { - final JsonGeneratorTool generatorTool = newJsonGenerator(); + final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final String message1 = "a71e6132-9867-457d-8b04-5021cd7a251f"; final Throwable throwable1 = new IllegalStateException(message1); final String message2 = "f1296959-5b86-45f7-853a-cdc25196710b"; @@ -99,9 +99,9 @@ public Deque getExceptions() { }; }}; - interfaceBinding.writeInterface(generatorTool.generator(), mockExceptionInterface); + interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockExceptionInterface); - assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/Exception3.json"))); + assertThat(jsonGeneratorParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/Exception3.json"))); } } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonTestTool.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonComparisonUtil.java similarity index 50% rename from raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonTestTool.java rename to raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonComparisonUtil.java index 288d1ae280d..17d31eabc9d 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonTestTool.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonComparisonUtil.java @@ -1,6 +1,5 @@ package net.kencochrane.raven.marshaller.json; -import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -8,70 +7,79 @@ import java.io.ByteArrayOutputStream; import java.io.OutputStream; -import java.io.StringWriter; -public final class JsonTestTool { +public final class JsonComparisonUtil { private static final ObjectMapper objectMapper = new ObjectMapper(); - private JsonTestTool() { + private JsonComparisonUtil() { } public static JsonNode jsonResource(String resourcePath) throws Exception { - return objectMapper.readTree(JsonTestTool.class.getResourceAsStream(resourcePath)); + return objectMapper.readTree(JsonComparisonUtil.class.getResourceAsStream(resourcePath)); } - public static JsonGeneratorTool newJsonGenerator() throws Exception { - return new JsonGeneratorTool(); + public static JsonGeneratorParser newJsonGenerator() throws Exception { + return new JsonGeneratorParser(); } - public static JsonOutputStreamTool newJsonOutputStream() throws Exception { - return new JsonOutputStreamTool(); + public static JsonOutputStreamParser newJsonOutputStream() throws Exception { + return new JsonOutputStreamParser(); } - public static class JsonGeneratorTool { + public static abstract class JsonStreamParser { + protected final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + protected abstract void closeStream(); + + public JsonNode value() throws Exception { + closeStream(); + return objectMapper.readTree(outputStream.toByteArray()); + } + + @Override + public String toString() { + closeStream(); + try { + return outputStream.toString(Charsets.UTF_8.name()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + public static class JsonGeneratorParser extends JsonStreamParser { private final JsonGenerator jsonGenerator; - private final StringWriter stringWriter = new StringWriter(); - private JsonGeneratorTool() throws Exception { - jsonGenerator = new JsonFactory().createGenerator(stringWriter); + private JsonGeneratorParser() throws Exception { + jsonGenerator = objectMapper.getFactory().createGenerator(outputStream); } + public JsonGenerator generator() { return jsonGenerator; } - public JsonNode value() throws Exception { - return objectMapper.readTree(toString()); - } - @Override - public String toString() { + protected void closeStream() { try { jsonGenerator.close(); - stringWriter.close(); - return stringWriter.toString(); + outputStream.close(); } catch (Exception e) { throw new RuntimeException(e); } } } - public static class JsonOutputStreamTool { - private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + public static class JsonOutputStreamParser extends JsonStreamParser { public OutputStream outputStream() { return outputStream; } - public JsonNode value() throws Exception { - return objectMapper.readTree(toString()); - } - @Override - public String toString() { + protected void closeStream() { try { outputStream.close(); - return outputStream.toString(Charsets.UTF_8.name()); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index b7613de9b2c..472bb4dd426 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -16,7 +16,7 @@ import java.util.Date; import java.util.UUID; -import static net.kencochrane.raven.marshaller.json.JsonTestTool.*; +import static net.kencochrane.raven.marshaller.json.JsonComparisonUtil.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -42,7 +42,7 @@ public void setUp() throws Exception { @Test public void testEventIdWrittenProperly(@Injectable final UUID mockUuid) throws Exception { - final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getId(); result = mockUuid; @@ -50,27 +50,27 @@ public void testEventIdWrittenProperly(@Injectable final UUID mockUuid) throws E result = "3b71fba5-413e-4022-ae98-5f0b80a155a5"; }}; - jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testEventId.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testEventId.json"))); } @Test public void testEventMessageWrittenProperly(@Injectable("message") final String mockMessage) throws Exception { - final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getMessage(); result = mockMessage; }}; - jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testMessage.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testMessage.json"))); } @Test public void testEventTimestampWrittenProperly(@Injectable final Date mockTimestamp) throws Exception { - final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getTimestamp(); result = mockTimestamp; @@ -79,9 +79,9 @@ public void testEventTimestampWrittenProperly(@Injectable final Date mockTimesta result = 1385266295338L; }}; - jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTimestamp.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTimestamp.json"))); } @DataProvider(name = "levelProvider") @@ -97,94 +97,94 @@ public Object[][] levelProvider() { @Test(dataProvider = "levelProvider") public void testEventLevelWrittenProperly(final Event.Level eventLevel, String levelFile) throws Exception { - final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getLevel(); result = eventLevel; }}; - jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(outputStreamTool.value(), is(jsonResource(levelFile))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource(levelFile))); } @Test public void testEventLoggerWrittenProperly(@Injectable("logger") final String mockLogger) throws Exception { - final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getLogger(); result = mockLogger; }}; - jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLogger.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testLogger.json"))); } @Test public void testEventPlaftormWrittenProperly(@Injectable("platform") final String mockPlatform) throws Exception { - final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getPlatform(); result = mockPlatform; }}; - jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testPlatform.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testPlatform.json"))); } @Test public void testEventCulpritWrittenProperly(@Injectable("culprit") final String mockCulprit) throws Exception { - final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getCulprit(); result = mockCulprit; }}; - jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testCulprit.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testCulprit.json"))); } @Test public void testEventTagsWrittenProperly(@Injectable("tagName") final String mockTagName, @Injectable("tagValue") final String mockTagValue) throws Exception { - final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getTags(); result = Collections.singletonMap(mockTagName, mockTagValue); }}; - jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTags.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testTags.json"))); } @Test public void testEventServerNameWrittenProperly(@Injectable("serverName") final String mockServerName) throws Exception { - final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getServerName(); result = mockServerName; }}; - jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testServerName.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testServerName.json"))); } @Test public void testEventChecksumWrittenProperly(@Injectable("1234567890abcdef") final String mockChecksum) throws Exception { - final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getChecksum(); result = mockChecksum; }}; - jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testChecksum.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testChecksum.json"))); } @DataProvider(name = "extraProvider") @@ -206,21 +206,21 @@ public Object[][] extraProvider() { @Test(dataProvider = "extraProvider") public void testEventExtraWrittenProperly(final String extraKey, final Object extraValue, String extraFile) throws Exception { - final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getExtra(); result = Collections.singletonMap(extraKey, extraValue); }}; - jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(outputStreamTool.value(), is(jsonResource(extraFile))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource(extraFile))); } @Test public void testEventExtraWrittenProperly(@Injectable("key") final String mockExtraKey, @Injectable final Object mockExtraValue) throws Exception { - final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getExtra(); result = Collections.singletonMap(mockExtraKey, mockExtraValue); @@ -228,16 +228,16 @@ public void testEventExtraWrittenProperly(@Injectable("key") final String mockEx result = "test"; }}; - jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json"))); } @Test public void testInterfaceBindingIsProperlyUsed( @Injectable final SentryInterface mockSentryInterface, @Injectable final InterfaceBinding mockInterfaceBinding) throws Exception { - final JsonOutputStreamTool outputStreamTool = newJsonOutputStream(); + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); new NonStrictExpectations() {{ mockEvent.getSentryInterfaces(); result = Collections.singletonMap("interfaceKey", mockSentryInterface); @@ -251,12 +251,12 @@ public void writeInterface(JsonGenerator generator, SentryInterface sentryInterf }}; jsonMarshaller.addInterfaceBinding(mockSentryInterface.getClass(), mockInterfaceBinding); - jsonMarshaller.marshall(mockEvent, outputStreamTool.outputStream()); + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); new Verifications() {{ mockInterfaceBinding.writeInterface((JsonGenerator) any, mockSentryInterface); }}; - assertThat(outputStreamTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json"))); } @Test diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java index 1fdd1531948..2828cac96c4 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/MessageInterfaceBindingTest.java @@ -9,7 +9,7 @@ import java.util.Arrays; import java.util.List; -import static net.kencochrane.raven.marshaller.json.JsonTestTool.*; +import static net.kencochrane.raven.marshaller.json.JsonComparisonUtil.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -21,7 +21,7 @@ public class MessageInterfaceBindingTest { @Test public void testSimpleMessage() throws Exception { - final JsonGeneratorTool generatorTool = newJsonGenerator(); + final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final String message = "550ee459-cbb5-438e-91d2-b0bbdefab670"; final List parameters = Arrays.asList("33ed929b-d803-46b6-a57b-9c0feab1f468", "5fc10379-6392-470d-9de5-e4cb805ab78c"); @@ -32,8 +32,8 @@ public void testSimpleMessage() throws Exception { result = parameters; }}; - interfaceBinding.writeInterface(generatorTool.generator(), mockMessageInterface); + interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockMessageInterface); - assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/Message1.json"))); + assertThat(jsonGeneratorParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/Message1.json"))); } } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java index 6bc2bd71b10..5c7cbb15f6d 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBindingTest.java @@ -6,7 +6,7 @@ import net.kencochrane.raven.event.interfaces.StackTraceInterface; import org.testng.annotations.Test; -import static net.kencochrane.raven.marshaller.json.JsonTestTool.*; +import static net.kencochrane.raven.marshaller.json.JsonComparisonUtil.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -18,7 +18,7 @@ public class StackTraceInterfaceBindingTest { @Test public void testSingleStackFrame() throws Exception { - final JsonGeneratorTool generatorTool = newJsonGenerator(); + final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final String methodName = "0cce55c9-478f-4386-8ede-4b6f000da3e6"; final String className = "31b26f01-9b97-442b-9f36-8a317f94ad76"; final int lineNumber = 1; @@ -28,14 +28,14 @@ public void testSingleStackFrame() throws Exception { result = new StackTraceElement[]{stackTraceElement}; }}; - interfaceBinding.writeInterface(generatorTool.generator(), mockStackTraceInterface); + interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockStackTraceInterface); - assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/StackTrace1.json"))); + assertThat(jsonGeneratorParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/StackTrace1.json"))); } @Test public void testFramesCommonWithEnclosing() throws Exception { - final JsonGeneratorTool generatorTool = newJsonGenerator(); + final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final StackTraceElement stackTraceElement = new StackTraceElement("", "", null, 0); new NonStrictExpectations() {{ mockStackTraceInterface.getStackTrace(); @@ -45,14 +45,14 @@ public void testFramesCommonWithEnclosing() throws Exception { }}; interfaceBinding.setRemoveCommonFramesWithEnclosing(true); - interfaceBinding.writeInterface(generatorTool.generator(), mockStackTraceInterface); + interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockStackTraceInterface); - assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/StackTrace2.json"))); + assertThat(jsonGeneratorParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/StackTrace2.json"))); } @Test public void testFramesCommonWithEnclosingDisabled() throws Exception { - final JsonGeneratorTool generatorTool = newJsonGenerator(); + final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final StackTraceElement stackTraceElement = new StackTraceElement("", "", null, 0); new NonStrictExpectations() {{ mockStackTraceInterface.getStackTrace(); @@ -62,8 +62,8 @@ public void testFramesCommonWithEnclosingDisabled() throws Exception { }}; interfaceBinding.setRemoveCommonFramesWithEnclosing(false); - interfaceBinding.writeInterface(generatorTool.generator(), mockStackTraceInterface); + interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockStackTraceInterface); - assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/StackTrace3.json"))); + assertThat(jsonGeneratorParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/StackTrace3.json"))); } } diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/UserInterfaceBindingTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/UserInterfaceBindingTest.java index 677da29926e..7696d471712 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/UserInterfaceBindingTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/UserInterfaceBindingTest.java @@ -4,22 +4,23 @@ import mockit.NonStrictExpectations; import mockit.Tested; import net.kencochrane.raven.event.interfaces.UserInterface; +import net.kencochrane.raven.marshaller.json.JsonComparisonUtil.JsonGeneratorParser; import org.testng.annotations.Test; -import static net.kencochrane.raven.marshaller.json.JsonTestTool.jsonResource; -import static net.kencochrane.raven.marshaller.json.JsonTestTool.newJsonGenerator; +import static net.kencochrane.raven.marshaller.json.JsonComparisonUtil.jsonResource; +import static net.kencochrane.raven.marshaller.json.JsonComparisonUtil.newJsonGenerator; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; public class UserInterfaceBindingTest { @Tested - private UserInterfaceBinding userInterfaceBinding; + private UserInterfaceBinding userInterfaceBinding = null; @Injectable private UserInterface mockUserInterface = null; @Test public void testSimpleMessage() throws Exception { - final JsonTestTool.JsonGeneratorTool generatorTool = newJsonGenerator(); + final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final String id = "970e9df6-6e0b-4a27-b2ee-0faf0f368354"; final String username = "3eaa555a-e813-4778-9852-7c1880bf0fd7"; final String email = "9bcade34-a58c-4616-9de7-bc8b456c96de"; @@ -35,8 +36,8 @@ public void testSimpleMessage() throws Exception { result = ipAddress; }}; - userInterfaceBinding.writeInterface(generatorTool.generator(), mockUserInterface); + userInterfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockUserInterface); - assertThat(generatorTool.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/User1.json"))); + assertThat(jsonGeneratorParser.value(), is(jsonResource("/net/kencochrane/raven/marshaller/json/User1.json"))); } } From 6ff699f6a71f8cdfa0d0da9a510b28edbe7787f2 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 6 Oct 2014 07:13:20 +1100 Subject: [PATCH 1315/2152] Fix api calls --- .../main/java/net/kencochrane/raven/jul/SentryHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index 92bc3d2cbd1..dcae57f59ba 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -195,9 +195,9 @@ protected Event buildEvent(LogRecord record) { if (mdc != null) { for (Map.Entry mdcEntry : mdc.entrySet()) { if (extraTags.contains(mdcEntry.getKey())) { - eventBuilder.addTag(mdcEntry.getKey(), mdcEntry.getValue()); + eventBuilder.withTag(mdcEntry.getKey(), mdcEntry.getValue()); } else { - eventBuilder.addExtra(mdcEntry.getKey(), mdcEntry.getValue()); + eventBuilder.withExtra(mdcEntry.getKey(), mdcEntry.getValue()); } } } From eee7c81176ebc6a76757fc5c812d5d64f21de9d7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 6 Oct 2014 07:20:35 +1100 Subject: [PATCH 1316/2152] Update documentation --- raven-log4j/README.md | 4 +++- raven-log4j2/README.md | 21 +++++++++++++++++++++ raven-logback/README.md | 19 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 93604c59071..d2b1b1b09f4 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -83,7 +83,9 @@ public class MyClass { ``` ### Mapped Tags -By default all MDC parameters are sent under the Additional Data Tab. By specifiy the mappedTags paramter in your properties file. You can specify MDC keys to send as tags in addition to including them in Additional Data. This allows them to be filtered within Sentry. +By default all MDC parameters are sent under the Additional Data Tab. By specify the mappedTags parameter in your +configuration file. You can specify MDC keys to send as tags instead of including them in Additional Data. +This allows them to be filtered within Sentry. ```properties log4j.appender.SentryAppender.mappedTags=User,OS diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index 1130eeedbc0..d763402ec36 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -68,6 +68,27 @@ which will add a tag `log4j2-Marker`. Both [the MDC and the NDC systems provided by Log4j 2](https://logging.apache.org/log4j/2.x/manual/thread-context.html) are usable, allowing to attach extras information to the event. +### Mapped Tags +By default all MDC parameters are sent under the Additional Data Tab. By specify the mappedTags parameter in your +configuration file. You can specify MDC keys to send as tags instead of including them in Additional Data. +This allows them to be filtered within Sentry. + +```xml + + User,OS + +``` +```java + void logWithExtras() { + // MDC extras + MDC.put("User", "test user"); + MDC.put("OS", "Linux"); + + // This adds a message with extras and MDC keys declared in mappedTags as tags to Sentry + logger.info("This is a test"); + } +``` + ### In practice ```java import org.apache.logging.log4j.LogManager; diff --git a/raven-logback/README.md b/raven-logback/README.md index a8878364a90..de23f4ce41e 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -50,6 +50,25 @@ add a tag `logback-Marker`. [The MDC system provided by logback](http://logback.qos.ch/manual/mdc.html) allows to add extra information to the event. +### Mapped Tags +By default all MDC parameters are sent under the Additional Data Tab. By specify the mappedTags parameter in your +configuration file. You can specify MDC keys to send as tags instead of including them in Additional Data. +This allows them to be filtered within Sentry. + +```xml +User,OS +``` +```java + void logWithExtras() { + // MDC extras + MDC.put("User", "test user"); + MDC.put("OS", "Linux"); + + // This adds a message with extras and MDC keys declared in mappedTags as tags to Sentry + logger.info("This is a test"); + } +``` + ### In practice ```java import org.slf4j.Logger; From 3191cbb93be90541ed5b7b053eafaef3db3a2220 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 8 Oct 2014 08:09:51 +1100 Subject: [PATCH 1317/2152] Fix style and indentation --- .../kencochrane/raven/log4j/SentryAppender.java | 2 -- .../net/kencochrane/raven/jul/SentryHandler.java | 15 +++++++-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index 959499ce2ad..e50b4d87419 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -58,7 +58,6 @@ public class SentryAppender extends AppenderSkeleton { * List of tags to look for in the MDC. These will be added as tags to be sent to sentry. *

    * Might be empty in which case no mapped tags are set. - *

    */ private Set extraTags = Collections.emptySet(); @@ -66,7 +65,6 @@ public class SentryAppender extends AppenderSkeleton { * List of tags to look for in log4j's MDC. These will be added as tags to be sent to sentry. *

    * Might be empty in which case no mapped tags are set. - *

    */ protected List mappedTags = Collections.emptyList(); diff --git a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java index dcae57f59ba..8d0bacb4ea2 100644 --- a/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java +++ b/raven/src/main/java/net/kencochrane/raven/jul/SentryHandler.java @@ -47,21 +47,20 @@ public class SentryHandler extends Handler { */ protected Map tags = Collections.emptyMap(); - /** - * Creates an instance of SentryHandler. - */ - public SentryHandler() { - retrieveProperties(); - } - /** * Set of tags to look for in the MDC. These will be added as tags to be sent to sentry. *

    * Might be empty in which case no mapped tags are set. - *

    */ private Set extraTags = Collections.emptySet(); + /** + * Creates an instance of SentryHandler. + */ + public SentryHandler() { + retrieveProperties(); + } + /** * Creates an instance of SentryHandler. * From a9c25c4a7791d2b8d24ad35fa506fe972009c9a8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Wed, 8 Oct 2014 08:25:50 +1100 Subject: [PATCH 1318/2152] Make extraTags protected not private --- .../main/java/net/kencochrane/raven/log4j/SentryAppender.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index e50b4d87419..b00f03ffb30 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -59,7 +59,7 @@ public class SentryAppender extends AppenderSkeleton { *

    * Might be empty in which case no mapped tags are set. */ - private Set extraTags = Collections.emptySet(); + protected Set extraTags = Collections.emptySet(); /** * List of tags to look for in log4j's MDC. These will be added as tags to be sent to sentry. From 17773b5daf357530fe4dbaa1645617f4ed061ec3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Nov 2014 10:55:56 +1100 Subject: [PATCH 1319/2152] Ensure that exceptions are serializable --- .../kencochrane/raven/event/interfaces/SentryException.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java index 81e538ea94c..5e9d805e36c 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java +++ b/raven/src/main/java/net/kencochrane/raven/event/interfaces/SentryException.java @@ -1,5 +1,6 @@ package net.kencochrane.raven.event.interfaces; +import java.io.Serializable; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; @@ -8,7 +9,7 @@ /** * Class associating a Sentry exception to its {@link StackTraceInterface}. */ -public final class SentryException { +public final class SentryException implements Serializable { /** * Name used when the class' package is the default one. */ From 4bf3c584ef3363495479d5711b8029e42a669be1 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Nov 2014 12:11:27 +1100 Subject: [PATCH 1320/2152] Ensure that events are fully serializable --- .../net/kencochrane/raven/event/Event.java | 41 ++++++++++++++++++- .../kencochrane/raven/event/EventTest.java | 26 ++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/event/Event.java b/raven/src/main/java/net/kencochrane/raven/event/Event.java index bb9aa1cb732..27f73c4113d 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/Event.java +++ b/raven/src/main/java/net/kencochrane/raven/event/Event.java @@ -2,6 +2,9 @@ import net.kencochrane.raven.event.interfaces.SentryInterface; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Date; import java.util.HashMap; @@ -67,8 +70,11 @@ public class Event implements Serializable { * A map or list of additional properties for this event. *

    * Automatically created with a Map that is made unmodifiable by the {@link EventBuilder}. + *

    + * This transient map may contain objects which aren't serializable. They will be automatically be taken care of + * by {@link #readObject(ObjectInputStream)} and {@link #writeObject(ObjectOutputStream)}. */ - private Map extra = new HashMap<>(); + private transient Map extra = new HashMap<>(); /** * Checksum for the event, allowing to group events with a similar checksum. */ @@ -183,6 +189,39 @@ void setSentryInterfaces(Map sentryInterfaces) { this.sentryInterfaces = sentryInterfaces; } + @SuppressWarnings("unchecked") + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + extra = (Map) stream.readObject(); + } + + private void writeObject(ObjectOutputStream stream) + throws IOException { + stream.defaultWriteObject(); + stream.writeObject(convertToSerializable(extra)); + } + + /** + * Returns a serializable Map (HashMap) with the content of the parameter Map. + *

    + * Serializable objects are kept as is in the Map, while the non serializable ones are converted into string + * using the {@code toString()} method. + * + * @param objectMap original Map containing various Objects. + * @return A serializable map which contains only serializable entries. + */ + private static HashMap convertToSerializable(Map objectMap) { + HashMap serializableMap = new HashMap<>(objectMap.size()); + for (Map.Entry objectEntry : objectMap.entrySet()) { + if (objectEntry.getValue() instanceof Serializable) + serializableMap.put(objectEntry.getKey(), (Serializable) objectEntry.getValue()); + else + serializableMap.put(objectEntry.getKey(), objectEntry.getValue().toString()); + } + return serializableMap; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/raven/src/test/java/net/kencochrane/raven/event/EventTest.java b/raven/src/test/java/net/kencochrane/raven/event/EventTest.java index 41987105c46..f5a942c4e76 100644 --- a/raven/src/test/java/net/kencochrane/raven/event/EventTest.java +++ b/raven/src/test/java/net/kencochrane/raven/event/EventTest.java @@ -2,12 +2,15 @@ import mockit.Injectable; import mockit.NonStrictExpectations; +import org.hamcrest.Matchers; import org.testng.annotations.Test; +import java.io.*; import java.util.Date; import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.sameInstance; @@ -32,4 +35,27 @@ public void returnsCloneOfTimestamp(@Injectable final Date mockTimestamp, assertThat(event.getTimestamp(), is(sameInstance(mockCloneTimestamp))); } + + @Test + public void serializedEventContainsSerializableExtras(@Injectable final Object nonSerializableObject) + throws Exception { + final Event event = new Event(UUID.fromString("fb3fe928-69af-41a5-b76b-1db4c324caf6")); + new NonStrictExpectations() {{ + nonSerializableObject.toString(); + result = "3c644639-9721-4e32-8cc8-a2b5b77f4424"; + }}; + event.getExtra().put("SerializableEntry", 38295L); + event.getExtra().put("NonSerializableEntry", nonSerializableObject); + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); + objectOutputStream.writeObject(event); + ObjectInputStream is = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); + Event receivedEvent = (Event) is.readObject(); + + assertThat(receivedEvent.getId(), equalTo(event.getId())); + assertThat(receivedEvent.getExtra().get("SerializableEntry"), Matchers.equalTo(38295L)); + assertThat(receivedEvent.getExtra().get("NonSerializableEntry"), + Matchers.equalTo("3c644639-9721-4e32-8cc8-a2b5b77f4424")); + } } From e7733a7cac32ed84f71ca9180454c0edd7f79274 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Nov 2014 17:46:13 +1100 Subject: [PATCH 1321/2152] Disable checkstyle when we need to return a HashMap --- raven/src/main/java/net/kencochrane/raven/event/Event.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/event/Event.java b/raven/src/main/java/net/kencochrane/raven/event/Event.java index 27f73c4113d..58e90e83e15 100644 --- a/raven/src/main/java/net/kencochrane/raven/event/Event.java +++ b/raven/src/main/java/net/kencochrane/raven/event/Event.java @@ -211,6 +211,7 @@ private void writeObject(ObjectOutputStream stream) * @param objectMap original Map containing various Objects. * @return A serializable map which contains only serializable entries. */ + //CHECKSTYLE.OFF: IllegalType private static HashMap convertToSerializable(Map objectMap) { HashMap serializableMap = new HashMap<>(objectMap.size()); for (Map.Entry objectEntry : objectMap.entrySet()) { @@ -221,6 +222,7 @@ private void writeObject(ObjectOutputStream stream) } return serializableMap; } + //CHECKSTYLE.ON: IllegalType @Override public boolean equals(Object o) { From d9f24d794d6c0ec12974d411455acc5e21c2fdd5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 2 Nov 2014 18:42:52 +1100 Subject: [PATCH 1322/2152] Do not mock during serialization/deserialization (duh) --- .../AppEngineAsyncConnectionTest.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java index 86a1b8f1f0e..7312dc40e9f 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java @@ -4,6 +4,7 @@ import mockit.*; import net.kencochrane.raven.connection.Connection; import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.event.EventBuilder; import org.hamcrest.Matchers; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -74,10 +75,10 @@ public void testUnregisterInstance( } @Test - public void testSendEventQueued(@Injectable final Event mockEvent, @Injectable UUID mockUuid) throws Exception { - setField(mockEvent, "id", mockUuid); + public void testSendEventQueued() throws Exception { + final Event event = new EventBuilder().build(); - asyncConnection.send(mockEvent); + asyncConnection.send(event); new Verifications() {{ TaskOptions taskOptions; @@ -85,14 +86,15 @@ public void testSendEventQueued(@Injectable final Event mockEvent, @Injectable U mockQueue.add(taskOptions = withCapture()); deferredTask = extractDeferredTask(taskOptions); - assertThat(getField(deferredTask, "event"), Matchers.equalTo(mockEvent)); + assertThat(getField(deferredTask, "event"), Matchers.equalTo(event)); }}; } @Test - public void testQueuedEventSubmitted(@Injectable final Event mockEvent, - @SuppressWarnings("unused") @Mocked("setDoNotRetry") DeferredTaskContext deferredTaskContext) + public void testQueuedEventSubmitted(@SuppressWarnings("unused") + @Mocked("setDoNotRetry") DeferredTaskContext deferredTaskContext) throws Exception { + final Event event = new EventBuilder().build(); new NonStrictExpectations() {{ mockQueue.add((TaskOptions) any); result = new Delegate() { @@ -108,7 +110,7 @@ TaskHandle add(TaskOptions taskOptions) { }; }}; - asyncConnection.send(mockEvent); + asyncConnection.send(event); new Verifications() {{ DeferredTaskContext.setDoNotRetry(true); @@ -118,12 +120,12 @@ TaskHandle add(TaskOptions taskOptions) { @Test public void testEventLinkedToCorrectConnection( - @Injectable("eb37bfe4-7316-47e8-94e4-073aefd0fbf8") final String mockConnectionId, - @Injectable final Event mockEvent) throws Exception { + @Injectable("eb37bfe4-7316-47e8-94e4-073aefd0fbf8") final String mockConnectionId) throws Exception { final AppEngineAsyncConnection asyncConnection2 = new AppEngineAsyncConnection(mockConnectionId, mockConnection); + final Event event = new EventBuilder().build(); - asyncConnection.send(mockEvent); - asyncConnection2.send(mockEvent); + asyncConnection.send(event); + asyncConnection2.send(event); new Verifications() {{ List taskOptionsList = new ArrayList<>(); From cad6da547121cb486fb2b5d0b93b4fb6525c5580 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 16 Nov 2014 10:09:06 +1100 Subject: [PATCH 1323/2152] Default to a noop connection if nothing is available --- .../raven/DefaultRavenFactory.java | 8 +++---- .../raven/connection/NoopConnection.java | 22 +++++++++++++++++++ .../java/net/kencochrane/raven/dsn/Dsn.java | 5 +++++ 3 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 raven/src/main/java/net/kencochrane/raven/connection/NoopConnection.java diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index abac32ebe7b..6db0b1444d9 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -1,9 +1,6 @@ package net.kencochrane.raven; -import net.kencochrane.raven.connection.AsyncConnection; -import net.kencochrane.raven.connection.Connection; -import net.kencochrane.raven.connection.HttpConnection; -import net.kencochrane.raven.connection.UdpConnection; +import net.kencochrane.raven.connection.*; import net.kencochrane.raven.dsn.Dsn; import net.kencochrane.raven.event.helper.HttpEventBuilderHelper; import net.kencochrane.raven.event.interfaces.*; @@ -93,6 +90,9 @@ protected Connection createConnection(Dsn dsn) { } else if (protocol.equalsIgnoreCase("udp")) { logger.info("Using an UDP connection to Sentry."); connection = createUdpConnection(dsn); + } else if (protocol.equalsIgnoreCase("noop")) { + logger.info("Using noop to send events."); + connection = new NoopConnection(); } else { throw new IllegalStateException("Couldn't create a connection for the protocol '" + protocol + "'"); } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/NoopConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/NoopConnection.java new file mode 100644 index 00000000000..729e6ee7dcf --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/connection/NoopConnection.java @@ -0,0 +1,22 @@ +package net.kencochrane.raven.connection; + +import com.google.common.base.Charsets; +import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.marshaller.Marshaller; + +import java.io.IOException; +import java.io.OutputStream; + +public class NoopConnection extends AbstractConnection { + public NoopConnection() { + super(null, null); + } + + @Override + protected void doSend(Event event) throws ConnectionException { + } + + @Override + public void close() throws IOException { + } +} diff --git a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java index dad0db54285..8555f6437cb 100644 --- a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java @@ -101,6 +101,11 @@ public static String dsnLookup() { if (dsn == null) dsn = System.getProperty(DSN_VARIABLE); + if (dsn == null) { + logger.warn("Couldn't find a suitable DSN, defaulting to a Noop one."); + dsn = "noop://user:password@localhost:0/0"; + } + return dsn; } From 2673979d82f83c187595a0f8ea9c0cb4eee45414 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 16 Nov 2014 10:10:20 +1100 Subject: [PATCH 1324/2152] Add a "out" connection that sends content on stdout --- .../raven/DefaultRavenFactory.java | 15 ++++++ .../connection/OutputStreamConnection.java | 39 +++++++++++++++ .../OutputStreamConnectionTest.java | 48 +++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 raven/src/main/java/net/kencochrane/raven/connection/OutputStreamConnection.java create mode 100644 raven/src/test/java/net/kencochrane/raven/connection/OutputStreamConnectionTest.java diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index 6db0b1444d9..c306cd3e17d 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -90,6 +90,9 @@ protected Connection createConnection(Dsn dsn) { } else if (protocol.equalsIgnoreCase("udp")) { logger.info("Using an UDP connection to Sentry."); connection = createUdpConnection(dsn); + } else if (protocol.equalsIgnoreCase("out")) { + logger.info("Using StdOut to send events."); + connection = createStdOutConnection(dsn); } else if (protocol.equalsIgnoreCase("noop")) { logger.info("Using noop to send events."); connection = new NoopConnection(); @@ -177,6 +180,18 @@ protected Connection createUdpConnection(Dsn dsn) { return udpConnection; } + /** + * Uses stdout to send the logs. + * + * @param dsn Data Source Name of the Sentry server. + * @return an {@link OutputStreamConnection} using {@code System.out}. + */ + protected Connection createStdOutConnection(Dsn dsn) { + OutputStreamConnection stdOutConnection = new OutputStreamConnection(System.out); + stdOutConnection.setMarshaller(createMarshaller(dsn)); + return stdOutConnection; + } + /** * Creates a JSON marshaller that will convert every {@link net.kencochrane.raven.event.Event} in a format * handled by the Sentry server. diff --git a/raven/src/main/java/net/kencochrane/raven/connection/OutputStreamConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/OutputStreamConnection.java new file mode 100644 index 00000000000..4cf9bfacbdf --- /dev/null +++ b/raven/src/main/java/net/kencochrane/raven/connection/OutputStreamConnection.java @@ -0,0 +1,39 @@ +package net.kencochrane.raven.connection; + +import com.google.common.base.Charsets; +import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.marshaller.Marshaller; + +import java.io.IOException; +import java.io.OutputStream; + +public class OutputStreamConnection extends AbstractConnection { + private final OutputStream outputStream; + private Marshaller marshaller; + + public OutputStreamConnection(OutputStream outputStream) { + super(null, null); + this.outputStream = outputStream; + } + + @Override + protected synchronized void doSend(Event event) throws ConnectionException { + try { + outputStream.write("Raven event:\n".getBytes(Charsets.UTF_8)); + marshaller.marshall(event, outputStream); + outputStream.write("\n".getBytes(Charsets.UTF_8)); + outputStream.flush(); + } catch (IOException e) { + throw new ConnectionException("Couldn't sent the event properly", e); + } + } + + @Override + public void close() throws IOException { + outputStream.close(); + } + + public void setMarshaller(Marshaller marshaller) { + this.marshaller = marshaller; + } +} diff --git a/raven/src/test/java/net/kencochrane/raven/connection/OutputStreamConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/OutputStreamConnectionTest.java new file mode 100644 index 00000000000..da3c5ca3fd8 --- /dev/null +++ b/raven/src/test/java/net/kencochrane/raven/connection/OutputStreamConnectionTest.java @@ -0,0 +1,48 @@ +package net.kencochrane.raven.connection; + +import mockit.*; +import net.kencochrane.raven.environment.RavenEnvironment; +import net.kencochrane.raven.event.Event; +import net.kencochrane.raven.marshaller.Marshaller; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSession; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.URL; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class OutputStreamConnectionTest { + @Tested + private OutputStreamConnection outputStreamConnection = null; + @Injectable + private Marshaller mockMarshaller = null; + @Injectable + private OutputStream mockOutputStream = null; + + @Test + public void testContentMarshalled(@Injectable final Event mockEvent) throws Exception { + outputStreamConnection.send(mockEvent); + + new Verifications() {{ + mockMarshaller.marshall(mockEvent, mockOutputStream); + }}; + } + + @Test + public void testClose() throws Exception { + outputStreamConnection.close(); + + new Verifications() {{ + mockOutputStream.close(); + }}; + } +} From b35c30ca5d9c863574a54f1d16d07045e4dd1ec3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 16 Nov 2014 10:22:02 +1100 Subject: [PATCH 1325/2152] Fix test ensuring that a default value is provided for a DSN --- raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java index 75d9ead6abc..05de0354825 100644 --- a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java @@ -57,7 +57,7 @@ public void testSimpleDsnFromValidURI() throws Exception { @Test public void testDsnLookupWithNothingSet() throws Exception { - assertThat(Dsn.dsnLookup(), is(nullValue())); + assertThat(Dsn.dsnLookup(), is("noop://user:password@localhost:0/0")); } @Test From 1fc4d1507ba3010403d008c3d345ec5a514f0f27 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 16 Nov 2014 10:55:38 +1100 Subject: [PATCH 1326/2152] Ignore style issue with System.out --- .../main/java/net/kencochrane/raven/DefaultRavenFactory.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index c306cd3e17d..ce224eec4ad 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -187,7 +187,9 @@ protected Connection createUdpConnection(Dsn dsn) { * @return an {@link OutputStreamConnection} using {@code System.out}. */ protected Connection createStdOutConnection(Dsn dsn) { + //CHECKSTYLE.OFF: RegexpSinglelineJava OutputStreamConnection stdOutConnection = new OutputStreamConnection(System.out); + //CHECKSTYLE.ON: RegexpSinglelineJava stdOutConnection.setMarshaller(createMarshaller(dsn)); return stdOutConnection; } From 1c0753067f0270c7109a10bd9ff3e733ba6eab4a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 16 Nov 2014 10:56:11 +1100 Subject: [PATCH 1327/2152] Comment, fix style issues and tests --- .../net/kencochrane/raven/connection/NoopConnection.java | 8 +++++--- .../raven/connection/OutputStreamConnection.java | 3 +++ raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java | 6 +++++- .../src/test/java/net/kencochrane/raven/dsn/DsnTest.java | 6 +++--- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/NoopConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/NoopConnection.java index 729e6ee7dcf..0941fdec9b3 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/NoopConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/NoopConnection.java @@ -1,12 +1,14 @@ package net.kencochrane.raven.connection; -import com.google.common.base.Charsets; import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.marshaller.Marshaller; import java.io.IOException; -import java.io.OutputStream; +/** + * Connection that drops events. + * + * Only use it when no DSN is set. + */ public class NoopConnection extends AbstractConnection { public NoopConnection() { super(null, null); diff --git a/raven/src/main/java/net/kencochrane/raven/connection/OutputStreamConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/OutputStreamConnection.java index 4cf9bfacbdf..880a62159be 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/OutputStreamConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/OutputStreamConnection.java @@ -7,6 +7,9 @@ import java.io.IOException; import java.io.OutputStream; +/** + * Connection using StdOut to sent marshalled events. + */ public class OutputStreamConnection extends AbstractConnection { private final OutputStream outputStream; private Marshaller marshaller; diff --git a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java index 8555f6437cb..2a6a8030b37 100644 --- a/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java +++ b/raven/src/main/java/net/kencochrane/raven/dsn/Dsn.java @@ -17,6 +17,10 @@ public class Dsn { * Name of the environment and system variables containing the DSN. */ public static final String DSN_VARIABLE = "SENTRY_DSN"; + /** + * Default DSN to use when auto detection fails. + */ + public static final String DEFAULT_DSN = "noop://user:password@localhost:0/0"; private static final Logger logger = LoggerFactory.getLogger(Dsn.class); private String secretKey; private String publicKey; @@ -103,7 +107,7 @@ public static String dsnLookup() { if (dsn == null) { logger.warn("Couldn't find a suitable DSN, defaulting to a Noop one."); - dsn = "noop://user:password@localhost:0/0"; + dsn = DEFAULT_DSN; } return dsn; diff --git a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java index 05de0354825..201b915fc76 100644 --- a/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java +++ b/raven/src/test/java/net/kencochrane/raven/dsn/DsnTest.java @@ -57,7 +57,7 @@ public void testSimpleDsnFromValidURI() throws Exception { @Test public void testDsnLookupWithNothingSet() throws Exception { - assertThat(Dsn.dsnLookup(), is("noop://user:password@localhost:0/0")); + assertThat(Dsn.dsnLookup(), is(Dsn.DEFAULT_DSN)); } @Test @@ -68,7 +68,7 @@ public void testJndiLookupFailsWithException( result = new ClassNotFoundException("Couldn't find the JNDI classes"); }}; - assertThat(Dsn.dsnLookup(), is(nullValue())); + assertThat(Dsn.dsnLookup(), is(Dsn.DEFAULT_DSN)); } @Test @@ -79,7 +79,7 @@ public void testJndiLookupFailsWithError( result = new NoClassDefFoundError("Couldn't find the JNDI classes"); }}; - assertThat(Dsn.dsnLookup(), is(nullValue())); + assertThat(Dsn.dsnLookup(), is(Dsn.DEFAULT_DSN)); } @Test From ddfdd67a5386a9ad5c20344683566856f9671b03 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 16 Nov 2014 10:58:38 +1100 Subject: [PATCH 1328/2152] More comments --- .../net/kencochrane/raven/connection/NoopConnection.java | 3 +++ .../kencochrane/raven/connection/OutputStreamConnection.java | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/NoopConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/NoopConnection.java index 0941fdec9b3..736e705378c 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/NoopConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/NoopConnection.java @@ -10,6 +10,9 @@ * Only use it when no DSN is set. */ public class NoopConnection extends AbstractConnection { + /** + * Creates a connection that drops events + */ public NoopConnection() { super(null, null); } diff --git a/raven/src/main/java/net/kencochrane/raven/connection/OutputStreamConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/OutputStreamConnection.java index 880a62159be..754150edc05 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/OutputStreamConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/OutputStreamConnection.java @@ -14,6 +14,11 @@ public class OutputStreamConnection extends AbstractConnection { private final OutputStream outputStream; private Marshaller marshaller; + /** + * Creates a connection that sends event on an outputStream. + * + * @param outputStream stream on which the events are submitted. + */ public OutputStreamConnection(OutputStream outputStream) { super(null, null); this.outputStream = outputStream; From fff734d884d13237aa609246f0239df1d51a2546 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 16 Nov 2014 11:08:16 +1100 Subject: [PATCH 1329/2152] Add missing dot --- .../java/net/kencochrane/raven/connection/NoopConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/NoopConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/NoopConnection.java index 736e705378c..eb5c315dae1 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/NoopConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/NoopConnection.java @@ -11,7 +11,7 @@ */ public class NoopConnection extends AbstractConnection { /** - * Creates a connection that drops events + * Creates a connection that drops events. */ public NoopConnection() { super(null, null); From 0fd7636fc1647a38995938e48f30b746c27aae61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=88mer=20Yildiz?= Date: Wed, 19 Nov 2014 11:18:55 +0100 Subject: [PATCH 1330/2152] use correct constant --- .../net/kencochrane/raven/appengine/AppEngineRavenFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java index c11b0e08e76..0a275e00498 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java +++ b/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java @@ -47,7 +47,7 @@ public Raven createRavenInstance(Dsn dsn) { @Override protected Connection createAsyncConnection(Dsn dsn, Connection connection) { String connectionIdentifier; - if (dsn.getOptions().containsKey(QUEUE_NAME)) { + if (dsn.getOptions().containsKey(CONNECTION_IDENTIFIER)) { connectionIdentifier = dsn.getOptions().get(CONNECTION_IDENTIFIER); } else { connectionIdentifier = AppEngineRavenFactory.class.getCanonicalName() + dsn + SystemProperty.version.get(); From af1f0b3778b499bf45167b874eb1ecb95914544e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 3 Jan 2015 18:56:36 +1100 Subject: [PATCH 1331/2152] Remove broken call to initRaven --- .../raven/logback/SentryAppenderEventBuildingTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java index c2632acbd0a..a3f8331faee 100644 --- a/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java +++ b/raven-logback/src/test/java/net/kencochrane/raven/logback/SentryAppenderEventBuildingTest.java @@ -42,7 +42,6 @@ public void setUp() throws Exception { sentryAppender = new SentryAppender(mockRaven); sentryAppender.setContext(mockContext); sentryAppender.setExtraTags(mockExtraTag); - sentryAppender.initRaven(); new NonStrictExpectations() {{ final BasicStatusManager statusManager = new BasicStatusManager(); From f50d6b9259c9bd9a98b0426eaa17d5d8263b2f73 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 3 Jan 2015 18:59:23 +1100 Subject: [PATCH 1332/2152] Update dependencies --- pom.xml | 6 +++--- raven-appengine/pom.xml | 2 +- raven-log4j2/README.md | 6 +++--- raven-log4j2/pom.xml | 2 +- raven/README.md | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 4b37ab31ba0..565b9e21684 100644 --- a/pom.xml +++ b/pom.xml @@ -106,11 +106,11 @@ 7 - 1.7.7 + 1.7.9 18.0 - 2.4.2 + 2.5.0 1.10 - 6.8.8 + 6.8.13 1.3 diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 2bba6c9f81c..1d4e5ab14ee 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -14,7 +14,7 @@ Raven module allowing to use GoogleApp Engine and its task queueing system. - 1.9.12 + 1.9.17a 1.1.2 diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index d763402ec36..ce8859ec823 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -21,9 +21,9 @@ Details in the [central Maven repository](https://search.maven.org/#artifactdeta Relies on: - [raven dependencies](../raven) - - [log4j-api-2.0-rc1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7C2.0-rc1%7Cjar) - - [log4j-core-2.0-rc1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7C2.0-rc1%7Cjar) - - [log4j-slf4j-impl-2.0-rc1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7C2.0-rc1%7Cjar) + - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7C2.1%7Cjar) + - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7C2.1%7Cjar) + - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7C2.1%7Cjar) is recommended as the implementation of slf4j (instead of slf4j-jdk14). diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 7cb7374ebb5..2be818a7aae 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -15,7 +15,7 @@ log4j2 appender allowing to send logs to the Raven-Java client. - 2.0.2 + 2.1 diff --git a/raven/README.md b/raven/README.md index a0590302ef3..5876a9f47d0 100644 --- a/raven/README.md +++ b/raven/README.md @@ -21,9 +21,9 @@ Details in the [central Maven repository](https://search.maven.org/#artifactdeta Relies on: - [guava-18.0.jar](https://search.maven.org/#artifactdetails%7Ccom.google.guava%7Cguava%7C18.0%7Cjar) - - [jackson-core-2.4.0.jar](https://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.4.0%7Cjar) - - [slf4j-api-1.7.7.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.7%7Cjar) - - [slf4j-jdk14-1.7.7.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-jdk14%7C1.7.7%7Cjar) + - [jackson-core-2.5.0.jar](https://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.5.0%7Cjar) + - [slf4j-api-1.7.9.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.9%7Cjar) + - [slf4j-jdk14-1.7.9.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-jdk14%7C1.7.9%7Cjar) is recommended as the implementation of slf4j to capture the log events generated by Raven (connection errors, ...) if `java.util.logging` is used. From c8915fc7af3ef8420ae37b16add6a64e39ae1fc5 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 3 Jan 2015 19:11:51 +1100 Subject: [PATCH 1333/2152] Update maven plugins --- pom.xml | 30 +++++++++++++++--------------- raven/pom.xml | 1 - 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/pom.xml b/pom.xml index 565b9e21684..6dcd1ee9c51 100644 --- a/pom.xml +++ b/pom.xml @@ -225,7 +225,7 @@ org.codehaus.mojo animal-sniffer-maven-plugin - 1.11 + 1.13 org.codehaus.mojo.signature @@ -249,7 +249,7 @@ org.eclipse.jetty jetty-maven-plugin - 9.2.3.v20140905 + 9.3.0.M1 10 foo @@ -281,12 +281,12 @@ org.apache.maven.plugins maven-surefire-plugin - 2.17 + 2.18.1 org.apache.maven.plugins maven-failsafe-plugin - 2.17 + 2.18.1 integration-tests @@ -366,11 +366,11 @@ maven-clean-plugin - 2.6 + 2.6.1 maven-compiler-plugin - 3.1 + 3.2 maven-deploy-plugin @@ -382,11 +382,11 @@ maven-jar-plugin - 2.5 + 2.6 maven-resources-plugin - 2.6 + 2.7 maven-site-plugin @@ -394,7 +394,7 @@ maven-war-plugin - 2.4 + 2.5 @@ -410,12 +410,12 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.10 + 2.10.1 org.apache.maven.plugins maven-surefire-report-plugin - 2.17 + 2.18.1 org.apache.maven.plugins @@ -428,7 +428,7 @@ org.apache.maven.plugins maven-jxr-plugin - 2.4 + 2.5 @@ -442,7 +442,7 @@ org.apache.maven.plugins maven-source-plugin - 2.3 + 2.4 attach-sources @@ -482,7 +482,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.4 + 1.6.5 true sonatype-nexus-staging @@ -513,7 +513,7 @@ org.pitest pitest-maven - 1.1.0 + 1.1.3 *Test diff --git a/raven/pom.xml b/raven/pom.xml index 589b91bea56..68152cb876f 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -112,7 +112,6 @@ org.apache.maven.plugins maven-jar-plugin - 2.4 From 7492e01e7ea05ded6188d26e1e8c7c85a0bf4e15 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 3 Jan 2015 19:50:44 +1100 Subject: [PATCH 1334/2152] Simplify verifications for AppEngine --- .../AppEngineEventBuilderHelperTest.java | 24 ++----------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java index 0e6a28d6d20..8b791f20258 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java +++ b/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java @@ -71,17 +71,7 @@ public void ensureApplicationVersionIsAddedAsTag( eventBuilderHelper.helpBuildingEvent(mockEventBuilder); new Verifications() {{ - List tagNames = new LinkedList<>(); - List tagValues = new LinkedList<>(); - mockEventBuilder.withTag(withCapture(tagNames), withCapture(tagValues)); - - Map tags = new HashMap<>(); - for (int i = 0; i < tagNames.size(); i++) { - String tagName = tagNames.get(i); - tags.put(tagName, tagValues.get(i)); - } - - assertThat(tags, hasEntry("GAE Application Version", version)); + mockEventBuilder.withTag("GAE Application Version", version); }}; } @@ -96,17 +86,7 @@ public void ensureApplicationIdIsAddedAsTag( eventBuilderHelper.helpBuildingEvent(mockEventBuilder); new Verifications() {{ - List tagNames = new LinkedList<>(); - List tagValues = new LinkedList<>(); - mockEventBuilder.withTag(withCapture(tagNames), withCapture(tagValues)); - - Map tags = new HashMap<>(); - for (int i = 0; i < tagNames.size(); i++) { - String tagName = tagNames.get(i); - tags.put(tagName, tagValues.get(i)); - } - - assertThat(tags, hasEntry("GAE Application Id", applicationId)); + mockEventBuilder.withTag("GAE Application Id", applicationId); }}; } } From 78fcce29449cdfa7248e96ec4beebddfec818fd7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 3 Jan 2015 19:51:06 +1100 Subject: [PATCH 1335/2152] Revert jar-plugin to 2.5 (2.6 doesn't exist) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6dcd1ee9c51..2436d8c12d2 100644 --- a/pom.xml +++ b/pom.xml @@ -382,7 +382,7 @@ maven-jar-plugin - 2.6 + 2.5 maven-resources-plugin From da39852b9ddae8b7bf18823c49826ffc0ddf47e9 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 3 Jan 2015 19:51:32 +1100 Subject: [PATCH 1336/2152] New versions of jmockit cascade, for the result to null --- .../kencochrane/raven/marshaller/json/JsonMarshallerTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java index 472bb4dd426..2372a6a2e17 100644 --- a/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/net/kencochrane/raven/marshaller/json/JsonMarshallerTest.java @@ -37,6 +37,8 @@ public void setUp() throws Exception { result = UUID.fromString("00000000-0000-0000-0000-000000000000"); mockEvent.getTimestamp(); result = new Date(0); + mockEvent.getLevel(); + result = null; }}; } From f2611655d11df4a0fcb9e470b028027aa3f2520f Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 3 Jan 2015 19:52:44 +1100 Subject: [PATCH 1337/2152] Update jmockit to 1.14 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2436d8c12d2..aba77fc2c97 100644 --- a/pom.xml +++ b/pom.xml @@ -109,7 +109,7 @@ 1.7.9 18.0 2.5.0 - 1.10 + 1.14 6.8.13 1.3 From 69de2e2c97819a4b765e901382610a7c92975a09 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 3 Jan 2015 22:58:33 +1100 Subject: [PATCH 1338/2152] Update javadoc to 2.10.1 for reports --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index aba77fc2c97..46599beab26 100644 --- a/pom.xml +++ b/pom.xml @@ -455,7 +455,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.10 + 2.10.1 attach-javadocs From 19c06c2a680e5a67e585bd56e64f956dee4780b3 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 3 Jan 2015 22:59:49 +1100 Subject: [PATCH 1339/2152] [maven-release-plugin] prepare release v5.0.2 --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 46599beab26..9586cb41644 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 5.0.2-SNAPSHOT + 5.0.2 pom Raven-Java @@ -75,7 +75,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v5.0.2 https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 1d4e5ab14ee..01ad28f14db 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ net.kencochrane.raven raven-all - 5.0.2-SNAPSHOT + 5.0.2 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index eff99e1c2db..ce7353555a0 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.2-SNAPSHOT + 5.0.2 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 2be818a7aae..af2e0a8b4bb 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.2-SNAPSHOT + 5.0.2 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index ae6007878be..dad3fb27fdd 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.2-SNAPSHOT + 5.0.2 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 68152cb876f..1f020f0c46d 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.2-SNAPSHOT + 5.0.2 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index a6e59499e32..c93409344bc 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.2-SNAPSHOT + 5.0.2 sentry-stub From 46a7ff7501d05cef0dd39f65334bf8a0912b0269 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 3 Jan 2015 22:59:50 +1100 Subject: [PATCH 1340/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 9586cb41644..7a70690e23d 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 5.0.2 + 5.0.3-SNAPSHOT pom Raven-Java @@ -75,7 +75,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v5.0.2 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 01ad28f14db..8a462b6ba17 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ net.kencochrane.raven raven-all - 5.0.2 + 5.0.3-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index ce7353555a0..290ccb89ad5 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.2 + 5.0.3-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index af2e0a8b4bb..f3b884496ef 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.2 + 5.0.3-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index dad3fb27fdd..5cd34dac365 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.2 + 5.0.3-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 1f020f0c46d..aeb921540b0 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.2 + 5.0.3-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index c93409344bc..615ac1ac91a 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.2 + 5.0.3-SNAPSHOT sentry-stub From 5bc13317d12900cd6cfed30d89d6f41fad954c02 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 3 Jan 2015 23:42:32 +1100 Subject: [PATCH 1341/2152] Update secret keys --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8d2a5343711..5df737b102d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ cache: - $HOME/.m2 env: global: - - SONATYPE_USERNAME=JAZGbCvS - - secure: x6UWj3ICsut4u3HS0pFt3b5vxqT9gM+zFn9+3YgM01G9NPiM3yXtGCUsq3T3exajfsmH2ax2fd6vRDWv7wUIsyVCf9tJ84TRJuDFNU0Zkqo7doC6EaY2bgTrjREBpfWhDiIXtsHpU44yM/NTx9aTfW9Tm+GO4+lxymm6kb3IPpE= + - secure: "zMIHdOO4YgG/JRV7ehaETA0HVOxKgnqanHy3B9Pva4zBBi/N7egeFXFhupvFzsP87DcwJ3+2j7epZCJLz4VTHB0ipwB9MkUld1Ru01ipw0+Q8LIbpRbjOuxYFUyrWgZocU3LhE3blsx41m6DCBFDgyjLjt2Ls3ZOmW9XoFDjeGU=" + - secure: "DjtLq0ox97khmljLMUJGcEE77430uddUJFwPHxYtajQjXwz4AVQ2Wj2zTzUTLmHmxJ8Cuq4i+7LXsMApDfW2yUUlK2naWuknvSBFLOWa+HedSXodsZC3I937V4DGFB0v1gLxsOxuCCa50erB0y8Cc89hdDL8Xa1EHldKQvqOx5M=" after_success: - "{ [[ $TRAVIS_BRANCH == 'master' ]] || [[ $TRAVIS_BRANCH == raven-*.x ]]; } && { python .travis/addServer.py; mvn clean deploy --settings $HOME/.m2/mySettings.xml; };" From 35642f0b27a7960ed9c93b117df609e4b7816185 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 3 Jan 2015 23:45:43 +1100 Subject: [PATCH 1342/2152] Update documentation to reflect last version --- raven-appengine/README.md | 4 ++-- raven-log4j/README.md | 4 ++-- raven-log4j2/README.md | 4 ++-- raven-logback/README.md | 4 ++-- raven/README.md | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/raven-appengine/README.md b/raven-appengine/README.md index 51201bb91b8..5428aecabf7 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -15,12 +15,12 @@ __This module is not useful outside of Google App Engine.__ net.kencochrane.raven raven-appengine - 5.0 + 5.0.2 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-appengine%7C5.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-appengine%7C5.0.2%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j/README.md b/raven-log4j/README.md index d2b1b1b09f4..ca4fac48638 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,12 +10,12 @@ for log4j to send the logged events to Sentry. net.kencochrane.raven raven-log4j - 5.0 + 5.0.2 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j%7C5.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j%7C5.0.2%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index ce8859ec823..7e40f8b4ea7 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,12 +10,12 @@ for Log4j 2 to send the logged events to Sentry. net.kencochrane.raven raven-log4j2 - 5.0 + 5.0.2 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j2%7C5.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j2%7C5.0.2%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-logback/README.md b/raven-logback/README.md index de23f4ce41e..fe7c3762eaf 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,12 +10,12 @@ for logback to send the logged events to Sentry. net.kencochrane.raven raven-logback - 5.0 + 5.0.2 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-logback%7C5.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-logback%7C5.0.2%7Cjar). ### Manual dependency management Relies on: diff --git a/raven/README.md b/raven/README.md index 5876a9f47d0..a09a50db864 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,12 +10,12 @@ for `java.util.logging`. net.kencochrane.raven raven - 5.0 + 5.0.2 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C5.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C5.0.2%7Cjar). ### Manual dependency management Relies on: From 41dadee6b445b0401fbe095c5aa8c783186a92ad Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 4 Jan 2015 00:43:27 +1100 Subject: [PATCH 1343/2152] [maven-release-plugin] prepare release v5.0.2 --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 7a70690e23d..9586cb41644 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 5.0.3-SNAPSHOT + 5.0.2 pom Raven-Java @@ -75,7 +75,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v5.0.2 https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 8a462b6ba17..01ad28f14db 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ net.kencochrane.raven raven-all - 5.0.3-SNAPSHOT + 5.0.2 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 290ccb89ad5..ce7353555a0 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.3-SNAPSHOT + 5.0.2 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index f3b884496ef..af2e0a8b4bb 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.3-SNAPSHOT + 5.0.2 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 5cd34dac365..dad3fb27fdd 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.3-SNAPSHOT + 5.0.2 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index aeb921540b0..1f020f0c46d 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.3-SNAPSHOT + 5.0.2 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 615ac1ac91a..c93409344bc 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.3-SNAPSHOT + 5.0.2 sentry-stub From 66d365bcf0240e87082e48a164acdf17118aab03 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 4 Jan 2015 00:43:27 +1100 Subject: [PATCH 1344/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 9586cb41644..7a70690e23d 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 5.0.2 + 5.0.3-SNAPSHOT pom Raven-Java @@ -75,7 +75,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v5.0.2 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 01ad28f14db..8a462b6ba17 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ net.kencochrane.raven raven-all - 5.0.2 + 5.0.3-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index ce7353555a0..290ccb89ad5 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.2 + 5.0.3-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index af2e0a8b4bb..f3b884496ef 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.2 + 5.0.3-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index dad3fb27fdd..5cd34dac365 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.2 + 5.0.3-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 1f020f0c46d..aeb921540b0 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.2 + 5.0.3-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index c93409344bc..615ac1ac91a 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.2 + 5.0.3-SNAPSHOT sentry-stub From 7d350ea2b459b09d7338bb9123c1c10807edf0f8 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 3 Jan 2015 13:26:43 +1100 Subject: [PATCH 1345/2152] Document new version of the Sentry protocol --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c9f83545fef..fc7ec8f24ba 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,9 @@ Since Sentry 2.0, the major version of raven matches the version of the Sentry p | ------------- | ---------------- | -------------- | | Raven 2.x(old)| V2 | >= 2.0 | | Raven 3.x(old)| V3 | >= 5.1 | -| Raven 4.x | V4 | >= 6.0 | +| Raven 4.x(old)| V4 | >= 6.0 | | Raven 5.x | V5 | >= 6.4 | +| Raven 6.x | V6 | >= 7.0 | Each release of Sentry supports the last two version of the protocol From 8994cf90c0bb203198298ffa9dae79e6b5d96c14 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 3 Jan 2015 13:31:27 +1100 Subject: [PATCH 1346/2152] Update protocol to use 6 --- .../net/kencochrane/raven/connection/AbstractConnection.java | 2 +- .../net/kencochrane/raven/sentrystub/auth/AuthValidator.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java index 0ab11cfb24a..40a6c75b6c8 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AbstractConnection.java @@ -22,7 +22,7 @@ public abstract class AbstractConnection implements Connection { /** * Current sentry protocol version. */ - public static final String SENTRY_PROTOCOL_VERSION = "5"; + public static final String SENTRY_PROTOCOL_VERSION = "6"; /** * Default maximum duration for a lockdown. */ diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java index 10baabc9c36..9c2f2bd3aa2 100644 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java +++ b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/auth/AuthValidator.java @@ -12,7 +12,7 @@ */ public class AuthValidator { private static final Logger logger = Logger.getLogger(AuthValidator.class.getCanonicalName()); - private static final Collection SENTRY_PROTOCOL_VERSIONS = Arrays.asList("5"); + private static final Collection SENTRY_PROTOCOL_VERSIONS = Arrays.asList("6"); private static final String SENTRY_VERSION_PARAMETER = "Sentry sentry_version"; private static final String PUBLIC_KEY_PARAMETER = "sentry_key"; private static final String SECRET_KEY_PARAMETER = "sentry_secret"; From 5ceece67bf632932c74f093fd094a154712c42f0 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 3 Jan 2015 13:31:34 +1100 Subject: [PATCH 1347/2152] Remove support of UDP --- README.md | 15 +- .../raven/DefaultRavenFactory.java | 16 --- .../raven/connection/UdpConnection.java | 92 ------------ .../sentrystub/SentryUdpContextListener.java | 135 ------------------ 4 files changed, 1 insertion(+), 257 deletions(-) delete mode 100644 raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java delete mode 100644 sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java diff --git a/README.md b/README.md index fc7ec8f24ba..0a39333e5f7 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ While it's **strongly recommended to use one of the supported logging frameworks** to capture and send messages to Sentry, a it is possible to do so manually with the main project [raven](raven). -Raven supports both HTTP(S) and UDP as transport protocols to the Sentry +Raven supports both HTTP(S) as transport protocols to the Sentry instance. Support for [Google App Engine](https://appengine.google.com/) is provided in [raven-appengine](raven-appengine) @@ -86,8 +86,6 @@ Solutions exist for that problem: ## Connection and protocol It is possible to send events to Sentry over different protocols, depending on the security and performance requirements. -So far Sentry accepts HTTP(S) and UDP which are both fully supported by -Raven. ### HTTP The most common way send events to Sentry is through HTTP, this can be done by @@ -112,17 +110,6 @@ naive and ignore the hostname verification: naive+https://public:private@host:port/1 -### UDP -It is possible to use a DSN with the UDP protocol: - - udp://public:private@host:port/1 - -If not provided the port will default to `9001`. - -While being faster because there is no TCP and HTTP overhead, UDP doesn't wait -for a reply, and if a connection problem occurs, there will be no notification. - - ## Options It is possible to enable some options by adding data to the query string of the DSN: diff --git a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java index ce224eec4ad..472195feae2 100644 --- a/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/net/kencochrane/raven/DefaultRavenFactory.java @@ -87,9 +87,6 @@ protected Connection createConnection(Dsn dsn) { if (protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https")) { logger.info("Using an HTTP connection to Sentry."); connection = createHttpConnection(dsn); - } else if (protocol.equalsIgnoreCase("udp")) { - logger.info("Using an UDP connection to Sentry."); - connection = createUdpConnection(dsn); } else if (protocol.equalsIgnoreCase("out")) { logger.info("Using StdOut to send events."); connection = createStdOutConnection(dsn); @@ -167,19 +164,6 @@ protected Connection createHttpConnection(Dsn dsn) { return httpConnection; } - /** - * Creates an UDP connection to the Sentry server. - * - * @param dsn Data Source Name of the Sentry server. - * @return an {@link UdpConnection} to the server. - */ - protected Connection createUdpConnection(Dsn dsn) { - int port = dsn.getPort() != -1 ? dsn.getPort() : UdpConnection.DEFAULT_UDP_PORT; - UdpConnection udpConnection = new UdpConnection(dsn.getHost(), port, dsn.getPublicKey(), dsn.getSecretKey()); - udpConnection.setMarshaller(createMarshaller(dsn)); - return udpConnection; - } - /** * Uses stdout to send the logs. * diff --git a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java deleted file mode 100644 index cf8262bfbdc..00000000000 --- a/raven/src/main/java/net/kencochrane/raven/connection/UdpConnection.java +++ /dev/null @@ -1,92 +0,0 @@ -package net.kencochrane.raven.connection; - -import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.marshaller.Marshaller; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.InetSocketAddress; -import java.net.SocketException; - -/** - * Connection to a Sentry server through an UDP connection. - */ -public class UdpConnection extends AbstractConnection { - /** - * Default UDP port for a Sentry instance. - */ - public static final int DEFAULT_UDP_PORT = 9001; - private DatagramSocket socket; - private Marshaller marshaller; - - /** - * Creates an UDP connection to a Sentry server. - * - * @param hostname hostname of the Sentry server. - * @param publicKey public key of the current project. - * @param secretKey private key of the current project. - */ - public UdpConnection(String hostname, String publicKey, String secretKey) { - this(hostname, DEFAULT_UDP_PORT, publicKey, secretKey); - } - - /** - * Creates an UDP connection to a Sentry server. - * - * @param hostname hostname of the Sentry server. - * @param port Port on which the Sentry server listens. - * @param publicKey public key of the current project. - * @param secretKey private key of the current project. - */ - public UdpConnection(String hostname, int port, String publicKey, String secretKey) { - super(publicKey, secretKey); - openSocket(hostname, port); - } - - @Override - protected void doSend(Event event) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try (OutputStream outputStream = baos) { - writeHeader(outputStream); - marshaller.marshall(event, outputStream); - } catch (IOException e) { - throw new RuntimeException("Got an exception while marshalling a message", e); - } - byte[] message = baos.toByteArray(); - - try { - DatagramPacket packet = new DatagramPacket(message, message.length); - socket.send(packet); - } catch (IOException e) { - throw new ConnectionException( - "An exception occurred while trying to establish a connection to the sentry server", e); - } - } - - private void writeHeader(OutputStream os) throws IOException { - os.write(getAuthHeader().getBytes("UTF-8")); - os.write("\n\n".getBytes("UTF-8")); - } - - private void openSocket(String hostname, int port) { - try { - socket = new DatagramSocket(); - socket.connect(new InetSocketAddress(hostname, port)); - } catch (SocketException e) { - throw new ConnectionException("The UDP connection couldn't be used, impossible to send anything " - + "to sentry", e); - } - } - - public void setMarshaller(Marshaller marshaller) { - this.marshaller = marshaller; - } - - @Override - public void close() throws IOException { - socket.close(); - } -} diff --git a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java b/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java deleted file mode 100644 index 3e9971fc311..00000000000 --- a/sentry-stub/src/main/java/net/kencochrane/raven/sentrystub/SentryUdpContextListener.java +++ /dev/null @@ -1,135 +0,0 @@ -package net.kencochrane.raven.sentrystub; - -import net.kencochrane.raven.sentrystub.auth.InvalidAuthException; -import net.kencochrane.raven.sentrystub.event.Event; - -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; -import javax.servlet.annotation.WebListener; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.SocketException; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * ContextListener stating an UDP socket when the servlet container starts. - *

    - * This listener allows a {@link DatagramSocket} to be started to listen to UDP requests. - */ -@WebListener -public class SentryUdpContextListener implements ServletContextListener { - private static final Logger logger = Logger.getLogger(SentryUdpContextListener.class.getCanonicalName()); - private static final int DEFAULT_SENTRY_UDP_PORT = 9001; - private static final String SENTRY_UDP_PORT_PARAMETER = "sentryUdpPort"; - private final SentryStub sentryStub = SentryStub.getInstance(); - private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); - private DatagramSocket udpSocket; - - @Override - public void contextInitialized(ServletContextEvent sce) { - String sentryUdpPortParameter = sce.getServletContext().getInitParameter(SENTRY_UDP_PORT_PARAMETER); - startUdpSocket(sentryUdpPortParameter != null - ? Integer.parseInt(sentryUdpPortParameter) - : DEFAULT_SENTRY_UDP_PORT); - } - - private void startUdpSocket(int port) { - try { - udpSocket = new DatagramSocket(port); - new UdpListenerThread().start(); - } catch (SocketException e) { - throw new RuntimeException(e); - } - } - - @Override - public void contextDestroyed(ServletContextEvent sce) { - executorService.shutdownNow(); - udpSocket.close(); - } - - private final class UdpRequestHandler implements Runnable { - private final DatagramPacket datagramPacket; - - private UdpRequestHandler(DatagramPacket datagramPacket) { - this.datagramPacket = datagramPacket; - } - - @Override - public void run() { - InputStream bais = new ByteArrayInputStream(datagramPacket.getData(), - datagramPacket.getOffset(), - datagramPacket.getLength()); - validateAuthHeader(bais); - Event event = sentryStub.parseEvent(bais); - sentryStub.addEvent(event); - } - - /** - * Extracts the Auth Header from a binary stream and leave the stream as is once the header is parsed. - * - * @param inputStream - */ - private void validateAuthHeader(InputStream inputStream) { - try { - Map authHeader = parseAuthHeader(inputStream); - sentryStub.validateAuth(authHeader); - } catch (IOException e) { - throw new InvalidAuthException("Impossible to extract the auth header from an UDP packet", e); - } - } - - private Map parseAuthHeader(InputStream inputStream) throws IOException { - Map authHeader = new HashMap<>(); - - int i; - StringBuilder sb = new StringBuilder(); - String key = null; - while ((i = inputStream.read()) >= 0) { - if (i == '\n') { - authHeader.put(key, sb.toString().trim()); - //Assume it's the double \n, parse the next once - inputStream.read(); - break; - } else if (i == '=' && key == null) { - key = sb.toString().trim(); - sb = new StringBuilder(); - } else if (i == ',' && key != null) { - authHeader.put(key, sb.toString().trim()); - sb = new StringBuilder(); - key = null; - } else { - sb.append((char) i); - } - } - return authHeader; - } - } - - private class UdpListenerThread extends Thread { - @Override - public void run() { - // We'll assume that no-one sends a > 65KB datagram (max size allowed on IPV4). - final int datagramPacketSize = 65536; - while (!udpSocket.isClosed()) { - try { - byte[] buffer = new byte[datagramPacketSize]; - DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length); - udpSocket.receive(datagramPacket); - executorService.execute(new UdpRequestHandler(datagramPacket)); - } catch (IOException e) { - logger.log(Level.FINE, - "An exception occurred during the reception of a UDP packet (server probably closing).", e); - } - } - } - } -} From e09916b4fca8346c475af215ada3fb5d82644738 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 3 Jan 2015 13:32:19 +1100 Subject: [PATCH 1348/2152] Fix test relying on the sentry_version --- .../kencochrane/raven/connection/AbstractConnectionTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java index 78d177c68c7..07aaaa31075 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/AbstractConnectionTest.java @@ -31,7 +31,7 @@ public class AbstractConnectionTest { public void testAuthHeader() throws Exception { String authHeader = abstractConnection.getAuthHeader(); - assertThat(authHeader, is("Sentry sentry_version=5," + assertThat(authHeader, is("Sentry sentry_version=6," + "sentry_client=Raven-Java/Test," + "sentry_key=" + publicKey + "," + "sentry_secret=" + secretKey)); From 406125a3b5311ad3064651ec529766c8c13ff755 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 3 Jan 2015 13:32:34 +1100 Subject: [PATCH 1349/2152] Remove tests for the UDP protocol --- .../raven/connection/UdpConnectionTest.java | 99 ------------------- 1 file changed, 99 deletions(-) delete mode 100644 raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java diff --git a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java deleted file mode 100644 index 225b329ae02..00000000000 --- a/raven/src/test/java/net/kencochrane/raven/connection/UdpConnectionTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package net.kencochrane.raven.connection; - -import mockit.*; -import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.marshaller.Marshaller; -import org.testng.annotations.Test; - -import java.io.IOException; -import java.io.OutputStream; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.InetSocketAddress; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -public class UdpConnectionTest { - @Injectable - private final String hostname = "127.0.0.1"; - @Injectable - private final String publicKey = "44850120-9d2a-451b-8e00-998bddaa2800"; - @Injectable - private final String secretKey = "1de38091-6e8c-42df-8298-cf7f8098617a"; - @Tested - private UdpConnection udpConnection = null; - @Injectable - private Marshaller mockMarshaller = null; - @Mocked - private DatagramSocket mockDatagramSocket = null; - - @Test - public void testConnectionWorkingWithProperHost(@Injectable("customHostname") final String mockHostname, - @Injectable("1234") final int mockPort) throws Exception { - new UdpConnection(mockHostname, mockPort, publicKey, secretKey); - - new Verifications() {{ - InetSocketAddress generatedAddress; - mockDatagramSocket.connect(generatedAddress = withCapture()); - assertThat(generatedAddress.getHostName(), is(mockHostname)); - assertThat(generatedAddress.getPort(), is(mockPort)); - }}; - } - - @Test - public void testConnectionDefaultPortIsWorking(@Injectable("customHostname") final String mockHostname) - throws Exception { - new UdpConnection(mockHostname, publicKey, secretKey); - - new Verifications() {{ - InetSocketAddress generatedAddress; - mockDatagramSocket.connect(generatedAddress = withCapture()); - assertThat(generatedAddress.getPort(), is(UdpConnection.DEFAULT_UDP_PORT)); - }}; - } - - @Test - public void udpMessageSentWorksAsExpected(@Injectable final Event event) throws Exception { - final String marshalledContent = "marshalledContent"; - new NonStrictExpectations() {{ - mockMarshaller.marshall(event, (OutputStream) any); - result = new Delegate() { - @SuppressWarnings("unused") - public void marshall(Event event, OutputStream destination) { - try { - destination.write(marshalledContent.getBytes("UTF-8")); - } catch (IOException e) { - throw new RuntimeException("Couldn't write to destination", e); - } - } - }; - }}; - udpConnection.send(event); - - new Verifications() {{ - DatagramPacket capturedPacket; - mockDatagramSocket.send(capturedPacket = withCapture()); - assertThat(new String(capturedPacket.getData(), "UTF-8"), - is(udpConnection.getAuthHeader() + "\n\n" + marshalledContent)); - }}; - } - - @Test(expectedExceptions = RuntimeException.class) - public void marshallingIssuePreventSend(@Injectable final Event event) throws Exception { - new NonStrictExpectations() {{ - mockMarshaller.marshall(event, (OutputStream) any); - result = new RuntimeException("Marshalling doesn't work"); - }}; - udpConnection.send(event); - } - - @Test - public void testContentMarshalled(@Injectable final Event event) throws Exception { - udpConnection.send(event); - - new Verifications() {{ - mockMarshaller.marshall(event, (OutputStream) any); - }}; - } -} From bae6817aee2ad015535f9591deb60b69358babc7 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 4 Jan 2015 00:53:44 +1100 Subject: [PATCH 1350/2152] Update version of raven to 6.0.0 --- pom.xml | 2 +- raven-appengine/README.md | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/README.md | 4 ++-- raven-log4j/pom.xml | 2 +- raven-log4j2/README.md | 4 ++-- raven-log4j2/pom.xml | 2 +- raven-logback/README.md | 4 ++-- raven-logback/pom.xml | 2 +- raven/README.md | 4 ++-- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 12 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index 7a70690e23d..7004000710e 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 5.0.3-SNAPSHOT + 6.0.0-SNAPSHOT pom Raven-Java diff --git a/raven-appengine/README.md b/raven-appengine/README.md index 5428aecabf7..014b57047ac 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -15,12 +15,12 @@ __This module is not useful outside of Google App Engine.__ net.kencochrane.raven raven-appengine - 5.0.2 + 6.0.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-appengine%7C5.0.2%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-appengine%7C6.0.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 8a462b6ba17..17f83a11e8f 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ net.kencochrane.raven raven-all - 5.0.3-SNAPSHOT + 6.0.0-SNAPSHOT raven-appengine diff --git a/raven-log4j/README.md b/raven-log4j/README.md index ca4fac48638..d33443abab5 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,12 +10,12 @@ for log4j to send the logged events to Sentry. net.kencochrane.raven raven-log4j - 5.0.2 + 6.0.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j%7C5.0.2%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j%7C6.0.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 290ccb89ad5..f24cba8da36 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.3-SNAPSHOT + 6.0.0-SNAPSHOT raven-log4j diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index 7e40f8b4ea7..a83af8326d4 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,12 +10,12 @@ for Log4j 2 to send the logged events to Sentry. net.kencochrane.raven raven-log4j2 - 5.0.2 + 6.0.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j2%7C5.0.2%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j2%7C6.0.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index f3b884496ef..f5cd8ee40c9 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.3-SNAPSHOT + 6.0.0-SNAPSHOT raven-log4j2 diff --git a/raven-logback/README.md b/raven-logback/README.md index fe7c3762eaf..89d5d05c19f 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,12 +10,12 @@ for logback to send the logged events to Sentry. net.kencochrane.raven raven-logback - 5.0.2 + 6.0.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-logback%7C5.0.2%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-logback%7C6.0.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 5cd34dac365..5a55d7ed655 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.3-SNAPSHOT + 6.0.0-SNAPSHOT raven-logback diff --git a/raven/README.md b/raven/README.md index a09a50db864..df1099df01c 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,12 +10,12 @@ for `java.util.logging`. net.kencochrane.raven raven - 5.0.2 + 6.0.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C5.0.2%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven%7C6.0.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven/pom.xml b/raven/pom.xml index aeb921540b0..c341daddb4d 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.3-SNAPSHOT + 6.0.0-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 615ac1ac91a..2f662ddf090 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 5.0.3-SNAPSHOT + 6.0.0-SNAPSHOT sentry-stub From 8871fdeab5f0d0383e24da7ccbf0f859d02be95e Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 4 Jan 2015 00:56:00 +1100 Subject: [PATCH 1351/2152] [maven-release-plugin] prepare release v6.0.0 --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 7004000710e..17f665e8196 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 6.0.0-SNAPSHOT + 6.0.0 pom Raven-Java @@ -75,7 +75,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v6.0.0 https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 17f83a11e8f..4e026f14ff3 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ net.kencochrane.raven raven-all - 6.0.0-SNAPSHOT + 6.0.0 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index f24cba8da36..96818eacbed 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 6.0.0-SNAPSHOT + 6.0.0 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index f5cd8ee40c9..c24555de880 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 6.0.0-SNAPSHOT + 6.0.0 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 5a55d7ed655..088afde8a21 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 6.0.0-SNAPSHOT + 6.0.0 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index c341daddb4d..e11374af4ff 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 6.0.0-SNAPSHOT + 6.0.0 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 2f662ddf090..6110fd64f58 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 6.0.0-SNAPSHOT + 6.0.0 sentry-stub From 362cba859d2bcd53eab152e48307dfdf0387af93 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sun, 4 Jan 2015 00:56:00 +1100 Subject: [PATCH 1352/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 17f665e8196..c00cc868473 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ net.kencochrane.raven raven-all - 6.0.0 + 6.0.1-SNAPSHOT pom Raven-Java @@ -75,7 +75,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v6.0.0 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 4e026f14ff3..c0edd50a9d8 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ net.kencochrane.raven raven-all - 6.0.0 + 6.0.1-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 96818eacbed..0d6c5689876 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 6.0.0 + 6.0.1-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index c24555de880..12d3cbefa89 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 6.0.0 + 6.0.1-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 088afde8a21..b3ec4dec6bc 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 6.0.0 + 6.0.1-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index e11374af4ff..4f4b68ac3b9 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 6.0.0 + 6.0.1-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 6110fd64f58..de1ed4a743b 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ net.kencochrane.raven raven-all - 6.0.0 + 6.0.1-SNAPSHOT sentry-stub From 09764985adba39015a04bf0d49ce7ba4f2a1e2f6 Mon Sep 17 00:00:00 2001 From: Nicholas Cottrell Date: Tue, 10 Mar 2015 21:52:00 +0100 Subject: [PATCH 1353/2152] Added log4j.xml instructions --- raven-log4j/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index d33443abab5..26a07c65d90 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -38,6 +38,17 @@ log4j.appender.SentryAppender.tags=tag1:value1,tag2:value2 #log4j.appender.SentryAppender.ravenFactory=net.kencochrane.raven.DefaultRavenFactory ``` +Alternatively in the `log4j.xml` file set: + +``` + + + + + + +``` + ### Additional data and information It's possible to add extra details to events captured by the Log4j module thanks to both [the MDC](https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/MDC.html) From 1c7e83dddacfaa3ca63302053fa12b73b7b099a2 Mon Sep 17 00:00:00 2001 From: Matthew Buckett Date: Tue, 9 Jun 2015 12:51:22 +0100 Subject: [PATCH 1354/2152] Update documentation to extraTags. This closes #136 --- raven-log4j/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 26a07c65d90..6769548b567 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -94,12 +94,12 @@ public class MyClass { ``` ### Mapped Tags -By default all MDC parameters are sent under the Additional Data Tab. By specify the mappedTags parameter in your +By default all MDC parameters are sent under the Additional Data Tab. By specify the extraTags parameter in your configuration file. You can specify MDC keys to send as tags instead of including them in Additional Data. This allows them to be filtered within Sentry. ```properties -log4j.appender.SentryAppender.mappedTags=User,OS +log4j.appender.SentryAppender.extraTags=User,OS ``` ```java void logWithExtras() { @@ -107,7 +107,7 @@ log4j.appender.SentryAppender.mappedTags=User,OS MDC.put("User", "test user"); MDC.put("OS", "Linux"); - // This adds a message with extras and MDC keys declared in mappedTags as tags to Sentry + // This adds a message with extras and MDC keys declared in extraTags as tags to Sentry logger.info("This is a test"); } ``` From 092c7229c9645915a193a40f6069c5b4843c97cb Mon Sep 17 00:00:00 2001 From: Matthew L Daniel Date: Mon, 20 Jul 2015 14:00:37 -0700 Subject: [PATCH 1355/2152] Create a test showing StringIndexOutOfBoundsEx q.v. #144 --- .../raven/connection/HttpConnectionTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java index a217abeafbb..19be9c19eeb 100644 --- a/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/net/kencochrane/raven/connection/HttpConnectionTest.java @@ -19,6 +19,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isEmptyOrNullString; +import static org.hamcrest.Matchers.not; public class HttpConnectionTest { @Injectable @@ -145,4 +147,20 @@ public void testApiUrlCreation(@Injectable final URI sentryUri) throws Exception assertThat(sentryApiUrl.toString(), is(uri + "api/" + projectId + "/store/")); } + + @Test + public void testEmptyStringDoesNotSIOOBE(@Injectable final Event mockEvent) throws Exception { + new NonStrictExpectations() {{ + mockUrlConnection.getOutputStream(); + result = new IOException(); + mockUrlConnection.getErrorStream(); + result = new ByteArrayInputStream(new byte[0]); + }}; + try { + httpConnection.doSend(mockEvent); + assertThat("Should not exit normally with IOE", false); + } catch (ConnectionException ce) { + assertThat(ce.getMessage(), not(isEmptyOrNullString())); + } + } } From 9f1f21c0184ac124acd8bae536fdcb8c4f93a21e Mon Sep 17 00:00:00 2001 From: Matthew L Daniel Date: Mon, 20 Jul 2015 14:03:04 -0700 Subject: [PATCH 1356/2152] Fixes #144 This also changes the if statement in `doSend` such that if the error message IS empty, that the empty string is replaced by the default message (which is more-or-less what the code attempted to do previously) --- .../raven/connection/HttpConnection.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index b9451285625..6e6af0a4678 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -123,10 +123,11 @@ protected void doSend(Event event) { outputStream.close(); connection.getInputStream().close(); } catch (IOException e) { - String errorMessage; - if (connection.getErrorStream() != null) - errorMessage = getErrorMessageFromStream(connection.getErrorStream()); - else + String errorMessage = null; + final InputStream errorStream = connection.getErrorStream(); + if (errorStream != null) + errorMessage = getErrorMessageFromStream(errorStream); + if (null == errorMessage || errorMessage.isEmpty()) errorMessage = "An exception occurred while submitting the event to the sentry server."; throw new ConnectionException(errorMessage, e); } finally { @@ -139,11 +140,15 @@ private String getErrorMessageFromStream(InputStream errorStream) { StringBuilder sb = new StringBuilder(); try { String line; - while ((line = reader.readLine()) != null) - sb.append(line).append("\n"); - //Remove last \n - sb.deleteCharAt(sb.length() - 1); - + // ensure we do not add "\n" to the last line + boolean first = true; + while ((line = reader.readLine()) != null) { + if (! first) { + sb.append("\n"); + } + sb.append(line); + first = false; + } } catch (Exception e2) { logger.error("Exception while reading the error message from the connection.", e2); } From fd5a44c11d851180909ab7fe8c23cc3dc7773617 Mon Sep 17 00:00:00 2001 From: Matthew L Daniel Date: Mon, 20 Jul 2015 14:20:45 -0700 Subject: [PATCH 1357/2152] Fix the checkstyle complaint I'll open a separate PR to engage checkstyle during packaging, to avoid Travis CI bombs on unsuspecting contributors. --- .../java/net/kencochrane/raven/connection/HttpConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java index 6e6af0a4678..ab22055fd70 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/HttpConnection.java @@ -143,7 +143,7 @@ private String getErrorMessageFromStream(InputStream errorStream) { // ensure we do not add "\n" to the last line boolean first = true; while ((line = reader.readLine()) != null) { - if (! first) { + if (!first) { sb.append("\n"); } sb.append(line); From 2b12c81fcc44c81f578477a2094a6514e201afff Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 21 Jul 2015 09:56:23 +0200 Subject: [PATCH 1358/2152] Added docs from sentry-docs --- .gitignore | 1 + .gitmodules | 3 + docs/Makefile | 153 +++++++++++++++++++++++ docs/_sentryext | 1 + docs/conf.py | 249 +++++++++++++++++++++++++++++++++++++ docs/config.rst | 200 +++++++++++++++++++++++++++++ docs/index.rst | 25 ++++ docs/installation.rst | 71 +++++++++++ docs/make.bat | 190 ++++++++++++++++++++++++++++ docs/modules/appengine.rst | 61 +++++++++ docs/modules/index.rst | 14 +++ docs/modules/log4j.rst | 135 ++++++++++++++++++++ docs/modules/log4j2.rst | 148 ++++++++++++++++++++++ docs/modules/logback.rst | 121 ++++++++++++++++++ docs/modules/raven.rst | 190 ++++++++++++++++++++++++++++ 15 files changed, 1562 insertions(+) create mode 100644 .gitmodules create mode 100644 docs/Makefile create mode 160000 docs/_sentryext create mode 100644 docs/conf.py create mode 100644 docs/config.rst create mode 100644 docs/index.rst create mode 100644 docs/installation.rst create mode 100644 docs/make.bat create mode 100644 docs/modules/appengine.rst create mode 100644 docs/modules/index.rst create mode 100644 docs/modules/log4j.rst create mode 100644 docs/modules/log4j2.rst create mode 100644 docs/modules/logback.rst create mode 100644 docs/modules/raven.rst diff --git a/.gitignore b/.gitignore index 2f7896d1d13..0b5e2288930 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ target/ +docs/_build diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000000..1e6464ab94b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "docs/_sentryext"] + path = docs/_sentryext + url = https://github.com/getsentry/sentry-doc-support diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000000..60f8b84390a --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/raven-js.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/raven-js.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/raven-js" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/raven-js" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/_sentryext b/docs/_sentryext new file mode 160000 index 00000000000..7a2aab71ee1 --- /dev/null +++ b/docs/_sentryext @@ -0,0 +1 @@ +Subproject commit 7a2aab71ee148ac47df530c84375b7d86f2dfdb8 diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000000..1a9d81148da --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- +# +# raven-java documentation build configuration file, created by +# sphinx-quickstart on Mon Jan 21 21:04:27 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os, datetime, re + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'raven-java' +copyright = u'%s, Functional Software Inc.' % datetime.date.today().year + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# + +# The full version, including alpha/beta/rc tags. +with open('../pom.xml') as f: + release = re.search(r'(.*?)', f.read()).group(1) +# The short X.Y version. +version = release.rsplit('.', 1)[0] + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'raven-javadoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'raven-java.tex', u'raven-java Documentation', + u'Functional Software Inc.', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'raven-java', u'raven-java Documentation', + [u'Functional Software Inc.'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'raven-java', u'raven-java Documentation', + u'Functional Software Inc.', 'raven-java', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +if os.environ.get('SENTRY_FEDERATED_DOCS') != '1': + sys.path.insert(0, os.path.abspath('_sentryext')) + import sentryext + sentryext.activate() diff --git a/docs/config.rst b/docs/config.rst new file mode 100644 index 00000000000..ef46958f93d --- /dev/null +++ b/docs/config.rst @@ -0,0 +1,200 @@ +Configuration +============= + +Raven's configuration happens via the DSN value. This guides you through +the configuration that applies generally and configuration that is +specific to the Java client. + +Connection and Protocols +------------------------ + +It is possible to send events to Sentry over different protocols, +depending on the security and performance requirements. + +HTTP +```` + +The most common way send events to Sentry is through HTTP, this can be +done by using a DSN of this form:: + + http://:@sentryserver/ + +This is unavailable for Hosted Sentry which requires HTTPS. + +HTTPS +````` + +It is possible to use an encrypted connection to Sentry using HTTPS:: + + ___DSN___ + +HTTPS (naive) +````````````` + +If the certificate used over HTTPS is a wildcard certificate (which is not +handled by every version of Java), and the certificate isn't added to the +truststore, it is possible to add a protocol setting to tell the client to +be naive and ignore the hostname verification:: + + naive+___DSN___ + +Options +------- + +It is possible to enable some options by adding data to the query string +of the DSN:: + + ___DSN___?option1=value1&option2&option3=value3 + +Some options do not require a value, just being declared signifies that +the option is enabled. + +Async Settings +`````````````` + +Async connection: + In order to avoid performance issues due to a large amount of logs + being generated or a slow connection to the Sentry server, an + asynchronous connection is set up, using a low priority thread pool to + submit events to Sentry. + + To disable the async mode, add ``raven.async=false`` to the DSN:: + + ___DSN___?raven.async=false + +Graceful Shutdown (advanced): + In order to shutdown the asynchronous connection gracefully, a + ``ShutdownHook`` is created. This could lead to memory leaks in an + environment where the life cycle of Raven doesn't match the life cycle + of the JVM. + + An example would be in a JEE environment where the application using + Raven could be deployed and undeployed regularly. + + To avoid this behaviour, it is possible to disable the graceful + shutdown. This might lead to some log entries being lost if the log + application doesn't shut down the Raven instance nicely. + + The option to do so is ``raven.async.gracefulshutdown``:: + + ___DSN___?raven.async.gracefulshutdown=false + +Queue and Thread Settings +````````````````````````` + +Queue size (advanced): + The default queue used to store the not yet processed events doesn't + have a limit. Depending on the environment (if the memory is sparse) + it is important to be able to control the size of that queue to avoid + memory issues. + + It is possible to set a maximum with the option ``raven.async.queuesize``:: + + ___DSN__?raven.async.queuesize=100 + + This means that if the connection to the Sentry server is down, only + the 100 most recent events will be stored and processed as soon as the + server is back up. + +Threads count (advanced): + By default the thread pool used by the async connection contains one + thread per processor available to the JVM (more threads wouldn't be + useful). + + It's possible to manually set the number of threads (for example if + you want only one thread) with the option ``raven.async.threads``:: + + ___DSN___?raven.async.threads=1 + +Threads priority (advanced): + As in most cases sending logs to Sentry isn't as important as an + application running smoothly, the threads have a `minimal priority + `_. + + It is possible to customise this value to increase the priority of + those threads with the option ``raven.async.priority``:: + + ___DSN___?raven.async.priority=10 + +Inapp Classes Settings +`````````````````````` + +Sentry differentiate ``in_app`` stack frames (which are directly related +to your application) and the "not ``in_app``" ones. This difference is +visible in the Sentry web interface where only the ``in_app`` frames are +displayed by default. + +Same frame as enclosing exception: + Raven can use the ``in_app`` system to hide frames in the context of + chained exceptions. + + Usually when a ``StackTrace`` is printed, the result looks like this:: + + HighLevelException: MidLevelException: LowLevelException + at Main.a(Main.java:13) + at Main.main(Main.java:4) + Caused by: MidLevelException: LowLevelException + at Main.c(Main.java:23) + at Main.b(Main.java:17) + at Main.a(Main.java:11) + ... 1 more + Caused by: LowLevelException + at Main.e(Main.java:30) + at Main.d(Main.java:27) + at Main.c(Main.java:21) + ... 3 more + + Some frames are replaced by the ... N more line as they are the same + frames as in the enclosing exception. + + To enable a similar behaviour from raven use the + ``raven.stacktrace.hidecommon`` option:: + + ___DSN___?raven.stacktrace.hidecommon + +Hide frames based on the class name: + Raven can also mark some frames as ``in_app`` based on the name of the + class. + + This can be used to hide parts of the stacktrace that are irrelevant + to the problem for example the stack frames in the ``java.util`` + package will not help determining what the problem was and will just + create a longer stacktrace. + + Currently this is not configurable and some packages are ignored by default: + + * com.sun.* + * java.* + * javax.* + * org.omg.* + * sun.* + * junit.* + * com.intellij.rt.* + +Transmission Settings +````````````````````` + +Compression: + By default the content sent to Sentry is compressed and encoded in + base64 before being sent. This operation allows to send a smaller + amount of data for each event. However compressing and encoding the + data adds a CPU and memory overhead which might not be useful if the + connection to Sentry is fast and reliable. + + Depending on the limitations of the project (ie: a mobile application + with a limited connection, Sentry hosted on an external network), it + can be interesting to compress the data beforehand or not. + + It's possible to manually enable/disable the compression with the + option ``raven.compression``:: + + ___DSN___?raven.compression=false + +Timeout (advanced): + To avoid blocking the thread because of a connection taking too much + time, a timeout can be set by the connection. + + By default the connection will set up its own timeout, but it's + possible to manually set one with ``raven.timeout`` (in milliseconds):: + + ___DSN___?raven.timeout=10000 diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000000..d98ac3afd1b --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,25 @@ +.. class:: platform-java + +Java +==== + +Raven is the Java client for Sentry. Raven relies on the most popular +logging libraries to capture and convert logs before sending details to a +Sentry instance. + +While it's strongly recommended to use one of the supported logging +frameworks to capture and send messages to Sentry, a it is possible to do +so manually with the main project raven. + +.. toctree:: + :maxdepth: 2 + :titlesonly: + + installation + config + modules/index + +Resources: + +* `Bug Tracker `_ +* `Github Project `_ diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 00000000000..7aeb34704ba --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,71 @@ +Installation +============ + +When using Sentry with Java the strongly recommended way is to use one of +the supported logging framework integrations rather than the raw +"raven-java" clients. + +- `java.util.logging `_ + support is provided by the main project "raven-java" +- `log4j `_ support is provided in raven-log4j +- `log4j2 `_ can be used with raven-log4j2 +- `logback `_ support is provided in raven-logback + +Support for Google App Engine is provided in raven-appengine. + +Github +------ + +ALl modules are available on the `raven-java github project +`_. This includes raven-java +itself as well as all the logging integrations. + +Snapshot Versions +----------------- + +While the stable versions of raven are available on the central Maven +Repository, newer (but less stable) versions (AKA snapshots) are available +in Sonatype's snapshot repository. + +To use it with maven, add the following repository: + +.. sourcecode:: xml + + + sonatype-nexus-snapshots + Sonatype Nexus Snapshots + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + +Android +------- + +Raven works on Android, and relies on the `ServiceLoader +`_ +system which uses the content of ``META-INF/services``. This is used to +declare the ``RavenFactory`` implementations (to allow more control over +the automatically generated instances of ``Raven``) in +``META-INF/services/net.kencochrane.raven.RavenFactory``. + +Unfortunately, when the APK is build, the content of ``META-INF/services`` of +the dependencies is lost, this prevent Raven to work properly. Solutions +exist for that problem: + +* Use `maven-android-plugin + `_ which has already + solved `this problem `_ +* Create manually a + ``META-INF/services/net.kencochrane.raven.RavenFactory`` for the + project which will contain the canonical name of of implementation of + ``RavenFactory`` (ie. ``net.kencochrane.raven.DefaultRavenFactory``). +* Register manually the ``RavenFactory`` when the application starts: + + .. sourcecode:: java + + RavenFactory.registerFactory(new DefaultRavenFactory()); diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000000..13e2848a47d --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,190 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\raven-js.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\raven-js.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst new file mode 100644 index 00000000000..1845afaffe4 --- /dev/null +++ b/docs/modules/appengine.rst @@ -0,0 +1,61 @@ +Google App Engine +================= + +The `raven-appengine` module enables the support of the async connections +in Google App Engine. + +The project can be found on github: `raven-java/appengine +`_ + +Google App Engine doesn't support threads but provides instead a +TaskQueueing system allowing tasks to be run in the background. + +This module replaces the async system provided by default with one relying +on the tasks. + +This module is not useful outside of Google App Engine. + +Installation +------------ + +If you want to use Maven you can install Raven-AppEngine as dependency: + +.. sourcecode:: xml + + + net.kencochrane.raven + raven-appengine + 6.0.0 + + +Usage +----- + +This module provides a new ``RavenFactory`` which replaces the default async +system with a GAE compatible one. + +The queue size and thread options will not be used as they are specific to +the default multithreaded system. + +It is necessary to force the raven factory name to +``net.kencochrane.raven.appengine.AppEngineRavenFactory``. + +Queue Name +---------- + +By default, the default task queue will be used, but it's possible to +specify which one will be used with the ``raven.async.gae.queuename`` option:: + + ___DSN___?raven.async.gae.queuename=MyQueueName + +Connection Name +--------------- + +As the queued tasks are sent across different instances of the +application, it's important to be able to identify which connection should +be used when processing the event. To do so, the GAE module will identify +each connection based on an identifier either automatically generated or +user defined. TO manually set the connection identifier (only used +internally) use the option ``raven.async.connectionid``:: + + ___DSN___?raven.async.gae.connectionid=MyConnection diff --git a/docs/modules/index.rst b/docs/modules/index.rst new file mode 100644 index 00000000000..eb48f1f1cef --- /dev/null +++ b/docs/modules/index.rst @@ -0,0 +1,14 @@ +Modules +======= + +Raven-Java comes in different modules from low-level access to event +submission as well as integration into different packages. + +.. toctree:: + :maxdepth: 1 + + raven + log4j + log4j2 + logback + appengine diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst new file mode 100644 index 00000000000..6ebe3bc834c --- /dev/null +++ b/docs/modules/log4j.rst @@ -0,0 +1,135 @@ +Log4j +===== + +Raven-Java provides `log4j `_ +support for Raven. It provides an `Appender +`_ +for log4j to send the logged events to Sentry. + +The project can be found on github: `raven-java/raven-log4j +`_ + +Installation +------------ + +If you want to use Maven you can install Raven-Log4j as dependency: + +.. sourcecode:: xml + + + net.kencochrane.raven + raven-log4j + 6.0.0 + + +If you manually want to manage your dependencies: + +- :doc:`raven dependencies ` +- `log4j-1.2.17.jar `_ +- `slf4j-log4j12-1.7.7.jar + `_ + is recommended as the implementation of slf4j (instead of slf4j-jdk14). + +Usage +----- + +The following configuration (``logging.properties``) gets you started for +logging with log4j and Sentry: + +.. sourcecode:: ini + + log4j.rootLogger=WARN, SentryAppender + log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender + log4j.appender.SentryAppender.dsn=___DSN___ + log4j.appender.SentryAppender.tags=tag1:value1,tag2:value2 + # Optional, allows to select the ravenFactory + #log4j.appender.SentryAppender.ravenFactory=net.kencochrane.raven.DefaultRavenFactory + +Alternatively in the ``log4j.xml`` file set: + +.. sourcecode:: xml + + + + + + + + +It's possible to add extra details to events captured by the Log4j module +thanks to both `the MDC +`_ +and `the NDC +`_ +systems provided by Log4j are usable, allowing to attach extras +information to the event. + +Practical Example +----------------- + +.. sourcecode:: java + + import org.apache.log4j.Logger; + import org.apache.log4j.MDC; + import org.apache.log4j.NDC; + + public class MyClass { + private static final Logger logger = Logger.getLogger(MyClass.class); + + void logSimpleMessage() { + // This adds a simple message to the logs + logger.info("This is a test"); + } + + void logWithExtras() { + // MDC extras + MDC.put("extra_key", "extra_value"); + // NDC extras are sent under 'log4J-NDC' + NDC.push("Extra_details"); + // This adds a message with extras to the logs + logger.info("This is a test"); + } + + void logException() { + try { + unsafeMethod(); + } catch (Exception e) { + // This adds an exception to the logs + logger.error("Exception caught", e); + } + } + + void unsafeMethod() { + throw new UnsupportedOperationException("You shouldn't call that"); + } + } + +Mapped Tags +----------- + +By default all MDC parameters are sent under the Additional Data Tab. By +specify the mappedTags parameter in your configuration file. You can +specify MDC keys to send as tags instead of including them in Additional +Data. This allows them to be filtered within Sentry. + +.. sourcecode:: java + + log4j.appender.SentryAppender.mappedTags=User,OS + void logWithExtras() { + // MDC extras + MDC.put("User", "test user"); + MDC.put("OS", "Linux"); + + // This adds a message with extras and MDC keys declared in mappedTags as tags to Sentry + logger.info("This is a test"); + } + +Asynchronous Logging +-------------------- + +It is not recommended to attempt to set up ``SentryAppender`` within an +`AsyncAppender +`_. +While this is a common solution to avoid blocking the current thread until +the event is sent to Sentry, it is recommended to rely instead on the +asynchronous connection provided by Raven. diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst new file mode 100644 index 00000000000..574b5f9ea05 --- /dev/null +++ b/docs/modules/log4j2.rst @@ -0,0 +1,148 @@ +Log4j 2 +======= + +Raven-Java provides `Log4j 2 `_ +support for Raven. It provides an `Appender +`_ +for Log4j 2 to send the logged events to Sentry. + +The project can be found on github: `raven-java/raven-log4j2 +`_ + +Installation +------------ + +If you want to use Maven you can install Raven-Log4j2 as dependency: + +.. sourcecode:: xml + + + net.kencochrane.raven + raven-log4j2 + 6.0.0 + + +If you manually want to manage your dependencies: + +- :doc:`raven dependencies ` +- `log4j-api-2.1.jar + `_ +- `log4j-core-2.0.jar + `_ +- `log4j-slf4j-impl-2.1.jar + `_ + is recommended as the implementation of slf4j (instead of slf4j-jdk14). + +Usage +----- + +The following configuration (``log4j2.xml``) gets you started for +logging with log4j2 and Sentry: + +.. sourcecode:: java + + + + + + + https://publicKey:secretKey@host:port/1?options + + + tag1:value1,tag2:value2 + + + + + + + + + + + + + +It's possible to add extra details to events captured by the Log4j 2 +module thanks to the `marker system +`_ which will +add a tag log4j2-Marker. Both the MDC and the NDC systems provided by +Log4j 2 are usable, allowing to attach extras information to the event. + +Mapped Tags +----------- + +By default all MDC parameters are sent under the Additional Data Tab. By +specify the mappedTags parameter in your configuration file. You can +specify MDC keys to send as tags instead of including them in Additional +Data. This allows them to be filtered within Sentry. + +.. sourcecode:: xml + + + User,OS + + +.. sourcecode:: java + + void logWithExtras() { + // MDC extras + MDC.put("User", "test user"); + MDC.put("OS", "Linux"); + + // This adds a message with extras and MDC keys declared in mappedTags as tags to Sentry + logger.info("This is a test"); + } + +Practical Example +----------------- + +.. sourcecode:: java + + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + import org.apache.logging.log4j.Marker; + import org.apache.logging.log4j.MarkerManager; + + public class MyClass { + private static final Logger logger = LogManager.getLogger(MyClass.class); + private static final Marker MARKER = MarkerManager.getMarker("myMarker"); + + void logSimpleMessage() { + // This adds a simple message to the logs + logger.info("This is a test"); + } + + void logWithTag() { + // This adds a message with a tag to the logs named 'log4j2-Marker' + logger.info(MARKER, "This is a test"); + } + + void logWithExtras() { + // MDC extras + ThreadContext.put("extra_key", "extra_value"); + // NDC extras are sent under 'log4j2-NDC' + ThreadContext.push("Extra_details"); + // This adds a message with extras to the logs + logger.info("This is a test"); + } + + void logException() { + try { + unsafeMethod(); + } catch (Exception e) { + // This adds an exception to the logs + logger.error("Exception caught", e); + } + } + + void unsafeMethod() { + throw new UnsupportedOperationException("You shouldn't call that"); + } + } diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst new file mode 100644 index 00000000000..dd3f8344b01 --- /dev/null +++ b/docs/modules/logback.rst @@ -0,0 +1,121 @@ +Logback +======= + +Raven-Java provides `logback `_ +support for Raven. It provides an `Appender +`_ +for logback to send the logged events to Sentry. + +Installation +------------ + +If you want to use Maven you can install Raven-Logback as dependency: + +.. sourcecode:: xml + + + net.kencochrane.raven + raven-logback + 6.0.0 + + +- :doc:`raven dependencies ` +- `logback-core-1.1.2.jar + `_ +- `logback-classic-1.1.2.jar + `_ + will act as the implementation of slf4j (instead of slf4j-jdk14). + +Usage +----- + +In the ``logback.xml`` file set: + +.. sourcecode:: xml + + + + https://publicKey:secretKey@host:port/1?options + tag1:value1,tag2:value2 + + + + + + + + +It's possible to add extra details to events captured by the logback +module thanks to the `marker system +`_ which will add a tag +logback-Marker. The `MDC system provided by logback +`_ allows to add extra information +to the event. + +Mapped Tags +----------- + +By default all MDC parameters are sent under the Additional Data Tab. By +specify the mappedTags parameter in your configuration file. You can +specify MDC keys to send as tags instead of including them in Additional +Data. This allows them to be filtered within Sentry. + +.. sourcecode:: xml + + User,OS + +.. sourcecode:: java + + void logWithExtras() { + // MDC extras + MDC.put("User", "test user"); + MDC.put("OS", "Linux"); + + // This adds a message with extras and MDC keys declared in mappedTags as tags to Sentry + logger.info("This is a test"); + } + +Practical Example +----------------- + +.. sourcecode:: java + + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import org.slf4j.MDC; + import org.slf4j.MarkerFactory; + + public class MyClass { + private static final Logger logger = LoggerFactory.getLogger(MyClass.class); + private static final Marker MARKER = MarkerFactory.getMarker("myMarker"); + + void logSimpleMessage() { + // This adds a simple message to the logs + logger.info("This is a test"); + } + + void logWithTag() { + // This adds a message with a tag to the logs named 'logback-Marker' + logger.info(MARKER, "This is a test"); + } + + void logWithExtras() { + // MDC extras + MDC.put("extra_key", "extra_value"); + // This adds a message with extras to the logs + logger.info("This is a test"); + } + + void logException() { + try { + unsafeMethod(); + } catch (Exception e) { + // This adds an exception to the logs + logger.error("Exception caught", e); + } + } + + void unsafeMethod() { + throw new UnsupportedOperationException("You shouldn't call that"); + } + } diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst new file mode 100644 index 00000000000..2b2fc70fd6f --- /dev/null +++ b/docs/modules/raven.rst @@ -0,0 +1,190 @@ +Raven +===== + +Main module of the Raven project in java. It provides a client to send +messages to a Sentry server as well as an implementation of an `Handler +`_ +for ``java.util.logging``. + +The project can be found on github: `raven-java/raven +`_ + +Installation +------------ + +If you want to use Maven you can install Raven as dependency: + +.. sourcecode:: xml + + + net.kencochrane.raven + raven + 6.0.0 + + +If you manually want to manage your dependencies: + +- `guava-18.0.jar `_ +- `jackson-core-2.5.0.jar `_ +- `slf4j-api-1.7.9.jar `_ +- `slf4j-jdk14-1.7.9.jar `_ + is recommended as the implementation of slf4j to capture the log events + generated by Raven (connection errors, ...) if `java.util.logging` is + used. + +Usage +----- + +To use Raven you need to use it through ``java.util.logging``. The +following configuration (``logging.properties``) gets you started: + +.. sourcecode:: ini + + .level=WARN + handlers=net.kencochrane.raven.jul.SentryHandler + net.kencochrane.raven.jul.SentryHandler.dsn=___DSN___ + net.kencochrane.raven.jul.SentryHandler.tags=tag1:value1,tag2:value2 + # Optional, allows to select the ravenFactory + #net.kencochrane.raven.jul.SentryHandler.ravenFactory=net.kencochrane.raven.DefaultRavenFactory + +When starting your application, add the ``java.util.logging.config.file`` to +the system properties, with the full path to the ``logging.properties`` as +its value:: + + $ java -Djava.util.logging.config.file=/path/to/app.properties MyClass + +Practical Example +----------------- + +.. sourcecode:: java + + import java.util.logging.Level; + import java.util.logging.Logger; + + public class MyClass { + private static final Logger logger = Logger.getLogger(MyClass.class.getName()); + + void logSimpleMessage() { + // This adds a simple message to the logs + logger.log(Level.INFO, "This is a test"); + } + + void logException() { + try { + unsafeMethod(); + } catch (Exception e) { + // This adds an exception to the logs + logger.log(Level.SEVERE, "Exception caught", e); + } + } + + void unsafeMethod() { + throw new UnsupportedOperationException("You shouldn't call that"); + } + } + +``java.util.logging`` does not support either MDC nor NDC, meaning that it +is not possible to attach additional/custom context values to the logs. In +other terms, it is not possible to use the "extra" field supported by +Sentry. + +Manual Usage +------------ + +It is possible to use the client manually rather than using a logging +framework in order to send messages to Sentry. It is not recommended to +use this solution as the API is more verbose and requires the developer to +specify the value of each field sent to Sentry: + +.. sourcecode:: java + + import net.kencochrane.raven.Raven; + import net.kencochrane.raven.RavenFactory; + + public class MyClass { + private static Raven raven; + + public static void main(String... args) { + // Creation of the client with a specific DSN + String dsn = args[0]; + raven = RavenFactory.ravenInstance(dsn); + + // It is also possible to use the DSN detection system like this + raven = RavenFactory.ravenInstance(); + } + + void logSimpleMessage() { + // This adds a simple message to the logs + raven.sendMessage("This is a test"); + } + + void logException() { + try { + unsafeMethod(); + } catch (Exception e) { + // This adds an exception to the logs + raven.sendException(e); + } + } + + void unsafeMethod() { + throw new UnsupportedOperationException("You shouldn't call that"); + } + } + +For more complex messages, it will be necessary to build an ``Event`` with the +``EventBuilder`` class: + +.. sourcecode:: java + + import net.kencochrane.raven.Raven; + import net.kencochrane.raven.RavenFactory; + import net.kencochrane.raven.event.Event; + import net.kencochrane.raven.event.EventBuilder; + import net.kencochrane.raven.event.interfaces.ExceptionInterface; + import net.kencochrane.raven.event.interfaces.MessageInterface; + + public class MyClass { + private static Raven raven; + + public static void main(String... args) { + // Creation of the client with a specific DSN + String dsn = args[0]; + raven = RavenFactory.ravenInstance(dsn); + + // It is also possible to use the DSN detection system like this + raven = RavenFactory.ravenInstance(); + + // Advanced: To specify the ravenFactory used + raven = RavenFactory.ravenInstance(new Dsn(dsn), "net.kencochrane.raven.DefaultRavenFactory"); + } + + void logSimpleMessage() { + // This adds a simple message to the logs + EventBuilder eventBuilder = new EventBuilder() + .withMessage("This is a test") + .withLevel(Event.Level.INFO) + .withLogger(MyClass.class.getName()); + raven.runBuilderHelpers(eventBuilder); // Optional + raven.sendEvent(eventBuilder.build()); + } + + void logException() { + try { + unsafeMethod(); + } catch (Exception e) { + // This adds an exception to the logs + EventBuilder eventBuilder = new EventBuilder() + .withMessage("Exception caught") + .withLevel(Event.Level.ERROR) + .withLogger(MyClass.class.getName()) + .withSentryInterface(new ExceptionInterface(e)); + raven.runBuilderHelpers(eventBuilder); // Optional + raven.sendEvent(eventBuilder.build()); + } + } + + void unsafeMethod() { + throw new UnsupportedOperationException("You shouldn't call that"); + } + } From fba9ad56a77197296c139954fac4ecda072d9b81 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 21 Jul 2015 09:57:15 +0200 Subject: [PATCH 1359/2152] Different headers for self-build and hosted --- docs/index.rst | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index d98ac3afd1b..46437f2699c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,7 +1,14 @@ -.. class:: platform-java +.. sentry:edition:: self -Java -==== + Raven-Java + ========== + +.. sentry:edition:: on-premise, hosted + + .. class:: platform-java + + Java + ==== Raven is the Java client for Sentry. Raven relies on the most popular logging libraries to capture and convert logs before sending details to a From 009b0054b1809f03185c0e6f39e38f8b0870e505 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Wed, 16 Sep 2015 10:42:01 -0700 Subject: [PATCH 1360/2152] Correct all occurances of invalid mappedTags usage --- docs/modules/log4j.rst | 6 +++--- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 6 +++--- .../java/net/kencochrane/raven/log4j/SentryAppender.java | 9 +-------- raven-log4j2/README.md | 8 ++++---- .../net/kencochrane/raven/log4j2/SentryAppender.java | 2 +- raven-log4j2/src/test/resources/log4j2-integration.xml | 3 +++ raven-logback/README.md | 6 +++--- .../net/kencochrane/raven/logback/SentryAppender.java | 2 +- 9 files changed, 23 insertions(+), 27 deletions(-) diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 6ebe3bc834c..86c296ac96f 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -108,19 +108,19 @@ Mapped Tags ----------- By default all MDC parameters are sent under the Additional Data Tab. By -specify the mappedTags parameter in your configuration file. You can +specify the ``extraTags`` parameter in your configuration file. You can specify MDC keys to send as tags instead of including them in Additional Data. This allows them to be filtered within Sentry. .. sourcecode:: java - log4j.appender.SentryAppender.mappedTags=User,OS + log4j.appender.SentryAppender.extraTags=User,OS void logWithExtras() { // MDC extras MDC.put("User", "test user"); MDC.put("OS", "Linux"); - // This adds a message with extras and MDC keys declared in mappedTags as tags to Sentry + // This adds a message with extras and MDC keys declared in extraTags as tags to Sentry logger.info("This is a test"); } diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 574b5f9ea05..eb83bc37f9e 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -79,15 +79,15 @@ Mapped Tags ----------- By default all MDC parameters are sent under the Additional Data Tab. By -specify the mappedTags parameter in your configuration file. You can +specify the ``extraTags`` parameter in your configuration file. You can specify MDC keys to send as tags instead of including them in Additional Data. This allows them to be filtered within Sentry. .. sourcecode:: xml - + User,OS - + .. sourcecode:: java @@ -96,7 +96,7 @@ Data. This allows them to be filtered within Sentry. MDC.put("User", "test user"); MDC.put("OS", "Linux"); - // This adds a message with extras and MDC keys declared in mappedTags as tags to Sentry + // This adds a message with extras and MDC keys declared in extraTags as tags to Sentry logger.info("This is a test"); } diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index dd3f8344b01..32400c9b20e 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -56,13 +56,13 @@ Mapped Tags ----------- By default all MDC parameters are sent under the Additional Data Tab. By -specify the mappedTags parameter in your configuration file. You can +specify the extraTags parameter in your configuration file. You can specify MDC keys to send as tags instead of including them in Additional Data. This allows them to be filtered within Sentry. .. sourcecode:: xml - User,OS + User,OS .. sourcecode:: java @@ -71,7 +71,7 @@ Data. This allows them to be filtered within Sentry. MDC.put("User", "test user"); MDC.put("OS", "Linux"); - // This adds a message with extras and MDC keys declared in mappedTags as tags to Sentry + // This adds a message with extras and MDC keys declared in extraTags as tags to Sentry logger.info("This is a test"); } diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java index b00f03ffb30..b245fd102e7 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java @@ -61,13 +61,6 @@ public class SentryAppender extends AppenderSkeleton { */ protected Set extraTags = Collections.emptySet(); - /** - * List of tags to look for in log4j's MDC. These will be added as tags to be sent to sentry. - *

    - * Might be empty in which case no mapped tags are set. - */ - protected List mappedTags = Collections.emptyList(); - /** * Creates an instance of SentryAppender. */ @@ -230,7 +223,7 @@ public void setTags(String tags) { /** * Set the mapped extras that will be used to search MDC and upgrade key pair to a tag sent along with the events. * - * @param extraTags A String of mappedTags. mappedTags are separated by commas(,). + * @param extraTags A String of extraTags. extraTags are separated by commas(,). */ public void setExtraTags(String extraTags) { this.extraTags = new HashSet<>(Arrays.asList(extraTags.split(","))); diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index a83af8326d4..103ca8fbb19 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -69,14 +69,14 @@ Both [the MDC and the NDC systems provided by Log4j 2](https://logging.apache.or are usable, allowing to attach extras information to the event. ### Mapped Tags -By default all MDC parameters are sent under the Additional Data Tab. By specify the mappedTags parameter in your +By default all MDC parameters are sent under the Additional Data Tab. By specifying the extraTags parameter in your configuration file. You can specify MDC keys to send as tags instead of including them in Additional Data. This allows them to be filtered within Sentry. ```xml - + User,OS - + ``` ```java void logWithExtras() { @@ -84,7 +84,7 @@ This allows them to be filtered within Sentry. MDC.put("User", "test user"); MDC.put("OS", "Linux"); - // This adds a message with extras and MDC keys declared in mappedTags as tags to Sentry + // This adds a message with extras and MDC keys declared in extraTags as tags to Sentry logger.info("This is a test"); } ``` diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java index c667c7476aa..9fee92031f2 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java @@ -286,7 +286,7 @@ public void setTags(String tags) { /** * Set the mapped extras that will be used to search MDC and upgrade key pair to a tag sent along with the events. * - * @param extraTags A String of mappedTags. mappedTags are separated by commas(,). + * @param extraTags A String of extraTags. extraTags are separated by commas(,). */ public void setExtraTags(String extraTags) { this.extraTags = new HashSet<>(Arrays.asList(extraTags.split(","))); diff --git a/raven-log4j2/src/test/resources/log4j2-integration.xml b/raven-log4j2/src/test/resources/log4j2-integration.xml index a95397d6b54..f2836ef56e0 100644 --- a/raven-log4j2/src/test/resources/log4j2-integration.xml +++ b/raven-log4j2/src/test/resources/log4j2-integration.xml @@ -8,6 +8,9 @@ http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false + + User,OS + net.kencochrane.raven.DefaultRavenFactory diff --git a/raven-logback/README.md b/raven-logback/README.md index 89d5d05c19f..0f19f646646 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -51,12 +51,12 @@ add a tag `logback-Marker`. allows to add extra information to the event. ### Mapped Tags -By default all MDC parameters are sent under the Additional Data Tab. By specify the mappedTags parameter in your +By default all MDC parameters are sent under the Additional Data Tab. By specifying the extraTags parameter in your configuration file. You can specify MDC keys to send as tags instead of including them in Additional Data. This allows them to be filtered within Sentry. ```xml -User,OS +User,OS ``` ```java void logWithExtras() { @@ -64,7 +64,7 @@ This allows them to be filtered within Sentry. MDC.put("User", "test user"); MDC.put("OS", "Linux"); - // This adds a message with extras and MDC keys declared in mappedTags as tags to Sentry + // This adds a message with extras and MDC keys declared in extraTags as tags to Sentry logger.info("This is a test"); } ``` diff --git a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java index 3c346418ffd..497165bf6bc 100644 --- a/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/net/kencochrane/raven/logback/SentryAppender.java @@ -291,7 +291,7 @@ public void setTags(String tags) { /** * Set the mapped extras that will be used to search MDC and upgrade key pair to a tag sent along with the events. * - * @param extraTags A String of mappedTags. mappedTags are separated by commas(,). + * @param extraTags A String of extraTags. extraTags are separated by commas(,). */ public void setExtraTags(String extraTags) { this.extraTags = new HashSet<>(Arrays.asList(extraTags.split(","))); From 00abd1c998dd647ecfd879be1fc6fc9429334c11 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Wed, 23 Sep 2015 14:48:28 -0700 Subject: [PATCH 1361/2152] Add wizards to docs --- docs/sentry-doc-config.json | 54 +++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 docs/sentry-doc-config.json diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json new file mode 100644 index 00000000000..8e9d001fb22 --- /dev/null +++ b/docs/sentry-doc-config.json @@ -0,0 +1,54 @@ +{ + "wizards": { + "java": { + "name": "Java", + "client_lib": "raven-java", + "is_framework": false, + "doc_link": "modules/raven/", + "snippets": [ + "modules/raven#installation", + "modules/raven#usage" + ] + }, + "java-log4j": { + "name": "Log4j", + "client_lib": "raven-java", + "is_framework": true, + "doc_link": "modules/log4j/", + "snippets": [ + "modules/log4j#installation", + "modules/log4j#usage" + ] + }, + "java-log4j2": { + "name": "Log4j 2", + "client_lib": "raven-java", + "is_framework": true, + "doc_link": "modules/log4j2/", + "snippets": [ + "modules/log4j2#installation", + "modules/log4j2#usage" + ] + }, + "java-logback": { + "name": "Logback", + "client_lib": "raven-java", + "is_framework": true, + "doc_link": "modules/logback/", + "snippets": [ + "modules/logback#installation", + "modules/logback#usage" + ] + }, + "java-appengine": { + "name": "App Engine", + "client_lib": "raven-java", + "is_framework": true, + "doc_link": "modules/appengine/", + "snippets": [ + "modules/appengine#installation", + "modules/appengine#usage" + ] + } + } +} From 1da96c920f094c430ede1944bf5bbd4f80b01dda Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 29 Sep 2015 18:13:42 -0700 Subject: [PATCH 1362/2152] Update doc config --- docs/sentry-doc-config.json | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json index 8e9d001fb22..a3786e583a3 100644 --- a/docs/sentry-doc-config.json +++ b/docs/sentry-doc-config.json @@ -1,51 +1,46 @@ { - "wizards": { + "platforms": { "java": { "name": "Java", - "client_lib": "raven-java", - "is_framework": false, + "type": "language", "doc_link": "modules/raven/", - "snippets": [ + "wizards": [ "modules/raven#installation", "modules/raven#usage" ] }, - "java-log4j": { + "java.log4j": { "name": "Log4j", - "client_lib": "raven-java", - "is_framework": true, + "type": "framework", "doc_link": "modules/log4j/", - "snippets": [ + "wizards": [ "modules/log4j#installation", "modules/log4j#usage" ] }, - "java-log4j2": { + "java.log4j2": { "name": "Log4j 2", - "client_lib": "raven-java", - "is_framework": true, + "type": "framework", "doc_link": "modules/log4j2/", - "snippets": [ + "wizards": [ "modules/log4j2#installation", "modules/log4j2#usage" ] }, - "java-logback": { + "java.logback": { "name": "Logback", - "client_lib": "raven-java", - "is_framework": true, + "type": "framework", "doc_link": "modules/logback/", - "snippets": [ + "wizards": [ "modules/logback#installation", "modules/logback#usage" ] }, - "java-appengine": { + "java.appengine": { "name": "App Engine", - "client_lib": "raven-java", - "is_framework": true, + "type": "framework", "doc_link": "modules/appengine/", - "snippets": [ + "wizards": [ "modules/appengine#installation", "modules/appengine#usage" ] From 542fc7d5a1f629094166bd1b36e201be07b1ca8a Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 29 Sep 2015 18:37:23 -0700 Subject: [PATCH 1363/2152] wizards => wizard --- docs/sentry-doc-config.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json index a3786e583a3..10374966765 100644 --- a/docs/sentry-doc-config.json +++ b/docs/sentry-doc-config.json @@ -4,7 +4,7 @@ "name": "Java", "type": "language", "doc_link": "modules/raven/", - "wizards": [ + "wizard": [ "modules/raven#installation", "modules/raven#usage" ] @@ -13,7 +13,7 @@ "name": "Log4j", "type": "framework", "doc_link": "modules/log4j/", - "wizards": [ + "wizard": [ "modules/log4j#installation", "modules/log4j#usage" ] @@ -22,7 +22,7 @@ "name": "Log4j 2", "type": "framework", "doc_link": "modules/log4j2/", - "wizards": [ + "wizard": [ "modules/log4j2#installation", "modules/log4j2#usage" ] @@ -31,7 +31,7 @@ "name": "Logback", "type": "framework", "doc_link": "modules/logback/", - "wizards": [ + "wizard": [ "modules/logback#installation", "modules/logback#usage" ] @@ -40,7 +40,7 @@ "name": "App Engine", "type": "framework", "doc_link": "modules/appengine/", - "wizards": [ + "wizard": [ "modules/appengine#installation", "modules/appengine#usage" ] From c1382614f8f37a391d4c7a437ebf30222acacc23 Mon Sep 17 00:00:00 2001 From: Felipe G Almeida Date: Wed, 7 Oct 2015 16:03:00 -0300 Subject: [PATCH 1364/2152] support for "release" event property support for sending events with release property and support for release property on raven-logback --- pom.xml | 3 +++ raven-logback/README.md | 2 ++ .../raven/logback/SentryAppender.java | 13 +++++++++++ .../net/kencochrane/raven/event/Event.java | 12 ++++++++++ .../kencochrane/raven/event/EventBuilder.java | 11 +++++++++ .../raven/marshaller/json/JsonMarshaller.java | 7 +++++- .../marshaller/json/JsonMarshallerTest.java | 23 +++++++++++++++---- .../json/jsonmarshallertest/testChecksum.json | 1 + .../json/jsonmarshallertest/testCulprit.json | 1 + .../json/jsonmarshallertest/testEventId.json | 1 + .../jsonmarshallertest/testExtraArray.json | 1 + .../jsonmarshallertest/testExtraBoolean.json | 1 + .../testExtraCustomValue.json | 1 + .../jsonmarshallertest/testExtraIterable.json | 1 + .../json/jsonmarshallertest/testExtraMap.json | 1 + .../jsonmarshallertest/testExtraNull.json | 1 + .../testExtraNullKeyMap.json | 1 + .../jsonmarshallertest/testExtraNumber.json | 1 + .../testExtraObjectKeyMap.json | 1 + .../testExtraRecursiveArray.json | 1 + .../testExtraRecursiveMap.json | 1 + .../jsonmarshallertest/testExtraString.json | 1 + .../testInterfaceBinding.json | 1 + .../jsonmarshallertest/testLevelDebug.json | 1 + .../jsonmarshallertest/testLevelError.json | 1 + .../jsonmarshallertest/testLevelFatal.json | 1 + .../jsonmarshallertest/testLevelInfo.json | 1 + .../jsonmarshallertest/testLevelWarning.json | 1 + .../json/jsonmarshallertest/testLogger.json | 1 + .../json/jsonmarshallertest/testMessage.json | 1 + .../json/jsonmarshallertest/testPlatform.json | 1 + .../jsonmarshallertest/testServerName.json | 1 + .../json/jsonmarshallertest/testTags.json | 1 + .../jsonmarshallertest/testTimestamp.json | 1 + .../raven/sentrystub/event/Event.java | 2 ++ 35 files changed, 94 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index c00cc868473..b0539c89011 100644 --- a/pom.xml +++ b/pom.xml @@ -56,6 +56,9 @@ ccouturi + + Felipe G Almeida + diff --git a/raven-logback/README.md b/raven-logback/README.md index 0f19f646646..5e9b4e914e6 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -36,6 +36,8 @@ In the `logback.xml` file set: tag1:value1,tag2:value2 + + + From 3a88fa0e82a6803924531e7984ad4002d751b31d Mon Sep 17 00:00:00 2001 From: Ted Kaemming Date: Tue, 3 Nov 2015 18:38:51 -0800 Subject: [PATCH 1369/2152] Don't self-close paragraph tag in doc comment. This appeared to likely be inadvertent as part of c1382614 and was breaking `mvn site`: [ERROR] Failed to execute goal org.apache.maven.plugins:maven-site-plugin:3.4:site (default-site) on project raven-all: Error generating maven-javadoc-plugin:2.10.1:aggregate: [ERROR] Exit code: 1 - /Users/ted/workspaces/raven-java/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java:24: error: self-closing element not allowed [ERROR] *

    [ERROR] ^ --- .../net/kencochrane/raven/marshaller/json/JsonMarshaller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java index 2432fd10b70..deb4bb34033 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/JsonMarshaller.java @@ -21,7 +21,7 @@ /** * Event marshaller using JSON to send the data. - *

    + *

    * The content can also be compressed with {@link DeflaterOutputStream} in which case the binary result is encoded * in base 64. */ From 584ccecf7e94dfdf847de8564a382ca466bcad43 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 5 Nov 2015 03:13:25 +0100 Subject: [PATCH 1370/2152] Added support level --- docs/sentry-doc-config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json index 10374966765..f34027f8ae8 100644 --- a/docs/sentry-doc-config.json +++ b/docs/sentry-doc-config.json @@ -1,4 +1,5 @@ { + "support_level": "production", "platforms": { "java": { "name": "Java", From 6ead1e25f1b3c93dc2ba4ba934ebed2aee678a8a Mon Sep 17 00:00:00 2001 From: Ted Kaemming Date: Fri, 20 Nov 2015 14:43:54 -0800 Subject: [PATCH 1371/2152] Add filename to `StackTraceInterfaceBinding`. Fixes GH-153. --- .../raven/marshaller/json/StackTraceInterfaceBinding.java | 3 +-- .../marshaller/json/StackTraceInterfaceBindingTest.java | 6 +++--- .../net/kencochrane/raven/marshaller/json/StackTrace1.json | 3 ++- .../net/kencochrane/raven/marshaller/json/StackTrace2.json | 2 ++ .../net/kencochrane/raven/marshaller/json/StackTrace3.json | 2 ++ 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java index 250976e23bc..0134a0d7cbe 100644 --- a/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java +++ b/raven/src/main/java/net/kencochrane/raven/marshaller/json/StackTraceInterfaceBinding.java @@ -33,8 +33,7 @@ public class StackTraceInterfaceBinding implements InterfaceBinding Date: Sun, 13 Dec 2015 20:46:27 +0200 Subject: [PATCH 1372/2152] dont removeShutdownHook if gracefulShutdown is disabled --- .../kencochrane/raven/connection/AsyncConnection.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index e0d4241a269..686f446d974 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -35,6 +35,10 @@ public class AsyncConnection implements Connection { * Shutdown hook used to stop the async connection properly when the JVM quits. */ private final ShutDownHook shutDownHook = new ShutDownHook(); + /** + * Boolean used to check whether the graceful shutdown disabled or not. + */ + private boolean gracefulShutdown; /** * Boolean used to check whether the connection is still open or not. */ @@ -56,8 +60,10 @@ public AsyncConnection(Connection actualConnection, ExecutorService executorServ this.executorService = Executors.newSingleThreadExecutor(); else this.executorService = executorService; - if (gracefulShutdown) + if (gracefulShutdown) { + this.gracefulShutdown = gracefulShutdown; addShutdownHook(); + } } /** @@ -89,7 +95,8 @@ public void send(Event event) { */ @Override public void close() throws IOException { - Runtime.getRuntime().removeShutdownHook(shutDownHook); + if (gracefulShutdown) + Runtime.getRuntime().removeShutdownHook(shutDownHook); doClose(); } From 245e3ad3061b120f510092dea20e744474791e73 Mon Sep 17 00:00:00 2001 From: Ted Kaemming Date: Mon, 14 Dec 2015 18:24:43 -0800 Subject: [PATCH 1373/2152] tweak wording --- .../java/net/kencochrane/raven/connection/AsyncConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java index 686f446d974..7aa366e2d05 100644 --- a/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/net/kencochrane/raven/connection/AsyncConnection.java @@ -36,7 +36,7 @@ public class AsyncConnection implements Connection { */ private final ShutDownHook shutDownHook = new ShutDownHook(); /** - * Boolean used to check whether the graceful shutdown disabled or not. + * Boolean that represents if graceful shutdown is enabled. */ private boolean gracefulShutdown; /** From 985cc9378cca19b7c3ce4c91a0889feb581a2fc0 Mon Sep 17 00:00:00 2001 From: ted kaemming Date: Tue, 15 Dec 2015 15:40:28 -0800 Subject: [PATCH 1374/2152] Fix grammar in log4j documentation. --- raven-log4j/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 6769548b567..d8795017ae5 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -53,7 +53,7 @@ Alternatively in the `log4j.xml` file set: It's possible to add extra details to events captured by the Log4j module thanks to both [the MDC](https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/MDC.html) and [the NDC](https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/NDC.html) systems provided by Log4j are -usable, allowing to attach extras information to the event. +usable, allowing to attach extra information to the event. ### In practice ```java From 9331d4bb1a111f2b9b02f7fc6d4baa06d5bcc534 Mon Sep 17 00:00:00 2001 From: ted kaemming Date: Tue, 15 Dec 2015 15:42:13 -0800 Subject: [PATCH 1375/2152] Actually fix grammar in log4j documentation. This block isn't really informative, and should be rewritten later. --- raven-log4j/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index d8795017ae5..3323749fe38 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -50,10 +50,9 @@ Alternatively in the `log4j.xml` file set: ``` ### Additional data and information -It's possible to add extra details to events captured by the Log4j module +It's possible to add extra data to events, thanks to both [the MDC](https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/MDC.html) -and [the NDC](https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/NDC.html) systems provided by Log4j are -usable, allowing to attach extra information to the event. +and [the NDC](https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/NDC.html) systems provided by Log4j. ### In practice ```java From f5ad3dcdf8e77dcee117487e233b51ffe1bc0219 Mon Sep 17 00:00:00 2001 From: Ted Kaemming Date: Tue, 15 Dec 2015 16:50:47 -0800 Subject: [PATCH 1376/2152] Remove `minLevel` from logback documentation. This behavior is supported by logback itself with [Filters][f] -- we shouldn't need to implement it independently. I'm a little confused why this was implemented in the first place, when it seems like this can be implemented on a per-appender basis using a [ThresholdFilter][tf]. Fixes GH-157. [f]: http://logback.qos.ch/manual/filters.html [tf]: http://logback.qos.ch/manual/filters.html#thresholdFilter --- raven-logback/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/raven-logback/README.md b/raven-logback/README.md index fdd60adfcd7..5e9b4e914e6 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -38,8 +38,6 @@ In the `logback.xml` file set: - diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 0d7e159faea..d9440e55dfa 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -14,9 +14,9 @@ If you want to use Maven you can install Raven-Logback as dependency: .. sourcecode:: xml - net.kencochrane.raven + com.getsentry.raven raven-logback - 6.0.0 + 7.0.0 - :doc:`raven dependencies ` @@ -34,11 +34,11 @@ In the ``logback.xml`` file set: .. sourcecode:: xml - + ___DSN___?options tag1:value1,tag2:value2 - + diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index 2b2fc70fd6f..14057efd7fa 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -17,9 +17,9 @@ If you want to use Maven you can install Raven as dependency: .. sourcecode:: xml - net.kencochrane.raven + com.getsentry.raven raven - 6.0.0 + 7.0.0 If you manually want to manage your dependencies: @@ -41,11 +41,11 @@ following configuration (``logging.properties``) gets you started: .. sourcecode:: ini .level=WARN - handlers=net.kencochrane.raven.jul.SentryHandler - net.kencochrane.raven.jul.SentryHandler.dsn=___DSN___ - net.kencochrane.raven.jul.SentryHandler.tags=tag1:value1,tag2:value2 + handlers=com.getsentry.raven.jul.SentryHandler + com.getsentry.raven.jul.SentryHandler.dsn=___DSN___ + com.getsentry.raven.jul.SentryHandler.tags=tag1:value1,tag2:value2 # Optional, allows to select the ravenFactory - #net.kencochrane.raven.jul.SentryHandler.ravenFactory=net.kencochrane.raven.DefaultRavenFactory + #com.getsentry.raven.jul.SentryHandler.ravenFactory=com.getsentry.raven.DefaultRavenFactory When starting your application, add the ``java.util.logging.config.file`` to the system properties, with the full path to the ``logging.properties`` as @@ -98,8 +98,8 @@ specify the value of each field sent to Sentry: .. sourcecode:: java - import net.kencochrane.raven.Raven; - import net.kencochrane.raven.RavenFactory; + import com.getsentry.raven.Raven; + import com.getsentry.raven.RavenFactory; public class MyClass { private static Raven raven; @@ -137,12 +137,12 @@ For more complex messages, it will be necessary to build an ``Event`` with the .. sourcecode:: java - import net.kencochrane.raven.Raven; - import net.kencochrane.raven.RavenFactory; - import net.kencochrane.raven.event.Event; - import net.kencochrane.raven.event.EventBuilder; - import net.kencochrane.raven.event.interfaces.ExceptionInterface; - import net.kencochrane.raven.event.interfaces.MessageInterface; + import com.getsentry.raven.Raven; + import com.getsentry.raven.RavenFactory; + import com.getsentry.raven.event.Event; + import com.getsentry.raven.event.EventBuilder; + import com.getsentry.raven.event.interfaces.ExceptionInterface; + import com.getsentry.raven.event.interfaces.MessageInterface; public class MyClass { private static Raven raven; @@ -156,7 +156,7 @@ For more complex messages, it will be necessary to build an ``Event`` with the raven = RavenFactory.ravenInstance(); // Advanced: To specify the ravenFactory used - raven = RavenFactory.ravenInstance(new Dsn(dsn), "net.kencochrane.raven.DefaultRavenFactory"); + raven = RavenFactory.ravenInstance(new Dsn(dsn), "com.getsentry.raven.DefaultRavenFactory"); } void logSimpleMessage() { diff --git a/pom.xml b/pom.xml index b0539c89011..6125c9ac29e 100644 --- a/pom.xml +++ b/pom.xml @@ -8,9 +8,9 @@ 7 - net.kencochrane.raven + com.getsentry.raven raven-all - 6.0.1-SNAPSHOT + 7.0.0-SNAPSHOT pom Raven-Java @@ -42,6 +42,13 @@ hebert.colin@gmail.com https://github.com/ColinHebert + + bretthoerner + Brett Hoerner + brett@sentry.com + Sentry + https://getsentry.com/ + diff --git a/raven-appengine/README.md b/raven-appengine/README.md index 014b57047ac..a87f230c55d 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -13,14 +13,14 @@ __This module is not useful outside of Google App Engine.__ ### Maven ```xml - net.kencochrane.raven + com.getsentry.raven raven-appengine - 6.0.0 + 7.0.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-appengine%7C6.0.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.0.0%7Cjar). ### Manual dependency management Relies on: @@ -33,7 +33,7 @@ This module provides a new `RavenFactory` which replaces the default async syste The queue size and thread options will not be used as they are specific to the default multithreaded system. -It is necessary to force the raven factory name to `net.kencochrane.raven.appengine.AppEngineRavenFactory`. +It is necessary to force the raven factory name to `com.getsentry.raven.appengine.AppEngineRavenFactory`. ### Queue name diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index c0edd50a9d8..7a92ccf6a8e 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -2,9 +2,9 @@ 4.0.0 - net.kencochrane.raven + com.getsentry.raven raven-all - 6.0.1-SNAPSHOT + 7.0.0-SNAPSHOT raven-appengine diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java b/raven-appengine/src/main/java/com/getsentry/raven/appengine/AppEngineRavenFactory.java similarity index 86% rename from raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java rename to raven-appengine/src/main/java/com/getsentry/raven/appengine/AppEngineRavenFactory.java index 0a275e00498..d1dc86df182 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/AppEngineRavenFactory.java +++ b/raven-appengine/src/main/java/com/getsentry/raven/appengine/AppEngineRavenFactory.java @@ -1,12 +1,12 @@ -package net.kencochrane.raven.appengine; +package com.getsentry.raven.appengine; import com.google.appengine.api.utils.SystemProperty; -import net.kencochrane.raven.DefaultRavenFactory; -import net.kencochrane.raven.Raven; -import net.kencochrane.raven.appengine.connection.AppEngineAsyncConnection; -import net.kencochrane.raven.appengine.event.helper.AppEngineEventBuilderHelper; -import net.kencochrane.raven.connection.Connection; -import net.kencochrane.raven.dsn.Dsn; +import com.getsentry.raven.DefaultRavenFactory; +import com.getsentry.raven.Raven; +import com.getsentry.raven.appengine.connection.AppEngineAsyncConnection; +import com.getsentry.raven.appengine.event.helper.AppEngineEventBuilderHelper; +import com.getsentry.raven.connection.Connection; +import com.getsentry.raven.dsn.Dsn; /** * RavenFactory dedicated to create async connections within Google App Engine. diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java b/raven-appengine/src/main/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnection.java similarity index 95% rename from raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java rename to raven-appengine/src/main/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnection.java index 812437f885f..357759d22df 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnection.java +++ b/raven-appengine/src/main/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnection.java @@ -1,11 +1,11 @@ -package net.kencochrane.raven.appengine.connection; +package com.getsentry.raven.appengine.connection; import com.google.appengine.api.taskqueue.DeferredTask; import com.google.appengine.api.taskqueue.Queue; import com.google.appengine.api.taskqueue.QueueFactory; -import net.kencochrane.raven.connection.Connection; -import net.kencochrane.raven.environment.RavenEnvironment; -import net.kencochrane.raven.event.Event; +import com.getsentry.raven.connection.Connection; +import com.getsentry.raven.environment.RavenEnvironment; +import com.getsentry.raven.event.Event; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java b/raven-appengine/src/main/java/com/getsentry/raven/appengine/event/helper/AppEngineEventBuilderHelper.java similarity index 86% rename from raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java rename to raven-appengine/src/main/java/com/getsentry/raven/appengine/event/helper/AppEngineEventBuilderHelper.java index ecb9bba7643..60c9b95199b 100644 --- a/raven-appengine/src/main/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelper.java +++ b/raven-appengine/src/main/java/com/getsentry/raven/appengine/event/helper/AppEngineEventBuilderHelper.java @@ -1,9 +1,9 @@ -package net.kencochrane.raven.appengine.event.helper; +package com.getsentry.raven.appengine.event.helper; import com.google.appengine.api.utils.SystemProperty; import com.google.apphosting.api.ApiProxy; -import net.kencochrane.raven.event.EventBuilder; -import net.kencochrane.raven.event.helper.EventBuilderHelper; +import com.getsentry.raven.event.EventBuilder; +import com.getsentry.raven.event.helper.EventBuilderHelper; /** * EventBuildHelper defining Google App Engine specific properties (hostname). diff --git a/raven-appengine/src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory b/raven-appengine/src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory new file mode 100644 index 00000000000..2d9dfe00b06 --- /dev/null +++ b/raven-appengine/src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory @@ -0,0 +1 @@ +com.getsentry.raven.appengine.AppEngineRavenFactory diff --git a/raven-appengine/src/main/resources/META-INF/services/net.kencochrane.raven.RavenFactory b/raven-appengine/src/main/resources/META-INF/services/net.kencochrane.raven.RavenFactory deleted file mode 100644 index 25929c53ece..00000000000 --- a/raven-appengine/src/main/resources/META-INF/services/net.kencochrane.raven.RavenFactory +++ /dev/null @@ -1 +0,0 @@ -net.kencochrane.raven.appengine.AppEngineRavenFactory diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/AppEngineRavenFactoryTest.java b/raven-appengine/src/test/java/com/getsentry/raven/appengine/AppEngineRavenFactoryTest.java similarity index 92% rename from raven-appengine/src/test/java/net/kencochrane/raven/appengine/AppEngineRavenFactoryTest.java rename to raven-appengine/src/test/java/com/getsentry/raven/appengine/AppEngineRavenFactoryTest.java index bd89601da5f..b05e76559b9 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/AppEngineRavenFactoryTest.java +++ b/raven-appengine/src/test/java/com/getsentry/raven/appengine/AppEngineRavenFactoryTest.java @@ -1,10 +1,10 @@ -package net.kencochrane.raven.appengine; +package com.getsentry.raven.appengine; import mockit.*; -import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.appengine.connection.AppEngineAsyncConnection; -import net.kencochrane.raven.connection.Connection; -import net.kencochrane.raven.dsn.Dsn; +import com.getsentry.raven.RavenFactory; +import com.getsentry.raven.appengine.connection.AppEngineAsyncConnection; +import com.getsentry.raven.connection.Connection; +import com.getsentry.raven.dsn.Dsn; import org.hamcrest.Matchers; import org.testng.annotations.Test; diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java b/raven-appengine/src/test/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnectionTest.java similarity index 96% rename from raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java rename to raven-appengine/src/test/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnectionTest.java index 7312dc40e9f..18d042b8579 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/connection/AppEngineAsyncConnectionTest.java +++ b/raven-appengine/src/test/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnectionTest.java @@ -1,10 +1,10 @@ -package net.kencochrane.raven.appengine.connection; +package com.getsentry.raven.appengine.connection; import com.google.appengine.api.taskqueue.*; import mockit.*; -import net.kencochrane.raven.connection.Connection; -import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.EventBuilder; +import com.getsentry.raven.connection.Connection; +import com.getsentry.raven.event.Event; +import com.getsentry.raven.event.EventBuilder; import org.hamcrest.Matchers; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; diff --git a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java b/raven-appengine/src/test/java/com/getsentry/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java similarity index 96% rename from raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java rename to raven-appengine/src/test/java/com/getsentry/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java index 8b791f20258..4299f5b557a 100644 --- a/raven-appengine/src/test/java/net/kencochrane/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java +++ b/raven-appengine/src/test/java/com/getsentry/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java @@ -1,9 +1,9 @@ -package net.kencochrane.raven.appengine.event.helper; +package com.getsentry.raven.appengine.event.helper; import com.google.appengine.api.utils.SystemProperty; import com.google.apphosting.api.ApiProxy; import mockit.*; -import net.kencochrane.raven.event.EventBuilder; +import com.getsentry.raven.event.EventBuilder; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 3323749fe38..d9f617d844c 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -8,14 +8,14 @@ for log4j to send the logged events to Sentry. ### Maven ```xml - net.kencochrane.raven + com.getsentry.raven raven-log4j - 6.0.0 + 7.0.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j%7C6.0.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.0.0%7Cjar). ### Manual dependency management Relies on: @@ -31,17 +31,17 @@ In the `log4j.properties` file set: ```properties log4j.rootLogger=WARN, SentryAppender -log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender +log4j.appender.SentryAppender=com.getsentry.raven.log4j.SentryAppender log4j.appender.SentryAppender.dsn=https://publicKey:secretKey@host:port/1?options log4j.appender.SentryAppender.tags=tag1:value1,tag2:value2 # Optional, allows to select the ravenFactory -#log4j.appender.SentryAppender.ravenFactory=net.kencochrane.raven.DefaultRavenFactory +#log4j.appender.SentryAppender.ravenFactory=com.getsentry.raven.DefaultRavenFactory ``` Alternatively in the `log4j.xml` file set: ``` - + diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 0d6c5689876..2966229e792 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -3,9 +3,9 @@ 4.0.0 - net.kencochrane.raven + com.getsentry.raven raven-all - 6.0.1-SNAPSHOT + 7.0.0-SNAPSHOT raven-log4j diff --git a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java similarity index 94% rename from raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java rename to raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java index b245fd102e7..e430b68aec5 100644 --- a/raven-log4j/src/main/java/net/kencochrane/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java @@ -1,15 +1,15 @@ -package net.kencochrane.raven.log4j; +package com.getsentry.raven.log4j; import com.google.common.base.Splitter; -import net.kencochrane.raven.Raven; -import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.dsn.Dsn; -import net.kencochrane.raven.dsn.InvalidDsnException; -import net.kencochrane.raven.environment.RavenEnvironment; -import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.EventBuilder; -import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import com.getsentry.raven.Raven; +import com.getsentry.raven.RavenFactory; +import com.getsentry.raven.dsn.Dsn; +import com.getsentry.raven.dsn.InvalidDsnException; +import com.getsentry.raven.environment.RavenEnvironment; +import com.getsentry.raven.event.Event; +import com.getsentry.raven.event.EventBuilder; +import com.getsentry.raven.event.interfaces.ExceptionInterface; +import com.getsentry.raven.event.interfaces.StackTraceInterface; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; import org.apache.log4j.spi.ErrorCode; diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java b/raven-log4j/src/test/java/com/getsentry/raven/log4j/MockUpErrorHandler.java similarity index 95% rename from raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java rename to raven-log4j/src/test/java/com/getsentry/raven/log4j/MockUpErrorHandler.java index d9aa25fc526..fe8bb77b37c 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/MockUpErrorHandler.java +++ b/raven-log4j/src/test/java/com/getsentry/raven/log4j/MockUpErrorHandler.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven.log4j; +package com.getsentry.raven.log4j; import mockit.Mock; import mockit.MockUp; diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderCloseTest.java similarity index 95% rename from raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java rename to raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderCloseTest.java index 69501c49170..723b6c6316c 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderCloseTest.java +++ b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderCloseTest.java @@ -1,9 +1,9 @@ -package net.kencochrane.raven.log4j; +package com.getsentry.raven.log4j; import mockit.*; -import net.kencochrane.raven.Raven; -import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.dsn.Dsn; +import com.getsentry.raven.Raven; +import com.getsentry.raven.RavenFactory; +import com.getsentry.raven.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderDsnTest.java similarity index 92% rename from raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java rename to raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderDsnTest.java index a924d7457ce..740a882c98d 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderDsnTest.java +++ b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderDsnTest.java @@ -1,12 +1,12 @@ -package net.kencochrane.raven.log4j; +package com.getsentry.raven.log4j; import mockit.Expectations; import mockit.Injectable; import mockit.Mocked; import mockit.Tested; -import net.kencochrane.raven.Raven; -import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.dsn.Dsn; +import com.getsentry.raven.Raven; +import com.getsentry.raven.RavenFactory; +import com.getsentry.raven.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderEventBuildingTest.java similarity index 96% rename from raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java rename to raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderEventBuildingTest.java index f7de54dad82..933855196b6 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderEventBuildingTest.java +++ b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderEventBuildingTest.java @@ -1,13 +1,13 @@ -package net.kencochrane.raven.log4j; +package com.getsentry.raven.log4j; import com.google.common.base.Joiner; import mockit.*; -import net.kencochrane.raven.Raven; -import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.EventBuilder; -import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.SentryException; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import com.getsentry.raven.Raven; +import com.getsentry.raven.event.Event; +import com.getsentry.raven.event.EventBuilder; +import com.getsentry.raven.event.interfaces.ExceptionInterface; +import com.getsentry.raven.event.interfaces.SentryException; +import com.getsentry.raven.event.interfaces.StackTraceInterface; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.spi.LocationInfo; diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderFailuresTest.java similarity index 90% rename from raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java rename to raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderFailuresTest.java index 7020d0df01a..aed15a14529 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderFailuresTest.java +++ b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderFailuresTest.java @@ -1,11 +1,11 @@ -package net.kencochrane.raven.log4j; +package com.getsentry.raven.log4j; import mockit.*; -import net.kencochrane.raven.Raven; -import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.dsn.Dsn; -import net.kencochrane.raven.environment.RavenEnvironment; -import net.kencochrane.raven.event.Event; +import com.getsentry.raven.Raven; +import com.getsentry.raven.RavenFactory; +import com.getsentry.raven.dsn.Dsn; +import com.getsentry.raven.environment.RavenEnvironment; +import com.getsentry.raven.event.Event; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.spi.LoggingEvent; diff --git a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderIT.java similarity index 93% rename from raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java rename to raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderIT.java index 4145dce972d..2796374e4df 100644 --- a/raven-log4j/src/test/java/net/kencochrane/raven/log4j/SentryAppenderIT.java +++ b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderIT.java @@ -1,6 +1,6 @@ -package net.kencochrane.raven.log4j; +package com.getsentry.raven.log4j; -import net.kencochrane.raven.stub.SentryStub; +import com.getsentry.raven.stub.SentryStub; import org.apache.log4j.Logger; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; diff --git a/raven-log4j/src/test/resources/log4j-integration.properties b/raven-log4j/src/test/resources/log4j-integration.properties index 3507ddd7858..4e5dab23335 100644 --- a/raven-log4j/src/test/resources/log4j-integration.properties +++ b/raven-log4j/src/test/resources/log4j-integration.properties @@ -1,7 +1,7 @@ log4j.rootLogger=ALL, ConsoleAppender, SentryAppender -log4j.net.kencochrane.raven.stub=ALL, ConsoleAppender +log4j.com.getsentry.raven.stub=ALL, ConsoleAppender -log4j.appender.SentryAppender=net.kencochrane.raven.log4j.SentryAppender +log4j.appender.SentryAppender=com.getsentry.raven.log4j.SentryAppender log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender log4j.appender.SentryAppender.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index 103ca8fbb19..f69d1842356 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -8,14 +8,14 @@ for Log4j 2 to send the logged events to Sentry. ### Maven ```xml - net.kencochrane.raven + com.getsentry.raven raven-log4j2 - 6.0.0 + 7.0.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-log4j2%7C6.0.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.0.0%7Cjar). ### Manual dependency management Relies on: @@ -33,7 +33,7 @@ In the `log4j2.xml` file set: ```xml - + @@ -47,7 +47,7 @@ In the `log4j2.xml` file set: --> diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 12d3cbefa89..a3c6a1181d4 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -3,9 +3,9 @@ 4.0.0 - net.kencochrane.raven + com.getsentry.raven raven-all - 6.0.1-SNAPSHOT + 7.0.0-SNAPSHOT raven-log4j2 diff --git a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java similarity index 95% rename from raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java rename to raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java index 9fee92031f2..a15dcab4f31 100644 --- a/raven-log4j2/src/main/java/net/kencochrane/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java @@ -1,16 +1,16 @@ -package net.kencochrane.raven.log4j2; +package com.getsentry.raven.log4j2; import com.google.common.base.Splitter; -import net.kencochrane.raven.Raven; -import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.dsn.Dsn; -import net.kencochrane.raven.dsn.InvalidDsnException; -import net.kencochrane.raven.environment.RavenEnvironment; -import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.EventBuilder; -import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.MessageInterface; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import com.getsentry.raven.Raven; +import com.getsentry.raven.RavenFactory; +import com.getsentry.raven.dsn.Dsn; +import com.getsentry.raven.dsn.InvalidDsnException; +import com.getsentry.raven.environment.RavenEnvironment; +import com.getsentry.raven.event.Event; +import com.getsentry.raven.event.EventBuilder; +import com.getsentry.raven.event.interfaces.ExceptionInterface; +import com.getsentry.raven.event.interfaces.MessageInterface; +import com.getsentry.raven.event.interfaces.StackTraceInterface; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/MockUpErrorHandler.java similarity index 95% rename from raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java rename to raven-log4j2/src/test/java/com/getsentry/raven/log4j2/MockUpErrorHandler.java index 0bbde831069..0f875c0d5f0 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/MockUpErrorHandler.java +++ b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/MockUpErrorHandler.java @@ -1,4 +1,4 @@ -package net.kencochrane.raven.log4j2; +package com.getsentry.raven.log4j2; import mockit.Mock; import mockit.MockUp; diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderCloseTest.java similarity index 95% rename from raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java rename to raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderCloseTest.java index 85247ca1f44..7243c872a85 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderCloseTest.java +++ b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderCloseTest.java @@ -1,9 +1,9 @@ -package net.kencochrane.raven.log4j2; +package com.getsentry.raven.log4j2; import mockit.*; -import net.kencochrane.raven.Raven; -import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.dsn.Dsn; +import com.getsentry.raven.Raven; +import com.getsentry.raven.RavenFactory; +import com.getsentry.raven.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderDsnTest.java similarity index 92% rename from raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java rename to raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderDsnTest.java index 91f2766a345..9a91186c208 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderDsnTest.java +++ b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderDsnTest.java @@ -1,12 +1,12 @@ -package net.kencochrane.raven.log4j2; +package com.getsentry.raven.log4j2; import mockit.Expectations; import mockit.Injectable; import mockit.Mocked; import mockit.Tested; -import net.kencochrane.raven.Raven; -import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.dsn.Dsn; +import com.getsentry.raven.Raven; +import com.getsentry.raven.RavenFactory; +import com.getsentry.raven.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderEventBuildingTest.java similarity index 96% rename from raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java rename to raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderEventBuildingTest.java index 09c151a2c0f..5ce674e3608 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderEventBuildingTest.java +++ b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderEventBuildingTest.java @@ -1,15 +1,15 @@ -package net.kencochrane.raven.log4j2; +package com.getsentry.raven.log4j2; import mockit.Injectable; import mockit.Tested; import mockit.Verifications; -import net.kencochrane.raven.Raven; -import net.kencochrane.raven.event.Event; -import net.kencochrane.raven.event.EventBuilder; -import net.kencochrane.raven.event.interfaces.ExceptionInterface; -import net.kencochrane.raven.event.interfaces.MessageInterface; -import net.kencochrane.raven.event.interfaces.SentryException; -import net.kencochrane.raven.event.interfaces.StackTraceInterface; +import com.getsentry.raven.Raven; +import com.getsentry.raven.event.Event; +import com.getsentry.raven.event.EventBuilder; +import com.getsentry.raven.event.interfaces.ExceptionInterface; +import com.getsentry.raven.event.interfaces.MessageInterface; +import com.getsentry.raven.event.interfaces.SentryException; +import com.getsentry.raven.event.interfaces.StackTraceInterface; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.ThreadContext; diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderFailuresTest.java similarity index 90% rename from raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java rename to raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderFailuresTest.java index 80c823729ae..3ef41d7a5d5 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderFailuresTest.java +++ b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderFailuresTest.java @@ -1,14 +1,14 @@ -package net.kencochrane.raven.log4j2; +package com.getsentry.raven.log4j2; import mockit.Injectable; import mockit.Mocked; import mockit.NonStrictExpectations; import mockit.Verifications; -import net.kencochrane.raven.Raven; -import net.kencochrane.raven.RavenFactory; -import net.kencochrane.raven.dsn.Dsn; -import net.kencochrane.raven.environment.RavenEnvironment; -import net.kencochrane.raven.event.Event; +import com.getsentry.raven.Raven; +import com.getsentry.raven.RavenFactory; +import com.getsentry.raven.dsn.Dsn; +import com.getsentry.raven.environment.RavenEnvironment; +import com.getsentry.raven.event.Event; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.SimpleMessage; diff --git a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderIT.java similarity index 93% rename from raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java rename to raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderIT.java index b66e35f2286..5d0af7d24c2 100644 --- a/raven-log4j2/src/test/java/net/kencochrane/raven/log4j2/SentryAppenderIT.java +++ b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderIT.java @@ -1,6 +1,6 @@ -package net.kencochrane.raven.log4j2; +package com.getsentry.raven.log4j2; -import net.kencochrane.raven.stub.SentryStub; +import com.getsentry.raven.stub.SentryStub; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.testng.annotations.AfterMethod; diff --git a/raven-log4j2/src/test/resources/log4j2-integration.xml b/raven-log4j2/src/test/resources/log4j2-integration.xml index f2836ef56e0..31c3b28990f 100644 --- a/raven-log4j2/src/test/resources/log4j2-integration.xml +++ b/raven-log4j2/src/test/resources/log4j2-integration.xml @@ -1,5 +1,5 @@ - + @@ -11,7 +11,7 @@ User,OS - net.kencochrane.raven.DefaultRavenFactory + com.getsentry.raven.DefaultRavenFactory diff --git a/raven-log4j2/src/test/resources/log4j2-test.xml b/raven-log4j2/src/test/resources/log4j2-test.xml index ad0b230214b..b4aca8db1df 100644 --- a/raven-log4j2/src/test/resources/log4j2-test.xml +++ b/raven-log4j2/src/test/resources/log4j2-test.xml @@ -1,5 +1,5 @@ - + diff --git a/raven-logback/README.md b/raven-logback/README.md index 5e9b4e914e6..7656ceca963 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -8,14 +8,14 @@ for logback to send the logged events to Sentry. ### Maven ```xml - net.kencochrane.raven + com.getsentry.raven raven-logback - 6.0.0 + 7.0.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Cnet.kencochrane.raven%7Craven-logback%7C6.0.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.0.0%7Cjar). ### Manual dependency management Relies on: @@ -31,11 +31,11 @@ In the `logback.xml` file set: ```xml - + https://publicKey:secretKey@host:port/1?options tag1:value1,tag2:value2 - + @@ -69,6 +70,7 @@ + From abe4fb358e56a8c9edc0c6c6f50f1ea301e5d22b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 13 Apr 2016 14:37:26 -0700 Subject: [PATCH 1401/2152] Bump dependencies. (#199) --- pom.xml | 6 +++--- raven-appengine/pom.xml | 4 ++-- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 726a98b071b..a2f78addd6b 100644 --- a/pom.xml +++ b/pom.xml @@ -116,11 +116,11 @@ 7 - 1.7.9 + 1.7.21 18.0 - 2.5.0 + 2.7.3 1.14 - 6.8.13 + 6.9.10 1.3 diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 084b9d766c5..8f37b95713a 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -14,8 +14,8 @@ Raven module allowing to use GoogleApp Engine and its task queueing system. - 1.9.17a - 1.1.2 + 1.9.34 + 1.1.7 diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index aff66d05098..db2cc7b00fa 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -15,7 +15,7 @@ log4j2 appender allowing to send logs to the Raven-Java client. - 2.1 + 2.5 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index ff809d5b45f..a324515d76f 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -15,7 +15,7 @@ Logback appender allowing to send logs to the Raven-Java client. - 1.1.2 + 1.1.7 From b5f831d89700d35ad5530bf4bf01141feb781c39 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Sat, 16 Apr 2016 18:51:32 -0500 Subject: [PATCH 1402/2152] Bump CHANGES and docs to 7.2.0 --- CHANGES | 2 +- docs/modules/appengine.rst | 2 +- docs/modules/log4j.rst | 2 +- docs/modules/log4j2.rst | 2 +- docs/modules/logback.rst | 2 +- docs/modules/raven.rst | 2 +- raven-appengine/README.md | 4 ++-- raven-log4j/README.md | 4 ++-- raven-log4j2/README.md | 4 ++-- raven-logback/README.md | 4 ++-- raven/README.md | 4 ++-- 11 files changed, 16 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index b1ec783b5f3..a2cc9c39978 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,4 @@ -Version 7.1.1 +Version 7.2.0 ------------- - Add printfStyle option to JUL integration. (thanks giilby) diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 1f469388690..3b6759813aa 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -25,7 +25,7 @@ If you want to use Maven you can install Raven-AppEngine as dependency: com.getsentry.raven raven-appengine - 7.1.0 + 7.2.0 Usage diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 6536c75b72f..6f7c6e920af 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven-Log4j as dependency: com.getsentry.raven raven-log4j - 7.1.0 + 7.2.0 If you manually want to manage your dependencies: diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index c90f7db5c6b..eae0924dc83 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven-Log4j2 as dependency: com.getsentry.raven raven-log4j2 - 7.1.0 + 7.2.0 If you manually want to manage your dependencies: diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index ff733e52e85..b6ff847ba01 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -16,7 +16,7 @@ If you want to use Maven you can install Raven-Logback as dependency: com.getsentry.raven raven-logback - 7.1.0 + 7.2.0 - :doc:`raven dependencies ` diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index 22f6e53155c..2f5468a74ff 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven as dependency: com.getsentry.raven raven - 7.1.0 + 7.2.0 If you manually want to manage your dependencies: diff --git a/raven-appengine/README.md b/raven-appengine/README.md index 91cdb223172..359aacf0f2c 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -15,12 +15,12 @@ __This module is not useful outside of Google App Engine.__ com.getsentry.raven raven-appengine - 7.1.0 + 7.2.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.1.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.2.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j/README.md b/raven-log4j/README.md index b4a293b7161..814c2658b66 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,12 +10,12 @@ for log4j to send the logged events to Sentry. com.getsentry.raven raven-log4j - 7.1.0 + 7.2.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.1.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.2.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index 7b37a6af7ff..59996883a78 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,12 +10,12 @@ for Log4j 2 to send the logged events to Sentry. com.getsentry.raven raven-log4j2 - 7.1.0 + 7.2.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.1.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.2.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-logback/README.md b/raven-logback/README.md index 0081011a3f7..192bf933511 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,12 +10,12 @@ for logback to send the logged events to Sentry. com.getsentry.raven raven-logback - 7.1.0 + 7.2.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.1.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.2.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven/README.md b/raven/README.md index c91bb0e1a9b..d8398b77ad2 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,12 +10,12 @@ for `java.util.logging`. com.getsentry.raven raven - 7.1.0 + 7.2.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.1.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.2.0%7Cjar). ### Manual dependency management Relies on: From 5074aa5d76f48f1a6e73492fa4f974fd9bed5a01 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Sat, 16 Apr 2016 18:53:08 -0500 Subject: [PATCH 1403/2152] [maven-release-plugin] prepare release v7.2.0 --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index a2f78addd6b..b50efd0fb04 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.1.1-SNAPSHOT + 7.2.0 pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v7.2.0 https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 8f37b95713a..447a90501f2 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.1.1-SNAPSHOT + 7.2.0 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index df9940c73af..af6356e2674 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.1.1-SNAPSHOT + 7.2.0 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index db2cc7b00fa..74d3fcf66fd 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.1.1-SNAPSHOT + 7.2.0 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index a324515d76f..fa4e0d1c4d6 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.1.1-SNAPSHOT + 7.2.0 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index dac172ef775..d633dfeb9c7 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.1.1-SNAPSHOT + 7.2.0 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 45cc161a2a5..b7796215ac7 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.1.1-SNAPSHOT + 7.2.0 sentry-stub From 26e9eacd3a5f2bc04655c571da11c191d9038715 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Sat, 16 Apr 2016 18:53:08 -0500 Subject: [PATCH 1404/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index b50efd0fb04..437bac7f319 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.2.0 + 7.2.1-SNAPSHOT pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v7.2.0 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 447a90501f2..85936c06dab 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.2.0 + 7.2.1-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index af6356e2674..bcfe1be4809 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.0 + 7.2.1-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 74d3fcf66fd..c22a92a977e 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.0 + 7.2.1-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index fa4e0d1c4d6..d9a005d45ed 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.0 + 7.2.1-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index d633dfeb9c7..29a89028482 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.0 + 7.2.1-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index b7796215ac7..2e1995736a3 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.0 + 7.2.1-SNAPSHOT sentry-stub From effd50a2f6088b294e067b6583575395ef99fb29 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 21 Apr 2016 17:20:33 -0500 Subject: [PATCH 1405/2152] Drop dependency on Guava. --- CHANGES | 5 + docs/modules/raven.rst | 1 - pom.xml | 8 +- raven-log4j/pom.xml | 4 - .../getsentry/raven/log4j/SentryAppender.java | 16 +- .../SentryAppenderEventBuildingTest.java | 4 +- raven-log4j2/pom.xml | 4 - .../raven/log4j2/SentryAppender.java | 18 +- raven-logback/pom.xml | 4 - .../raven/logback/SentryAppender.java | 23 +- raven/README.md | 1 - raven/pom.xml | 5 +- .../com/getsentry/raven/RavenFactory.java | 10 +- .../raven/connection/HttpConnection.java | 5 +- .../connection/OutputStreamConnection.java | 7 +- .../getsentry/raven/event/EventBuilder.java | 11 +- .../helper/ForwardedAddressResolver.java | 4 +- .../getsentry/raven/jul/SentryHandler.java | 32 +- .../raven/marshaller/json/JsonMarshaller.java | 9 +- .../java/com/getsentry/raven/util/Base64.java | 663 ++++++++++++++++++ .../raven/util/Base64OutputStream.java | 142 ++++ .../java/com/getsentry/raven/util/Util.java | 51 ++ .../com/getsentry/raven/RavenFactoryTest.java | 8 +- .../marshaller/json/JsonComparisonUtil.java | 3 +- .../marshaller/json/JsonMarshallerTest.java | 3 +- sentry-stub/pom.xml | 4 - .../sentrystub/unmarshaller/JsonDecoder.java | 11 +- .../raven/sentrystub/util/Base64.java | 663 ++++++++++++++++++ .../sentrystub/util/Base64InputStream.java | 140 ++++ src/checkstyle/suppressions.xml | 1 + 30 files changed, 1769 insertions(+), 91 deletions(-) create mode 100644 raven/src/main/java/com/getsentry/raven/util/Base64.java create mode 100644 raven/src/main/java/com/getsentry/raven/util/Base64OutputStream.java create mode 100644 raven/src/main/java/com/getsentry/raven/util/Util.java create mode 100644 sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64.java create mode 100644 sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64InputStream.java diff --git a/CHANGES b/CHANGES index a2cc9c39978..fb9eb95dac6 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 7.2.1 +------------- + +- Drop dependency on Guava. + Version 7.2.0 ------------- diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index 2f5468a74ff..f15733a1c75 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -24,7 +24,6 @@ If you want to use Maven you can install Raven as dependency: If you manually want to manage your dependencies: -- `guava-18.0.jar `_ - `jackson-core-2.5.0.jar `_ - `slf4j-api-1.7.9.jar `_ - `slf4j-jdk14-1.7.9.jar `_ diff --git a/pom.xml b/pom.xml index 437bac7f319..412bae10190 100644 --- a/pom.xml +++ b/pom.xml @@ -117,7 +117,7 @@ 1.7.21 - 18.0 + 3.0.1 2.7.3 1.14 6.9.10 @@ -144,9 +144,9 @@ ${slf4j.version} - com.google.guava - guava - ${guava.version} + com.google.code.findbugs + jsr305 + ${findbugs.version} com.fasterxml.jackson.core diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index bcfe1be4809..4b7e85e742c 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -28,10 +28,6 @@ log4j ${log4j.version} - - com.google.guava - guava - diff --git a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java index 542d57d4c9e..8c4ccf689a1 100644 --- a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java @@ -1,6 +1,5 @@ package com.getsentry.raven.log4j; -import com.google.common.base.Splitter; import com.getsentry.raven.Raven; import com.getsentry.raven.RavenFactory; import com.getsentry.raven.dsn.Dsn; @@ -10,14 +9,19 @@ import com.getsentry.raven.event.EventBuilder; import com.getsentry.raven.event.interfaces.ExceptionInterface; import com.getsentry.raven.event.interfaces.StackTraceInterface; -import com.google.common.base.Strings; +import com.getsentry.raven.util.Util; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; import org.apache.log4j.spi.ErrorCode; import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; /** * Appender for log4j in charge of sending the logged events to a Sentry server. @@ -178,11 +182,11 @@ protected Event buildEvent(LoggingEvent loggingEvent) { .withLevel(formatLevel(loggingEvent.getLevel())) .withExtra(THREAD_NAME, loggingEvent.getThreadName()); - if (!Strings.isNullOrEmpty(serverName)) { + if (!Util.isNullOrEmpty(serverName)) { eventBuilder.withServerName(serverName.trim()); } - if (!Strings.isNullOrEmpty(release)) { + if (!Util.isNullOrEmpty(release)) { eventBuilder.withRelease(release.trim()); } @@ -242,7 +246,7 @@ public void setRelease(String release) { * @param tags A String of tags. key/values are separated by colon(:) and tags are separated by commas(,). */ public void setTags(String tags) { - this.tags = Splitter.on(",").withKeyValueSeparator(":").split(tags); + this.tags = Util.parseTags(tags); } /** diff --git a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderEventBuildingTest.java b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderEventBuildingTest.java index a126e551d45..501cbe02659 100644 --- a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderEventBuildingTest.java +++ b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderEventBuildingTest.java @@ -1,6 +1,5 @@ package com.getsentry.raven.log4j; -import com.google.common.base.Joiner; import mockit.*; import com.getsentry.raven.Raven; import com.getsentry.raven.event.Event; @@ -134,8 +133,7 @@ public void testMdcAddedToExtra() throws Exception { @Test public void testNdcAddedToExtra() throws Exception { - final String ndcEntries = Joiner.on(' ').join("930580ba-f92f-4893-855b-ac24efa1a6c2", - "fa32ad74-a015-492a-991f-c6a0e04accaf", "be9dd914-3690-4781-97b2-fe14aedb4cbd"); + final String ndcEntries = "930580ba-f92f-4893-855b-ac24efa1a6c2 fa32ad74-a015-492a-991f-c6a0e04accaf be9dd914-3690-4781-97b2-fe14aedb4cbd"; sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, null, ndcEntries, null, null)); diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index c22a92a977e..7926fd03700 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -33,10 +33,6 @@ log4j-core ${log4j2.version} - - com.google.guava - guava - diff --git a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java index df666cf8025..7f4ec7c3090 100644 --- a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java @@ -1,6 +1,5 @@ package com.getsentry.raven.log4j2; -import com.google.common.base.Splitter; import com.getsentry.raven.Raven; import com.getsentry.raven.RavenFactory; import com.getsentry.raven.dsn.Dsn; @@ -11,7 +10,7 @@ import com.getsentry.raven.event.interfaces.ExceptionInterface; import com.getsentry.raven.event.interfaces.MessageInterface; import com.getsentry.raven.event.interfaces.StackTraceInterface; -import com.google.common.base.Strings; +import com.getsentry.raven.util.Util; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; @@ -22,7 +21,14 @@ import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.message.Message; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * Appender for log4j2 in charge of sending the logged events to a Sentry server. @@ -245,11 +251,11 @@ protected Event buildEvent(LogEvent event) { .withLevel(formatLevel(event.getLevel())) .withExtra(THREAD_NAME, event.getThreadName()); - if (!Strings.isNullOrEmpty(serverName)) { + if (!Util.isNullOrEmpty(serverName)) { eventBuilder.withServerName(serverName.trim()); } - if (!Strings.isNullOrEmpty(release)) { + if (!Util.isNullOrEmpty(release)) { eventBuilder.withRelease(release.trim()); } @@ -317,7 +323,7 @@ public void setServerName(String serverName) { * @param tags A String of tags. key/values are separated by colon(:) and tags are separated by commas(,). */ public void setTags(String tags) { - this.tags = Splitter.on(",").withKeyValueSeparator(":").split(tags); + this.tags = Util.parseTags(tags); } /** diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index d9a005d45ed..969c35152e6 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -37,10 +37,6 @@ logback-classic ${logback.version} - - com.google.guava - guava - diff --git a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java index e44ec8e8612..37febffe7b2 100644 --- a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java @@ -5,7 +5,6 @@ import ch.qos.logback.classic.spi.IThrowableProxy; import ch.qos.logback.classic.spi.StackTraceElementProxy; import ch.qos.logback.core.AppenderBase; -import com.google.common.base.Splitter; import com.getsentry.raven.Raven; import com.getsentry.raven.RavenFactory; import com.getsentry.raven.dsn.Dsn; @@ -17,9 +16,18 @@ import com.getsentry.raven.event.interfaces.MessageInterface; import com.getsentry.raven.event.interfaces.SentryException; import com.getsentry.raven.event.interfaces.StackTraceInterface; -import com.google.common.base.Strings; - -import java.util.*; +import com.getsentry.raven.util.Util; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.Deque; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * Appender for logback in charge of sending the logged events to a Sentry server. @@ -188,11 +196,12 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { .withLevel(formatLevel(iLoggingEvent.getLevel())) .withExtra(THREAD_NAME, iLoggingEvent.getThreadName()); - if (!Strings.isNullOrEmpty(serverName)) { + + if (!Util.isNullOrEmpty(serverName)) { eventBuilder.withServerName(serverName.trim()); } - if (!Strings.isNullOrEmpty(release)) { + if (!Util.isNullOrEmpty(release)) { eventBuilder.withRelease(release.trim()); } @@ -321,7 +330,7 @@ public void setMinLevel(String minLevel) { * @param tags A String of tags. key/values are separated by colon(:) and tags are separated by commas(,). */ public void setTags(String tags) { - this.tags = Splitter.on(",").withKeyValueSeparator(":").split(tags); + this.tags = Util.parseTags(tags); } /** diff --git a/raven/README.md b/raven/README.md index d8398b77ad2..275708d9b5d 100644 --- a/raven/README.md +++ b/raven/README.md @@ -20,7 +20,6 @@ Details in the [central Maven repository](https://search.maven.org/#artifactdeta ### Manual dependency management Relies on: - - [guava-18.0.jar](https://search.maven.org/#artifactdetails%7Ccom.google.guava%7Cguava%7C18.0%7Cjar) - [jackson-core-2.5.0.jar](https://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.5.0%7Cjar) - [slf4j-api-1.7.9.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.9%7Cjar) - [slf4j-jdk14-1.7.9.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-jdk14%7C1.7.9%7Cjar) diff --git a/raven/pom.xml b/raven/pom.xml index 29a89028482..bf501b41bbf 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -24,8 +24,9 @@ slf4j-api - com.google.guava - guava + com.google.code.findbugs + jsr305 + provided com.fasterxml.jackson.core diff --git a/raven/src/main/java/com/getsentry/raven/RavenFactory.java b/raven/src/main/java/com/getsentry/raven/RavenFactory.java index 311a7e61b2f..854d3170442 100644 --- a/raven/src/main/java/com/getsentry/raven/RavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/RavenFactory.java @@ -1,11 +1,12 @@ package com.getsentry.raven; -import com.google.common.collect.Iterables; import com.getsentry.raven.dsn.Dsn; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; import java.util.ServiceLoader; import java.util.Set; @@ -34,7 +35,12 @@ public static void registerFactory(RavenFactory ravenFactory) { } private static Iterable getRegisteredFactories() { - return Iterables.concat(MANUALLY_REGISTERED_FACTORIES, AUTO_REGISTERED_FACTORIES); + List ravenFactories = new LinkedList<>(); + ravenFactories.addAll(MANUALLY_REGISTERED_FACTORIES); + for (RavenFactory autoRegisteredFactory : AUTO_REGISTERED_FACTORIES) { + ravenFactories.add(autoRegisteredFactory); + } + return ravenFactories; } /** diff --git a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java b/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java index 6994f65a9cd..04152b2935a 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java @@ -1,6 +1,5 @@ package com.getsentry.raven.connection; -import com.google.common.base.Charsets; import com.getsentry.raven.environment.RavenEnvironment; import com.getsentry.raven.event.Event; import com.getsentry.raven.marshaller.Marshaller; @@ -15,6 +14,7 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URL; +import java.nio.charset.Charset; import java.util.concurrent.TimeUnit; /** @@ -23,6 +23,7 @@ * It is possible to enable the "naive mode" to allow a connection over SSL using a certificate with a wildcard. */ public class HttpConnection extends AbstractConnection { + private static final Charset UTF_8 = Charset.forName("UTF-8"); private static final Logger logger = LoggerFactory.getLogger(HttpConnection.class); /** * HTTP Header for the user agent. @@ -136,7 +137,7 @@ protected void doSend(Event event) { } private String getErrorMessageFromStream(InputStream errorStream) { - BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream, Charsets.UTF_8)); + BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream, UTF_8)); StringBuilder sb = new StringBuilder(); try { String line; diff --git a/raven/src/main/java/com/getsentry/raven/connection/OutputStreamConnection.java b/raven/src/main/java/com/getsentry/raven/connection/OutputStreamConnection.java index 5d14c70737c..14a638bec0f 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/OutputStreamConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/OutputStreamConnection.java @@ -1,16 +1,17 @@ package com.getsentry.raven.connection; -import com.google.common.base.Charsets; import com.getsentry.raven.event.Event; import com.getsentry.raven.marshaller.Marshaller; import java.io.IOException; import java.io.OutputStream; +import java.nio.charset.Charset; /** * Connection using StdOut to sent marshalled events. */ public class OutputStreamConnection extends AbstractConnection { + private static final Charset UTF_8 = Charset.forName("UTF-8"); private final OutputStream outputStream; private Marshaller marshaller; @@ -27,9 +28,9 @@ public OutputStreamConnection(OutputStream outputStream) { @Override protected synchronized void doSend(Event event) throws ConnectionException { try { - outputStream.write("Raven event:\n".getBytes(Charsets.UTF_8)); + outputStream.write("Raven event:\n".getBytes(UTF_8)); marshaller.marshall(event, outputStream); - outputStream.write("\n".getBytes(Charsets.UTF_8)); + outputStream.write("\n".getBytes(UTF_8)); outputStream.flush(); } catch (IOException e) { throw new ConnectionException("Couldn't sent the event properly", e); diff --git a/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java b/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java index 1bee511a2e7..ebc40dea0fa 100644 --- a/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java +++ b/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java @@ -1,12 +1,12 @@ package com.getsentry.raven.event; -import com.google.common.base.Charsets; -import com.google.common.collect.Lists; import com.getsentry.raven.event.interfaces.SentryInterface; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.InetAddress; +import java.nio.charset.Charset; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; @@ -35,6 +35,7 @@ public class EventBuilder { * @see HostnameCache */ public static final long HOSTNAME_CACHE_DURATION = TimeUnit.HOURS.toMillis(5); + private static final Charset UTF_8 = Charset.forName("UTF-8"); private static final HostnameCache HOSTNAME_CACHE = new HostnameCache(HOSTNAME_CACHE_DURATION); private final Event event; private boolean alreadyBuilt = false; @@ -64,7 +65,7 @@ public EventBuilder(UUID eventId) { * @return a checksum allowing two events with the same properties to be grouped later. */ private static String calculateChecksum(String string) { - byte[] bytes = string.getBytes(Charsets.UTF_8); + byte[] bytes = string.getBytes(UTF_8); Checksum checksum = new CRC32(); checksum.update(bytes, 0, bytes.length); return Long.toHexString(checksum.getValue()).toUpperCase(); @@ -250,7 +251,9 @@ public EventBuilder withExtra(String extraName, Object extraValue) { * @return the current {@code EventBuilder} for chained calls. */ public EventBuilder withFingerprint(String... fingerprint) { - event.setFingerprint(Lists.newArrayList(fingerprint)); + List list = new ArrayList<>(fingerprint.length); + Collections.addAll(list, fingerprint); + event.setFingerprint(list); return this; } diff --git a/raven/src/main/java/com/getsentry/raven/event/helper/ForwardedAddressResolver.java b/raven/src/main/java/com/getsentry/raven/event/helper/ForwardedAddressResolver.java index 70d2b20778d..637fca57d93 100644 --- a/raven/src/main/java/com/getsentry/raven/event/helper/ForwardedAddressResolver.java +++ b/raven/src/main/java/com/getsentry/raven/event/helper/ForwardedAddressResolver.java @@ -1,6 +1,6 @@ package com.getsentry.raven.event.helper; -import com.google.common.base.Strings; +import com.getsentry.raven.util.Util; import javax.servlet.http.HttpServletRequest; import java.util.Arrays; @@ -30,7 +30,7 @@ private static String firstAddress(String csvAddrs) { @Override public String getRemoteAddress(HttpServletRequest request) { String forwarded = request.getHeader("X-FORWARDED-FOR"); - if (!Strings.isNullOrEmpty(forwarded)) { + if (!Util.isNullOrEmpty(forwarded)) { return firstAddress(forwarded); } return basicRemoteAddressResolver.getRemoteAddress(request); diff --git a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java index 74a3d76ff28..19400bb8a63 100644 --- a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java +++ b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java @@ -1,6 +1,5 @@ package com.getsentry.raven.jul; -import com.google.common.base.Splitter; import com.getsentry.raven.Raven; import com.getsentry.raven.RavenFactory; import com.getsentry.raven.dsn.Dsn; @@ -10,12 +9,24 @@ import com.getsentry.raven.event.EventBuilder; import com.getsentry.raven.event.interfaces.ExceptionInterface; import com.getsentry.raven.event.interfaces.MessageInterface; -import com.google.common.base.Strings; +import com.getsentry.raven.util.Util; import org.slf4j.MDC; import java.text.MessageFormat; -import java.util.*; -import java.util.logging.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.MissingFormatArgumentException; +import java.util.Set; +import java.util.logging.ErrorManager; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; /** * Logging handler in charge of sending the java.util.logging records to a Sentry server. @@ -103,12 +114,7 @@ public void setPrintfStyle(boolean printfStyle) { * "tag1:value1,tag2:value2". */ public void setTags(String tagsProperty) { - this.tags = parseTags(tagsProperty); - } - - private Map parseTags(String tagsProperty) { - return tagsProperty == null ? Collections.emptyMap() - : Splitter.on(",").withKeyValueSeparator(":").split(tagsProperty); + this.tags = Util.parseTags(tagsProperty); } /** @@ -156,7 +162,7 @@ protected void retrieveProperties() { release = manager.getProperty(className + ".release"); serverName = manager.getProperty(className + ".serverName"); String tagsProperty = manager.getProperty(className + ".tags"); - tags = parseTags(tagsProperty); + tags = Util.parseTags(tagsProperty); String extraTagsProperty = manager.getProperty(className + ".extraTags"); if (extraTagsProperty != null) extraTags = new HashSet<>(Arrays.asList(extraTagsProperty.split(","))); @@ -259,11 +265,11 @@ protected Event buildEvent(LogRecord record) { eventBuilder.withExtra(THREAD_ID, record.getThreadID()); - if (!Strings.isNullOrEmpty(release)) { + if (!Util.isNullOrEmpty(release)) { eventBuilder.withRelease(release.trim()); } - if (!Strings.isNullOrEmpty(serverName)) { + if (!Util.isNullOrEmpty(serverName)) { eventBuilder.withServerName(serverName.trim()); } diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java index a072776e44a..63d0457143e 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java @@ -2,7 +2,8 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; -import com.google.common.base.Charsets; +import com.getsentry.raven.util.Base64; +import com.getsentry.raven.util.Base64OutputStream; import com.getsentry.raven.event.Event; import com.getsentry.raven.event.interfaces.SentryInterface; import com.getsentry.raven.marshaller.Marshaller; @@ -11,14 +12,11 @@ import java.io.IOException; import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; import java.util.zip.DeflaterOutputStream; -import static com.google.common.io.BaseEncoding.base64; - /** * Event marshaller using JSON to send the data. *

    @@ -112,8 +110,7 @@ public void marshall(Event event, OutputStream destination) { destination = new UncloseableOutputStream(destination); if (compression) - destination = new DeflaterOutputStream(base64().encodingStream( - new OutputStreamWriter(destination, Charsets.UTF_8))); + destination = new DeflaterOutputStream(new Base64OutputStream(destination, Base64.NO_WRAP)); try (JsonGenerator generator = jsonFactory.createGenerator(destination)) { writeContent(generator, event); diff --git a/raven/src/main/java/com/getsentry/raven/util/Base64.java b/raven/src/main/java/com/getsentry/raven/util/Base64.java new file mode 100644 index 00000000000..6cc527a8294 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/util/Base64.java @@ -0,0 +1,663 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.getsentry.raven.util; + +import java.io.UnsupportedEncodingException; + +/** + * Utilities for encoding and decoding the Base64 representation of + * binary data. See RFCs 2045 and 3548. + */ +public class Base64 { + /** + * Default values for encoder/decoder flags. + */ + public static final int DEFAULT = 0; + /** + * Encoder flag bit to omit the padding '=' characters at the end + * of the output (if any). + */ + public static final int NO_PADDING = 1; + /** + * Encoder flag bit to omit all line terminators (i.e., the output + * will be on one long line). + */ + public static final int NO_WRAP = 2; + /** + * Encoder flag bit to indicate lines should be terminated with a + * CRLF pair instead of just an LF. Has no effect if {@code + * NO_WRAP} is specified as well. + */ + public static final int CRLF = 4; + /** + * Encoder/decoder flag bit to indicate using the "URL and + * filename safe" variant of Base64 (see RFC 3548 section 4) where + * {@code -} and {@code _} are used in place of {@code +} and + * {@code /}. + */ + public static final int URL_SAFE = 8; + /** + * Flag to pass to {@link Base64OutputStream} to indicate that it + * should not close the output stream it is wrapping when it + * itself is closed. + */ + public static final int NO_CLOSE = 16; + // -------------------------------------------------------- + // shared code + // -------------------------------------------------------- + /* package */ static abstract class Coder { + public byte[] output; + public int op; + /** + * Encode/decode another block of input data. this.output is + * provided by the caller, and must be big enough to hold all + * the coded data. On exit, this.opwill be set to the length + * of the coded data. + * + * @param finish true if this is the final call to process for + * this object. Will finalize the coder state and + * include any final bytes in the output. + * + * @return true if the input so far is good; false if some + * error has been detected in the input stream.. + */ + public abstract boolean process(byte[] input, int offset, int len, boolean finish); + /** + * @return the maximum number of bytes a call to process() + * could produce for the given number of input bytes. This may + * be an overestimate. + */ + public abstract int maxOutputSize(int len); + } + // -------------------------------------------------------- + // decoding + // -------------------------------------------------------- + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

    The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param str the input String to decode, which is converted to + * bytes using the default charset + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(String str, int flags) { + return decode(str.getBytes(), flags); + } + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

    The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param input the input array to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(byte[] input, int flags) { + return decode(input, 0, input.length, flags); + } + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

    The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param input the data to decode + * @param offset the position within the input array at which to start + * @param len the number of bytes of input to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(byte[] input, int offset, int len, int flags) { + // Allocate space for the most data the input could represent. + // (It could contain less if it contains whitespace, etc.) + Decoder decoder = new Decoder(flags, new byte[len*3/4]); + if (!decoder.process(input, offset, len, true)) { + throw new IllegalArgumentException("bad base-64"); + } + // Maybe we got lucky and allocated exactly enough output space. + if (decoder.op == decoder.output.length) { + return decoder.output; + } + // Need to shorten the array, so allocate a new one of the + // right size and copy. + byte[] temp = new byte[decoder.op]; + System.arraycopy(decoder.output, 0, temp, 0, decoder.op); + return temp; + } + /* package */ static class Decoder extends Coder { + /** + * Lookup table for turning bytes into their position in the + * Base64 alphabet. + */ + private static final int DECODE[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + /** + * Decode lookup table for the "web safe" variant (RFC 3548 + * sec. 4) where - and _ replace + and /. + */ + private static final int DECODE_WEBSAFE[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + /** Non-data values in the DECODE arrays. */ + private static final int SKIP = -1; + private static final int EQUALS = -2; + /** + * States 0-3 are reading through the next input tuple. + * State 4 is having read one '=' and expecting exactly + * one more. + * State 5 is expecting no more data or padding characters + * in the input. + * State 6 is the error state; an error has been detected + * in the input and no future input can "fix" it. + */ + private int state; // state number (0 to 6) + private int value; + final private int[] alphabet; + public Decoder(int flags, byte[] output) { + this.output = output; + alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE; + state = 0; + value = 0; + } + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could decode to. + */ + public int maxOutputSize(int len) { + return len * 3/4 + 10; + } + /** + * Decode another block of input data. + * + * @return true if the state machine is still healthy. false if + * bad base-64 data has been detected in the input stream. + */ + public boolean process(byte[] input, int offset, int len, boolean finish) { + if (this.state == 6) return false; + int p = offset; + len += offset; + // Using local variables makes the decoder about 12% + // faster than if we manipulate the member variables in + // the loop. (Even alphabet makes a measurable + // difference, which is somewhat surprising to me since + // the member variable is final.) + int state = this.state; + int value = this.value; + int op = 0; + final byte[] output = this.output; + final int[] alphabet = this.alphabet; + while (p < len) { + // Try the fast path: we're starting a new tuple and the + // next four bytes of the input stream are all data + // bytes. This corresponds to going through states + // 0-1-2-3-0. We expect to use this method for most of + // the data. + // + // If any of the next four bytes of input are non-data + // (whitespace, etc.), value will end up negative. (All + // the non-data values in decode are small negative + // numbers, so shifting any of them up and or'ing them + // together will result in a value with its top bit set.) + // + // You can remove this whole block and the output should + // be the same, just slower. + if (state == 0) { + while (p+4 <= len && + (value = ((alphabet[input[p] & 0xff] << 18) | + (alphabet[input[p+1] & 0xff] << 12) | + (alphabet[input[p+2] & 0xff] << 6) | + (alphabet[input[p+3] & 0xff]))) >= 0) { + output[op+2] = (byte) value; + output[op+1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + p += 4; + } + if (p >= len) break; + } + // The fast path isn't available -- either we've read a + // partial tuple, or the next four input bytes aren't all + // data, or whatever. Fall back to the slower state + // machine implementation. + int d = alphabet[input[p++] & 0xff]; + switch (state) { + case 0: + if (d >= 0) { + value = d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 1: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 2: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect exactly one more padding character. + output[op++] = (byte) (value >> 4); + state = 4; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 3: + if (d >= 0) { + // Emit the output triple and return to state 0. + value = (value << 6) | d; + output[op+2] = (byte) value; + output[op+1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + state = 0; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect no further data or padding characters. + output[op+1] = (byte) (value >> 2); + output[op] = (byte) (value >> 10); + op += 2; + state = 5; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 4: + if (d == EQUALS) { + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 5: + if (d != SKIP) { + this.state = 6; + return false; + } + break; + } + } + if (!finish) { + // We're out of input, but a future call could provide + // more. + this.state = state; + this.value = value; + this.op = op; + return true; + } + // Done reading input. Now figure out where we are left in + // the state machine and finish up. + switch (state) { + case 0: + // Output length is a multiple of three. Fine. + break; + case 1: + // Read one extra input byte, which isn't enough to + // make another output byte. Illegal. + this.state = 6; + return false; + case 2: + // Read two extra input bytes, enough to emit 1 more + // output byte. Fine. + output[op++] = (byte) (value >> 4); + break; + case 3: + // Read three extra input bytes, enough to emit 2 more + // output bytes. Fine. + output[op++] = (byte) (value >> 10); + output[op++] = (byte) (value >> 2); + break; + case 4: + // Read one padding '=' when we expected 2. Illegal. + this.state = 6; + return false; + case 5: + // Read all the padding '='s we expected and no more. + // Fine. + break; + } + this.state = state; + this.op = op; + return true; + } + } + // -------------------------------------------------------- + // encoding + // -------------------------------------------------------- + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString(byte[] input, int flags) { + try { + return new String(encode(input, flags), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + // US-ASCII is guaranteed to be available. + throw new AssertionError(e); + } + } + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString(byte[] input, int offset, int len, int flags) { + try { + return new String(encode(input, offset, len, flags), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + // US-ASCII is guaranteed to be available. + throw new AssertionError(e); + } + } + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode(byte[] input, int flags) { + return encode(input, 0, input.length, flags); + } + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode(byte[] input, int offset, int len, int flags) { + Encoder encoder = new Encoder(flags, null); + // Compute the exact length of the array we will produce. + int output_len = len / 3 * 4; + // Account for the tail of the data and the padding bytes, if any. + if (encoder.do_padding) { + if (len % 3 > 0) { + output_len += 4; + } + } else { + switch (len % 3) { + case 0: break; + case 1: output_len += 2; break; + case 2: output_len += 3; break; + } + } + // Account for the newlines, if any. + if (encoder.do_newline && len > 0) { + output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) * + (encoder.do_cr ? 2 : 1); + } + encoder.output = new byte[output_len]; + encoder.process(input, offset, len, true); + assert encoder.op == output_len; + return encoder.output; + } + /* package */ static class Encoder extends Coder { + /** + * Emit a new line every this many output tuples. Corresponds to + * a 76-character line length (the maximum allowable according to + * RFC 2045). + */ + public static final int LINE_GROUPS = 19; + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte ENCODE[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', + }; + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte ENCODE_WEBSAFE[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', + }; + final private byte[] tail; + /* package */ int tailLen; + private int count; + final public boolean do_padding; + final public boolean do_newline; + final public boolean do_cr; + final private byte[] alphabet; + public Encoder(int flags, byte[] output) { + this.output = output; + do_padding = (flags & NO_PADDING) == 0; + do_newline = (flags & NO_WRAP) == 0; + do_cr = (flags & CRLF) != 0; + alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE; + tail = new byte[2]; + tailLen = 0; + count = do_newline ? LINE_GROUPS : -1; + } + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could encode to. + */ + public int maxOutputSize(int len) { + return len * 8/5 + 10; + } + public boolean process(byte[] input, int offset, int len, boolean finish) { + // Using local variables makes the encoder about 9% faster. + final byte[] alphabet = this.alphabet; + final byte[] output = this.output; + int op = 0; + int count = this.count; + int p = offset; + len += offset; + int v = -1; + // First we need to concatenate the tail of the previous call + // with any input bytes available now and see if we can empty + // the tail. + switch (tailLen) { + case 0: + // There was no tail. + break; + case 1: + if (p+2 <= len) { + // A 1-byte tail with at least 2 bytes of + // input available now. + v = ((tail[0] & 0xff) << 16) | + ((input[p++] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + }; + break; + case 2: + if (p+1 <= len) { + // A 2-byte tail with at least 1 byte of input. + v = ((tail[0] & 0xff) << 16) | + ((tail[1] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + } + break; + } + if (v != -1) { + output[op++] = alphabet[(v >> 18) & 0x3f]; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + // At this point either there is no tail, or there are fewer + // than 3 bytes of input available. + // The main loop, turning 3 input bytes into 4 output bytes on + // each iteration. + while (p+3 <= len) { + v = ((input[p] & 0xff) << 16) | + ((input[p+1] & 0xff) << 8) | + (input[p+2] & 0xff); + output[op] = alphabet[(v >> 18) & 0x3f]; + output[op+1] = alphabet[(v >> 12) & 0x3f]; + output[op+2] = alphabet[(v >> 6) & 0x3f]; + output[op+3] = alphabet[v & 0x3f]; + p += 3; + op += 4; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + if (finish) { + // Finish up the tail of the input. Note that we need to + // consume any bytes in tail before any bytes + // remaining in input; there should be at most two bytes + // total. + if (p-tailLen == len-1) { + int t = 0; + v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4; + tailLen -= t; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (p-tailLen == len-2) { + int t = 0; + v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) | + (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2); + tailLen -= t; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (do_newline && op > 0 && count != LINE_GROUPS) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + assert tailLen == 0; + assert p == len; + } else { + // Save the leftovers in tail to be consumed on the next + // call to encodeInternal. + if (p == len-1) { + tail[tailLen++] = input[p]; + } else if (p == len-2) { + tail[tailLen++] = input[p]; + tail[tailLen++] = input[p+1]; + } + } + this.op = op; + this.count = count; + return true; + } + } + private Base64() { } // don't instantiate +} \ No newline at end of file diff --git a/raven/src/main/java/com/getsentry/raven/util/Base64OutputStream.java b/raven/src/main/java/com/getsentry/raven/util/Base64OutputStream.java new file mode 100644 index 00000000000..c1ed703c023 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/util/Base64OutputStream.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.getsentry.raven.util; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * An OutputStream that does Base64 encoding on the data written to + * it, writing the resulting data to another OutputStream. + */ +public class Base64OutputStream extends FilterOutputStream { + private final Base64.Coder coder; + private final int flags; + private byte[] buffer = null; + private int bpos = 0; + private static byte[] EMPTY = new byte[0]; + /** + * Performs Base64 encoding on the data written to the stream, + * writing the encoded data to another OutputStream. + * + * @param out the OutputStream to write the encoded data to + * @param flags bit flags for controlling the encoder; see the + * constants in {@link Base64} + */ + public Base64OutputStream(OutputStream out, int flags) { + this(out, flags, true); + } + /** + * Performs Base64 encoding or decoding on the data written to the + * stream, writing the encoded/decoded data to another + * OutputStream. + * + * @param out the OutputStream to write the encoded data to + * @param flags bit flags for controlling the encoder; see the + * constants in {@link Base64} + * @param encode true to encode, false to decode + * + * @hide + */ + public Base64OutputStream(OutputStream out, int flags, boolean encode) { + super(out); + this.flags = flags; + if (encode) { + coder = new Base64.Encoder(flags, null); + } else { + coder = new Base64.Decoder(flags, null); + } + } + public void write(int b) throws IOException { + // To avoid invoking the encoder/decoder routines for single + // bytes, we buffer up calls to write(int) in an internal + // byte array to transform them into writes of decently-sized + // arrays. + if (buffer == null) { + buffer = new byte[1024]; + } + if (bpos >= buffer.length) { + // internal buffer full; write it out. + internalWrite(buffer, 0, bpos, false); + bpos = 0; + } + buffer[bpos++] = (byte) b; + } + /** + * Flush any buffered data from calls to write(int). Needed + * before doing a write(byte[], int, int) or a close(). + */ + private void flushBuffer() throws IOException { + if (bpos > 0) { + internalWrite(buffer, 0, bpos, false); + bpos = 0; + } + } + public void write(byte[] b, int off, int len) throws IOException { + if (len <= 0) return; + flushBuffer(); + internalWrite(b, off, len, false); + } + public void close() throws IOException { + IOException thrown = null; + try { + flushBuffer(); + internalWrite(EMPTY, 0, 0, true); + } catch (IOException e) { + thrown = e; + } + try { + if ((flags & Base64.NO_CLOSE) == 0) { + out.close(); + } else { + out.flush(); + } + } catch (IOException e) { + if (thrown != null) { + thrown = e; + } + } + if (thrown != null) { + throw thrown; + } + } + /** + * Write the given bytes to the encoder/decoder. + * + * @param finish true if this is the last batch of input, to cause + * encoder/decoder state to be finalized. + */ + private void internalWrite(byte[] b, int off, int len, boolean finish) throws IOException { + coder.output = embiggen(coder.output, coder.maxOutputSize(len)); + if (!coder.process(b, off, len, finish)) { + throw new IOException("bad base-64"); + } + out.write(coder.output, 0, coder.op); + } + /** + * If b.length is at least len, return b. Otherwise return a new + * byte array of length len. + */ + private byte[] embiggen(byte[] b, int len) { + if (b == null || b.length < len) { + return new byte[len]; + } else { + return b; + } + } +} \ No newline at end of file diff --git a/raven/src/main/java/com/getsentry/raven/util/Util.java b/raven/src/main/java/com/getsentry/raven/util/Util.java new file mode 100644 index 00000000000..183a1beaae0 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/util/Util.java @@ -0,0 +1,51 @@ +package com.getsentry.raven.util; + +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Raven static Utility class. + */ +public final class Util { + + // Hide the constructor. + private Util() { + + } + + /** + * Returns {@code true} if the given string is null or is the empty string. + * + * @param string a string reference to check + * @return {@code true} if the string is null or is the empty string + */ + public static boolean isNullOrEmpty(@Nullable String string) { + return string == null || string.length() == 0; // string.isEmpty() in Java 6 + } + + /** + * Parses the provided tags string into a Map of String -> String. + * + * @param tagsString comma-delimited key-value pairs, e.g. "tag1:value1,tag2:value2". + * @return Map of tags e.g. (tag1 -> value1, tag2 -> value2) + */ + public static Map parseTags(String tagsString) { + if (isNullOrEmpty(tagsString)) { + return Collections.emptyMap(); + } + + String[] entries = tagsString.split(","); + Map map = new LinkedHashMap(); + for (String entry : entries) { + String[] split = entry.split(":"); + if (split.length != 2) { + throw new IllegalArgumentException("Invalid tags entry: " + entry); + } + map.put(split[0], split[1]); + } + return map; + } + +} diff --git a/raven/src/test/java/com/getsentry/raven/RavenFactoryTest.java b/raven/src/test/java/com/getsentry/raven/RavenFactoryTest.java index 1c57c96b349..3058e4703c5 100644 --- a/raven/src/test/java/com/getsentry/raven/RavenFactoryTest.java +++ b/raven/src/test/java/com/getsentry/raven/RavenFactoryTest.java @@ -1,14 +1,16 @@ package com.getsentry.raven; -import com.google.common.collect.Iterators; import mockit.*; import com.getsentry.raven.dsn.Dsn; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.ServiceLoader; import static mockit.Deencapsulation.setField; @@ -28,7 +30,7 @@ public void setUp() throws Exception { new NonStrictExpectations() {{ mockServiceLoader.iterator(); - result = Iterators.emptyIterator(); + result = Collections.emptyIterator(); }}; } @@ -47,7 +49,7 @@ public void testGetFactoriesFromServiceLoader(@Injectable final Raven mockRaven, result = new Delegate>() { @SuppressWarnings("unused") public Iterator iterator() { - return Iterators.singletonIterator(ravenFactory); + return Collections.singletonList(ravenFactory).iterator(); } }; ravenFactory.createRavenInstance(mockDsn); diff --git a/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonComparisonUtil.java b/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonComparisonUtil.java index 8a3e6c82912..b641133a9dd 100644 --- a/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonComparisonUtil.java +++ b/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonComparisonUtil.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Charsets; import java.io.ByteArrayOutputStream; import java.io.OutputStream; @@ -40,7 +39,7 @@ public JsonNode value() throws Exception { public String toString() { closeStream(); try { - return outputStream.toString(Charsets.UTF_8.name()); + return outputStream.toString("UTF-8"); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java index 0e43362f5f7..23e11de2bbf 100644 --- a/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java @@ -1,7 +1,6 @@ package com.getsentry.raven.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; -import com.google.common.base.Charsets; import mockit.*; import com.getsentry.raven.event.Event; import com.getsentry.raven.event.interfaces.SentryInterface; @@ -296,7 +295,7 @@ public void testCompressedDataIsWorking() throws Exception { jsonMarshaller.marshall(mockEvent, outputStream); - assertThat(new String(outputStream.toByteArray(), Charsets.UTF_8.name()), is("" + assertThat(new String(outputStream.toByteArray(), "UTF-8"), is("" + "eJyFjU0KAjEMhe+SdYXOSuw53A+hxlpMOyVpB2GYu" + "1vU6dbwNl94PxvQSrnO8QYO7J8DA4lUMRC43JgN1N" + "gfFVPp6elytic7dV2tdR/1APd+Puy8hEByUGGs90X" diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 2e1995736a3..719a0f35f87 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -19,10 +19,6 @@ - - com.google.guava - guava - com.fasterxml.jackson.core jackson-core diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/unmarshaller/JsonDecoder.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/unmarshaller/JsonDecoder.java index fd7da42337b..6651d051632 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/unmarshaller/JsonDecoder.java +++ b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/unmarshaller/JsonDecoder.java @@ -2,17 +2,16 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; +import com.getsentry.raven.sentrystub.util.Base64; +import com.getsentry.raven.sentrystub.util.Base64InputStream; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.InflaterInputStream; -import static com.google.common.io.BaseEncoding.base64; - /** * Decodes a Stream as a JSON stream. *

    @@ -43,12 +42,12 @@ public InputStream decapsulateContent(InputStream originalStream) throws IOExcep originalStream.mark(messageSize); InputStream inputStream = originalStream; if (!isJson(originalStream)) { - inputStream = base64().decodingStream(new InputStreamReader(inputStream)); + inputStream = new Base64InputStream(inputStream, Base64.NO_WRAP); originalStream.reset(); - if (!isJson(base64().decodingStream(new InputStreamReader(originalStream)))) { + if (!isJson(new Base64InputStream(originalStream, Base64.NO_WRAP))) { inputStream = new InflaterInputStream(inputStream); originalStream.reset(); - if (!isJson(new InflaterInputStream(base64().decodingStream(new InputStreamReader(originalStream))))) { + if (!isJson(new InflaterInputStream(new Base64InputStream(originalStream, Base64.NO_WRAP)))) { throw new IllegalArgumentException("The given Stream is neither JSON, Base64'd JSON " + "nor Base64'd deflated JSON."); } diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64.java new file mode 100644 index 00000000000..13d25116d1a --- /dev/null +++ b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64.java @@ -0,0 +1,663 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.getsentry.raven.sentrystub.util; + +import java.io.UnsupportedEncodingException; + +/** + * Utilities for encoding and decoding the Base64 representation of + * binary data. See RFCs 2045 and 3548. + */ +public class Base64 { + /** + * Default values for encoder/decoder flags. + */ + public static final int DEFAULT = 0; + /** + * Encoder flag bit to omit the padding '=' characters at the end + * of the output (if any). + */ + public static final int NO_PADDING = 1; + /** + * Encoder flag bit to omit all line terminators (i.e., the output + * will be on one long line). + */ + public static final int NO_WRAP = 2; + /** + * Encoder flag bit to indicate lines should be terminated with a + * CRLF pair instead of just an LF. Has no effect if {@code + * NO_WRAP} is specified as well. + */ + public static final int CRLF = 4; + /** + * Encoder/decoder flag bit to indicate using the "URL and + * filename safe" variant of Base64 (see RFC 3548 section 4) where + * {@code -} and {@code _} are used in place of {@code +} and + * {@code /}. + */ + public static final int URL_SAFE = 8; + /** + * Flag to pass to {@link Base64OutputStream} to indicate that it + * should not close the output stream it is wrapping when it + * itself is closed. + */ + public static final int NO_CLOSE = 16; + // -------------------------------------------------------- + // shared code + // -------------------------------------------------------- + /* package */ static abstract class Coder { + public byte[] output; + public int op; + /** + * Encode/decode another block of input data. this.output is + * provided by the caller, and must be big enough to hold all + * the coded data. On exit, this.opwill be set to the length + * of the coded data. + * + * @param finish true if this is the final call to process for + * this object. Will finalize the coder state and + * include any final bytes in the output. + * + * @return true if the input so far is good; false if some + * error has been detected in the input stream.. + */ + public abstract boolean process(byte[] input, int offset, int len, boolean finish); + /** + * @return the maximum number of bytes a call to process() + * could produce for the given number of input bytes. This may + * be an overestimate. + */ + public abstract int maxOutputSize(int len); + } + // -------------------------------------------------------- + // decoding + // -------------------------------------------------------- + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

    The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param str the input String to decode, which is converted to + * bytes using the default charset + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(String str, int flags) { + return decode(str.getBytes(), flags); + } + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

    The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param input the input array to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(byte[] input, int flags) { + return decode(input, 0, input.length, flags); + } + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

    The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param input the data to decode + * @param offset the position within the input array at which to start + * @param len the number of bytes of input to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(byte[] input, int offset, int len, int flags) { + // Allocate space for the most data the input could represent. + // (It could contain less if it contains whitespace, etc.) + Decoder decoder = new Decoder(flags, new byte[len*3/4]); + if (!decoder.process(input, offset, len, true)) { + throw new IllegalArgumentException("bad base-64"); + } + // Maybe we got lucky and allocated exactly enough output space. + if (decoder.op == decoder.output.length) { + return decoder.output; + } + // Need to shorten the array, so allocate a new one of the + // right size and copy. + byte[] temp = new byte[decoder.op]; + System.arraycopy(decoder.output, 0, temp, 0, decoder.op); + return temp; + } + /* package */ static class Decoder extends Coder { + /** + * Lookup table for turning bytes into their position in the + * Base64 alphabet. + */ + private static final int DECODE[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + /** + * Decode lookup table for the "web safe" variant (RFC 3548 + * sec. 4) where - and _ replace + and /. + */ + private static final int DECODE_WEBSAFE[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + /** Non-data values in the DECODE arrays. */ + private static final int SKIP = -1; + private static final int EQUALS = -2; + /** + * States 0-3 are reading through the next input tuple. + * State 4 is having read one '=' and expecting exactly + * one more. + * State 5 is expecting no more data or padding characters + * in the input. + * State 6 is the error state; an error has been detected + * in the input and no future input can "fix" it. + */ + private int state; // state number (0 to 6) + private int value; + final private int[] alphabet; + public Decoder(int flags, byte[] output) { + this.output = output; + alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE; + state = 0; + value = 0; + } + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could decode to. + */ + public int maxOutputSize(int len) { + return len * 3/4 + 10; + } + /** + * Decode another block of input data. + * + * @return true if the state machine is still healthy. false if + * bad base-64 data has been detected in the input stream. + */ + public boolean process(byte[] input, int offset, int len, boolean finish) { + if (this.state == 6) return false; + int p = offset; + len += offset; + // Using local variables makes the decoder about 12% + // faster than if we manipulate the member variables in + // the loop. (Even alphabet makes a measurable + // difference, which is somewhat surprising to me since + // the member variable is final.) + int state = this.state; + int value = this.value; + int op = 0; + final byte[] output = this.output; + final int[] alphabet = this.alphabet; + while (p < len) { + // Try the fast path: we're starting a new tuple and the + // next four bytes of the input stream are all data + // bytes. This corresponds to going through states + // 0-1-2-3-0. We expect to use this method for most of + // the data. + // + // If any of the next four bytes of input are non-data + // (whitespace, etc.), value will end up negative. (All + // the non-data values in decode are small negative + // numbers, so shifting any of them up and or'ing them + // together will result in a value with its top bit set.) + // + // You can remove this whole block and the output should + // be the same, just slower. + if (state == 0) { + while (p+4 <= len && + (value = ((alphabet[input[p] & 0xff] << 18) | + (alphabet[input[p+1] & 0xff] << 12) | + (alphabet[input[p+2] & 0xff] << 6) | + (alphabet[input[p+3] & 0xff]))) >= 0) { + output[op+2] = (byte) value; + output[op+1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + p += 4; + } + if (p >= len) break; + } + // The fast path isn't available -- either we've read a + // partial tuple, or the next four input bytes aren't all + // data, or whatever. Fall back to the slower state + // machine implementation. + int d = alphabet[input[p++] & 0xff]; + switch (state) { + case 0: + if (d >= 0) { + value = d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 1: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 2: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect exactly one more padding character. + output[op++] = (byte) (value >> 4); + state = 4; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 3: + if (d >= 0) { + // Emit the output triple and return to state 0. + value = (value << 6) | d; + output[op+2] = (byte) value; + output[op+1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + state = 0; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect no further data or padding characters. + output[op+1] = (byte) (value >> 2); + output[op] = (byte) (value >> 10); + op += 2; + state = 5; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 4: + if (d == EQUALS) { + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 5: + if (d != SKIP) { + this.state = 6; + return false; + } + break; + } + } + if (!finish) { + // We're out of input, but a future call could provide + // more. + this.state = state; + this.value = value; + this.op = op; + return true; + } + // Done reading input. Now figure out where we are left in + // the state machine and finish up. + switch (state) { + case 0: + // Output length is a multiple of three. Fine. + break; + case 1: + // Read one extra input byte, which isn't enough to + // make another output byte. Illegal. + this.state = 6; + return false; + case 2: + // Read two extra input bytes, enough to emit 1 more + // output byte. Fine. + output[op++] = (byte) (value >> 4); + break; + case 3: + // Read three extra input bytes, enough to emit 2 more + // output bytes. Fine. + output[op++] = (byte) (value >> 10); + output[op++] = (byte) (value >> 2); + break; + case 4: + // Read one padding '=' when we expected 2. Illegal. + this.state = 6; + return false; + case 5: + // Read all the padding '='s we expected and no more. + // Fine. + break; + } + this.state = state; + this.op = op; + return true; + } + } + // -------------------------------------------------------- + // encoding + // -------------------------------------------------------- + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString(byte[] input, int flags) { + try { + return new String(encode(input, flags), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + // US-ASCII is guaranteed to be available. + throw new AssertionError(e); + } + } + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString(byte[] input, int offset, int len, int flags) { + try { + return new String(encode(input, offset, len, flags), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + // US-ASCII is guaranteed to be available. + throw new AssertionError(e); + } + } + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode(byte[] input, int flags) { + return encode(input, 0, input.length, flags); + } + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode(byte[] input, int offset, int len, int flags) { + Encoder encoder = new Encoder(flags, null); + // Compute the exact length of the array we will produce. + int output_len = len / 3 * 4; + // Account for the tail of the data and the padding bytes, if any. + if (encoder.do_padding) { + if (len % 3 > 0) { + output_len += 4; + } + } else { + switch (len % 3) { + case 0: break; + case 1: output_len += 2; break; + case 2: output_len += 3; break; + } + } + // Account for the newlines, if any. + if (encoder.do_newline && len > 0) { + output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) * + (encoder.do_cr ? 2 : 1); + } + encoder.output = new byte[output_len]; + encoder.process(input, offset, len, true); + assert encoder.op == output_len; + return encoder.output; + } + /* package */ static class Encoder extends Coder { + /** + * Emit a new line every this many output tuples. Corresponds to + * a 76-character line length (the maximum allowable according to + * RFC 2045). + */ + public static final int LINE_GROUPS = 19; + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte ENCODE[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', + }; + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte ENCODE_WEBSAFE[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', + }; + final private byte[] tail; + /* package */ int tailLen; + private int count; + final public boolean do_padding; + final public boolean do_newline; + final public boolean do_cr; + final private byte[] alphabet; + public Encoder(int flags, byte[] output) { + this.output = output; + do_padding = (flags & NO_PADDING) == 0; + do_newline = (flags & NO_WRAP) == 0; + do_cr = (flags & CRLF) != 0; + alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE; + tail = new byte[2]; + tailLen = 0; + count = do_newline ? LINE_GROUPS : -1; + } + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could encode to. + */ + public int maxOutputSize(int len) { + return len * 8/5 + 10; + } + public boolean process(byte[] input, int offset, int len, boolean finish) { + // Using local variables makes the encoder about 9% faster. + final byte[] alphabet = this.alphabet; + final byte[] output = this.output; + int op = 0; + int count = this.count; + int p = offset; + len += offset; + int v = -1; + // First we need to concatenate the tail of the previous call + // with any input bytes available now and see if we can empty + // the tail. + switch (tailLen) { + case 0: + // There was no tail. + break; + case 1: + if (p+2 <= len) { + // A 1-byte tail with at least 2 bytes of + // input available now. + v = ((tail[0] & 0xff) << 16) | + ((input[p++] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + }; + break; + case 2: + if (p+1 <= len) { + // A 2-byte tail with at least 1 byte of input. + v = ((tail[0] & 0xff) << 16) | + ((tail[1] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + } + break; + } + if (v != -1) { + output[op++] = alphabet[(v >> 18) & 0x3f]; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + // At this point either there is no tail, or there are fewer + // than 3 bytes of input available. + // The main loop, turning 3 input bytes into 4 output bytes on + // each iteration. + while (p+3 <= len) { + v = ((input[p] & 0xff) << 16) | + ((input[p+1] & 0xff) << 8) | + (input[p+2] & 0xff); + output[op] = alphabet[(v >> 18) & 0x3f]; + output[op+1] = alphabet[(v >> 12) & 0x3f]; + output[op+2] = alphabet[(v >> 6) & 0x3f]; + output[op+3] = alphabet[v & 0x3f]; + p += 3; + op += 4; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + if (finish) { + // Finish up the tail of the input. Note that we need to + // consume any bytes in tail before any bytes + // remaining in input; there should be at most two bytes + // total. + if (p-tailLen == len-1) { + int t = 0; + v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4; + tailLen -= t; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (p-tailLen == len-2) { + int t = 0; + v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) | + (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2); + tailLen -= t; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (do_newline && op > 0 && count != LINE_GROUPS) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + assert tailLen == 0; + assert p == len; + } else { + // Save the leftovers in tail to be consumed on the next + // call to encodeInternal. + if (p == len-1) { + tail[tailLen++] = input[p]; + } else if (p == len-2) { + tail[tailLen++] = input[p]; + tail[tailLen++] = input[p+1]; + } + } + this.op = op; + this.count = count; + return true; + } + } + private Base64() { } // don't instantiate +} \ No newline at end of file diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64InputStream.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64InputStream.java new file mode 100644 index 00000000000..a7fbfe74173 --- /dev/null +++ b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64InputStream.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.getsentry.raven.sentrystub.util; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * An InputStream that does Base64 decoding on the data read through + * it. + */ +public class Base64InputStream extends FilterInputStream { + private final Base64.Coder coder; + private static byte[] EMPTY = new byte[0]; + private static final int BUFFER_SIZE = 2048; + private boolean eof; + private byte[] inputBuffer; + private int outputStart; + private int outputEnd; + /** + * An InputStream that performs Base64 decoding on the data read + * from the wrapped stream. + * + * @param in the InputStream to read the source data from + * @param flags bit flags for controlling the decoder; see the + * constants in {@link Base64} + */ + public Base64InputStream(InputStream in, int flags) { + this(in, flags, false); + } + /** + * Performs Base64 encoding or decoding on the data read from the + * wrapped InputStream. + * + * @param in the InputStream to read the source data from + * @param flags bit flags for controlling the decoder; see the + * constants in {@link Base64} + * @param encode true to encode, false to decode + * + * @hide + */ + public Base64InputStream(InputStream in, int flags, boolean encode) { + super(in); + eof = false; + inputBuffer = new byte[BUFFER_SIZE]; + if (encode) { + coder = new Base64.Encoder(flags, null); + } else { + coder = new Base64.Decoder(flags, null); + } + coder.output = new byte[coder.maxOutputSize(BUFFER_SIZE)]; + outputStart = 0; + outputEnd = 0; + } + public boolean markSupported() { + return false; + } + public void mark(int readlimit) { + throw new UnsupportedOperationException(); + } + public void reset() { + throw new UnsupportedOperationException(); + } + public void close() throws IOException { + in.close(); + inputBuffer = null; + } + public int available() { + return outputEnd - outputStart; + } + public long skip(long n) throws IOException { + if (outputStart >= outputEnd) { + refill(); + } + if (outputStart >= outputEnd) { + return 0; + } + long bytes = Math.min(n, outputEnd-outputStart); + outputStart += bytes; + return bytes; + } + public int read() throws IOException { + if (outputStart >= outputEnd) { + refill(); + } + if (outputStart >= outputEnd) { + return -1; + } else { + return coder.output[outputStart++] & 0xff; + } + } + public int read(byte[] b, int off, int len) throws IOException { + if (outputStart >= outputEnd) { + refill(); + } + if (outputStart >= outputEnd) { + return -1; + } + int bytes = Math.min(len, outputEnd-outputStart); + System.arraycopy(coder.output, outputStart, b, off, bytes); + outputStart += bytes; + return bytes; + } + /** + * Read data from the input stream into inputBuffer, then + * decode/encode it into the empty coder.output, and reset the + * outputStart and outputEnd pointers. + */ + private void refill() throws IOException { + if (eof) return; + int bytesRead = in.read(inputBuffer); + boolean success; + if (bytesRead == -1) { + eof = true; + success = coder.process(EMPTY, 0, 0, true); + } else { + success = coder.process(inputBuffer, 0, bytesRead, false); + } + if (!success) { + throw new IOException("bad base-64"); + } + outputEnd = coder.op; + outputStart = 0; + } +} \ No newline at end of file diff --git a/src/checkstyle/suppressions.xml b/src/checkstyle/suppressions.xml index 9fa69a9e33e..827607b7921 100644 --- a/src/checkstyle/suppressions.xml +++ b/src/checkstyle/suppressions.xml @@ -5,4 +5,5 @@ + From 7795cbec233d6aa614cd26fe5f1e73e72e3e7e1f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 21 Apr 2016 17:21:34 -0500 Subject: [PATCH 1406/2152] Bump CHANGES and docs to 7.2.1 --- docs/modules/appengine.rst | 2 +- docs/modules/log4j.rst | 2 +- docs/modules/log4j2.rst | 2 +- docs/modules/logback.rst | 2 +- docs/modules/raven.rst | 2 +- raven-appengine/README.md | 4 ++-- raven-log4j/README.md | 4 ++-- raven-log4j2/README.md | 4 ++-- raven-logback/README.md | 4 ++-- raven/README.md | 4 ++-- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 3b6759813aa..b2ee5d424ea 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -25,7 +25,7 @@ If you want to use Maven you can install Raven-AppEngine as dependency: com.getsentry.raven raven-appengine - 7.2.0 + 7.2.1 Usage diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 6f7c6e920af..96604a84d89 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven-Log4j as dependency: com.getsentry.raven raven-log4j - 7.2.0 + 7.2.1 If you manually want to manage your dependencies: diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index eae0924dc83..d2d122fdc20 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven-Log4j2 as dependency: com.getsentry.raven raven-log4j2 - 7.2.0 + 7.2.1 If you manually want to manage your dependencies: diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index b6ff847ba01..adba22e7336 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -16,7 +16,7 @@ If you want to use Maven you can install Raven-Logback as dependency: com.getsentry.raven raven-logback - 7.2.0 + 7.2.1 - :doc:`raven dependencies ` diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index f15733a1c75..f5d7c4d6f95 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven as dependency: com.getsentry.raven raven - 7.2.0 + 7.2.1 If you manually want to manage your dependencies: diff --git a/raven-appengine/README.md b/raven-appengine/README.md index 359aacf0f2c..dfb73107a93 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -15,12 +15,12 @@ __This module is not useful outside of Google App Engine.__ com.getsentry.raven raven-appengine - 7.2.0 + 7.2.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.2.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.2.1%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 814c2658b66..f8bbfa3dd70 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,12 +10,12 @@ for log4j to send the logged events to Sentry. com.getsentry.raven raven-log4j - 7.2.0 + 7.2.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.2.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.2.1%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index 59996883a78..acf8dfcf118 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,12 +10,12 @@ for Log4j 2 to send the logged events to Sentry. com.getsentry.raven raven-log4j2 - 7.2.0 + 7.2.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.2.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.2.1%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-logback/README.md b/raven-logback/README.md index 192bf933511..b48982b5182 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,12 +10,12 @@ for logback to send the logged events to Sentry. com.getsentry.raven raven-logback - 7.2.0 + 7.2.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.2.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.2.1%7Cjar). ### Manual dependency management Relies on: diff --git a/raven/README.md b/raven/README.md index 275708d9b5d..f56061146af 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,12 +10,12 @@ for `java.util.logging`. com.getsentry.raven raven - 7.2.0 + 7.2.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.2.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.2.1%7Cjar). ### Manual dependency management Relies on: From 692c69bf88b2aaaf875d059ff999b7f97ef68192 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 22 Apr 2016 13:03:14 -0500 Subject: [PATCH 1407/2152] Fix JavaDoc and checkstyle for new classes. --- pom.xml | 5 ++++- .../java/com/getsentry/raven/util/Base64.java | 14 +++++++++++++ .../raven/util/Base64OutputStream.java | 2 -- .../java/com/getsentry/raven/util/Util.java | 4 ++-- .../raven/sentrystub/util/Base64.java | 20 +++++++++++++------ .../sentrystub/util/Base64InputStream.java | 2 -- 6 files changed, 34 insertions(+), 13 deletions(-) diff --git a/pom.xml b/pom.xml index 412bae10190..586eb4e4708 100644 --- a/pom.xml +++ b/pom.xml @@ -420,7 +420,10 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.10.1 + 2.10.3 + + com.getsentry.raven.util.base64*:com.getsentry.raven.sentrystub.util.base64 + org.apache.maven.plugins diff --git a/raven/src/main/java/com/getsentry/raven/util/Base64.java b/raven/src/main/java/com/getsentry/raven/util/Base64.java index 6cc527a8294..20f54474122 100644 --- a/raven/src/main/java/com/getsentry/raven/util/Base64.java +++ b/raven/src/main/java/com/getsentry/raven/util/Base64.java @@ -101,6 +101,8 @@ public class Base64 { * * @throws IllegalArgumentException if the input contains * incorrect padding + * + * @return byte[] */ public static byte[] decode(String str, int flags) { return decode(str.getBytes(), flags); @@ -118,6 +120,8 @@ public static byte[] decode(String str, int flags) { * * @throws IllegalArgumentException if the input contains * incorrect padding + * + * @return byte[] */ public static byte[] decode(byte[] input, int flags) { return decode(input, 0, input.length, flags); @@ -137,6 +141,8 @@ public static byte[] decode(byte[] input, int flags) { * * @throws IllegalArgumentException if the input contains * incorrect padding + * + * @return byte[] */ public static byte[] decode(byte[] input, int offset, int len, int flags) { // Allocate space for the most data the input could represent. @@ -407,6 +413,8 @@ public boolean process(byte[] input, int offset, int len, boolean finish) { * @param flags controls certain features of the encoded output. * Passing {@code DEFAULT} results in output that * adheres to RFC 2045. + * + * @return String */ public static String encodeToString(byte[] input, int flags) { try { @@ -427,6 +435,8 @@ public static String encodeToString(byte[] input, int flags) { * @param flags controls certain features of the encoded output. * Passing {@code DEFAULT} results in output that * adheres to RFC 2045. + * + * @return String */ public static String encodeToString(byte[] input, int offset, int len, int flags) { try { @@ -444,6 +454,8 @@ public static String encodeToString(byte[] input, int offset, int len, int flags * @param flags controls certain features of the encoded output. * Passing {@code DEFAULT} results in output that * adheres to RFC 2045. + * + * @return byte[] */ public static byte[] encode(byte[] input, int flags) { return encode(input, 0, input.length, flags); @@ -459,6 +471,8 @@ public static byte[] encode(byte[] input, int flags) { * @param flags controls certain features of the encoded output. * Passing {@code DEFAULT} results in output that * adheres to RFC 2045. + * + * @return byte[] */ public static byte[] encode(byte[] input, int offset, int len, int flags) { Encoder encoder = new Encoder(flags, null); diff --git a/raven/src/main/java/com/getsentry/raven/util/Base64OutputStream.java b/raven/src/main/java/com/getsentry/raven/util/Base64OutputStream.java index c1ed703c023..61612f6ac20 100644 --- a/raven/src/main/java/com/getsentry/raven/util/Base64OutputStream.java +++ b/raven/src/main/java/com/getsentry/raven/util/Base64OutputStream.java @@ -50,8 +50,6 @@ public Base64OutputStream(OutputStream out, int flags) { * @param flags bit flags for controlling the encoder; see the * constants in {@link Base64} * @param encode true to encode, false to decode - * - * @hide */ public Base64OutputStream(OutputStream out, int flags, boolean encode) { super(out); diff --git a/raven/src/main/java/com/getsentry/raven/util/Util.java b/raven/src/main/java/com/getsentry/raven/util/Util.java index 183a1beaae0..a17addd54ed 100644 --- a/raven/src/main/java/com/getsentry/raven/util/Util.java +++ b/raven/src/main/java/com/getsentry/raven/util/Util.java @@ -26,10 +26,10 @@ public static boolean isNullOrEmpty(@Nullable String string) { } /** - * Parses the provided tags string into a Map of String -> String. + * Parses the provided tags string into a Map of String -> String. * * @param tagsString comma-delimited key-value pairs, e.g. "tag1:value1,tag2:value2". - * @return Map of tags e.g. (tag1 -> value1, tag2 -> value2) + * @return Map of tags e.g. (tag1 -> value1, tag2 -> value2) */ public static Map parseTags(String tagsString) { if (isNullOrEmpty(tagsString)) { diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64.java index 13d25116d1a..ab3a5da0f35 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64.java +++ b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64.java @@ -51,12 +51,6 @@ public class Base64 { * {@code /}. */ public static final int URL_SAFE = 8; - /** - * Flag to pass to {@link Base64OutputStream} to indicate that it - * should not close the output stream it is wrapping when it - * itself is closed. - */ - public static final int NO_CLOSE = 16; // -------------------------------------------------------- // shared code // -------------------------------------------------------- @@ -101,6 +95,8 @@ public class Base64 { * * @throws IllegalArgumentException if the input contains * incorrect padding + * + * @return byte[] */ public static byte[] decode(String str, int flags) { return decode(str.getBytes(), flags); @@ -118,6 +114,8 @@ public static byte[] decode(String str, int flags) { * * @throws IllegalArgumentException if the input contains * incorrect padding + * + * @return byte[] */ public static byte[] decode(byte[] input, int flags) { return decode(input, 0, input.length, flags); @@ -137,6 +135,8 @@ public static byte[] decode(byte[] input, int flags) { * * @throws IllegalArgumentException if the input contains * incorrect padding + * + * @return byte[] */ public static byte[] decode(byte[] input, int offset, int len, int flags) { // Allocate space for the most data the input could represent. @@ -407,6 +407,8 @@ public boolean process(byte[] input, int offset, int len, boolean finish) { * @param flags controls certain features of the encoded output. * Passing {@code DEFAULT} results in output that * adheres to RFC 2045. + * + * @return String */ public static String encodeToString(byte[] input, int flags) { try { @@ -427,6 +429,8 @@ public static String encodeToString(byte[] input, int flags) { * @param flags controls certain features of the encoded output. * Passing {@code DEFAULT} results in output that * adheres to RFC 2045. + * + * @return String */ public static String encodeToString(byte[] input, int offset, int len, int flags) { try { @@ -444,6 +448,8 @@ public static String encodeToString(byte[] input, int offset, int len, int flags * @param flags controls certain features of the encoded output. * Passing {@code DEFAULT} results in output that * adheres to RFC 2045. + * + * @return byte[] */ public static byte[] encode(byte[] input, int flags) { return encode(input, 0, input.length, flags); @@ -459,6 +465,8 @@ public static byte[] encode(byte[] input, int flags) { * @param flags controls certain features of the encoded output. * Passing {@code DEFAULT} results in output that * adheres to RFC 2045. + * + * @return byte[] */ public static byte[] encode(byte[] input, int offset, int len, int flags) { Encoder encoder = new Encoder(flags, null); diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64InputStream.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64InputStream.java index a7fbfe74173..1e6adf67448 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64InputStream.java +++ b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64InputStream.java @@ -51,8 +51,6 @@ public Base64InputStream(InputStream in, int flags) { * @param flags bit flags for controlling the decoder; see the * constants in {@link Base64} * @param encode true to encode, false to decode - * - * @hide */ public Base64InputStream(InputStream in, int flags, boolean encode) { super(in); From 69e7ae498dd97fdc54a076186e545ac23c15f37c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 22 Apr 2016 13:07:36 -0500 Subject: [PATCH 1408/2152] [maven-release-plugin] prepare release v7.2.1 --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 586eb4e4708..aabb282bf0c 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.2.1-SNAPSHOT + 7.2.1 pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v7.2.1 https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 85936c06dab..b10fe6b2f7a 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.2.1-SNAPSHOT + 7.2.1 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 4b7e85e742c..530f4d07ea3 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.1-SNAPSHOT + 7.2.1 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 7926fd03700..0078cb8ad45 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.1-SNAPSHOT + 7.2.1 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 969c35152e6..198d5119ff2 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.1-SNAPSHOT + 7.2.1 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index bf501b41bbf..8fa23990d8e 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.1-SNAPSHOT + 7.2.1 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 719a0f35f87..0aaf54c11b1 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.1-SNAPSHOT + 7.2.1 sentry-stub From ea7b8392fa2db2d7351aaecade3a3ab46efa5fc2 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 22 Apr 2016 13:07:37 -0500 Subject: [PATCH 1409/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index aabb282bf0c..cb8cf1e2c7a 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.2.1 + 7.2.2-SNAPSHOT pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v7.2.1 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index b10fe6b2f7a..17aba53a12f 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.2.1 + 7.2.2-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 530f4d07ea3..c58baada9d1 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.1 + 7.2.2-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 0078cb8ad45..17d244bdab9 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.1 + 7.2.2-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 198d5119ff2..c2826451ce9 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.1 + 7.2.2-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 8fa23990d8e..5a49d9ad15b 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.1 + 7.2.2-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 0aaf54c11b1..c1c116eb39b 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.1 + 7.2.2-SNAPSHOT sentry-stub From 19fc9e32dbf078b2261a45264ffdce28f0fdcaaa Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 29 Apr 2016 07:53:33 -0500 Subject: [PATCH 1410/2152] Improve configuration docs, add setServerName method for parity with other options. (#202) --- raven-log4j/README.md | 7 ++++++- .../getsentry/raven/log4j/SentryAppender.java | 4 ++++ raven-log4j2/README.md | 17 ++++++++++++++++- raven-logback/README.md | 11 +++++++---- .../getsentry/raven/logback/SentryAppender.java | 4 ++++ raven/README.md | 9 +++++++-- .../com/getsentry/raven/jul/SentryHandler.java | 4 ++++ 7 files changed, 48 insertions(+), 8 deletions(-) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index f8bbfa3dd70..3ee16c53c9c 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -33,8 +33,13 @@ In the `log4j.properties` file set: log4j.rootLogger=WARN, SentryAppender log4j.appender.SentryAppender=com.getsentry.raven.log4j.SentryAppender log4j.appender.SentryAppender.dsn=https://publicKey:secretKey@host:port/1?options +// Optional, provide tags log4j.appender.SentryAppender.tags=tag1:value1,tag2:value2 -# Optional, allows to select the ravenFactory +// Optional, provide release version of your application +log4j.appender.SentryAppender.release=1.0.0 +// Optional, override the server name (rather than looking it up dynamically) +log4j.appender.SentryAppender.serverName=server1 +# Optional, select the ravenFactory class #log4j.appender.SentryAppender.ravenFactory=com.getsentry.raven.DefaultRavenFactory ``` diff --git a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java index 8c4ccf689a1..3eb20f49503 100644 --- a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java @@ -240,6 +240,10 @@ public void setRelease(String release) { this.release = release; } + public void setServerName(String serverName) { + this.serverName = serverName; + } + /** * Set the tags that should be sent along with the events. * diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index acf8dfcf118..c15eadb91ae 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -39,11 +39,26 @@ In the `log4j2.xml` file set: https://publicKey:secretKey@host:port/1?options + tag1:value1,tag2:value2 + + 1.0.0 + + + + server1 + + tag1:value1,tag2:value2 - - - - + 1.0.0 + + server1 + + com.getsentry.raven.DefaultRavenFactory diff --git a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java index 37febffe7b2..de79ca9c9f4 100644 --- a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java @@ -320,6 +320,10 @@ public void setRelease(String release) { this.release = release; } + public void setServerName(String serverName) { + this.serverName = serverName; + } + public void setMinLevel(String minLevel) { this.minLevel = Level.toLevel(minLevel, Level.WARN); } diff --git a/raven/README.md b/raven/README.md index f56061146af..a68590e79b2 100644 --- a/raven/README.md +++ b/raven/README.md @@ -35,9 +35,14 @@ In the `logging.properties` file set: .level=WARN handlers=com.getsentry.raven.jul.SentryHandler com.getsentry.raven.jul.SentryHandler.dsn=https://publicKey:secretKey@host:port/1?options +# Optional, provide tags com.getsentry.raven.jul.SentryHandler.tags=tag1:value1,tag2:value2 -# Optional, allows to select the ravenFactory -#com.getsentry.raven.jul.SentryHandler.ravenFactory=com.getsentry.raven.DefaultRavenFactory +# Optional, provide release version of your application +com.getsentry.raven.jul.SentryHandler.release=1.0.0 +# Optional, override the server name (rather than looking it up dynamically) +com.getsentry.raven.jul.SentryHandler.serverName=server1 +# Optional, select the ravenFactory class +com.getsentry.raven.jul.SentryHandler.ravenFactory=com.getsentry.raven.DefaultRavenFactory ``` When starting your application, add the `java.util.logging.config.file` to the diff --git a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java index 19400bb8a63..8b60fa36329 100644 --- a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java +++ b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java @@ -297,4 +297,8 @@ public void close() throws SecurityException { public void setRelease(String release) { this.release = release; } + + public void setServerName(String serverName) { + this.serverName = serverName; + } } From ef089cfc27cd5b649d3e7a062d8c3091f7672413 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 2 May 2016 16:35:14 -0500 Subject: [PATCH 1411/2152] Bump CHANGES and docs to 7.2.2 --- CHANGES | 5 +++++ docs/modules/appengine.rst | 2 +- docs/modules/log4j.rst | 2 +- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 2 +- docs/modules/raven.rst | 2 +- raven-appengine/README.md | 4 ++-- raven-log4j/README.md | 4 ++-- raven-log4j2/README.md | 10 +++++----- raven-logback/README.md | 4 ++-- raven/README.md | 4 ++-- 11 files changed, 26 insertions(+), 21 deletions(-) diff --git a/CHANGES b/CHANGES index fb9eb95dac6..6e914474abb 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 7.2.2 +------------- + +- Fix ServerName configuration in ``raven-log4j``. + Version 7.2.1 ------------- diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index b2ee5d424ea..8f1f1d5fb5d 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -25,7 +25,7 @@ If you want to use Maven you can install Raven-AppEngine as dependency: com.getsentry.raven raven-appengine - 7.2.1 + 7.2.2 Usage diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 96604a84d89..123ad2735ef 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven-Log4j as dependency: com.getsentry.raven raven-log4j - 7.2.1 + 7.2.2 If you manually want to manage your dependencies: diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index d2d122fdc20..4f011c9d20e 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,18 +19,18 @@ If you want to use Maven you can install Raven-Log4j2 as dependency: com.getsentry.raven raven-log4j2 - 7.2.1 + 7.2.2 If you manually want to manage your dependencies: - :doc:`raven dependencies ` - `log4j-api-2.1.jar - `_ + `_ - `log4j-core-2.0.jar - `_ + `_ - `log4j-slf4j-impl-2.1.jar - `_ + `_ is recommended as the implementation of slf4j (instead of slf4j-jdk14). Usage diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index adba22e7336..7a2ab343317 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -16,7 +16,7 @@ If you want to use Maven you can install Raven-Logback as dependency: com.getsentry.raven raven-logback - 7.2.1 + 7.2.2 - :doc:`raven dependencies ` diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index f5d7c4d6f95..8f1d2a387f9 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven as dependency: com.getsentry.raven raven - 7.2.1 + 7.2.2 If you manually want to manage your dependencies: diff --git a/raven-appengine/README.md b/raven-appengine/README.md index dfb73107a93..1a19b9e4c1f 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -15,12 +15,12 @@ __This module is not useful outside of Google App Engine.__ com.getsentry.raven raven-appengine - 7.2.1 + 7.2.2 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.2.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.2.2%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 3ee16c53c9c..db13a7c6b9d 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,12 +10,12 @@ for log4j to send the logged events to Sentry. com.getsentry.raven raven-log4j - 7.2.1 + 7.2.2 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.2.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.2.2%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index c15eadb91ae..eebfcb46586 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,20 +10,20 @@ for Log4j 2 to send the logged events to Sentry. com.getsentry.raven raven-log4j2 - 7.2.1 + 7.2.2 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.2.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.2.2%7Cjar). ### Manual dependency management Relies on: - [raven dependencies](../raven) - - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7C2.1%7Cjar) - - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7C2.1%7Cjar) - - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7C2.1%7Cjar) + - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.2.2%7Cjar) + - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.2.2%7Cjar) + - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.2.2%7Cjar) is recommended as the implementation of slf4j (instead of slf4j-jdk14). diff --git a/raven-logback/README.md b/raven-logback/README.md index 7b20a0dae42..74bf7987612 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,12 +10,12 @@ for logback to send the logged events to Sentry. com.getsentry.raven raven-logback - 7.2.1 + 7.2.2 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.2.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.2.2%7Cjar). ### Manual dependency management Relies on: diff --git a/raven/README.md b/raven/README.md index a68590e79b2..bb56024636d 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,12 +10,12 @@ for `java.util.logging`. com.getsentry.raven raven - 7.2.1 + 7.2.2 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.2.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.2.2%7Cjar). ### Manual dependency management Relies on: From 55b300f67ff88e2791a8be558718a16c594a9a65 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 2 May 2016 16:36:33 -0500 Subject: [PATCH 1412/2152] [maven-release-plugin] prepare release v7.2.2 --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index cb8cf1e2c7a..9793880c078 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.2.2-SNAPSHOT + 7.2.2 pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v7.2.2 https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 17aba53a12f..b3231e5a750 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.2.2-SNAPSHOT + 7.2.2 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index c58baada9d1..c807b37e00e 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.2-SNAPSHOT + 7.2.2 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 17d244bdab9..396537d96e6 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.2-SNAPSHOT + 7.2.2 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index c2826451ce9..8a2423c6cd7 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.2-SNAPSHOT + 7.2.2 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 5a49d9ad15b..6c4a501e159 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.2-SNAPSHOT + 7.2.2 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index c1c116eb39b..89f6188f11c 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.2-SNAPSHOT + 7.2.2 sentry-stub From 032ccc3cc1bc9add8a0c10ef2c969058ecf67967 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 2 May 2016 16:36:34 -0500 Subject: [PATCH 1413/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 9793880c078..aeaac5aa6ee 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.2.2 + 7.2.3-SNAPSHOT pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v7.2.2 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index b3231e5a750..e15420cb3b3 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.2.2 + 7.2.3-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index c807b37e00e..50d86abe81c 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.2 + 7.2.3-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 396537d96e6..2370ac369cc 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.2 + 7.2.3-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 8a2423c6cd7..b1e843c1abd 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.2 + 7.2.3-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 6c4a501e159..b801ace2d32 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.2 + 7.2.3-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 89f6188f11c..4ea08602ea7 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.2 + 7.2.3-SNAPSHOT sentry-stub From 1f9ac9f58d3d3e62d72d4497e914fd70f94684c8 Mon Sep 17 00:00:00 2001 From: ted kaemming Date: Wed, 11 May 2016 16:37:59 -0700 Subject: [PATCH 1414/2152] Fix typo in README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a374f78f857..418b7ca89b0 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ before sending details to a Sentry instance. - [logback](http://logback.qos.ch/) support is provided in [raven-logback](raven-logback) While it's **strongly recommended to use one of the supported logging -frameworks** to capture and send messages to Sentry, a it is possible to do so +frameworks** to capture and send messages to Sentry, it is also possible to do so manually with the main project [raven](raven). Raven supports both HTTP and HTTPS as transport protocols to the Sentry From bbddfe1c36d0e0c275bf72b70b254a00479aba50 Mon Sep 17 00:00:00 2001 From: Ted Kaemming Date: Thu, 12 May 2016 10:53:43 -0700 Subject: [PATCH 1415/2152] Use union merge strategy for `CHANGES`. --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..8383fff94e2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +CHANGES merge=union From a02828f02d2c85ee34ed78c18e3ac12b5dbcb131 Mon Sep 17 00:00:00 2001 From: ted kaemming Date: Thu, 12 May 2016 11:16:39 -0700 Subject: [PATCH 1416/2152] Accept subclasses of `Throwable` in `Raven.sendException`. (#207) * Accept subclasses of `Throwable` in `Raven.sendException`. The `ExceptionInterface` constructor already accepts `Throwable`, so this maintains consistency with that API. * Update CHANGES. --- CHANGES | 5 +++++ raven/src/main/java/com/getsentry/raven/Raven.java | 12 ++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 6e914474abb..b724a95caca 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 7.2.3 +------------- + +- Accept `Throwable` instances as parameter to `Raven.sendException`. + Version 7.2.2 ------------- diff --git a/raven/src/main/java/com/getsentry/raven/Raven.java b/raven/src/main/java/com/getsentry/raven/Raven.java index c2074ebb1e4..0c2306d1908 100644 --- a/raven/src/main/java/com/getsentry/raven/Raven.java +++ b/raven/src/main/java/com/getsentry/raven/Raven.java @@ -66,16 +66,16 @@ public void sendMessage(String message) { } /** - * Sends an exception to the Sentry server. + * Sends an exception (or throwable) to the Sentry server. *

    - * The Exception will be logged at the {@link Event.Level#ERROR} level. + * The exception will be logged at the {@link Event.Level#ERROR} level. * - * @param exception exception to send to Sentry. + * @param throwable exception to send to Sentry. */ - public void sendException(Exception exception) { - EventBuilder eventBuilder = new EventBuilder().withMessage(exception.getMessage()) + public void sendException(Throwable throwable) { + EventBuilder eventBuilder = new EventBuilder().withMessage(throwable.getMessage()) .withLevel(Event.Level.ERROR) - .withSentryInterface(new ExceptionInterface(exception)); + .withSentryInterface(new ExceptionInterface(throwable)); runBuilderHelpers(eventBuilder); sendEvent(eventBuilder.build()); } From 80f8ccb30fade73209be57cb7ff8fd0312cab579 Mon Sep 17 00:00:00 2001 From: ted kaemming Date: Thu, 12 May 2016 12:34:09 -0700 Subject: [PATCH 1417/2152] Remove Logback `minLevel` default parameter value. (#208) * Remove Logback `minLevel` default parameter value. * Update CHANGES. --- CHANGES | 1 + .../java/com/getsentry/raven/logback/SentryAppender.java | 8 +++++--- .../raven/logback/SentryAppenderEventLevelFilterTest.java | 8 ++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index b724a95caca..2166de84952 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 7.2.3 ------------- - Accept `Throwable` instances as parameter to `Raven.sendException`. +- Remove default `WARNING` filter level for the Logback appender. Version 7.2.2 ------------- diff --git a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java index de79ca9c9f4..75ffce8a4e1 100644 --- a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java @@ -73,9 +73,11 @@ public class SentryAppender extends AppenderBase { */ protected String serverName; /** - * If set, only events with level = minLevel and up will be recorded. + * If set, only events with level = minLevel and up will be recorded. (This + * configuration parameter is deprecated in favor of using Logback + * Filters.) */ - protected Level minLevel = Level.WARN; + protected Level minLevel; /** * Additional tags to be sent to sentry. *

    @@ -325,7 +327,7 @@ public void setServerName(String serverName) { } public void setMinLevel(String minLevel) { - this.minLevel = Level.toLevel(minLevel, Level.WARN); + this.minLevel = minLevel != null ? Level.toLevel(minLevel) : null; } /** diff --git a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderEventLevelFilterTest.java b/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderEventLevelFilterTest.java index 355b5ab8ae1..6301e83f91b 100644 --- a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderEventLevelFilterTest.java +++ b/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderEventLevelFilterTest.java @@ -39,8 +39,8 @@ private Object[][] levelConversions() { {"WARN", 2}, {"ERROR", 1}, {"error", 1}, - {"xxx", 2}, - {null, 2}}; + {"xxx", 4}, // invalid level will be coerced to DEBUG + {null, 5}}; } @Test(dataProvider = "levels") @@ -69,8 +69,8 @@ public void testDefaultLevelFilter() throws Exception { new Verifications() {{ mockRaven.sendEvent((Event) any); - minTimes = 2; - maxTimes = 2; + minTimes = 5; + maxTimes = 5; }}; } From a5b4ab5942857ecf19521a9709eeefa781575757 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 12 May 2016 16:18:24 -0500 Subject: [PATCH 1418/2152] Add `raven.async.shutdowntimeout` option. --- CHANGES | 1 + README.md | 14 +++++-- .../getsentry/raven/DefaultRavenFactory.java | 12 +++++- .../raven/connection/AsyncConnection.java | 41 ++++++++++++++++--- 4 files changed, 59 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index 2166de84952..371d9fa78ae 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 7.2.3 ------------- - Accept `Throwable` instances as parameter to `Raven.sendException`. +- Add `raven.async.shutdowntimeout` option. - Remove default `WARNING` filter level for the Logback appender. Version 7.2.2 diff --git a/README.md b/README.md index 418b7ca89b0..58a1482db89 100644 --- a/README.md +++ b/README.md @@ -151,9 +151,17 @@ To disable the async mode, add `raven.async=false` to the DSN: #### Graceful Shutdown (advanced) In order to shutdown the asynchronous connection gracefully, a `ShutdownHook` -is created. -This could lead to memory leaks in an environment where the life cycle of -Raven doesn't match the life cycle of the JVM. +is created. By default, the asynchronous connection is given 1 second +to shutdown gracefully, but this can be adjusted via +`raven.async.shutdowntimeout` (represented in milliseconds): + + http://public:private@host:port/1?raven.async.shutdowntimeout=5000 + +The special value `-1` can be used to disable the timeout and wait +indefinitely for the executor to terminate. + +The `ShutdownHook` could lead to memory leaks in an environment where +the life cycle of Raven doesn't match the life cycle of the JVM. An example would be in a JEE environment where the application using Raven could be deployed and undeployed regularly. diff --git a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java index eb3e1f16292..114d3f3f95d 100644 --- a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java @@ -54,6 +54,10 @@ public class DefaultRavenFactory extends RavenFactory { * Option for the maximum size of the queue. */ public static final String QUEUE_SIZE_OPTION = "raven.async.queuesize"; + /** + * Option for the graceful shutdown timeout of the async executor, in milliseconds. + */ + public static final String SHUTDOWN_TIMEOUT_OPTION = "raven.async.shutdowntimeout"; /** * Option to hide common stackframes with enclosing exceptions. */ @@ -146,7 +150,13 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { boolean gracefulShutdown = !FALSE.equalsIgnoreCase(dsn.getOptions().get(GRACEFUL_SHUTDOWN_OPTION)); - return new AsyncConnection(connection, executorService, gracefulShutdown); + String shutdownTimeoutStr = dsn.getOptions().get(SHUTDOWN_TIMEOUT_OPTION); + if (shutdownTimeoutStr != null) { + long shutdownTimeout = Long.parseLong(shutdownTimeoutStr); + return new AsyncConnection(connection, executorService, gracefulShutdown, shutdownTimeout); + } else { + return new AsyncConnection(connection, executorService, gracefulShutdown); + } } /** diff --git a/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java b/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java index 881811fe75c..bba5ecdded9 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java @@ -20,9 +20,13 @@ public class AsyncConnection implements Connection { private static final Logger logger = LoggerFactory.getLogger(AsyncConnection.class); /** - * Timeout of the {@link #executorService}. + * Default timeout of the {@link #executorService}, in milliseconds. */ - private static final long SHUTDOWN_TIMEOUT = TimeUnit.SECONDS.toMillis(1); + private static final long DEFAULT_SHUTDOWN_TIMEOUT = TimeUnit.SECONDS.toMillis(1); + /** + * Timeout of the {@link #executorService}, in milliseconds. + */ + private final long shutdownTimeout; /** * Connection used to actually send the events. */ @@ -53,8 +57,10 @@ public class AsyncConnection implements Connection { * @param executorService executorService used to process events, if null, the executorService will automatically * be set to {@code Executors.newSingleThreadExecutor()} * @param gracefulShutdown Indicates whether or not the shutdown operation should be managed by a ShutdownHook. + * @param shutdownTimeout timeout for graceful shutdown of the executor, in milliseconds. */ - public AsyncConnection(Connection actualConnection, ExecutorService executorService, boolean gracefulShutdown) { + public AsyncConnection(Connection actualConnection, ExecutorService executorService, boolean gracefulShutdown, + long shutdownTimeout) { this.actualConnection = actualConnection; if (executorService == null) this.executorService = Executors.newSingleThreadExecutor(); @@ -64,6 +70,21 @@ public AsyncConnection(Connection actualConnection, ExecutorService executorServ this.gracefulShutdown = gracefulShutdown; addShutdownHook(); } + this.shutdownTimeout = shutdownTimeout; + } + + /** + * Creates a connection which will rely on an executor to send events. + *

    + * Will propagate the {@link #close()} operation. + * + * @param actualConnection connection used to send the events. + * @param executorService executorService used to process events, if null, the executorService will automatically + * be set to {@code Executors.newSingleThreadExecutor()} + * @param gracefulShutdown Indicates whether or not the shutdown operation should be managed by a ShutdownHook. + */ + public AsyncConnection(Connection actualConnection, ExecutorService executorService, boolean gracefulShutdown) { + this(actualConnection, executorService, gracefulShutdown, DEFAULT_SHUTDOWN_TIMEOUT); } /** @@ -89,7 +110,7 @@ public void send(Event event) { * {@inheritDoc}. *

    * Closing the {@link AsyncConnection} will attempt a graceful shutdown of the {@link #executorService} with a - * timeout of {@link #SHUTDOWN_TIMEOUT}, allowing the current events to be submitted while new events will + * timeout of {@link #shutdownTimeout}, allowing the current events to be submitted while new events will * be rejected.
    * If the shutdown times out, the {@code executorService} will be forced to shutdown. */ @@ -105,12 +126,22 @@ public void close() throws IOException { * * @see #close() */ + @SuppressWarnings("checkstyle:magicnumber") private void doClose() throws IOException { logger.info("Gracefully shutdown sentry threads."); closed = true; executorService.shutdown(); try { - if (!executorService.awaitTermination(SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS)) { + if (shutdownTimeout == -1L) { + // Block until the executor terminates, but log periodically. + long waitBetweenLoggingMs = 5000L; + while (true) { + if (executorService.awaitTermination(waitBetweenLoggingMs, TimeUnit.MILLISECONDS)) { + break; + } + logger.info("Still waiting on async executor to terminate."); + } + } else if (!executorService.awaitTermination(shutdownTimeout, TimeUnit.MILLISECONDS)) { logger.warn("Graceful shutdown took too much time, forcing the shutdown."); List tasks = executorService.shutdownNow(); logger.info("{} tasks failed to execute before the shutdown.", tasks.size()); From 0685c5406f5bf0df17dcae31455ded78ea0c702f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 12 May 2016 16:21:04 -0500 Subject: [PATCH 1419/2152] Bump CHANGES and docs to 7.2.3 --- docs/modules/appengine.rst | 2 +- docs/modules/log4j.rst | 2 +- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 2 +- docs/modules/raven.rst | 2 +- raven-appengine/README.md | 4 ++-- raven-log4j/README.md | 4 ++-- raven-log4j2/README.md | 10 +++++----- raven-logback/README.md | 4 ++-- raven/README.md | 4 ++-- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 8f1f1d5fb5d..2088a8bd6ff 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -25,7 +25,7 @@ If you want to use Maven you can install Raven-AppEngine as dependency: com.getsentry.raven raven-appengine - 7.2.2 + 7.2.3 Usage diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 123ad2735ef..445cfbbefd5 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven-Log4j as dependency: com.getsentry.raven raven-log4j - 7.2.2 + 7.2.3 If you manually want to manage your dependencies: diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 4f011c9d20e..993a4b9f2bc 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,18 +19,18 @@ If you want to use Maven you can install Raven-Log4j2 as dependency: com.getsentry.raven raven-log4j2 - 7.2.2 + 7.2.3 If you manually want to manage your dependencies: - :doc:`raven dependencies ` - `log4j-api-2.1.jar - `_ + `_ - `log4j-core-2.0.jar - `_ + `_ - `log4j-slf4j-impl-2.1.jar - `_ + `_ is recommended as the implementation of slf4j (instead of slf4j-jdk14). Usage diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 7a2ab343317..72dcd2837f9 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -16,7 +16,7 @@ If you want to use Maven you can install Raven-Logback as dependency: com.getsentry.raven raven-logback - 7.2.2 + 7.2.3 - :doc:`raven dependencies ` diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index 8f1d2a387f9..8263c208fce 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven as dependency: com.getsentry.raven raven - 7.2.2 + 7.2.3 If you manually want to manage your dependencies: diff --git a/raven-appengine/README.md b/raven-appengine/README.md index 1a19b9e4c1f..06a34578af6 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -15,12 +15,12 @@ __This module is not useful outside of Google App Engine.__ com.getsentry.raven raven-appengine - 7.2.2 + 7.2.3 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.2.2%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.2.3%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j/README.md b/raven-log4j/README.md index db13a7c6b9d..4b1fce70587 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,12 +10,12 @@ for log4j to send the logged events to Sentry. com.getsentry.raven raven-log4j - 7.2.2 + 7.2.3 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.2.2%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.2.3%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index eebfcb46586..bd6f6d8a5a5 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,20 +10,20 @@ for Log4j 2 to send the logged events to Sentry. com.getsentry.raven raven-log4j2 - 7.2.2 + 7.2.3 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.2.2%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.2.3%7Cjar). ### Manual dependency management Relies on: - [raven dependencies](../raven) - - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.2.2%7Cjar) - - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.2.2%7Cjar) - - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.2.2%7Cjar) + - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.2.3%7Cjar) + - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.2.3%7Cjar) + - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.2.3%7Cjar) is recommended as the implementation of slf4j (instead of slf4j-jdk14). diff --git a/raven-logback/README.md b/raven-logback/README.md index 74bf7987612..0747656e5dd 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,12 +10,12 @@ for logback to send the logged events to Sentry. com.getsentry.raven raven-logback - 7.2.2 + 7.2.3 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.2.2%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.2.3%7Cjar). ### Manual dependency management Relies on: diff --git a/raven/README.md b/raven/README.md index bb56024636d..e5f18b4c935 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,12 +10,12 @@ for `java.util.logging`. com.getsentry.raven raven - 7.2.2 + 7.2.3 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.2.2%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.2.3%7Cjar). ### Manual dependency management Relies on: From e4df53ccbaa809ff05b06c7f0758e402ea7cc98e Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 12 May 2016 16:22:19 -0500 Subject: [PATCH 1420/2152] [maven-release-plugin] prepare release v7.2.3 --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index aeaac5aa6ee..c298f8e8835 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.2.3-SNAPSHOT + 7.2.3 pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v7.2.3 https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index e15420cb3b3..26ba0e6f74a 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.2.3-SNAPSHOT + 7.2.3 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 50d86abe81c..1a1067e0dbe 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.3-SNAPSHOT + 7.2.3 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 2370ac369cc..862174b1f58 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.3-SNAPSHOT + 7.2.3 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index b1e843c1abd..77726ffb441 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.3-SNAPSHOT + 7.2.3 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index b801ace2d32..dfc1e830156 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.3-SNAPSHOT + 7.2.3 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 4ea08602ea7..c37ff43f824 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.3-SNAPSHOT + 7.2.3 sentry-stub From 6a56b07164516708621eca8f8237eac79c22fb15 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 12 May 2016 16:22:20 -0500 Subject: [PATCH 1421/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index c298f8e8835..3dfa9c70b53 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.2.3 + 7.2.4-SNAPSHOT pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v7.2.3 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 26ba0e6f74a..f44503a1eea 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.2.3 + 7.2.4-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 1a1067e0dbe..1da9527335e 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.3 + 7.2.4-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 862174b1f58..6e27e07f7aa 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.3 + 7.2.4-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 77726ffb441..4eb9bf29061 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.3 + 7.2.4-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index dfc1e830156..228e4429521 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.3 + 7.2.4-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index c37ff43f824..ef24e654eb8 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.3 + 7.2.4-SNAPSHOT sentry-stub From ca3ed6561b5d161684c129faccf49b534afb061b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 20 May 2016 11:22:18 +0200 Subject: [PATCH 1422/2152] Fixed a bad dsn in the docs --- docs/config.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index ef46958f93d..28202e150b5 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -90,7 +90,7 @@ Queue size (advanced): It is possible to set a maximum with the option ``raven.async.queuesize``:: - ___DSN__?raven.async.queuesize=100 + ___DSN___?raven.async.queuesize=100 This means that if the connection to the Sentry server is down, only the 100 most recent events will be stored and processed as soon as the From a494bedc14a476d40649a59e5ba4d7e4e4f33f97 Mon Sep 17 00:00:00 2001 From: ted kaemming Date: Thu, 26 May 2016 10:16:54 -0700 Subject: [PATCH 1423/2152] Catch possible `NullPointerException` in Log4j appender. (#214) This ensures that Log4j is able to handle log events that don't include throwable information in certain situations. Also upgrades `maven-checkstyle-plugin` to 2.17, and changes some configuration for compatibility. --- pom.xml | 4 ++-- .../com/getsentry/raven/log4j/SentryAppender.java | 12 ++++++++++-- .../main/java/com/getsentry/raven/event/Event.java | 2 +- src/checkstyle/checkstyle.xml | 7 +++---- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 3dfa9c70b53..dee680a7922 100644 --- a/pom.xml +++ b/pom.xml @@ -357,7 +357,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 2.13 + 2.17 src/checkstyle/checkstyle.xml true @@ -433,7 +433,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 2.13 + 2.17 src/checkstyle/checkstyle.xml diff --git a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java index 3eb20f49503..4d0f32efa88 100644 --- a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java @@ -15,6 +15,7 @@ import org.apache.log4j.spi.ErrorCode; import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; +import org.apache.log4j.spi.ThrowableInformation; import java.util.Arrays; import java.util.Collections; @@ -190,8 +191,15 @@ protected Event buildEvent(LoggingEvent loggingEvent) { eventBuilder.withRelease(release.trim()); } - if (loggingEvent.getThrowableInformation() != null) { - Throwable throwable = loggingEvent.getThrowableInformation().getThrowable(); + ThrowableInformation throwableInformation = null; + try { + throwableInformation = loggingEvent.getThrowableInformation(); + } catch (NullPointerException expected) { + // `throwableInformation` is already set. + } + + if (throwableInformation != null) { + Throwable throwable = throwableInformation.getThrowable(); eventBuilder.withSentryInterface(new ExceptionInterface(throwable)); } else if (loggingEvent.getLocationInformation().fullInfo != null) { LocationInfo location = loggingEvent.getLocationInformation(); diff --git a/raven/src/main/java/com/getsentry/raven/event/Event.java b/raven/src/main/java/com/getsentry/raven/event/Event.java index 008140d6020..185dd91e41e 100644 --- a/raven/src/main/java/com/getsentry/raven/event/Event.java +++ b/raven/src/main/java/com/getsentry/raven/event/Event.java @@ -275,7 +275,7 @@ public String toString() { /** * Levels of log available in Sentry. */ - public static enum Level { + public enum Level { /** * Fatal is the highest form of log available, use it for unrecoverable issues. */ diff --git a/src/checkstyle/checkstyle.xml b/src/checkstyle/checkstyle.xml index f18d892d895..cfda47df861 100644 --- a/src/checkstyle/checkstyle.xml +++ b/src/checkstyle/checkstyle.xml @@ -153,6 +153,9 @@ + + + @@ -179,10 +182,6 @@ - - - - From ab7edd23f286050a4fd745721f5c15c7a77f8cd3 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 9 Jun 2016 13:20:55 -0500 Subject: [PATCH 1424/2152] Add (manual) support for breadcrumbs to event objects. (#216) --- CHANGES | 5 + .../com/getsentry/raven/event/Breadcrumb.java | 100 ++++++++++++++++++ .../raven/event/BreadcrumbBuilder.java | 95 +++++++++++++++++ .../java/com/getsentry/raven/event/Event.java | 13 +++ .../getsentry/raven/event/EventBuilder.java | 14 +++ .../raven/marshaller/json/JsonMarshaller.java | 44 ++++++++ .../marshaller/json/JsonMarshallerTest.java | 35 ++++++ .../jsonmarshallertest/testBreadcrumbs.json | 20 ++++ 8 files changed, 326 insertions(+) create mode 100644 raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java create mode 100644 raven/src/main/java/com/getsentry/raven/event/BreadcrumbBuilder.java create mode 100644 raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testBreadcrumbs.json diff --git a/CHANGES b/CHANGES index 371d9fa78ae..1faacee2bf7 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 7.2.4 +------------- + +- Add (manual) support for breadcrumbs to event objects. + Version 7.2.3 ------------- diff --git a/raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java b/raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java new file mode 100644 index 00000000000..45e9e725809 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java @@ -0,0 +1,100 @@ +package com.getsentry.raven.event; + +import java.util.Date; +import java.util.Map; + +/** + * An object that represents a single breadcrumb. Events may include a list + * of breadcrumbs that help users re-create the path of actions that occurred + * which lead to the Event happening. + */ +public class Breadcrumb { + + /** + * (Optional) Type of the breadcrumb. + */ + private final String type; + /** + * Timestamp when the breadcrumb occurred. + */ + private final Date timestamp; + /** + * Level of the breadcrumb. + */ + private final String level; + /** + * Message of the breadcrumb. + */ + private final String message; + /** + * Category of the breadcrumb. + */ + private final String category; + /** + * Data related to the breadcrumb. + */ + private final Map data; + + /** + * Create an immutable {@link Breadcrumb} object. + * + * @param type String + * @param timestamp Date + * @param level String + * @param message String + * @param category String + * @param data Map of String to String + */ + Breadcrumb(String type, Date timestamp, String level, String message, + String category, Map data) { + + if (timestamp == null) { + timestamp = new Date(); + } + + checkNotNull(level, "level"); + checkNotNull(category, "category"); + + if (message == null && (data == null || data.size() < 1)) { + throw new IllegalArgumentException("one of 'message' or 'data' must be set"); + } + + this.type = type; + this.timestamp = timestamp; + this.level = level; + this.message = message; + this.category = category; + this.data = data; + } + + private void checkNotNull(String str, String name) { + if (str == null) { + throw new IllegalArgumentException("field '" + name + "' is required but got null"); + } + } + + public String getType() { + return type; + } + + public Date getTimestamp() { + return timestamp; + } + + public String getLevel() { + return level; + } + + public String getMessage() { + return message; + } + + public String getCategory() { + return category; + } + + public Map getData() { + return data; + } + +} diff --git a/raven/src/main/java/com/getsentry/raven/event/BreadcrumbBuilder.java b/raven/src/main/java/com/getsentry/raven/event/BreadcrumbBuilder.java new file mode 100644 index 00000000000..06ebafac2fe --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/event/BreadcrumbBuilder.java @@ -0,0 +1,95 @@ +package com.getsentry.raven.event; + +import java.util.Date; +import java.util.Map; + +/** + * Builder to assist the creation of {@link Breadcrumb}s. + */ +public class BreadcrumbBuilder { + + private String type; + private Date timestamp; + private String level; + private String message; + private String category; + private Map data; + + /** + * Type of the {@link Breadcrumb}. + * + * @param newType String + * @return current BreadcrumbBuilder + */ + public BreadcrumbBuilder setType(String newType) { + this.type = newType; + return this; + } + + /** + * Timestamp of the {@link Breadcrumb}. + * + * @param newTimestamp Date + * @return current BreadcrumbBuilder + */ + public BreadcrumbBuilder setTimestamp(Date newTimestamp) { + this.timestamp = newTimestamp; + return this; + } + + /** + * Level of the {@link Breadcrumb}. + * + * @param newLevel String + * @return current BreadcrumbBuilder + */ + public BreadcrumbBuilder setLevel(String newLevel) { + this.level = newLevel; + return this; + } + + /** + * Message of the {@link Breadcrumb}. At least one of message or + * data is required. + * + * @param newMessage String + * @return current BreadcrumbBuilder + */ + public BreadcrumbBuilder setMessage(String newMessage) { + this.message = newMessage; + return this; + } + + /** + * Category of the {@link Breadcrumb}. + * + * @param newCategory String + * @return current BreadcrumbBuilder + */ + public BreadcrumbBuilder setCategory(String newCategory) { + this.category = newCategory; + return this; + } + + /** + * Data related to the {@link Breadcrumb}. At least one of message or + * data is required. + * + * @param newData Map of String to String + * @return current BreadcrumbBuilder + */ + public BreadcrumbBuilder setData(Map newData) { + this.data = newData; + return this; + } + + /** + * Build and return the {@link Breadcrumb} object. + * + * @return Breadcrumb + */ + public Breadcrumb build() { + return new Breadcrumb(type, timestamp, level, message, category, data); + } + +} diff --git a/raven/src/main/java/com/getsentry/raven/event/Event.java b/raven/src/main/java/com/getsentry/raven/event/Event.java index 185dd91e41e..29aa90a0de2 100644 --- a/raven/src/main/java/com/getsentry/raven/event/Event.java +++ b/raven/src/main/java/com/getsentry/raven/event/Event.java @@ -6,6 +6,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -63,6 +64,10 @@ public class Event implements Serializable { * Automatically created with a Map that is made unmodifiable by the {@link EventBuilder}. */ private Map tags = new HashMap<>(); + /** + * List of Breadcrumb objects related to the event. + */ + private List breadcrumbs = new ArrayList<>(); /** * Identifies the host client from which the event was recorded. */ @@ -159,6 +164,14 @@ void setCulprit(String culprit) { this.culprit = culprit; } + public List getBreadcrumbs() { + return breadcrumbs; + } + + void setBreadcrumbs(List breadcrumbs) { + this.breadcrumbs = breadcrumbs; + } + public Map getTags() { return tags; } diff --git a/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java b/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java index ebc40dea0fa..225cad3f27c 100644 --- a/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java +++ b/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java @@ -99,6 +99,9 @@ private static void makeImmutable(Event event) { // Make the tags unmodifiable event.setTags(Collections.unmodifiableMap(event.getTags())); + // Make the breadcrumbs unmodifiable + event.setBreadcrumbs(Collections.unmodifiableList(event.getBreadcrumbs())); + // Make the extra properties unmodifiable (everything in it is still mutable though) event.setExtra(Collections.unmodifiableMap(event.getExtra())); @@ -221,6 +224,17 @@ public EventBuilder withTag(String tagKey, String tagValue) { return this; } + /** + * Adds a list of {@code Breadcrumb}s to the event. + * + * @param breadcrumbs list of breadcrumbs + * @return the current {@code EventBuilder} for chained calls. + */ + public EventBuilder withBreadcrumbs(List breadcrumbs) { + event.setBreadcrumbs(breadcrumbs); + return this; + } + /** * Sets the serverName in the event. * diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java index 63d0457143e..f5765c2c357 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; +import com.getsentry.raven.event.Breadcrumb; import com.getsentry.raven.util.Base64; import com.getsentry.raven.util.Base64OutputStream; import com.getsentry.raven.event.Event; @@ -56,6 +57,10 @@ public class JsonMarshaller implements Marshaller { * A map or list of tags for this event. */ public static final String TAGS = "tags"; + /** + * List of breadcrumbs for this event. + */ + public static final String BREADCRUMBS = "breadcrumbs"; /** * Identifies the host client from which the event was recorded. */ @@ -130,6 +135,7 @@ private void writeContent(JsonGenerator generator, Event event) throws IOExcepti generator.writeStringField(PLATFORM, event.getPlatform()); generator.writeStringField(CULPRIT, event.getCulprit()); writeTags(generator, event.getTags()); + writeBreadcumbs(generator, event.getBreadcrumbs()); generator.writeStringField(SERVER_NAME, event.getServerName()); generator.writeStringField(RELEASE, event.getRelease()); writeExtras(generator, event.getExtra()); @@ -223,6 +229,44 @@ private void writeTags(JsonGenerator generator, Map tags) throws generator.writeEndObject(); } + @SuppressWarnings("checkstyle:magicnumber") + private void writeBreadcumbs(JsonGenerator generator, List breadcrumbs) throws IOException { + if (breadcrumbs.size() < 1) { + return; + } + + generator.writeObjectFieldStart(BREADCRUMBS); + generator.writeArrayFieldStart("values"); + for (Breadcrumb breadcrumb : breadcrumbs) { + generator.writeStartObject(); + // getTime() returns ts in millis, but breadcrumbs expect seconds + generator.writeNumberField("timestamp", breadcrumb.getTimestamp().getTime() / 1000); + + if (breadcrumb.getType() != null) { + generator.writeStringField("type", breadcrumb.getType()); + } + if (breadcrumb.getLevel() != null) { + generator.writeStringField("level", breadcrumb.getLevel()); + } + if (breadcrumb.getMessage() != null) { + generator.writeStringField("message", breadcrumb.getMessage()); + } + if (breadcrumb.getCategory() != null) { + generator.writeStringField("category", breadcrumb.getCategory()); + } + if (breadcrumb.getData() != null && breadcrumb.getData().size() > 0) { + generator.writeObjectFieldStart("data"); + for (Map.Entry entry : breadcrumb.getData().entrySet()) { + generator.writeStringField(entry.getKey(), entry.getValue()); + } + generator.writeEndObject(); + } + generator.writeEndObject(); + } + generator.writeEndArray(); + generator.writeEndObject(); + } + /** * Formats a message, ensuring that the maximum length {@link #MAX_MESSAGE_LENGTH} isn't reached. * diff --git a/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java index 23e11de2bbf..9d342750569 100644 --- a/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java @@ -1,6 +1,8 @@ package com.getsentry.raven.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; +import com.getsentry.raven.event.Breadcrumb; +import com.getsentry.raven.event.BreadcrumbBuilder; import mockit.*; import com.getsentry.raven.event.Event; import com.getsentry.raven.event.interfaces.SentryInterface; @@ -10,9 +12,11 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; +import java.util.List; import java.util.UUID; import static com.getsentry.raven.marshaller.json.JsonComparisonUtil.*; @@ -203,6 +207,37 @@ public void testEventReleaseWrittenProperly(@Injectable("release") final String assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testRelease.json"))); } + @Test + public void testEventBreadcrumbsWrittenProperly() throws Exception { + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); + + Breadcrumb breadcrumb1 = new BreadcrumbBuilder() + .setTimestamp(new Date(1463169342000L)) + .setLevel("info") + .setCategory("foo") + .setMessage("test1") + .build(); + Breadcrumb breadcrumb2 = new BreadcrumbBuilder() + .setTimestamp(new Date(1463169343000L)) + .setLevel("info") + .setCategory("foo") + .setMessage("test2") + .build(); + + final List breadcrumbs = new ArrayList<>(); + breadcrumbs.add(breadcrumb1); + breadcrumbs.add(breadcrumb2); + + new NonStrictExpectations() {{ + mockEvent.getBreadcrumbs(); + result = breadcrumbs; + }}; + + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); + + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testBreadcrumbs.json"))); + } + @Test public void testEventChecksumWrittenProperly(@Injectable("1234567890abcdef") final String mockChecksum) throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testBreadcrumbs.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testBreadcrumbs.json new file mode 100644 index 00000000000..b92633280ee --- /dev/null +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testBreadcrumbs.json @@ -0,0 +1,20 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "release": null, + "extra": {}, + "checksum": null, + "breadcrumbs": { + "values": [ + {"timestamp":1463169342,"level":"info","message":"test1","category":"foo"}, + {"timestamp":1463169343,"level":"info","message":"test2","category":"foo"} + ] + } +} From deb7a83158f80a0ebc473121396244a5ec3955b4 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Jun 2016 14:34:13 -0500 Subject: [PATCH 1425/2152] Add sendEvent(EventBuilder) method which calls builder helpers before building and sending the Event. (#218) --- CHANGES | 1 + .../main/java/com/getsentry/raven/Raven.java | 10 ++++++++++ .../java/com/getsentry/raven/RavenTest.java | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/CHANGES b/CHANGES index 1faacee2bf7..7ba06e79dff 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 7.2.4 ------------- - Add (manual) support for breadcrumbs to event objects. +- Add ``sendEvent(EventBuilder)`` method which calls builder helpers before building and sending the ``Event``. Version 7.2.3 ------------- diff --git a/raven/src/main/java/com/getsentry/raven/Raven.java b/raven/src/main/java/com/getsentry/raven/Raven.java index 0c2306d1908..8fa36c98129 100644 --- a/raven/src/main/java/com/getsentry/raven/Raven.java +++ b/raven/src/main/java/com/getsentry/raven/Raven.java @@ -51,6 +51,16 @@ public void sendEvent(Event event) { } } + /** + * Builds and sends an {@link Event} to the Sentry server. + * + * @param eventBuilder {@link EventBuilder} to send to Sentry. + */ + public void sendEvent(EventBuilder eventBuilder) { + runBuilderHelpers(eventBuilder); + sendEvent(eventBuilder.build()); + } + /** * Sends a message to the Sentry server. *

    diff --git a/raven/src/test/java/com/getsentry/raven/RavenTest.java b/raven/src/test/java/com/getsentry/raven/RavenTest.java index b344c3ed1bf..9e200a03a54 100644 --- a/raven/src/test/java/com/getsentry/raven/RavenTest.java +++ b/raven/src/test/java/com/getsentry/raven/RavenTest.java @@ -35,6 +35,24 @@ public void testSendEvent() throws Exception { }}; } + @Test + public void testSendEventBuilder() throws Exception { + final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; + raven.addBuilderHelper(mockEventBuilderHelper); + + raven.sendEvent(new EventBuilder() + .withMessage(message) + .withLevel(Event.Level.INFO)); + + new Verifications() {{ + Event event; + mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); + mockConnection.send(event = withCapture()); + assertThat(event.getLevel(), equalTo(Event.Level.INFO)); + assertThat(event.getMessage(), equalTo(message)); + }}; + } + @Test public void testSendEventFailingIsCaught() throws Exception { new NonStrictExpectations() {{ From 34798e1566d9b2f1473d7b19ec32ff53f742489f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Jun 2016 14:36:51 -0500 Subject: [PATCH 1426/2152] Add RavenContext and Breadcrumbs helper. (#217) --- CHANGES | 2 + .../getsentry/raven/DefaultRavenFactory.java | 2 + .../main/java/com/getsentry/raven/Raven.java | 12 + .../com/getsentry/raven/RavenContext.java | 125 +++++ .../getsentry/raven/event/Breadcrumbs.java | 28 ++ .../event/helper/ContextBuilderHelper.java | 41 ++ .../raven/util/CircularFifoQueue.java | 426 ++++++++++++++++++ .../com/getsentry/raven/RavenContextTest.java | 89 ++++ src/checkstyle/suppressions.xml | 1 + 9 files changed, 726 insertions(+) create mode 100644 raven/src/main/java/com/getsentry/raven/RavenContext.java create mode 100644 raven/src/main/java/com/getsentry/raven/event/Breadcrumbs.java create mode 100644 raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java create mode 100644 raven/src/main/java/com/getsentry/raven/util/CircularFifoQueue.java create mode 100644 raven/src/test/java/com/getsentry/raven/RavenContextTest.java diff --git a/CHANGES b/CHANGES index 7ba06e79dff..9dfbf069b74 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,8 @@ Version 7.2.4 - Add (manual) support for breadcrumbs to event objects. - Add ``sendEvent(EventBuilder)`` method which calls builder helpers before building and sending the ``Event``. +- Add ``RavenContext`` which tracks thread-local state. +- Add ``Breadcrumbs`` helper to log breadcrumbs from anywhere without manually passing context around. Version 7.2.3 ------------- diff --git a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java index 114d3f3f95d..6d518b9f964 100644 --- a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java @@ -2,6 +2,7 @@ import com.getsentry.raven.connection.*; import com.getsentry.raven.dsn.Dsn; +import com.getsentry.raven.event.helper.ContextBuilderHelper; import com.getsentry.raven.event.helper.HttpEventBuilderHelper; import com.getsentry.raven.event.interfaces.*; import com.getsentry.raven.marshaller.Marshaller; @@ -79,6 +80,7 @@ public Raven createRavenInstance(Dsn dsn) { logger.debug("The current environment doesn't provide access to servlets," + "or provides an unsupported version."); } + raven.addBuilderHelper(new ContextBuilderHelper(raven)); return raven; } diff --git a/raven/src/main/java/com/getsentry/raven/Raven.java b/raven/src/main/java/com/getsentry/raven/Raven.java index 8fa36c98129..391d8517494 100644 --- a/raven/src/main/java/com/getsentry/raven/Raven.java +++ b/raven/src/main/java/com/getsentry/raven/Raven.java @@ -25,6 +25,14 @@ public class Raven { private static final Logger logger = LoggerFactory.getLogger(Raven.class); private final Set builderHelpers = new HashSet<>(); private Connection connection; + private ThreadLocal context = new ThreadLocal() { + @Override + protected RavenContext initialValue() { + RavenContext ctx = new RavenContext(); + ctx.activate(); + return ctx; + } + }; /** * Runs the {@link EventBuilderHelper} against the {@link EventBuilder} to obtain additional information with a @@ -129,6 +137,10 @@ public void setConnection(Connection connection) { this.connection = connection; } + public RavenContext getContext() { + return context.get(); + } + @Override public String toString() { return "Raven{" diff --git a/raven/src/main/java/com/getsentry/raven/RavenContext.java b/raven/src/main/java/com/getsentry/raven/RavenContext.java new file mode 100644 index 00000000000..2848d547e99 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/RavenContext.java @@ -0,0 +1,125 @@ +package com.getsentry.raven; + +import com.getsentry.raven.event.Breadcrumb; +import com.getsentry.raven.util.CircularFifoQueue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; + +/** + * RavenContext is used to hold {@link ThreadLocal} context data (such as + * {@link Breadcrumb}s) so that data may be collected in different parts + * of an application and then sent together when an exception occurs. + */ +public class RavenContext implements AutoCloseable { + + /** + * Thread local set of active context objects. Note that an {@link IdentityHashMap} + * is used instead of a Set because there is no identity-set in the Java + * standard library. + * + * A set of active contexts is required in order to support running multiple Raven + * clients within a single process. In *most* cases this set will contain a single + * active context object. + * + * This must be static and {@link ThreadLocal} so that users can retrieve any active + * context objects globally, without passing context objects all the way down their + * stacks. See {@link com.getsentry.raven.event.Breadcrumbs} for an example of how this may be used. + */ + private static ThreadLocal> activeContexts = + new ThreadLocal>() { + @Override + protected IdentityHashMap initialValue() { + return new IdentityHashMap<>(); + } + }; + + /** + * The number of {@link Breadcrumb}s to keep in the ring buffer by default. + */ + private static final int DEFAULT_BREADCRUMB_LIMIT = 100; + + /** + * Ring buffer of {@link Breadcrumb} objects. + */ + private CircularFifoQueue breadcrumbs; + + /** + * Create a new (empty) RavenContext object with the default Breadcrumb limit. + */ + public RavenContext() { + this(DEFAULT_BREADCRUMB_LIMIT); + } + + /** + * Create a new (empty) RavenContext object with the specified Breadcrumb limit. + * + * @param breadcrumbLimit Number of Breadcrumb objects to retain in ring buffer. + */ + public RavenContext(int breadcrumbLimit) { + breadcrumbs = new CircularFifoQueue<>(breadcrumbLimit); + } + + /** + * Add this context to the active contexts for this thread. + */ + public void activate() { + activeContexts.get().put(this, this); + } + + /** + * Remove this context from the active contexts for this thread. + */ + public void deactivate() { + activeContexts.get().remove(this); + } + + /** + * Clear state from this context. + */ + public void clear() { + breadcrumbs.clear(); + } + + /** + * Calls deactivate, used by try-with-resources ({@link AutoCloseable}). + */ + @Override + public void close() { + deactivate(); + } + + /** + * Returns all active contexts for the current thread. + * + * @return List of active {@link RavenContext} objects. + */ + public static List getActiveContexts() { + Collection ravenContexts = activeContexts.get().values(); + List list = new ArrayList<>(ravenContexts.size()); + list.addAll(ravenContexts); + return list; + } + + /** + * Return {@link Breadcrumb}s attached to this RavenContext. + * + * @return Iterator of {@link Breadcrumb}s. + */ + public Iterator getBreadcrumbs() { + return breadcrumbs.iterator(); + } + + /** + * Record a single {@link Breadcrumb} into this context. + * + * @param breadcrumb Breadcrumb object to record + */ + public void recordBreadcrumb(Breadcrumb breadcrumb) { + breadcrumbs.add(breadcrumb); + } + +} diff --git a/raven/src/main/java/com/getsentry/raven/event/Breadcrumbs.java b/raven/src/main/java/com/getsentry/raven/event/Breadcrumbs.java new file mode 100644 index 00000000000..f81080f78ea --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/event/Breadcrumbs.java @@ -0,0 +1,28 @@ +package com.getsentry.raven.event; + +import com.getsentry.raven.RavenContext; + +/** + * Helpers for dealing with {@link Breadcrumb}s. + */ +public final class Breadcrumbs { + + /** + * Private constructor because this is a utility class. + */ + private Breadcrumbs() { + + } + + /** + * Record a {@link Breadcrumb} into all of this thread's active contexts. + * + * @param breadcrumb Breadcrumb to record + */ + public static void record(Breadcrumb breadcrumb) { + for (RavenContext context : RavenContext.getActiveContexts()) { + context.recordBreadcrumb(breadcrumb); + } + } + +} diff --git a/raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java b/raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java new file mode 100644 index 00000000000..7bb60043ee1 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java @@ -0,0 +1,41 @@ +package com.getsentry.raven.event.helper; + +import com.getsentry.raven.Raven; +import com.getsentry.raven.event.Breadcrumb; +import com.getsentry.raven.event.EventBuilder; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * {@link EventBuilderHelper} that extracts and sends any data attached to the + * provided {@link Raven}'s {@link com.getsentry.raven.RavenContext}. + */ +public class ContextBuilderHelper implements EventBuilderHelper { + + /** + * Raven object where the RavenContext comes from. + */ + private Raven raven; + + /** + * {@link EventBuilderHelper} that extracts context data from the provided {@link Raven} client. + * + * @param raven Raven client which holds RavenContext to be used. + */ + public ContextBuilderHelper(Raven raven) { + this.raven = raven; + } + + @Override + public void helpBuildingEvent(EventBuilder eventBuilder) { + List breadcrumbs = new ArrayList<>(); + Iterator iter = raven.getContext().getBreadcrumbs(); + while (iter.hasNext()) { + breadcrumbs.add(iter.next()); + } + eventBuilder.withBreadcrumbs(breadcrumbs); + } + +} diff --git a/raven/src/main/java/com/getsentry/raven/util/CircularFifoQueue.java b/raven/src/main/java/com/getsentry/raven/util/CircularFifoQueue.java new file mode 100644 index 00000000000..88dfad8ab27 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/util/CircularFifoQueue.java @@ -0,0 +1,426 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.getsentry.raven.util; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Queue; + +/** + * CircularFifoQueue is a first-in first-out queue with a fixed size that + * replaces its oldest element if full. + *

    + * The removal order of a {@link CircularFifoQueue} is based on the + * insertion order; elements are removed in the same order in which they + * were added. The iteration order is the same as the removal order. + *

    + * The {@link #add(Object)}, {@link #remove()}, {@link #peek()}, {@link #poll}, + * {@link #offer(Object)} operations all perform in constant time. + * All other operations perform in linear time or worse. + *

    + * This queue prevents null objects from being added. + * + * @since 4.0 + * @version $Id$ + */ +public class CircularFifoQueue extends AbstractCollection + implements Queue, Serializable { + + /** Serialization version. */ + private static final long serialVersionUID = -8423413834657610406L; + + /** Underlying storage array. */ + private transient E[] elements; + + /** Array index of first (oldest) queue element. */ + private transient int start = 0; + + /** + * Index mod maxElements of the array position following the last queue + * element. Queue elements start at elements[start] and "wrap around" + * elements[maxElements-1], ending at elements[decrement(end)]. + * For example, elements = {c,a,b}, start=1, end=1 corresponds to + * the queue [a,b,c]. + */ + private transient int end = 0; + + /** Flag to indicate if the queue is currently full. */ + private transient boolean full = false; + + /** Capacity of the queue. */ + private final int maxElements; + + /** + * Constructor that creates a queue with the default size of 32. + */ + public CircularFifoQueue() { + this(32); + } + + /** + * Constructor that creates a queue with the specified size. + * + * @param size the size of the queue (cannot be changed) + * @throws IllegalArgumentException if the size is < 1 + */ + @SuppressWarnings("unchecked") + public CircularFifoQueue(final int size) { + if (size <= 0) { + throw new IllegalArgumentException("The size must be greater than 0"); + } + elements = (E[]) new Object[size]; + maxElements = elements.length; + } + + /** + * Constructor that creates a queue from the specified collection. + * The collection size also sets the queue size. + * + * @param coll the collection to copy into the queue, may not be null + * @throws NullPointerException if the collection is null + */ + public CircularFifoQueue(final Collection coll) { + this(coll.size()); + addAll(coll); + } + + //----------------------------------------------------------------------- + /** + * Write the queue out using a custom routine. + * + * @param out the output stream + * @throws IOException if an I/O error occurs while writing to the output stream + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeInt(size()); + for (final E e : this) { + out.writeObject(e); + } + } + + /** + * Read the queue in using a custom routine. + * + * @param in the input stream + * @throws IOException if an I/O error occurs while writing to the output stream + * @throws ClassNotFoundException if the class of a serialized object can not be found + */ + @SuppressWarnings("unchecked") + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + elements = (E[]) new Object[maxElements]; + final int size = in.readInt(); + for (int i = 0; i < size; i++) { + elements[i] = (E) in.readObject(); + } + start = 0; + full = size == maxElements; + if (full) { + end = 0; + } else { + end = size; + } + } + + //----------------------------------------------------------------------- + /** + * Returns the number of elements stored in the queue. + * + * @return this queue's size + */ + @Override + public int size() { + int size = 0; + + if (end < start) { + size = maxElements - start + end; + } else if (end == start) { + size = full ? maxElements : 0; + } else { + size = end - start; + } + + return size; + } + + /** + * Returns true if this queue is empty; false otherwise. + * + * @return true if this queue is empty + */ + @Override + public boolean isEmpty() { + return size() == 0; + } + + /** + * {@inheritDoc} + *

    + * A {@code CircularFifoQueue} can never be full, thus this returns always + * {@code false}. + * + * @return always returns {@code false} + */ + public boolean isFull() { + return false; + } + + /** + * Returns {@code true} if the capacity limit of this queue has been reached, + * i.e. the number of elements stored in the queue equals its maximum size. + * + * @return {@code true} if the capacity limit has been reached, {@code false} otherwise + * @since 4.1 + */ + public boolean isAtFullCapacity() { + return size() == maxElements; + } + + /** + * Gets the maximum size of the collection (the bound). + * + * @return the maximum number of elements the collection can hold + */ + public int maxSize() { + return maxElements; + } + + /** + * Clears this queue. + */ + @Override + public void clear() { + full = false; + start = 0; + end = 0; + Arrays.fill(elements, null); + } + + /** + * Adds the given element to this queue. If the queue is full, the least recently added + * element is discarded so that a new element can be inserted. + * + * @param element the element to add + * @return true, always + * @throws NullPointerException if the given element is null + */ + @Override + public boolean add(final E element) { + if (null == element) { + throw new NullPointerException("Attempted to add null object to queue"); + } + + if (isAtFullCapacity()) { + remove(); + } + + elements[end++] = element; + + if (end >= maxElements) { + end = 0; + } + + if (end == start) { + full = true; + } + + return true; + } + + /** + * Returns the element at the specified position in this queue. + * + * @param index the position of the element in the queue + * @return the element at position {@code index} + * @throws NoSuchElementException if the requested position is outside the range [0, size) + */ + public E get(final int index) { + final int sz = size(); + if (index < 0 || index >= sz) { + throw new NoSuchElementException( + String.format("The specified index (%1$d) is outside the available range [0, %2$d)", + Integer.valueOf(index), Integer.valueOf(sz))); + } + + final int idx = (start + index) % maxElements; + return elements[idx]; + } + + //----------------------------------------------------------------------- + + /** + * Adds the given element to this queue. If the queue is full, the least recently added + * element is discarded so that a new element can be inserted. + * + * @param element the element to add + * @return true, always + * @throws NullPointerException if the given element is null + */ + @Override + public boolean offer(E element) { + return add(element); + } + + @Override + public E poll() { + if (isEmpty()) { + return null; + } + return remove(); + } + + @Override + public E element() { + if (isEmpty()) { + throw new NoSuchElementException("queue is empty"); + } + return peek(); + } + + @Override + public E peek() { + if (isEmpty()) { + return null; + } + return elements[start]; + } + + @Override + public E remove() { + if (isEmpty()) { + throw new NoSuchElementException("queue is empty"); + } + + final E element = elements[start]; + if (null != element) { + elements[start++] = null; + + if (start >= maxElements) { + start = 0; + } + full = false; + } + return element; + } + + //----------------------------------------------------------------------- + /** + * Increments the internal index. + * + * @param index the index to increment + * @return the updated index + */ + private int increment(int index) { + index++; + if (index >= maxElements) { + index = 0; + } + return index; + } + + /** + * Decrements the internal index. + * + * @param index the index to decrement + * @return the updated index + */ + private int decrement(int index) { + index--; + if (index < 0) { + index = maxElements - 1; + } + return index; + } + + /** + * Returns an iterator over this queue's elements. + * + * @return an iterator over this queue's elements + */ + @Override + public Iterator iterator() { + return new Iterator() { + + private int index = start; + private int lastReturnedIndex = -1; + private boolean isFirst = full; + + @Override + public boolean hasNext() { + return isFirst || index != end; + } + + @Override + public E next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + isFirst = false; + lastReturnedIndex = index; + index = increment(index); + return elements[lastReturnedIndex]; + } + + @Override + public void remove() { + if (lastReturnedIndex == -1) { + throw new IllegalStateException(); + } + + // First element can be removed quickly + if (lastReturnedIndex == start) { + CircularFifoQueue.this.remove(); + lastReturnedIndex = -1; + return; + } + + int pos = lastReturnedIndex + 1; + if (start < lastReturnedIndex && pos < end) { + // shift in one part + System.arraycopy(elements, pos, elements, lastReturnedIndex, end - pos); + } else { + // Other elements require us to shift the subsequent elements + while (pos != end) { + if (pos >= maxElements) { + elements[pos - 1] = elements[0]; + pos = 0; + } else { + elements[decrement(pos)] = elements[pos]; + pos = increment(pos); + } + } + } + + lastReturnedIndex = -1; + end = decrement(end); + elements[end] = null; + full = false; + index = decrement(index); + } + + }; + } + +} diff --git a/raven/src/test/java/com/getsentry/raven/RavenContextTest.java b/raven/src/test/java/com/getsentry/raven/RavenContextTest.java new file mode 100644 index 00000000000..adef64068b6 --- /dev/null +++ b/raven/src/test/java/com/getsentry/raven/RavenContextTest.java @@ -0,0 +1,89 @@ +package com.getsentry.raven; + +import com.getsentry.raven.event.Breadcrumb; +import com.getsentry.raven.event.BreadcrumbBuilder; +import org.hamcrest.Matchers; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.emptyCollectionOf; +import static org.hamcrest.Matchers.equalTo; + +public class RavenContextTest { + + @Test + public void testActivateDeactivate() { + RavenContext context = new RavenContext(); + + assertThat(RavenContext.getActiveContexts(), emptyCollectionOf(RavenContext.class)); + + context.activate(); + + List match = new ArrayList<>(1); + match.add(context); + assertThat(RavenContext.getActiveContexts(), equalTo(match)); + + context.deactivate(); + + assertThat(RavenContext.getActiveContexts(), emptyCollectionOf(RavenContext.class)); + } + + @Test + public void testBreadcrumbs() { + RavenContext context = new RavenContext(); + + Breadcrumb breadcrumb = new BreadcrumbBuilder() + .setLevel("info") + .setCategory("foo") + .setMessage("test") + .build(); + context.recordBreadcrumb(breadcrumb); + + List breadcrumbMatch = new ArrayList<>(); + breadcrumbMatch.add(breadcrumb); + + List breadcrumbs = new ArrayList<>(); + Iterator iter = context.getBreadcrumbs(); + while (iter.hasNext()) { + breadcrumbs.add(iter.next()); + } + + assertThat(breadcrumbs, equalTo(breadcrumbMatch)); + } + + @Test + public void breadcrumbLimit() { + RavenContext context = new RavenContext(1); + + Breadcrumb breadcrumb1 = new BreadcrumbBuilder() + .setLevel("info") + .setCategory("foo") + .setMessage("test1") + .build(); + Breadcrumb breadcrumb2 = new BreadcrumbBuilder() + .setLevel("info") + .setCategory("foo") + .setMessage("test2") + .build(); + + context.recordBreadcrumb(breadcrumb1); + context.recordBreadcrumb(breadcrumb2); + + List breadcrumbMatch = new ArrayList<>(); + breadcrumbMatch.add(breadcrumb2); + + List breadcrumbs = new ArrayList<>(); + Iterator iter = context.getBreadcrumbs(); + while (iter.hasNext()) { + breadcrumbs.add(iter.next()); + } + + assertThat(breadcrumbs, equalTo(breadcrumbMatch)); + + } + +} diff --git a/src/checkstyle/suppressions.xml b/src/checkstyle/suppressions.xml index 827607b7921..72e80a7ee12 100644 --- a/src/checkstyle/suppressions.xml +++ b/src/checkstyle/suppressions.xml @@ -6,4 +6,5 @@ + From 13d2d80c035868b0bc9933022e7c2206a0dfb9d3 Mon Sep 17 00:00:00 2001 From: ted kaemming Date: Fri, 10 Jun 2016 13:02:51 -0700 Subject: [PATCH 1427/2152] Minor syntax changes in CHANGES for consistency. --- CHANGES | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 9dfbf069b74..0b2e0837dd2 100644 --- a/CHANGES +++ b/CHANGES @@ -9,9 +9,9 @@ Version 7.2.4 Version 7.2.3 ------------- -- Accept `Throwable` instances as parameter to `Raven.sendException`. -- Add `raven.async.shutdowntimeout` option. -- Remove default `WARNING` filter level for the Logback appender. +- Accept ``Throwable`` instances as parameter to ``Raven.sendException``. +- Add ``raven.async.shutdowntimeout`` option. +- Remove default ``WARNING`` filter level for the Logback appender. Version 7.2.2 ------------- From dbeb5986a6081dafbae7b5060fab302a24644804 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Jun 2016 15:13:01 -0500 Subject: [PATCH 1428/2152] Bump CHANGES and docs to 7.3.0 --- CHANGES | 2 +- docs/modules/appengine.rst | 2 +- docs/modules/log4j.rst | 2 +- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 2 +- docs/modules/raven.rst | 2 +- raven-appengine/README.md | 4 ++-- raven-log4j/README.md | 4 ++-- raven-log4j2/README.md | 10 +++++----- raven-logback/README.md | 4 ++-- raven/README.md | 4 ++-- 11 files changed, 22 insertions(+), 22 deletions(-) diff --git a/CHANGES b/CHANGES index 0b2e0837dd2..4d9147ab80b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,4 @@ -Version 7.2.4 +Version 7.3.0 ------------- - Add (manual) support for breadcrumbs to event objects. diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 2088a8bd6ff..f692795bad5 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -25,7 +25,7 @@ If you want to use Maven you can install Raven-AppEngine as dependency: com.getsentry.raven raven-appengine - 7.2.3 + 7.3.0 Usage diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 445cfbbefd5..caa18662f5c 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven-Log4j as dependency: com.getsentry.raven raven-log4j - 7.2.3 + 7.3.0 If you manually want to manage your dependencies: diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 993a4b9f2bc..0f1f94dd37b 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,18 +19,18 @@ If you want to use Maven you can install Raven-Log4j2 as dependency: com.getsentry.raven raven-log4j2 - 7.2.3 + 7.3.0 If you manually want to manage your dependencies: - :doc:`raven dependencies ` - `log4j-api-2.1.jar - `_ + `_ - `log4j-core-2.0.jar - `_ + `_ - `log4j-slf4j-impl-2.1.jar - `_ + `_ is recommended as the implementation of slf4j (instead of slf4j-jdk14). Usage diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 72dcd2837f9..e50b212be19 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -16,7 +16,7 @@ If you want to use Maven you can install Raven-Logback as dependency: com.getsentry.raven raven-logback - 7.2.3 + 7.3.0 - :doc:`raven dependencies ` diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index 8263c208fce..b89f6bf3825 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven as dependency: com.getsentry.raven raven - 7.2.3 + 7.3.0 If you manually want to manage your dependencies: diff --git a/raven-appengine/README.md b/raven-appengine/README.md index 06a34578af6..889ac2278c1 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -15,12 +15,12 @@ __This module is not useful outside of Google App Engine.__ com.getsentry.raven raven-appengine - 7.2.3 + 7.3.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.2.3%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.3.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 4b1fce70587..06ba32f931e 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,12 +10,12 @@ for log4j to send the logged events to Sentry. com.getsentry.raven raven-log4j - 7.2.3 + 7.3.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.2.3%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.3.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index bd6f6d8a5a5..659b711f601 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,20 +10,20 @@ for Log4j 2 to send the logged events to Sentry. com.getsentry.raven raven-log4j2 - 7.2.3 + 7.3.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.2.3%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.3.0%7Cjar). ### Manual dependency management Relies on: - [raven dependencies](../raven) - - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.2.3%7Cjar) - - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.2.3%7Cjar) - - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.2.3%7Cjar) + - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.3.0%7Cjar) + - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.3.0%7Cjar) + - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.3.0%7Cjar) is recommended as the implementation of slf4j (instead of slf4j-jdk14). diff --git a/raven-logback/README.md b/raven-logback/README.md index 0747656e5dd..6f5f3f48945 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,12 +10,12 @@ for logback to send the logged events to Sentry. com.getsentry.raven raven-logback - 7.2.3 + 7.3.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.2.3%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.3.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven/README.md b/raven/README.md index e5f18b4c935..b265bb6e84f 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,12 +10,12 @@ for `java.util.logging`. com.getsentry.raven raven - 7.2.3 + 7.3.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.2.3%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.3.0%7Cjar). ### Manual dependency management Relies on: From 2fdb1520b8178d733decbbb834ca551e8a195e0b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Jun 2016 15:14:26 -0500 Subject: [PATCH 1429/2152] [maven-release-plugin] prepare release v7.3.0 --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index dee680a7922..f6095b3e258 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.2.4-SNAPSHOT + 7.3.0 pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v7.3.0 https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index f44503a1eea..41291da0fa4 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.2.4-SNAPSHOT + 7.3.0 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 1da9527335e..f01d16ae944 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.4-SNAPSHOT + 7.3.0 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 6e27e07f7aa..4fac521227e 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.4-SNAPSHOT + 7.3.0 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 4eb9bf29061..45ed2587bda 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.4-SNAPSHOT + 7.3.0 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 228e4429521..049217d3801 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.4-SNAPSHOT + 7.3.0 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index ef24e654eb8..a6466116a60 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.2.4-SNAPSHOT + 7.3.0 sentry-stub From e23d8ce388f3eade88571f0a11ee7a8479212920 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Jun 2016 15:14:26 -0500 Subject: [PATCH 1430/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index f6095b3e258..9681e5eb0c0 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.3.0 + 7.3.1-SNAPSHOT pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v7.3.0 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 41291da0fa4..efe31eb30c0 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.3.0 + 7.3.1-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index f01d16ae944..9ae41f050e6 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.3.0 + 7.3.1-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 4fac521227e..f1eb8420c6b 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.3.0 + 7.3.1-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 45ed2587bda..237ace137bf 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.3.0 + 7.3.1-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 049217d3801..eb87f89009a 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.3.0 + 7.3.1-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index a6466116a60..4f113b09e33 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.3.0 + 7.3.1-SNAPSHOT sentry-stub From 4aaa3c8328d54baf709821747cb30f7c808179b0 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 27 Jun 2016 19:24:56 -0500 Subject: [PATCH 1431/2152] Changed default raven.async.queuesize from unlimited to 50. (#221) --- CHANGES | 5 +++++ README.md | 9 +++++++-- docs/config.rst | 13 +++++++++---- .../com/getsentry/raven/DefaultRavenFactory.java | 14 ++++++++++++-- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index 4d9147ab80b..d564991eff9 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 7.4.0 +------------- + +- Changed default ``raven.async.queuesize`` from unlimited to 50. + Version 7.3.0 ------------- diff --git a/README.md b/README.md index 58a1482db89..6c515c42a2c 100644 --- a/README.md +++ b/README.md @@ -175,8 +175,9 @@ The option to do so is `raven.async.gracefulshutdown`: http://public:private@host:port/1?raven.async.gracefulshutdown=false #### Queue size (advanced) -The default queue used to store unprocessed events doesn't have a -limit. +The default queue used to store unprocessed events is limited to 50 +items. Additional items added once the queue is full are dropped and +never sent to the Sentry server. Depending on the environment (if the memory is sparse) it is important to be able to control the size of that queue to avoid memory issues. @@ -187,6 +188,10 @@ It is possible to set a maximum with the option `raven.async.queuesize`: This means that if the connection to the Sentry server is down, only the 100 most recent events will be stored and processed as soon as the server is back up. +The special value `-1` can be used to enable an unlimited queue. Beware +that network connectivity or Sentry server issues could mean your process +will run out of memory. + #### Threads count (advanced) By default the thread pool used by the async connection contains one thread per processor available to the JVM. diff --git a/docs/config.rst b/docs/config.rst index 28202e150b5..59738eb8a3f 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -83,10 +83,11 @@ Queue and Thread Settings ````````````````````````` Queue size (advanced): - The default queue used to store the not yet processed events doesn't - have a limit. Depending on the environment (if the memory is sparse) - it is important to be able to control the size of that queue to avoid - memory issues. + The default queue used to store unprocessed events is limited to 50 + items. Additional items added once the queue is full are dropped and + never sent to the Sentry server. Depending on the environment (if the + memory is sparse) it is important to be able to control the size of + that queue to avoid memory issues. It is possible to set a maximum with the option ``raven.async.queuesize``:: @@ -96,6 +97,10 @@ Queue size (advanced): the 100 most recent events will be stored and processed as soon as the server is back up. + The special value ``-1`` can be used to enable an unlimited queue. Beware + that network connectivity or Sentry server issues could mean your process + will run out of memory. + Threads count (advanced): By default the thread pool used by the async connection contains one thread per processor available to the JVM (more threads wouldn't be diff --git a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java index 6d518b9f964..17508613d94 100644 --- a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java @@ -63,6 +63,11 @@ public class DefaultRavenFactory extends RavenFactory { * Option to hide common stackframes with enclosing exceptions. */ public static final String HIDE_COMMON_FRAMES_OPTION = "raven.stacktrace.hidecommon"; + /** + * The default async queue size if none is provided. + */ + public static final int QUEUE_SIZE_DEFAULT = 50; + private static final Logger logger = LoggerFactory.getLogger(DefaultRavenFactory.class); private static final String FALSE = Boolean.FALSE.toString(); @@ -141,9 +146,14 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { BlockingDeque queue; if (dsn.getOptions().containsKey(QUEUE_SIZE_OPTION)) { - queue = new LinkedBlockingDeque<>(Integer.parseInt(dsn.getOptions().get(QUEUE_SIZE_OPTION))); + int queueSize = Integer.parseInt(dsn.getOptions().get(QUEUE_SIZE_OPTION)); + if (queueSize == -1) { + queue = new LinkedBlockingDeque<>(); + } else { + queue = new LinkedBlockingDeque<>(queueSize); + } } else { - queue = new LinkedBlockingDeque<>(); + queue = new LinkedBlockingDeque<>(QUEUE_SIZE_DEFAULT); } ExecutorService executorService = new ThreadPoolExecutor( From 19b9bd84906585b6d6a901062a44c6eeef3b0b37 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 14 Jul 2016 09:12:50 -0500 Subject: [PATCH 1432/2152] Add EventSendFailureCallback, called on send failure. (#201) * Add EventSendFailureCallback, called on send failure. --- CHANGES | 1 + .../connection/AppEngineAsyncConnection.java | 6 ++ .../raven/connection/AbstractConnection.java | 28 ++++++++ .../raven/connection/AsyncConnection.java | 5 ++ .../raven/connection/Connection.java | 11 +++- .../connection/EventSendFailureCallback.java | 20 ++++++ .../connection/AbstractConnectionTest.java | 30 +++++++++ .../EventSendFailureCallbackTest.java | 66 +++++++++++++++++++ 8 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 raven/src/main/java/com/getsentry/raven/connection/EventSendFailureCallback.java create mode 100644 raven/src/test/java/com/getsentry/raven/connection/EventSendFailureCallbackTest.java diff --git a/CHANGES b/CHANGES index d564991eff9..5d9e2cbbe8d 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 7.4.0 ------------- - Changed default ``raven.async.queuesize`` from unlimited to 50. +- Add callback system for exceptions raised while attempting to send events. Version 7.3.0 ------------- diff --git a/raven-appengine/src/main/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnection.java b/raven-appengine/src/main/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnection.java index 357759d22df..48d534f1129 100644 --- a/raven-appengine/src/main/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnection.java +++ b/raven-appengine/src/main/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnection.java @@ -1,5 +1,6 @@ package com.getsentry.raven.appengine.connection; +import com.getsentry.raven.connection.EventSendFailureCallback; import com.google.appengine.api.taskqueue.DeferredTask; import com.google.appengine.api.taskqueue.Queue; import com.google.appengine.api.taskqueue.QueueFactory; @@ -75,6 +76,11 @@ public void send(Event event) { queue.add(withPayload(new EventSubmitter(id, event))); } + @Override + public void addEventSendFailureCallback(EventSendFailureCallback eventSendFailureCallback) { + actualConnection.addEventSendFailureCallback(eventSendFailureCallback); + } + /** * {@inheritDoc}. *

    diff --git a/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java b/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java index 57a0341ed65..90171fc574d 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java @@ -5,6 +5,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Condition; @@ -47,6 +49,11 @@ public abstract class AbstractConnection implements Connection { */ private long baseWaitingTime = DEFAULT_BASE_WAITING_TIME; private long waitingTime = baseWaitingTime; + /** + * Set of callbacks that will be called when an exception occurs while attempting to + * send events to the Sentry server. + */ + private Set eventSendFailureCallbacks; /** * Creates a connection based on the public and secret keys. @@ -55,6 +62,7 @@ public abstract class AbstractConnection implements Connection { * @param secretKey secret key (password) to the Sentry server. */ protected AbstractConnection(String publicKey, String secretKey) { + eventSendFailureCallbacks = new HashSet<>(); authHeader = "Sentry sentry_version=" + SENTRY_PROTOCOL_VERSION + "," + "sentry_client=" + RavenEnvironment.NAME + "," + "sentry_key=" + publicKey + "," @@ -80,6 +88,15 @@ public final void send(Event event) { } catch (ConnectionException e) { logger.warn("An exception due to the connection occurred, a lockdown will be initiated.", e); lockDown(); + + for (EventSendFailureCallback eventSendFailureCallback : eventSendFailureCallbacks) { + try { + eventSendFailureCallback.onFailure(event, e); + } catch (Exception exc) { + logger.warn("An exception occurred while running an EventSendFailureCallback: " + + eventSendFailureCallback.getClass().getName(), exc); + } + } } } @@ -144,4 +161,15 @@ public void setMaxWaitingTime(long maxWaitingTime) { public void setBaseWaitingTime(long baseWaitingTime) { this.baseWaitingTime = baseWaitingTime; } + + /** + * Add a callback that is called when an exception occurs while attempting to + * send events to the Sentry server. + * + * @param eventSendFailureCallback callback instance + */ + public void addEventSendFailureCallback(EventSendFailureCallback eventSendFailureCallback) { + eventSendFailureCallbacks.add(eventSendFailureCallback); + } + } diff --git a/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java b/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java index bba5ecdded9..f8dc670eb98 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java @@ -106,6 +106,11 @@ public void send(Event event) { executorService.execute(new EventSubmitter(event)); } + @Override + public void addEventSendFailureCallback(EventSendFailureCallback eventSendFailureCallback) { + actualConnection.addEventSendFailureCallback(eventSendFailureCallback); + } + /** * {@inheritDoc}. *

    diff --git a/raven/src/main/java/com/getsentry/raven/connection/Connection.java b/raven/src/main/java/com/getsentry/raven/connection/Connection.java index cca2b9b810e..7a8f97a765e 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/Connection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/Connection.java @@ -9,9 +9,18 @@ */ public interface Connection extends Closeable { /** - * Sends an event to the sentry server. + * Sends an event to the Sentry server. * * @param event captured event to add in Sentry. */ void send(Event event); + + /** + * Add a callback that is called when an exception occurs while attempting to + * send events to the Sentry server. + * + * @param eventSendFailureCallback callback instance + */ + void addEventSendFailureCallback(EventSendFailureCallback eventSendFailureCallback); + } diff --git a/raven/src/main/java/com/getsentry/raven/connection/EventSendFailureCallback.java b/raven/src/main/java/com/getsentry/raven/connection/EventSendFailureCallback.java new file mode 100644 index 00000000000..2bfa3490559 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/connection/EventSendFailureCallback.java @@ -0,0 +1,20 @@ +package com.getsentry.raven.connection; + +import com.getsentry.raven.event.Event; + +/** + * Callback that is called when an exception occurs while attempting to + * send events to the Sentry server. + */ +public interface EventSendFailureCallback { + + /** + * Called when an exception occurs while attempting to + * send events to the Sentry server. + * + * @param event Event that couldn't be sent + * @param exception Exception that occurred while attempting to send the Event + */ + void onFailure(Event event, Exception exception); + +} diff --git a/raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java b/raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java index d51b0a5c184..741b2b1df42 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java +++ b/raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java @@ -4,6 +4,10 @@ import com.getsentry.raven.event.Event; import org.testng.annotations.Test; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; @@ -99,4 +103,30 @@ public void testLockDownDoesntDoubleItAtMax(@Injectable final Event mockEvent) t Thread.sleep(AbstractConnection.DEFAULT_MAX_WAITING_TIME); }}; } + + @Test + public void testEventSendFailureCallback(@Injectable final Event mockEvent) throws Exception { + final AtomicBoolean callbackCalled = new AtomicBoolean(false); + EventSendFailureCallback callback = new EventSendFailureCallback() { + + @Override + public void onFailure(Event event, Exception exception) { + callbackCalled.set(true); + } + + }; + HashSet callbacks = new HashSet<>(); + callbacks.add(callback); + + setField(abstractConnection, "eventSendFailureCallbacks", callbacks); + new NonStrictExpectations() {{ + abstractConnection.doSend((Event) any); + result = new ConnectionException(); + }}; + + abstractConnection.send(mockEvent); + + assertThat(callbackCalled.get(), is(true)); + } + } diff --git a/raven/src/test/java/com/getsentry/raven/connection/EventSendFailureCallbackTest.java b/raven/src/test/java/com/getsentry/raven/connection/EventSendFailureCallbackTest.java new file mode 100644 index 00000000000..ad211ed991d --- /dev/null +++ b/raven/src/test/java/com/getsentry/raven/connection/EventSendFailureCallbackTest.java @@ -0,0 +1,66 @@ +package com.getsentry.raven.connection; + +import com.getsentry.raven.DefaultRavenFactory; +import com.getsentry.raven.Raven; +import com.getsentry.raven.dsn.Dsn; +import com.getsentry.raven.event.Event; +import org.testng.annotations.Test; + +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class EventSendFailureCallbackTest { + + @Test + public void testSimpleCallback() { + final AtomicBoolean flag = new AtomicBoolean(false); + + DefaultRavenFactory factory = new DefaultRavenFactory() { + @Override + protected Connection createConnection(Dsn dsn) { + Connection connection = super.createConnection(dsn); + + connection.addEventSendFailureCallback(new EventSendFailureCallback() { + @Override + public void onFailure(Event event, Exception exception) { + flag.set(true); + } + }); + + return connection; + } + }; + + String dsn = "https://foo:bar@localhost:1234/1?raven.async=false"; + Raven raven = factory.createRavenInstance(new Dsn(dsn)); + raven.sendMessage("Message that will fail because DSN points to garbage."); + + assertThat(flag.get(), is(true)); + } + + @Test + public void testExceptionInsideCallback() { + DefaultRavenFactory factory = new DefaultRavenFactory() { + @Override + protected Connection createConnection(Dsn dsn) { + Connection connection = super.createConnection(dsn); + + connection.addEventSendFailureCallback(new EventSendFailureCallback() { + @Override + public void onFailure(Event event, Exception exception) { + throw new RuntimeException("Error inside of EventSendFailureCallback"); + } + }); + + return connection; + } + }; + + String dsn = "https://foo:bar@localhost:1234/1?raven.async=false"; + Raven raven = factory.createRavenInstance(new Dsn(dsn)); + raven.sendMessage("Message that will fail because DSN points to garbage."); + } + +} From 3c31a1b2575b530a43b4c4863920f4364e2717c8 Mon Sep 17 00:00:00 2001 From: ted kaemming Date: Thu, 14 Jul 2016 15:50:28 -0700 Subject: [PATCH 1433/2152] Clarify the log4j configuration. (#224) This describes adding the `sentry` appender to the root logger, since this can be easy to forget if you're in a rush or not familiar with the configuration format. --- raven-log4j/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 06ba32f931e..e58c37a085b 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -54,6 +54,16 @@ Alternatively in the `log4j.xml` file set: ``` +You'll also need to associate the `sentry` appender with your root logger, like so: + +``` + + + + + +``` + ### Additional data and information It's possible to add extra data to events, thanks to both [the MDC](https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/MDC.html) From 76c09fb07b4a21bfe43adf762c690d93381e213c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 15 Jul 2016 09:16:04 -0500 Subject: [PATCH 1434/2152] Bump CHANGES and docs to 7.4.0 --- docs/modules/appengine.rst | 2 +- docs/modules/log4j.rst | 2 +- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 2 +- docs/modules/raven.rst | 2 +- raven-appengine/README.md | 4 ++-- raven-log4j/README.md | 4 ++-- raven-log4j2/README.md | 10 +++++----- raven-logback/README.md | 4 ++-- raven/README.md | 4 ++-- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index f692795bad5..3baa989fc7c 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -25,7 +25,7 @@ If you want to use Maven you can install Raven-AppEngine as dependency: com.getsentry.raven raven-appengine - 7.3.0 + 7.4.0 Usage diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index caa18662f5c..ee34fb38dc6 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven-Log4j as dependency: com.getsentry.raven raven-log4j - 7.3.0 + 7.4.0 If you manually want to manage your dependencies: diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 0f1f94dd37b..cd0a098c6fe 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,18 +19,18 @@ If you want to use Maven you can install Raven-Log4j2 as dependency: com.getsentry.raven raven-log4j2 - 7.3.0 + 7.4.0 If you manually want to manage your dependencies: - :doc:`raven dependencies ` - `log4j-api-2.1.jar - `_ + `_ - `log4j-core-2.0.jar - `_ + `_ - `log4j-slf4j-impl-2.1.jar - `_ + `_ is recommended as the implementation of slf4j (instead of slf4j-jdk14). Usage diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index e50b212be19..b2a3a43ecdc 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -16,7 +16,7 @@ If you want to use Maven you can install Raven-Logback as dependency: com.getsentry.raven raven-logback - 7.3.0 + 7.4.0 - :doc:`raven dependencies ` diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index b89f6bf3825..b4fdedcc983 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven as dependency: com.getsentry.raven raven - 7.3.0 + 7.4.0 If you manually want to manage your dependencies: diff --git a/raven-appengine/README.md b/raven-appengine/README.md index 889ac2278c1..2ab6bd95cfc 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -15,12 +15,12 @@ __This module is not useful outside of Google App Engine.__ com.getsentry.raven raven-appengine - 7.3.0 + 7.4.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.3.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.4.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j/README.md b/raven-log4j/README.md index e58c37a085b..151524969a2 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,12 +10,12 @@ for log4j to send the logged events to Sentry. com.getsentry.raven raven-log4j - 7.3.0 + 7.4.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.3.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.4.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index 659b711f601..00f63e77572 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,20 +10,20 @@ for Log4j 2 to send the logged events to Sentry. com.getsentry.raven raven-log4j2 - 7.3.0 + 7.4.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.3.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.4.0%7Cjar). ### Manual dependency management Relies on: - [raven dependencies](../raven) - - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.3.0%7Cjar) - - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.3.0%7Cjar) - - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.3.0%7Cjar) + - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.4.0%7Cjar) + - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.4.0%7Cjar) + - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.4.0%7Cjar) is recommended as the implementation of slf4j (instead of slf4j-jdk14). diff --git a/raven-logback/README.md b/raven-logback/README.md index 6f5f3f48945..e33824abf33 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,12 +10,12 @@ for logback to send the logged events to Sentry. com.getsentry.raven raven-logback - 7.3.0 + 7.4.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.3.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.4.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven/README.md b/raven/README.md index b265bb6e84f..eaddb007ac3 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,12 +10,12 @@ for `java.util.logging`. com.getsentry.raven raven - 7.3.0 + 7.4.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.3.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.4.0%7Cjar). ### Manual dependency management Relies on: From f7b015d8369091206d575bb72a1e2a825e71f112 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 15 Jul 2016 09:17:29 -0500 Subject: [PATCH 1435/2152] [maven-release-plugin] prepare release v7.4.0 --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 9681e5eb0c0..0d3aa374926 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.3.1-SNAPSHOT + 7.4.0 pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v7.4.0 https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index efe31eb30c0..f5a96e2cb9e 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.3.1-SNAPSHOT + 7.4.0 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 9ae41f050e6..3d05740d8ab 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.3.1-SNAPSHOT + 7.4.0 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index f1eb8420c6b..17e01446eaf 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.3.1-SNAPSHOT + 7.4.0 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 237ace137bf..34dcb2a06ca 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.3.1-SNAPSHOT + 7.4.0 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index eb87f89009a..2adb515ed24 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.3.1-SNAPSHOT + 7.4.0 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 4f113b09e33..6d2ea8623c0 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.3.1-SNAPSHOT + 7.4.0 sentry-stub From be044e84b749889576a73696bb810985e6f3b3cc Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 15 Jul 2016 09:17:29 -0500 Subject: [PATCH 1436/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 0d3aa374926..22323f5e134 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.4.0 + 7.4.1-SNAPSHOT pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v7.4.0 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index f5a96e2cb9e..301cd7d42f2 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.4.0 + 7.4.1-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 3d05740d8ab..56bea239ced 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.4.0 + 7.4.1-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 17e01446eaf..7ee3d3414ad 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.4.0 + 7.4.1-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 34dcb2a06ca..8ed6bcf41af 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.4.0 + 7.4.1-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 2adb515ed24..b12808775d4 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.4.0 + 7.4.1-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 6d2ea8623c0..b783377c828 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.4.0 + 7.4.1-SNAPSHOT sentry-stub From 0f05f6a920211faacf5eb5733a485ba4b6311ac9 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 21 Jul 2016 09:51:46 -0500 Subject: [PATCH 1437/2152] Add more debugging information to ``RavenFactory.ravenInstance``. (#229) * Add more debugging information to ``RavenFactory.ravenInstance``. --- CHANGES | 5 ++ .../com/getsentry/raven/RavenFactory.java | 54 +++++++++++++++++-- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 5d9e2cbbe8d..d5cc63b17df 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 7.4.1 +------------- + +- Add more debugging information to ``RavenFactory.ravenInstance``. + Version 7.4.0 ------------- diff --git a/raven/src/main/java/com/getsentry/raven/RavenFactory.java b/raven/src/main/java/com/getsentry/raven/RavenFactory.java index 854d3170442..be1c5c5eefd 100644 --- a/raven/src/main/java/com/getsentry/raven/RavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/RavenFactory.java @@ -4,6 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -81,23 +82,70 @@ public static Raven ravenInstance(Dsn dsn) { * @throws IllegalStateException when no instance of Raven has been created. */ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { - //Loop through registered factories logger.debug("Attempting to find a working Raven factory"); + + // Loop through registered factories, keeping track of which classes we skip, which we try to instantiate, + // and the last exception thrown. + ArrayList skippedFactories = new ArrayList<>(); + ArrayList triedFactories = new ArrayList<>(); + RuntimeException lastExc = null; + for (RavenFactory ravenFactory : getRegisteredFactories()) { - if (ravenFactoryName != null && !ravenFactoryName.equals(ravenFactory.getClass().getName())) + String name = ravenFactory.getClass().getName(); + if (ravenFactoryName != null && !ravenFactoryName.equals(name)) { + skippedFactories.add(name); continue; + } logger.debug("Attempting to use '{}' as a Raven factory.", ravenFactory); + triedFactories.add(name); try { Raven ravenInstance = ravenFactory.createRavenInstance(dsn); logger.debug("The raven factory '{}' created an instance of Raven.", ravenFactory); return ravenInstance; } catch (RuntimeException e) { + lastExc = e; logger.debug("The raven factory '{}' couldn't create an instance of Raven.", ravenFactory, e); } } - throw new IllegalStateException("Couldn't create a raven instance for '" + dsn + "'"); + // Throw an IllegalStateException that attempts to be helpful. + StringBuilder sb = new StringBuilder(); + sb.append("Couldn't create a raven instance for: '"); + sb.append(dsn); + sb.append('\''); + if (ravenFactoryName != null) { + sb.append("; ravenFactoryName: "); + sb.append(ravenFactoryName); + + if (skippedFactories.isEmpty()) { + sb.append("; no skipped factories"); + } else { + sb.append("; skipped factories: "); + String delim = ""; + for (String skippedFactory : skippedFactories) { + sb.append(delim); + sb.append(skippedFactory); + delim = ", "; + } + } + } + + if (triedFactories.isEmpty()) { + sb.append("; no factories tried!"); + throw new IllegalStateException(sb.toString()); + } + + sb.append("; tried factories: "); + String delim = ""; + for (String triedFactory : triedFactories) { + sb.append(delim); + sb.append(triedFactory); + delim = ", "; + } + + sb.append("; cause contains exception thrown by the last factory tried."); + throw new IllegalStateException(sb.toString(), lastExc); } /** From 72be2ed7a960087c0e4d47bbc9ba367fafd907b4 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 21 Jul 2016 10:00:01 -0500 Subject: [PATCH 1438/2152] Add ability to set HTTP proxy in DSN. (#226) * Add ability to set HTTP proxy in DSN. --- CHANGES | 3 +- README.md | 12 +++++-- .../getsentry/raven/DefaultRavenFactory.java | 33 ++++++++++++++++++- .../raven/connection/HttpConnection.java | 25 +++++++++++++- 4 files changed, 67 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index d5cc63b17df..e773d1ff228 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ -Version 7.4.1 +Version 7.5.0 ------------- +- Add support for configuring an HTTP proxy in the DSN. - Add more debugging information to ``RavenFactory.ravenInstance``. Version 7.4.0 diff --git a/README.md b/README.md index 6c515c42a2c..aad70e9fbb0 100644 --- a/README.md +++ b/README.md @@ -106,10 +106,11 @@ naive and ignore hostname verification: naive+https://public:private@host:port/1 ### Proxying HTTP(S) connections -If your application sends outbound requests through an HTTP proxy, you can use -JVM networking properties to configure the proxy information. +If your application needs to send outbound requests through an HTTP proxy, +you can configure the proxy information via JVM networking properties or +as part of the Sentry DSN. -For example, +For example, using JVM networking properties (affects the entire JVM process), ``` java \ @@ -131,6 +132,11 @@ See [Java Networking and Proxies](http://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html) for more information about the proxy properties. +Alternatively, using the Sentry DSN (only affects the Sentry HTTP client, +useful inside shared application containers), + + http://public:private@host:port/1?raven.http.proxy.host=proxy.example.com&raven.http.proxy.port=8080 + ## Options It is possible to enable some options by adding data to the query string of the DSN: diff --git a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java index 17508613d94..1f42cb9568e 100644 --- a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java @@ -10,6 +10,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.net.InetSocketAddress; +import java.net.Proxy; import java.net.URL; import java.util.Arrays; import java.util.Collection; @@ -63,10 +65,22 @@ public class DefaultRavenFactory extends RavenFactory { * Option to hide common stackframes with enclosing exceptions. */ public static final String HIDE_COMMON_FRAMES_OPTION = "raven.stacktrace.hidecommon"; + /** + * Option to set an HTTP proxy hostname for Sentry connections. + */ + public static final String HTTP_PROXY_HOST_OPTION = "raven.http.proxy.host"; + /** + * Option to set an HTTP proxy port for Sentry connections. + */ + public static final String HTTP_PROXY_PORT_OPTION = "raven.http.proxy.port"; /** * The default async queue size if none is provided. */ public static final int QUEUE_SIZE_DEFAULT = 50; + /** + * The default HTTP proxy port to use if an HTTP Proxy hostname is set but port is not. + */ + public static final int HTTP_PROXY_PORT_DEFAULT = 80; private static final Logger logger = LoggerFactory.getLogger(DefaultRavenFactory.class); private static final String FALSE = Boolean.FALSE.toString(); @@ -179,7 +193,24 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { */ protected Connection createHttpConnection(Dsn dsn) { URL sentryApiUrl = HttpConnection.getSentryApiUrl(dsn.getUri(), dsn.getProjectId()); - HttpConnection httpConnection = new HttpConnection(sentryApiUrl, dsn.getPublicKey(), dsn.getSecretKey()); + + String proxyHost = null; + if (dsn.getOptions().containsKey(HTTP_PROXY_HOST_OPTION)) { + proxyHost = dsn.getOptions().get(HTTP_PROXY_HOST_OPTION); + } + + int proxyPort = HTTP_PROXY_PORT_DEFAULT; + if (dsn.getOptions().containsKey(HTTP_PROXY_PORT_OPTION)) { + proxyPort = Integer.parseInt(dsn.getOptions().get(HTTP_PROXY_PORT_OPTION)); + } + + Proxy proxy = null; + if (proxyHost != null) { + InetSocketAddress proxyAddr = new InetSocketAddress(proxyHost, proxyPort); + proxy = new Proxy(Proxy.Type.HTTP, proxyAddr); + } + + HttpConnection httpConnection = new HttpConnection(sentryApiUrl, dsn.getPublicKey(), dsn.getSecretKey(), proxy); httpConnection.setMarshaller(createMarshaller(dsn)); // Set the naive mode diff --git a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java b/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java index 04152b2935a..b4356fd5cea 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java @@ -12,6 +12,7 @@ import java.io.*; import java.net.HttpURLConnection; import java.net.MalformedURLException; +import java.net.Proxy; import java.net.URI; import java.net.URL; import java.nio.charset.Charset; @@ -50,6 +51,10 @@ public boolean verify(String hostname, SSLSession sslSession) { * URL of the Sentry endpoint. */ private final URL sentryUrl; + /** + * Optional instance of an HTTP proxy server to use. + */ + private final Proxy proxy; /** * Marshaller used to transform and send the {@link Event} over a stream. */ @@ -72,8 +77,20 @@ public boolean verify(String hostname, SSLSession sslSession) { * @param secretKey private key of the current project. */ public HttpConnection(URL sentryUrl, String publicKey, String secretKey) { + this(sentryUrl, publicKey, secretKey, null); + } + + /** + * Creates an HTTP connection to a Sentry server. + * @param sentryUrl URL to the Sentry API. + * @param publicKey public key of the current project. + * @param secretKey private key of the current project. + * @param proxy address of HTTP proxy or null if using direct connections. + */ + public HttpConnection(URL sentryUrl, String publicKey, String secretKey, Proxy proxy) { super(publicKey, secretKey); this.sentryUrl = sentryUrl; + this.proxy = proxy; } /** @@ -99,7 +116,13 @@ public static URL getSentryApiUrl(URI sentryUri, String projectId) { */ protected HttpURLConnection getConnection() { try { - HttpURLConnection connection = (HttpURLConnection) sentryUrl.openConnection(); + HttpURLConnection connection; + if (proxy != null) { + connection = (HttpURLConnection) sentryUrl.openConnection(proxy); + } else { + connection = (HttpURLConnection) sentryUrl.openConnection(); + } + if (bypassSecurity && connection instanceof HttpsURLConnection) { ((HttpsURLConnection) connection).setHostnameVerifier(NAIVE_VERIFIER); } From 052cee1a14eb4d023d8993a46a432c1612098bac Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 21 Jul 2016 10:07:35 -0500 Subject: [PATCH 1439/2152] Add formatted field to JSON if possible. (#220) * Add formatted field to JSON if possible. --- CHANGES | 1 + .../raven/log4j2/SentryAppender.java | 6 ++- .../raven/logback/SentryAppender.java | 6 ++- .../event/interfaces/MessageInterface.java | 44 ++++++++++++++----- .../getsentry/raven/jul/SentryHandler.java | 37 ++++++++++------ .../raven/marshaller/json/JsonMarshaller.java | 8 ++-- .../json/MessageInterfaceBinding.java | 12 +++-- 7 files changed, 78 insertions(+), 36 deletions(-) diff --git a/CHANGES b/CHANGES index e773d1ff228..ef56cbe33e0 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ Version 7.5.0 - Add support for configuring an HTTP proxy in the DSN. - Add more debugging information to ``RavenFactory.ravenInstance``. +- Add ``formatted`` field to JSON payload if possible. Version 7.4.0 ------------- diff --git a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java index 7f4ec7c3090..ad797b17e5e 100644 --- a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java @@ -260,8 +260,10 @@ protected Event buildEvent(LogEvent event) { } if (!eventMessage.getFormattedMessage().equals(eventMessage.getFormat())) { - eventBuilder.withSentryInterface(new MessageInterface(eventMessage.getFormat(), - formatMessageParameters(eventMessage.getParameters()))); + eventBuilder.withSentryInterface(new MessageInterface( + eventMessage.getFormat(), + formatMessageParameters(eventMessage.getParameters()), + eventMessage.getFormattedMessage())); } Throwable throwable = event.getThrown(); diff --git a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java index 75ffce8a4e1..6dc03626a75 100644 --- a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java @@ -208,8 +208,10 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { } if (iLoggingEvent.getArgumentArray() != null) { - eventBuilder.withSentryInterface(new MessageInterface(iLoggingEvent.getMessage(), - formatMessageParameters(iLoggingEvent.getArgumentArray()))); + eventBuilder.withSentryInterface(new MessageInterface( + iLoggingEvent.getMessage(), + formatMessageParameters(iLoggingEvent.getArgumentArray()), + iLoggingEvent.getFormattedMessage())); } if (iLoggingEvent.getThrowableProxy() != null) { diff --git a/raven/src/main/java/com/getsentry/raven/event/interfaces/MessageInterface.java b/raven/src/main/java/com/getsentry/raven/event/interfaces/MessageInterface.java index 48ec99b3fef..fcf08651d45 100644 --- a/raven/src/main/java/com/getsentry/raven/event/interfaces/MessageInterface.java +++ b/raven/src/main/java/com/getsentry/raven/event/interfaces/MessageInterface.java @@ -1,9 +1,11 @@ package com.getsentry.raven.event.interfaces; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; /** * The Message interface for Sentry allows to send the original pattern to Sentry, allowing the events to be grouped @@ -29,6 +31,7 @@ public class MessageInterface implements SentryInterface { public static final String MESSAGE_INTERFACE = "sentry.interfaces.Message"; private final String message; private final List parameters; + @Nullable private final String formatted; /** * Creates a non parametrised message. @@ -37,9 +40,7 @@ public class MessageInterface implements SentryInterface { * recommended to use {@link com.getsentry.raven.event.EventBuilder#withMessage(String)} instead. * * @param message message to add to the event. - * @deprecated Use {@link com.getsentry.raven.event.EventBuilder#withMessage(String)} instead. */ - @Deprecated public MessageInterface(String message) { this(message, Collections.emptyList()); } @@ -61,8 +62,20 @@ public MessageInterface(String message, String... params) { * @param parameters parameters of the message. */ public MessageInterface(String message, List parameters) { + this(message, parameters, null); + } + + /** + * Creates a parametrised message for an {@link com.getsentry.raven.event.Event}. + * + * @param message original message. + * @param parameters parameters of the message. + * @param formatted message formatted with parameters + */ + public MessageInterface(String message, List parameters, String formatted) { this.message = message; this.parameters = Collections.unmodifiableList(new ArrayList<>(parameters)); + this.formatted = formatted; } @Override @@ -78,26 +91,35 @@ public List getParameters() { return parameters; } + public String getFormatted() { + return formatted; + } + @Override public String toString() { return "MessageInterface{" - + "message='" + message + '\'' - + ", parameters=" + parameters - + '}'; + + "message='" + message + '\'' + + ", parameters=" + parameters + + ", formatted=" + formatted + + '}'; } @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } MessageInterface that = (MessageInterface) o; - - return message.equals(that.message) && parameters.equals(that.parameters); + return Objects.equals(message, that.message) + && Objects.equals(parameters, that.parameters) + && Objects.equals(formatted, that.formatted); } @Override public int hashCode() { - return message.hashCode(); + return Objects.hash(message, parameters, formatted); } } diff --git a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java index 8b60fa36329..e5f31cb047a 100644 --- a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java +++ b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java @@ -20,7 +20,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.MissingFormatArgumentException; import java.util.Set; import java.util.logging.ErrorManager; import java.util.logging.Handler; @@ -220,21 +219,23 @@ protected Event buildEvent(LogRecord record) { if (record.getResourceBundle() != null && record.getResourceBundle().containsKey(record.getMessage())) { message = record.getResourceBundle().getString(record.getMessage()); } - if (record.getParameters() != null) { + + String topLevelMessage = message; + if (record.getParameters() == null) { + eventBuilder.withSentryInterface(new MessageInterface(message)); + } else { + String formatted; List parameters = formatMessageParameters(record.getParameters()); - eventBuilder.withSentryInterface(new MessageInterface(message, parameters)); - if (printfStyle) { - try { - message = String.format(message, record.getParameters()); - } catch (MissingFormatArgumentException e) { - // use unformatted message - message = record.getMessage(); - } - } else { - message = MessageFormat.format(message, record.getParameters()); + try { + formatted = formatMessage(message, record.getParameters()); + topLevelMessage = formatted; // write out formatted as Event's message key + } catch (Exception e) { + // local formatting failed, send message and parameters without formatted string + formatted = null; } + eventBuilder.withSentryInterface(new MessageInterface(message, parameters, formatted)); } - eventBuilder.withMessage(message); + eventBuilder.withMessage(topLevelMessage); Throwable throwable = record.getThrown(); if (throwable != null) @@ -277,6 +278,16 @@ protected Event buildEvent(LogRecord record) { return eventBuilder.build(); } + private String formatMessage(String message, Object[] parameters) { + String formatted; + if (printfStyle) { + formatted = String.format(message, parameters); + } else { + formatted = MessageFormat.format(message, parameters); + } + return formatted; + } + @Override public void flush() { } diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java index f5765c2c357..1512fd913ea 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java @@ -128,7 +128,7 @@ private void writeContent(JsonGenerator generator, Event event) throws IOExcepti generator.writeStartObject(); generator.writeStringField(EVENT_ID, formatId(event.getId())); - generator.writeStringField(MESSAGE, formatMessage(event.getMessage())); + generator.writeStringField(MESSAGE, trimMessage(event.getMessage())); generator.writeStringField(TIMESTAMP, ISO_FORMAT.get().format(event.getTimestamp())); generator.writeStringField(LEVEL, formatLevel(event.getLevel())); generator.writeStringField(LOGGER, event.getLogger()); @@ -268,12 +268,12 @@ private void writeBreadcumbs(JsonGenerator generator, List breadcrum } /** - * Formats a message, ensuring that the maximum length {@link #MAX_MESSAGE_LENGTH} isn't reached. + * Trims a message, ensuring that the maximum length {@link #MAX_MESSAGE_LENGTH} isn't reached. * * @param message message to format. - * @return formatted message (shortened if necessary). + * @return trimmed message (shortened if necessary). */ - private String formatMessage(String message) { + private String trimMessage(String message) { if (message == null) return null; else if (message.length() > MAX_MESSAGE_LENGTH) diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java index 2cb3a2b0a3c..74c3059bae1 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java @@ -15,14 +15,15 @@ public class MessageInterfaceBinding implements InterfaceBinding MAX_MESSAGE_LENGTH) @@ -33,12 +34,15 @@ else if (message.length() > MAX_MESSAGE_LENGTH) @Override public void writeInterface(JsonGenerator generator, MessageInterface messageInterface) throws IOException { generator.writeStartObject(); - generator.writeStringField(MESSAGE_PARAMETER, formatMessage(messageInterface.getMessage())); + generator.writeStringField(MESSAGE_PARAMETER, trimMessage(messageInterface.getMessage())); generator.writeArrayFieldStart(PARAMS_PARAMETER); for (String parameter : messageInterface.getParameters()) { generator.writeString(parameter); } generator.writeEndArray(); + if (messageInterface.getFormatted() != null) { + generator.writeStringField(FORMATTED_PARAMETER, trimMessage(messageInterface.getFormatted())); + } generator.writeEndObject(); } } From 584eae3b2c7b8709ea8a3271a7c2098cb0c6b908 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 21 Jul 2016 12:27:18 -0500 Subject: [PATCH 1440/2152] Bump CHANGES and docs to 7.5.0 --- docs/modules/appengine.rst | 2 +- docs/modules/log4j.rst | 2 +- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 2 +- docs/modules/raven.rst | 2 +- raven-appengine/README.md | 4 ++-- raven-log4j/README.md | 4 ++-- raven-log4j2/README.md | 10 +++++----- raven-logback/README.md | 4 ++-- raven/README.md | 4 ++-- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 3baa989fc7c..bdf98e56a81 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -25,7 +25,7 @@ If you want to use Maven you can install Raven-AppEngine as dependency: com.getsentry.raven raven-appengine - 7.4.0 + 7.5.0 Usage diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index ee34fb38dc6..f6b52b3ea2d 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven-Log4j as dependency: com.getsentry.raven raven-log4j - 7.4.0 + 7.5.0 If you manually want to manage your dependencies: diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index cd0a098c6fe..ea813fdcf7a 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,18 +19,18 @@ If you want to use Maven you can install Raven-Log4j2 as dependency: com.getsentry.raven raven-log4j2 - 7.4.0 + 7.5.0 If you manually want to manage your dependencies: - :doc:`raven dependencies ` - `log4j-api-2.1.jar - `_ + `_ - `log4j-core-2.0.jar - `_ + `_ - `log4j-slf4j-impl-2.1.jar - `_ + `_ is recommended as the implementation of slf4j (instead of slf4j-jdk14). Usage diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index b2a3a43ecdc..a8bb9995ca0 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -16,7 +16,7 @@ If you want to use Maven you can install Raven-Logback as dependency: com.getsentry.raven raven-logback - 7.4.0 + 7.5.0 - :doc:`raven dependencies ` diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index b4fdedcc983..d8c912d962b 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven as dependency: com.getsentry.raven raven - 7.4.0 + 7.5.0 If you manually want to manage your dependencies: diff --git a/raven-appengine/README.md b/raven-appengine/README.md index 2ab6bd95cfc..9b946c82623 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -15,12 +15,12 @@ __This module is not useful outside of Google App Engine.__ com.getsentry.raven raven-appengine - 7.4.0 + 7.5.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.4.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.5.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 151524969a2..85e9b59459c 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,12 +10,12 @@ for log4j to send the logged events to Sentry. com.getsentry.raven raven-log4j - 7.4.0 + 7.5.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.4.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.5.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index 00f63e77572..dc7da727123 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,20 +10,20 @@ for Log4j 2 to send the logged events to Sentry. com.getsentry.raven raven-log4j2 - 7.4.0 + 7.5.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.4.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.5.0%7Cjar). ### Manual dependency management Relies on: - [raven dependencies](../raven) - - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.4.0%7Cjar) - - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.4.0%7Cjar) - - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.4.0%7Cjar) + - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.5.0%7Cjar) + - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.5.0%7Cjar) + - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.5.0%7Cjar) is recommended as the implementation of slf4j (instead of slf4j-jdk14). diff --git a/raven-logback/README.md b/raven-logback/README.md index e33824abf33..982660e5246 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,12 +10,12 @@ for logback to send the logged events to Sentry. com.getsentry.raven raven-logback - 7.4.0 + 7.5.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.4.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.5.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven/README.md b/raven/README.md index eaddb007ac3..f8910bd50c9 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,12 +10,12 @@ for `java.util.logging`. com.getsentry.raven raven - 7.4.0 + 7.5.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.4.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.5.0%7Cjar). ### Manual dependency management Relies on: From 36594e740cb7aa16401a0e84892a7b5a6f930255 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 21 Jul 2016 12:29:09 -0500 Subject: [PATCH 1441/2152] [maven-release-plugin] prepare release v7.5.0 --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 22323f5e134..bb0116218f6 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.4.1-SNAPSHOT + 7.5.0 pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v7.5.0 https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 301cd7d42f2..0bf51940138 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.4.1-SNAPSHOT + 7.5.0 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 56bea239ced..633359604b8 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.4.1-SNAPSHOT + 7.5.0 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 7ee3d3414ad..31a11d29b01 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.4.1-SNAPSHOT + 7.5.0 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 8ed6bcf41af..0fe40c7751a 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.4.1-SNAPSHOT + 7.5.0 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index b12808775d4..a268daaeda7 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.4.1-SNAPSHOT + 7.5.0 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index b783377c828..3bd5875bc4a 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.4.1-SNAPSHOT + 7.5.0 sentry-stub From b4da5f9c4cdad179b6fcaf1f01cc16e1f228cda6 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 21 Jul 2016 12:29:10 -0500 Subject: [PATCH 1442/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index bb0116218f6..c87b2894a99 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.5.0 + 7.5.1-SNAPSHOT pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v7.5.0 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 0bf51940138..c9660d01996 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.5.0 + 7.5.1-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 633359604b8..e7734e76cec 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.5.0 + 7.5.1-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 31a11d29b01..d418c2559a2 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.5.0 + 7.5.1-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 0fe40c7751a..4c17efb6821 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.5.0 + 7.5.1-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index a268daaeda7..f33a0f7ec6b 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.5.0 + 7.5.1-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 3bd5875bc4a..64b070e458d 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.5.0 + 7.5.1-SNAPSHOT sentry-stub From 100b070690df881600e43232d8afce7c3bd14f94 Mon Sep 17 00:00:00 2001 From: ted kaemming Date: Tue, 26 Jul 2016 13:20:57 -0700 Subject: [PATCH 1443/2152] Drop level of EventBuilderHelper addition/removal logging to debug. (#235) --- raven/src/main/java/com/getsentry/raven/Raven.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/com/getsentry/raven/Raven.java b/raven/src/main/java/com/getsentry/raven/Raven.java index 391d8517494..1c9cd5cf13d 100644 --- a/raven/src/main/java/com/getsentry/raven/Raven.java +++ b/raven/src/main/java/com/getsentry/raven/Raven.java @@ -104,7 +104,7 @@ public void sendException(Throwable throwable) { * @param builderHelper builder helper to remove. */ public void removeBuilderHelper(EventBuilderHelper builderHelper) { - logger.info("Removing '{}' from the list of builder helpers.", builderHelper); + logger.debug("Removing '{}' from the list of builder helpers.", builderHelper); builderHelpers.remove(builderHelper); } @@ -114,7 +114,7 @@ public void removeBuilderHelper(EventBuilderHelper builderHelper) { * @param builderHelper builder helper to add. */ public void addBuilderHelper(EventBuilderHelper builderHelper) { - logger.info("Adding '{}' to the list of builder helpers.", builderHelper); + logger.debug("Adding '{}' to the list of builder helpers.", builderHelper); builderHelpers.add(builderHelper); } From 77438f5492408fc6f4dedfad9dde91b39c8f3710 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 29 Jul 2016 07:18:08 -0500 Subject: [PATCH 1444/2152] Add `environment` property to events and appenders. (#233) --- CHANGES | 5 ++++ raven-log4j/README.md | 2 ++ .../getsentry/raven/log4j/SentryAppender.java | 18 +++++++++++++-- .../SentryAppenderEventBuildingTest.java | 15 ++++++++++++ raven-log4j2/README.md | 6 +++++ .../raven/log4j2/SentryAppender.java | 22 ++++++++++++++++-- .../SentryAppenderEventBuildingTest.java | 15 ++++++++++++ raven-logback/README.md | 2 ++ .../raven/logback/SentryAppender.java | 14 +++++++++++ .../SentryAppenderEventBuildingTest.java | 15 ++++++++++++ raven/README.md | 2 ++ .../java/com/getsentry/raven/event/Event.java | 20 ++++++++++++---- .../getsentry/raven/event/EventBuilder.java | 11 +++++++++ .../getsentry/raven/jul/SentryHandler.java | 19 +++++++++++++-- .../raven/marshaller/json/JsonMarshaller.java | 7 +++++- .../jul/SentryHandlerEventBuildingTest.java | 16 +++++++++++++ .../marshaller/json/JsonMarshallerTest.java | 23 +++++++++++++++---- .../jsonmarshallertest/testBreadcrumbs.json | 1 + .../json/jsonmarshallertest/testChecksum.json | 1 + .../json/jsonmarshallertest/testCulprit.json | 1 + .../jsonmarshallertest/testEnvironment.json | 15 ++++++++++++ .../json/jsonmarshallertest/testEventId.json | 1 + .../jsonmarshallertest/testExtraArray.json | 1 + .../jsonmarshallertest/testExtraBoolean.json | 1 + .../testExtraCustomValue.json | 1 + .../jsonmarshallertest/testExtraIterable.json | 1 + .../json/jsonmarshallertest/testExtraMap.json | 1 + .../jsonmarshallertest/testExtraNull.json | 1 + .../testExtraNullKeyMap.json | 1 + .../jsonmarshallertest/testExtraNumber.json | 1 + .../testExtraObjectKeyMap.json | 1 + .../testExtraRecursiveArray.json | 1 + .../testExtraRecursiveMap.json | 1 + .../jsonmarshallertest/testExtraString.json | 1 + .../jsonmarshallertest/testFingerprint.json | 1 + .../testInterfaceBinding.json | 1 + .../jsonmarshallertest/testLevelDebug.json | 1 + .../jsonmarshallertest/testLevelError.json | 1 + .../jsonmarshallertest/testLevelFatal.json | 1 + .../jsonmarshallertest/testLevelInfo.json | 1 + .../jsonmarshallertest/testLevelWarning.json | 1 + .../json/jsonmarshallertest/testLogger.json | 1 + .../json/jsonmarshallertest/testMessage.json | 1 + .../json/jsonmarshallertest/testPlatform.json | 1 + .../json/jsonmarshallertest/testRelease.json | 1 + .../jsonmarshallertest/testServerName.json | 1 + .../json/jsonmarshallertest/testTags.json | 1 + .../jsonmarshallertest/testTimestamp.json | 1 + .../raven/sentrystub/event/Event.java | 2 ++ 49 files changed, 243 insertions(+), 16 deletions(-) create mode 100644 raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEnvironment.json diff --git a/CHANGES b/CHANGES index ef56cbe33e0..e4402e248c0 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 7.6.0 +------------- + +- Add `environment` property to events and appenders. + Version 7.5.0 ------------- diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 85e9b59459c..6e7969b277e 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -37,6 +37,8 @@ log4j.appender.SentryAppender.dsn=https://publicKey:secretKey@host:port/1?option log4j.appender.SentryAppender.tags=tag1:value1,tag2:value2 // Optional, provide release version of your application log4j.appender.SentryAppender.release=1.0.0 +// Optional, provide environment your application is running in +log4j.appender.SentryAppender.environment=production // Optional, override the server name (rather than looking it up dynamically) log4j.appender.SentryAppender.serverName=server1 # Optional, select the ravenFactory class diff --git a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java index 4d0f32efa88..b220c5bdb33 100644 --- a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java @@ -55,11 +55,17 @@ public class SentryAppender extends AppenderSkeleton { */ protected String ravenFactory; /** - * Release to be sent to sentry. + * Identifies the version of the application. *

    - * Might be null in which case no release is sent. + * Might be null in which case the release information will not be sent with the event. */ protected String release; + /** + * Identifies the environment the application is running in. + *

    + * Might be null in which case the environment information will not be sent with the event. + */ + protected String environment; /** * Server name to be sent to sentry. *

    @@ -191,6 +197,10 @@ protected Event buildEvent(LoggingEvent loggingEvent) { eventBuilder.withRelease(release.trim()); } + if (!Util.isNullOrEmpty(environment)) { + eventBuilder.withEnvironment(environment.trim()); + } + ThrowableInformation throwableInformation = null; try { throwableInformation = loggingEvent.getThrowableInformation(); @@ -248,6 +258,10 @@ public void setRelease(String release) { this.release = release; } + public void setEnvironment(String environment) { + this.environment = environment; + } + public void setServerName(String serverName) { this.serverName = serverName; } diff --git a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderEventBuildingTest.java b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderEventBuildingTest.java index 501cbe02659..10dc9bf74e2 100644 --- a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderEventBuildingTest.java +++ b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderEventBuildingTest.java @@ -279,4 +279,19 @@ public void testReleaseAddedToEvent() throws Exception { }}; assertNoErrorsInErrorHandler(); } + + @Test + public void testEnvironmentAddedToEvent() throws Exception { + final String environment = "d7b4a6a0-1a0a-4381-a519-e2ccab609003"; + sentryAppender.setEnvironment(environment); + + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getEnvironment(), is(environment)); + }}; + assertNoErrorsInErrorHandler(); + } } diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index dc7da727123..c450909da0a 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -51,6 +51,12 @@ In the `log4j2.xml` file set: 1.0.0 + + + production + diff --git a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java index ad797b17e5e..17d78295f06 100644 --- a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java @@ -70,11 +70,17 @@ public class SentryAppender extends AbstractAppender { */ protected String ravenFactory; /** - * Release to be sent to Sentry. + * Identifies the version of the application. *

    - * Might be null in which case no release is sent. + * Might be null in which case the release information will not be sent with the event. */ protected String release; + /** + * Identifies the environment the application is running in. + *

    + * Might be null in which case the environment information will not be sent with the event. + */ + protected String environment; /** * Server name to be sent to sentry. *

    @@ -123,6 +129,7 @@ private SentryAppender(String name, Filter filter) { * @param dsn Data Source Name to access the Sentry server. * @param ravenFactory Name of the factory to use to build the {@link Raven} instance. * @param release Release to be sent to Sentry. + * @param environment Environment to be sent to Sentry. * @param serverName serverName to be sent to Sentry. * @param tags Tags to add to each event. * @param extraTags Tags to search through the Thread Context Map. @@ -135,6 +142,7 @@ public static SentryAppender createAppender(@PluginAttribute("name") final Strin @PluginAttribute("dsn") final String dsn, @PluginAttribute("ravenFactory") final String ravenFactory, @PluginAttribute("release") final String release, + @PluginAttribute("environment") final String environment, @PluginAttribute("serverName") final String serverName, @PluginAttribute("tags") final String tags, @PluginAttribute("extraTags") final String extraTags, @@ -148,6 +156,8 @@ public static SentryAppender createAppender(@PluginAttribute("name") final Strin sentryAppender.setDsn(dsn); if (release != null) sentryAppender.setRelease(release); + if (environment != null) + sentryAppender.setEnvironment(environment); if (serverName != null) sentryAppender.setServerName(serverName); if (tags != null) @@ -259,6 +269,10 @@ protected Event buildEvent(LogEvent event) { eventBuilder.withRelease(release.trim()); } + if (!Util.isNullOrEmpty(environment)) { + eventBuilder.withEnvironment(environment.trim()); + } + if (!eventMessage.getFormattedMessage().equals(eventMessage.getFormat())) { eventBuilder.withSentryInterface(new MessageInterface( eventMessage.getFormat(), @@ -315,6 +329,10 @@ public void setRelease(String release) { this.release = release; } + public void setEnvironment(String environment) { + this.environment = environment; + } + public void setServerName(String serverName) { this.serverName = serverName; } diff --git a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderEventBuildingTest.java b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderEventBuildingTest.java index e1b3554c884..f77ad920345 100644 --- a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderEventBuildingTest.java +++ b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderEventBuildingTest.java @@ -263,4 +263,19 @@ public void testReleaseAddedToEvent() throws Exception { }}; assertNoErrorsInErrorHandler(); } + + @Test + public void testEnvironmentAddedToEvent() throws Exception { + final String environment = "d7b4a6a0-1a0a-4381-a519-e2ccab609003"; + sentryAppender.setEnvironment(environment); + + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.ERROR, new SimpleMessage(""), null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getEnvironment(), is(environment)); + }}; + assertNoErrorsInErrorHandler(); + } } diff --git a/raven-logback/README.md b/raven-logback/README.md index 982660e5246..3a6402763b5 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -37,6 +37,8 @@ In the `logback.xml` file set: tag1:value1,tag2:value2 1.0.0 + + production server1 diff --git a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java index 6dc03626a75..20175c4b4fa 100644 --- a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java @@ -66,6 +66,12 @@ public class SentryAppender extends AppenderBase { * Might be null in which case the release information will not be sent with the event. */ protected String release; + /** + * Identifies the environment the application is running in. + *

    + * Might be null in which case the environment information will not be sent with the event. + */ + protected String environment; /** * Server name to be sent to sentry. *

    @@ -207,6 +213,10 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { eventBuilder.withRelease(release.trim()); } + if (!Util.isNullOrEmpty(environment)) { + eventBuilder.withEnvironment(environment.trim()); + } + if (iLoggingEvent.getArgumentArray() != null) { eventBuilder.withSentryInterface(new MessageInterface( iLoggingEvent.getMessage(), @@ -324,6 +334,10 @@ public void setRelease(String release) { this.release = release; } + public void setEnvironment(String environment) { + this.environment = environment; + } + public void setServerName(String serverName) { this.serverName = serverName; } diff --git a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderEventBuildingTest.java b/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderEventBuildingTest.java index 47531f0c3c6..143b3ecdf22 100644 --- a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderEventBuildingTest.java +++ b/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderEventBuildingTest.java @@ -304,4 +304,19 @@ public void testReleaseAddedToEvent() throws Exception { }}; assertNoErrorsInStatusManager(); } + + @Test + public void testEnvironmentAddedToEvent() throws Exception { + final String environment = "d7b4a6a0-1a0a-4381-a519-e2ccab609003"; + sentryAppender.setEnvironment(environment); + + sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null).getMockInstance()); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getEnvironment(), is(environment)); + }}; + assertNoErrorsInStatusManager(); + } } diff --git a/raven/README.md b/raven/README.md index f8910bd50c9..f8afce52be2 100644 --- a/raven/README.md +++ b/raven/README.md @@ -39,6 +39,8 @@ com.getsentry.raven.jul.SentryHandler.dsn=https://publicKey:secretKey@host:port/ com.getsentry.raven.jul.SentryHandler.tags=tag1:value1,tag2:value2 # Optional, provide release version of your application com.getsentry.raven.jul.SentryHandler.release=1.0.0 +# Optional, provide environment your application is running in +com.getsentry.raven.jul.SentryHandler.environment=production # Optional, override the server name (rather than looking it up dynamically) com.getsentry.raven.jul.SentryHandler.serverName=server1 # Optional, select the ravenFactory class diff --git a/raven/src/main/java/com/getsentry/raven/event/Event.java b/raven/src/main/java/com/getsentry/raven/event/Event.java index 29aa90a0de2..d96618362cd 100644 --- a/raven/src/main/java/com/getsentry/raven/event/Event.java +++ b/raven/src/main/java/com/getsentry/raven/event/Event.java @@ -68,14 +68,18 @@ public class Event implements Serializable { * List of Breadcrumb objects related to the event. */ private List breadcrumbs = new ArrayList<>(); - /** - * Identifies the host client from which the event was recorded. - */ - private String serverName; /** * Identifies the version of the application. */ private String release; + /** + * Identifies the environment the application is running in. + */ + private String environment; + /** + * Identifies the host client from which the event was recorded. + */ + private String serverName; /** * A map or list of additional properties for this event. *

    @@ -196,6 +200,14 @@ void setRelease(String release) { this.release = release; } + public String getEnvironment() { + return environment; + } + + void setEnvironment(String environment) { + this.environment = environment; + } + public Map getExtra() { return extra; } diff --git a/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java b/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java index 225cad3f27c..9d0dd4b3a58 100644 --- a/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java +++ b/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java @@ -153,6 +153,17 @@ public EventBuilder withRelease(String release) { return this; } + /** + * Sets application environment in the event. + * + * @param environment application environment in the event. + * @return the current {@code EventBuilder} for chained calls. + */ + public EventBuilder withEnvironment(String environment) { + event.setEnvironment(environment); + return this; + } + /** * Sets the logger in the event. * diff --git a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java index e5f31cb047a..f15a242df61 100644 --- a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java +++ b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java @@ -60,11 +60,17 @@ public class SentryHandler extends Handler { */ protected String ravenFactory; /** - * Release to be sent to sentry. + * Identifies the version of the application. *

    - * Might be null in which case no release is sent. + * Might be null in which case the release information will not be sent with the event. */ protected String release; + /** + * Identifies the environment the application is running in. + *

    + * Might be null in which case the environment information will not be sent with the event. + */ + protected String environment; /** * Server name to be sent to sentry. *

    @@ -159,6 +165,7 @@ protected void retrieveProperties() { printfStyle = Boolean.valueOf(manager.getProperty(className + ".printfStyle")); ravenFactory = manager.getProperty(className + ".ravenFactory"); release = manager.getProperty(className + ".release"); + environment = manager.getProperty(className + ".environment"); serverName = manager.getProperty(className + ".serverName"); String tagsProperty = manager.getProperty(className + ".tags"); tags = Util.parseTags(tagsProperty); @@ -270,6 +277,10 @@ protected Event buildEvent(LogRecord record) { eventBuilder.withRelease(release.trim()); } + if (!Util.isNullOrEmpty(environment)) { + eventBuilder.withEnvironment(environment.trim()); + } + if (!Util.isNullOrEmpty(serverName)) { eventBuilder.withServerName(serverName.trim()); } @@ -309,6 +320,10 @@ public void setRelease(String release) { this.release = release; } + public void setEnvironment(String environment) { + this.environment = environment; + } + public void setServerName(String serverName) { this.serverName = serverName; } diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java index 1512fd913ea..fb83380bcdf 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java @@ -66,9 +66,13 @@ public class JsonMarshaller implements Marshaller { */ public static final String SERVER_NAME = "server_name"; /** - * Identifies the host client from which the event was recorded. + * Identifies the the version of the application. */ public static final String RELEASE = "release"; + /** + * Identifies the environment the application is running in. + */ + public static final String ENVIRONMENT = "environment"; /** * Event fingerprint, a list of strings used to dictate the deduplicating for this event. */ @@ -138,6 +142,7 @@ private void writeContent(JsonGenerator generator, Event event) throws IOExcepti writeBreadcumbs(generator, event.getBreadcrumbs()); generator.writeStringField(SERVER_NAME, event.getServerName()); generator.writeStringField(RELEASE, event.getRelease()); + generator.writeStringField(ENVIRONMENT, event.getEnvironment()); writeExtras(generator, event.getExtra()); writeCollection(generator, FINGERPRINT, event.getFingerprint()); generator.writeStringField(CHECKSUM, event.getChecksum()); diff --git a/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerEventBuildingTest.java b/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerEventBuildingTest.java index 16c40ee6aaf..8316a23c10f 100644 --- a/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerEventBuildingTest.java +++ b/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerEventBuildingTest.java @@ -172,4 +172,20 @@ public void testReleaseAddedToEvent() throws Exception { }}; assertNoErrorsInErrorManager(); } + + @Test + public void testEnvironmentAddedToEvent() throws Exception { + final String environment = "d7b4a6a0-1a0a-4381-a519-e2ccab609003"; + sentryHandler.setEnvironment(environment); + + sentryHandler.publish(newLogRecord(null, Level.INFO, null, null, null)); + + new Verifications() {{ + Event event; + mockRaven.sendEvent(event = withCapture()); + assertThat(event.getEnvironment(), is(environment)); + }}; + assertNoErrorsInErrorManager(); + } + } diff --git a/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java index 9d342750569..754b01d715a 100644 --- a/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java @@ -207,6 +207,19 @@ public void testEventReleaseWrittenProperly(@Injectable("release") final String assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testRelease.json"))); } + @Test + public void testEventEnvironmentWrittenProperly(@Injectable("environment") final String mockEnvironment) throws Exception { + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); + new NonStrictExpectations() {{ + mockEvent.getEnvironment(); + result = mockEnvironment; + }}; + + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); + + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEnvironment.json"))); + } + @Test public void testEventBreadcrumbsWrittenProperly() throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); @@ -331,10 +344,10 @@ public void testCompressedDataIsWorking() throws Exception { jsonMarshaller.marshall(mockEvent, outputStream); assertThat(new String(outputStream.toByteArray(), "UTF-8"), is("" - + "eJyFjU0KAjEMhe+SdYXOSuw53A+hxlpMOyVpB2GYu" - + "1vU6dbwNl94PxvQSrnO8QYO7J8DA4lUMRC43JgN1N" - + "gfFVPp6elytic7dV2tdR/1APd+Puy8hEByUGGs90X" - + "Swb5xkVhHNwYFt+0GlGQlmTOmMSzEhDqQXlXwa/YP" - + "8k9tv9b9DbsbRo8=")); + + "eJyFT8sKAjEQ+5c5V+iexH6H92WoYy1OH0zbRVj23" + + "y1qe3XIJWGSkB1oo1hXfwMD+s+BgkCloCMwsTErqL" + + "4LFUPu7uVy1ie9dFy1Nh90A/d8Hu+cnCMZLDPWe5I" + + "wuG2cxdeZja6A2Q8FhWQjWSOGWSzEhGVSipuXFEOf" + + "MqVXFfz67YPss7Rf0fEGRZpNqQ==")); } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testBreadcrumbs.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testBreadcrumbs.json index b92633280ee..0ec60fab7d5 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testBreadcrumbs.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testBreadcrumbs.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": {}, "checksum": null, "breadcrumbs": { diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testChecksum.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testChecksum.json index d18fad6e8e3..62d660e2ca8 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testChecksum.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testChecksum.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": {}, "checksum": "1234567890abcdef" } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testCulprit.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testCulprit.json index 51693aa3f22..3447f32969b 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testCulprit.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testCulprit.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": {}, "checksum": null } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEnvironment.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEnvironment.json new file mode 100644 index 00000000000..f11fd125cde --- /dev/null +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEnvironment.json @@ -0,0 +1,15 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "release": null, + "environment": "environment", + "extra": {}, + "checksum": null +} diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEventId.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEventId.json index 4f95e982058..f5bbb0cb6c0 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEventId.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEventId.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": {}, "checksum": null } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraArray.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraArray.json index 13b776d7f29..e7eca2117e0 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraArray.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraArray.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": { "key": [ "string", diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json index 500c361075f..6434aa97b3c 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": { "key": true }, diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json index 42a92993ffc..a209f7ca707 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": { "key": "test" }, diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json index 23d300324b2..40b2cae5eab 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": { "key": [ true, diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraMap.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraMap.json index b0d8d986569..90f5ac1bdb1 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraMap.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraMap.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": { "key": { "key": "value" diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNull.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNull.json index e17fd2bb2f8..bff59b32319 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNull.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNull.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": { "key": null }, diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json index b5b8a8b7607..90e017ab898 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": { "key": { "null": "value" diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json index fa70fd23616..8357bfeefb3 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": { "key": 1 }, diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json index 20b4a292677..2a6e3d2e172 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": { "key": { "true": "value" diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json index 851c9322fef..231cfd9913a 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": { "key": [ [ diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json index 383671918d2..3397d14d396 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": { "key": { "key": [ diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraString.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraString.json index ae38befc5c4..84f4f750da6 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraString.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraString.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": { "key": "string" }, diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testFingerprint.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testFingerprint.json index ae9d7504995..0269baf4838 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testFingerprint.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testFingerprint.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": {}, "fingerprint": [ "fingerprint1", diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json index f8edb7738d8..176e6bd4170 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": {}, "checksum": null, "interfaceKey": null diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json index b5d7f0e36c6..d2d48f38834 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": {}, "checksum": null } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelError.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelError.json index 94647951f7f..bfd520094e9 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelError.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelError.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": {}, "checksum": null } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json index 62b50312f4f..10f5044afd6 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": {}, "checksum": null } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json index 4e596a68816..191983c5363 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": {}, "checksum": null } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json index 7d82b3c12fd..636e8a0789c 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": {}, "checksum": null } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLogger.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLogger.json index f778c54676a..7c4d2acfb48 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLogger.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLogger.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": {}, "checksum": null } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testMessage.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testMessage.json index 2de8c734d6c..4eb0681b5e4 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testMessage.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testMessage.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": {}, "checksum": null } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testPlatform.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testPlatform.json index 9108661f368..96ea413a8ea 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testPlatform.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testPlatform.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "environment": null, "extra": {}, "checksum": null } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testRelease.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testRelease.json index e69b5583cae..d1447f907d7 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testRelease.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testRelease.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": "release", + "environment": null, "extra": {}, "checksum": null } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testServerName.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testServerName.json index cb630f2c1b9..f3a57076375 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testServerName.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testServerName.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": "serverName", "release": null, + "environment": null, "extra": {}, "checksum": null } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTags.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTags.json index 2e19bc16a4d..0e7ce261855 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTags.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTags.json @@ -11,6 +11,7 @@ }, "server_name": null, "release": null, + "environment": null, "extra": {}, "checksum": null } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTimestamp.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTimestamp.json index 0a5e1c2f11f..f9cb01c46db 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTimestamp.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTimestamp.json @@ -10,5 +10,6 @@ "server_name": null, "release": null, "extra": {}, + "environment": null, "checksum": null } diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/Event.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/Event.java index c251d906a24..83663e21ca9 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/Event.java +++ b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/Event.java @@ -32,6 +32,8 @@ public class Event { private String serverName; @JsonProperty(value = "release") private String release; + @JsonProperty(value = "environment") + private String environment; @JsonProperty(value = "modules") private Map modules; @JsonProperty(value = "extra") From 4be3646696739832c59c1956dd35cb6c7fe85482 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 29 Jul 2016 07:20:15 -0500 Subject: [PATCH 1445/2152] Bump CHANGES and docs to 7.6.0 --- docs/modules/appengine.rst | 2 +- docs/modules/log4j.rst | 2 +- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 2 +- docs/modules/raven.rst | 2 +- raven-appengine/README.md | 4 ++-- raven-log4j/README.md | 4 ++-- raven-log4j2/README.md | 10 +++++----- raven-logback/README.md | 4 ++-- raven/README.md | 4 ++-- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index bdf98e56a81..b198da2b917 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -25,7 +25,7 @@ If you want to use Maven you can install Raven-AppEngine as dependency: com.getsentry.raven raven-appengine - 7.5.0 + 7.6.0 Usage diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index f6b52b3ea2d..2c95302e68c 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven-Log4j as dependency: com.getsentry.raven raven-log4j - 7.5.0 + 7.6.0 If you manually want to manage your dependencies: diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index ea813fdcf7a..edb3ea69c30 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,18 +19,18 @@ If you want to use Maven you can install Raven-Log4j2 as dependency: com.getsentry.raven raven-log4j2 - 7.5.0 + 7.6.0 If you manually want to manage your dependencies: - :doc:`raven dependencies ` - `log4j-api-2.1.jar - `_ + `_ - `log4j-core-2.0.jar - `_ + `_ - `log4j-slf4j-impl-2.1.jar - `_ + `_ is recommended as the implementation of slf4j (instead of slf4j-jdk14). Usage diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index a8bb9995ca0..057370d14db 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -16,7 +16,7 @@ If you want to use Maven you can install Raven-Logback as dependency: com.getsentry.raven raven-logback - 7.5.0 + 7.6.0 - :doc:`raven dependencies ` diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index d8c912d962b..4906cbc8e70 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven as dependency: com.getsentry.raven raven - 7.5.0 + 7.6.0 If you manually want to manage your dependencies: diff --git a/raven-appengine/README.md b/raven-appengine/README.md index 9b946c82623..727e204a46c 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -15,12 +15,12 @@ __This module is not useful outside of Google App Engine.__ com.getsentry.raven raven-appengine - 7.5.0 + 7.6.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.5.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.6.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 6e7969b277e..2f714073328 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,12 +10,12 @@ for log4j to send the logged events to Sentry. com.getsentry.raven raven-log4j - 7.5.0 + 7.6.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.5.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.6.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index c450909da0a..e0df3912505 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,20 +10,20 @@ for Log4j 2 to send the logged events to Sentry. com.getsentry.raven raven-log4j2 - 7.5.0 + 7.6.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.5.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.6.0%7Cjar). ### Manual dependency management Relies on: - [raven dependencies](../raven) - - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.5.0%7Cjar) - - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.5.0%7Cjar) - - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.5.0%7Cjar) + - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.6.0%7Cjar) + - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.6.0%7Cjar) + - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.6.0%7Cjar) is recommended as the implementation of slf4j (instead of slf4j-jdk14). diff --git a/raven-logback/README.md b/raven-logback/README.md index 3a6402763b5..f27d91db893 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,12 +10,12 @@ for logback to send the logged events to Sentry. com.getsentry.raven raven-logback - 7.5.0 + 7.6.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.5.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.6.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven/README.md b/raven/README.md index f8afce52be2..a449297c506 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,12 +10,12 @@ for `java.util.logging`. com.getsentry.raven raven - 7.5.0 + 7.6.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.5.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.6.0%7Cjar). ### Manual dependency management Relies on: From db416421cd2f7eabfdb3d3024f2f92a0dae27913 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 29 Jul 2016 07:21:39 -0500 Subject: [PATCH 1446/2152] [maven-release-plugin] prepare release v7.6.0 --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index c87b2894a99..2b4bae9883d 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.5.1-SNAPSHOT + 7.6.0 pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v7.6.0 https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index c9660d01996..95a5e15022e 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.5.1-SNAPSHOT + 7.6.0 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index e7734e76cec..62b0288c5c5 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.5.1-SNAPSHOT + 7.6.0 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index d418c2559a2..653d758dd59 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.5.1-SNAPSHOT + 7.6.0 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 4c17efb6821..d3bd6d910f4 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.5.1-SNAPSHOT + 7.6.0 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index f33a0f7ec6b..2e1a22967e6 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.5.1-SNAPSHOT + 7.6.0 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 64b070e458d..1dfc1ee3ba4 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.5.1-SNAPSHOT + 7.6.0 sentry-stub From 5326289863ca242d0239d64307f7b9f82bda0d76 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 29 Jul 2016 07:21:39 -0500 Subject: [PATCH 1447/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 2b4bae9883d..aeea54db305 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.6.0 + 7.6.1-SNAPSHOT pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v7.6.0 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 95a5e15022e..617cc1c072b 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.6.0 + 7.6.1-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 62b0288c5c5..95a904fae3c 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.6.0 + 7.6.1-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 653d758dd59..c9a7d9987be 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.6.0 + 7.6.1-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index d3bd6d910f4..f239b750231 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.6.0 + 7.6.1-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 2e1a22967e6..f4617b2ebbb 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.6.0 + 7.6.1-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 1dfc1ee3ba4..e0b0f6a9d02 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.6.0 + 7.6.1-SNAPSHOT sentry-stub From 8207d186f0ab03c72d0ea04fec983251b68214c2 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 29 Jul 2016 17:35:31 -0500 Subject: [PATCH 1448/2152] =?UTF-8?q?Add=20static=20`Raven.capture`=20meth?= =?UTF-8?q?ods=20that=20send=20to=20the=20most=20recently=20con=E2=80=A6?= =?UTF-8?q?=20(#236)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add static Raven.capture methods that send to the most recently constructed Raven instance. --- CHANGES | 7 +- raven/README.md | 36 ++++++ .../getsentry/raven/DefaultRavenFactory.java | 3 +- .../main/java/com/getsentry/raven/Raven.java | 116 ++++++++++++++++-- .../java/com/getsentry/raven/RavenTest.java | 66 ++++++++++ 5 files changed, 215 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index e4402e248c0..b0e3806c700 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,12 @@ +Version 7.7.0 +------------- + +- Add static ``Raven.capture`` methods that send to the most recently constructed Raven instance. + Version 7.6.0 ------------- -- Add `environment` property to events and appenders. +- Add ``environment`` property to events and appenders. Version 7.5.0 ------------- diff --git a/raven/README.md b/raven/README.md index a449297c506..2c19e25bd63 100644 --- a/raven/README.md +++ b/raven/README.md @@ -191,3 +191,39 @@ public class MyClass { This gives more control over the content of the `Event` and gives access to the complete API supported by Sentry. + +### Static access + +The most recently constructed `Raven` instance is stored statically so it may +be used easily from anywhere in your application. + +```java +import com.getsentry.raven.Raven; +import com.getsentry.raven.RavenFactory; + +public class MyClass { + public static void main(String... args) { + // Create a Raven instance + RavenFactory.ravenInstance(); + } + + public somewhereElse() { + // Use the Raven instance statically. Note that we are + // using the Class (and a static method) here + Raven.capture("Error message"); + + // Or pass it a throwable + Raven.capture(new Exception("Error message")); + + // Or build an event yourself + EventBuilder eventBuilder = new EventBuilder() + .withMessage("Exception caught") + .withLevel(Event.Level.ERROR); + Raven.capture(eventBuilder.build()); + } + +} +``` + +Note that a Raven instance *must* be created before you can use the `Raven.capture` +method, otherwise a `NullPointerException` (with an explaination) will be thrown. \ No newline at end of file diff --git a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java index 1f42cb9568e..1fec0dd1eeb 100644 --- a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java @@ -87,8 +87,7 @@ public class DefaultRavenFactory extends RavenFactory { @Override public Raven createRavenInstance(Dsn dsn) { - Raven raven = new Raven(); - raven.setConnection(createConnection(dsn)); + Raven raven = new Raven(createConnection(dsn)); try { // `ServletRequestListener` was added in the Servlet 2.4 API, and // is used as part of the `HttpEventBuilderHelper`, see: diff --git a/raven/src/main/java/com/getsentry/raven/Raven.java b/raven/src/main/java/com/getsentry/raven/Raven.java index 1c9cd5cf13d..d2ef66e9bfb 100644 --- a/raven/src/main/java/com/getsentry/raven/Raven.java +++ b/raven/src/main/java/com/getsentry/raven/Raven.java @@ -11,8 +11,8 @@ import java.io.IOException; import java.util.Collections; -import java.util.HashSet; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; /** * Raven is a client for Sentry allowing to send an {@link Event} that will be processed and sent to a Sentry server. @@ -23,9 +23,21 @@ */ public class Raven { private static final Logger logger = LoggerFactory.getLogger(Raven.class); - private final Set builderHelpers = new HashSet<>(); - private Connection connection; - private ThreadLocal context = new ThreadLocal() { + /** + * The most recently constructed Raven instance, used by static helper methods like {@link Raven#capture(Event)}. + */ + private static volatile Raven stored = null; + /** + * The underlying {@link Connection} to use for sending events to Sentry. + */ + private volatile Connection connection; + /** + * Set of {@link EventBuilderHelper}s. Note that we wrap a {@link ConcurrentHashMap} because there + * isn't a concurrent set in the standard library. + */ + private final Set builderHelpers = + Collections.newSetFromMap(new ConcurrentHashMap()); + private final ThreadLocal context = new ThreadLocal() { @Override protected RavenContext initialValue() { RavenContext ctx = new RavenContext(); @@ -34,6 +46,33 @@ protected RavenContext initialValue() { } }; + /** + * Constructs a Raven instance. + * + * Note that the most recently constructed instance is stored statically so it can be used with + * the static helper methods. + * + * @deprecated in favor of {@link Raven#Raven(Connection)} because until you call + * {@link Raven#setConnection(Connection)} this instance will throw exceptions when used. + */ + @Deprecated + public Raven() { + stored = this; + } + + /** + * Constructs a Raven instance using the provided connection. + * + * Note that the most recently constructed instance is stored statically so it can be used with + * the static helper methods. + * + * @param connection Underlying Connection instance to use for sending events + */ + public Raven(Connection connection) { + this.connection = connection; + stored = this; + } + /** * Runs the {@link EventBuilderHelper} against the {@link EventBuilder} to obtain additional information with a * MDC-like system. @@ -66,7 +105,8 @@ public void sendEvent(Event event) { */ public void sendEvent(EventBuilder eventBuilder) { runBuilderHelpers(eventBuilder); - sendEvent(eventBuilder.build()); + Event event = eventBuilder.build(); + sendEvent(event); } /** @@ -78,9 +118,10 @@ public void sendEvent(EventBuilder eventBuilder) { */ public void sendMessage(String message) { EventBuilder eventBuilder = new EventBuilder().withMessage(message) - .withLevel(Event.Level.INFO); + .withLevel(Event.Level.INFO); runBuilderHelpers(eventBuilder); - sendEvent(eventBuilder.build()); + Event event = eventBuilder.build(); + sendEvent(event); } /** @@ -92,10 +133,11 @@ public void sendMessage(String message) { */ public void sendException(Throwable throwable) { EventBuilder eventBuilder = new EventBuilder().withMessage(throwable.getMessage()) - .withLevel(Event.Level.ERROR) - .withSentryInterface(new ExceptionInterface(throwable)); + .withLevel(Event.Level.ERROR) + .withSentryInterface(new ExceptionInterface(throwable)); runBuilderHelpers(eventBuilder); - sendEvent(eventBuilder.build()); + Event event = eventBuilder.build(); + sendEvent(event); } /** @@ -148,4 +190,58 @@ public String toString() { + ", connection=" + connection + '}'; } + + // -------------------------------------------------------- + // Static helper methods follow + // -------------------------------------------------------- + + private static Raven getStoredInstance() { + if (stored == null) { + throw new NullPointerException("No stored Raven instance is available to use." + + " You must construct a Raven instance before using the static Raven methods."); + } + return stored; + } + + /** + * Send an Event using the statically stored Raven instance. + * + * @param event Event to send to the Sentry server + */ + public static void capture(Event event) { + getStoredInstance().sendEvent(event); + } + + /** + * Sends an exception (or throwable) to the Sentry server using the statically stored Raven instance. + *

    + * The exception will be logged at the {@link Event.Level#ERROR} level. + * + * @param throwable exception to send to Sentry. + */ + public static void capture(Throwable throwable) { + getStoredInstance().sendException(throwable); + } + + /** + * Sends a message to the Sentry server using the statically stored Raven instance. + *

    + * The message will be logged at the {@link Event.Level#INFO} level. + * + * @param message message to send to Sentry. + */ + public static void capture(String message) { + getStoredInstance().sendMessage(message); + } + + /** + * Builds and sends an {@link Event} to the Sentry server using the statically stored Raven instance. + * + * @param eventBuilder {@link EventBuilder} to send to Sentry. + */ + public static void capture(EventBuilder eventBuilder) { + getStoredInstance(); + stored.sendEvent(eventBuilder); + } + } diff --git a/raven/src/test/java/com/getsentry/raven/RavenTest.java b/raven/src/test/java/com/getsentry/raven/RavenTest.java index 9e200a03a54..694a13f1571 100644 --- a/raven/src/test/java/com/getsentry/raven/RavenTest.java +++ b/raven/src/test/java/com/getsentry/raven/RavenTest.java @@ -159,4 +159,70 @@ public void testCloseConnectionFailed() throws Exception { raven.closeConnection(); } + + @Test + public void testSendEventStatically() throws Exception { + final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; + Event event = new EventBuilder().withMessage(message).withLevel(Event.Level.ERROR).build(); + + Raven.capture(event); + + new Verifications() {{ + Event event; + mockConnection.send(event = withCapture()); + assertThat(event.getLevel(), equalTo(Event.Level.ERROR)); + assertThat(event.getMessage(), equalTo(message)); + }}; + } + + @Test + public void testSendEventBuilderStatically() throws Exception { + final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; + EventBuilder eventBuilder = new EventBuilder().withMessage(message).withLevel(Event.Level.ERROR); + raven.addBuilderHelper(mockEventBuilderHelper); + + Raven.capture(eventBuilder); + + new Verifications() {{ + Event event; + mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); + mockConnection.send(event = withCapture()); + assertThat(event.getLevel(), equalTo(Event.Level.ERROR)); + assertThat(event.getMessage(), equalTo(message)); + }}; + } + + @Test + public void testSendMessageStatically() throws Exception { + final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; + raven.addBuilderHelper(mockEventBuilderHelper); + + Raven.capture(message); + + new Verifications() {{ + Event event; + mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); + mockConnection.send(event = withCapture()); + assertThat(event.getLevel(), equalTo(Event.Level.INFO)); + assertThat(event.getMessage(), equalTo(message)); + }}; + } + + @Test + public void testSendExceptionStatically() throws Exception { + final String message = "7b61ddb1-eb32-428d-bad9-a7d842605ba7"; + final Exception exception = new Exception(message); + raven.addBuilderHelper(mockEventBuilderHelper); + + Raven.capture(exception); + + new Verifications() {{ + Event event; + mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); + mockConnection.send(event = withCapture()); + assertThat(event.getLevel(), equalTo(Event.Level.ERROR)); + assertThat(event.getMessage(), equalTo(message)); + assertThat(event.getSentryInterfaces(), hasKey(ExceptionInterface.EXCEPTION_INTERFACE)); + }}; + } } From d2b657a54344efe906ad853ca31b3f945964c95e Mon Sep 17 00:00:00 2001 From: ted kaemming Date: Mon, 8 Aug 2016 10:22:14 -0700 Subject: [PATCH 1449/2152] Add `setExtraTags` to JUL handler. (#239) --- raven/README.md | 11 ++++++----- .../java/com/getsentry/raven/jul/SentryHandler.java | 4 ++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/raven/README.md b/raven/README.md index 2c19e25bd63..c3805eb2b93 100644 --- a/raven/README.md +++ b/raven/README.md @@ -35,7 +35,7 @@ In the `logging.properties` file set: .level=WARN handlers=com.getsentry.raven.jul.SentryHandler com.getsentry.raven.jul.SentryHandler.dsn=https://publicKey:secretKey@host:port/1?options -# Optional, provide tags +# Optional, provide tags com.getsentry.raven.jul.SentryHandler.tags=tag1:value1,tag2:value2 # Optional, provide release version of your application com.getsentry.raven.jul.SentryHandler.release=1.0.0 @@ -45,6 +45,8 @@ com.getsentry.raven.jul.SentryHandler.environment=production com.getsentry.raven.jul.SentryHandler.serverName=server1 # Optional, select the ravenFactory class com.getsentry.raven.jul.SentryHandler.ravenFactory=com.getsentry.raven.DefaultRavenFactory +# Optional, provide tag names to be extracted from MDC when using SLF4J +com.getsentry.raven.jul.SentryHandler.extraTags=foo,bar,baz ``` When starting your application, add the `java.util.logging.config.file` to the @@ -81,10 +83,9 @@ public class MyClass { ``` ### Unsupported features -`java.util.logging` does not support either MDC nor NDC, meaning that it is not -possible to attach additional/custom context values to the logs. -In other terms, it is not possible to use the "extra" field supported by Sentry. +As `java.util.logging` has no notion of MDC, the `extraTags` parameter is only +available when logging via SLF4J. ## Manual usage (NOT RECOMMENDED) It is possible to use the client manually rather than using a logging framework @@ -226,4 +227,4 @@ public class MyClass { ``` Note that a Raven instance *must* be created before you can use the `Raven.capture` -method, otherwise a `NullPointerException` (with an explaination) will be thrown. \ No newline at end of file +method, otherwise a `NullPointerException` (with an explaination) will be thrown. diff --git a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java index f15a242df61..c9e9d82f4fe 100644 --- a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java +++ b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java @@ -122,6 +122,10 @@ public void setTags(String tagsProperty) { this.tags = Util.parseTags(tagsProperty); } + public void setExtraTags(String extraTags) { + this.extraTags = new HashSet<>(Arrays.asList(extraTags.split(","))); + } + /** * Transforms a {@link Level} into an {@link Event.Level}. * From ff25e781970a3d629ee934629a129abbf115ee83 Mon Sep 17 00:00:00 2001 From: ted kaemming Date: Tue, 9 Aug 2016 03:28:53 -0700 Subject: [PATCH 1450/2152] Ensure contexts are removed from active contexts set after test completion. (#238) * Ensure contexts are removed from active contexts set after test completion. --- .../raven/connection/EventSendFailureCallbackTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/raven/src/test/java/com/getsentry/raven/connection/EventSendFailureCallbackTest.java b/raven/src/test/java/com/getsentry/raven/connection/EventSendFailureCallbackTest.java index ad211ed991d..170c5e8a794 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/EventSendFailureCallbackTest.java +++ b/raven/src/test/java/com/getsentry/raven/connection/EventSendFailureCallbackTest.java @@ -2,8 +2,10 @@ import com.getsentry.raven.DefaultRavenFactory; import com.getsentry.raven.Raven; +import com.getsentry.raven.RavenContext; import com.getsentry.raven.dsn.Dsn; import com.getsentry.raven.event.Event; +import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; import java.util.concurrent.atomic.AtomicBoolean; @@ -13,6 +15,13 @@ public class EventSendFailureCallbackTest { + @AfterMethod + private void afterMethod() { + for (RavenContext ctx : RavenContext.getActiveContexts()) { + ctx.deactivate(); + } + } + @Test public void testSimpleCallback() { final AtomicBoolean flag = new AtomicBoolean(false); From f9abcd6777d0f8901cc0a19d25ed6b1da2481234 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 19 Aug 2016 16:46:47 -0500 Subject: [PATCH 1451/2152] Minor improvements to resource cleanup and copying found by FindBugs and Infer. (#245) --- .gitignore | 1 + .../com/getsentry/raven/event/BreadcrumbBuilder.java | 2 +- .../getsentry/raven/marshaller/json/JsonMarshaller.java | 9 ++++++++- .../getsentry/raven/sentrystub/auth/AuthValidator.java | 6 ++++-- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 0b5e2288930..7ce9ca80fed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target/ docs/_build +infer-out/ diff --git a/raven/src/main/java/com/getsentry/raven/event/BreadcrumbBuilder.java b/raven/src/main/java/com/getsentry/raven/event/BreadcrumbBuilder.java index 06ebafac2fe..dbd5cf02f67 100644 --- a/raven/src/main/java/com/getsentry/raven/event/BreadcrumbBuilder.java +++ b/raven/src/main/java/com/getsentry/raven/event/BreadcrumbBuilder.java @@ -33,7 +33,7 @@ public BreadcrumbBuilder setType(String newType) { * @return current BreadcrumbBuilder */ public BreadcrumbBuilder setTimestamp(Date newTimestamp) { - this.timestamp = newTimestamp; + this.timestamp = new Date(newTimestamp.getTime()); return this; } diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java index fb83380bcdf..f871f529269 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java @@ -118,13 +118,20 @@ public void marshall(Event event, OutputStream destination) { // Prevent the stream from being closed automatically destination = new UncloseableOutputStream(destination); - if (compression) + if (compression) { destination = new DeflaterOutputStream(new Base64OutputStream(destination, Base64.NO_WRAP)); + } try (JsonGenerator generator = jsonFactory.createGenerator(destination)) { writeContent(generator, event); } catch (IOException e) { logger.error("An exception occurred while serialising the event.", e); + } finally { + try { + destination.close(); + } catch (IOException e) { + logger.error("An exception occurred while serialising the event.", e); + } } } diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/auth/AuthValidator.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/auth/AuthValidator.java index 1b8b8fc7c33..ad23651c4b1 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/auth/AuthValidator.java +++ b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/auth/AuthValidator.java @@ -1,5 +1,6 @@ package com.getsentry.raven.sentrystub.auth; +import java.io.InputStream; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; @@ -146,8 +147,9 @@ private void validateClient(String client, InvalidAuthException invalidAuthExcep public void loadSentryUsers(String resourceName) { Properties sentryProperties = new Properties(); - try { - sentryProperties.load(AuthValidator.class.getResourceAsStream(resourceName)); + + try(InputStream resourceAsStream = AuthValidator.class.getResourceAsStream(resourceName)) { + sentryProperties.load(resourceAsStream); int userCount = Integer.parseInt(sentryProperties.getProperty("sentry.user.count", "0")); for (int i = 1; i <= userCount; i++) { String publicKey = sentryProperties.getProperty("sentry.user." + i + ".publicKey"); From 371e326392dec696474411721a75f9865cd8eb36 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Sat, 3 Sep 2016 10:43:05 -0500 Subject: [PATCH 1452/2152] =?UTF-8?q?Add=20support=20for=20setting=20``rav?= =?UTF-8?q?enFactory``,=20``release``,=20``environment`=E2=80=A6=20(#246)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add support for setting ``ravenFactory``, ``release``, ``environment`` and ``serverName`` via System Environment or Java System Properties. * Add to logback README. * Try a table. * Fix table? * Update READMEs. * Add extraTags, clean up docs. * Prefer system props, use lowercase period separated names. * Fix DSN test. * Really fix test. * Some feedback. * Test new table. * More table changes. * And more. * Shorten example. * Sync. * Too long. * Sync up READMEs. * Sync RavenFactory examples. --- CHANGES | 2 + raven-log4j/README.md | 72 +++++++++++--- .../getsentry/raven/log4j/SentryAppender.java | 12 ++- raven-log4j2/README.md | 79 +++++++++++++-- .../raven/log4j2/SentryAppender.java | 14 ++- raven-logback/README.md | 61 +++++++++++- .../raven/logback/SentryAppender.java | 23 +++-- .../raven/logback/SentryAppenderIT.java | 1 + raven/README.md | 61 ++++++++++-- .../getsentry/raven/config/JndiLookup.java | 45 +++++++++ .../com/getsentry/raven/config/Lookup.java | 52 ++++++++++ .../java/com/getsentry/raven/dsn/Dsn.java | 24 +---- .../com/getsentry/raven/dsn/JndiLookup.java | 53 ----------- .../getsentry/raven/jul/SentryHandler.java | 95 ++++++++++++------- .../java/com/getsentry/raven/util/Util.java | 17 ++++ .../java/com/getsentry/raven/dsn/DsnTest.java | 9 +- 16 files changed, 461 insertions(+), 159 deletions(-) create mode 100644 raven/src/main/java/com/getsentry/raven/config/JndiLookup.java create mode 100644 raven/src/main/java/com/getsentry/raven/config/Lookup.java delete mode 100644 raven/src/main/java/com/getsentry/raven/dsn/JndiLookup.java diff --git a/CHANGES b/CHANGES index b0e3806c700..cecefa76612 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,8 @@ Version 7.7.0 ------------- - Add static ``Raven.capture`` methods that send to the most recently constructed Raven instance. +- Add support for setting ``ravenFactory``, ``release``, ``environment`` and ``serverName`` + via JNDI, System Environment or Java System Properties (like the DSN). Version 7.6.0 ------------- diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 2f714073328..509accd66de 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -27,29 +27,17 @@ Relies on: ## Usage ### Configuration -In the `log4j.properties` file set: +Add the `SentryAppender` to the `log4j.properties` file: ```properties log4j.rootLogger=WARN, SentryAppender log4j.appender.SentryAppender=com.getsentry.raven.log4j.SentryAppender -log4j.appender.SentryAppender.dsn=https://publicKey:secretKey@host:port/1?options -// Optional, provide tags -log4j.appender.SentryAppender.tags=tag1:value1,tag2:value2 -// Optional, provide release version of your application -log4j.appender.SentryAppender.release=1.0.0 -// Optional, provide environment your application is running in -log4j.appender.SentryAppender.environment=production -// Optional, override the server name (rather than looking it up dynamically) -log4j.appender.SentryAppender.serverName=server1 -# Optional, select the ravenFactory class -#log4j.appender.SentryAppender.ravenFactory=com.getsentry.raven.DefaultRavenFactory ``` Alternatively in the `log4j.xml` file set: ``` - @@ -66,6 +54,64 @@ You'll also need to associate the `sentry` appender with your root logger, like ``` +Next, you'll need to configure your DSN (client key) and optionally other +values such as `environment` and `release`. See below for the two +ways you can do this. + +#### Configuration via runtime environment + +This is the most flexible method to configure the `SentryAppender`, +because it can be easily changed based on the environment you run your +application in. + +The following can be set as System Environment variables: + +```bash +SENTRY_EXAMPLE=xxx java -jar app.jar +``` + +or as Java System Properties: + +```bash +java -Dsentry.example=xxx -jar app.jar +``` + +Configuration parameters follow: + +| Environment variable | Java System Property | Example value | Description | +|---|---|---|---| +| `SENTRY_DSN` | `sentry.dsn` | `https://host:port/1?options` | Your Sentry DSN (client key), if left blank Raven will no-op | +| `SENTRY_RELEASE` | `sentry.release` | `1.0.0` | Optional, provide release version of your application | +| `SENTRY_ENVIRONMENT` | `sentry.environment` | `production` | Optional, provide environment your application is running in | +| `SENTRY_SERVERNAME` | `sentry.servername` | `server1` | Optional, override the server name (rather than looking it up dynamically) | +| `SENTRY_RAVENFACTORY` | `sentry.ravenfactory` | `com.foo.RavenFactory` | Optional, select the ravenFactory class | +| `SENTRY_TAGS` | `sentry.tags` | `tag1:value1,tag2:value2` | Optional, provide tags | +| `SENTRY_EXTRA_TAGS` | `sentry.extratags` | `foo,bar,baz` | Optional, provide tag names to be extracted from MDC when using SLF4J | + +#### Configuration via `log4j.properties` + +You can also configure everything statically within the `log4j.properties` file +itself. This is less flexible because it's harder to change when you run +your application in different environments. + +```properties +log4j.rootLogger=WARN, SentryAppender +log4j.appender.SentryAppender=com.getsentry.raven.log4j.SentryAppender +log4j.appender.SentryAppender.dsn=https://host:port/1?options +// Optional, provide release version of your application +log4j.appender.SentryAppender.release=1.0.0 +// Optional, provide environment your application is running in +log4j.appender.SentryAppender.environment=production +// Optional, override the server name (rather than looking it up dynamically) +log4j.appender.SentryAppender.serverName=server1 +# Optional, select the ravenFactory class +#log4j.appender.SentryAppender.ravenFactory=com.foo.RavenFactory +// Optional, provide tags +log4j.appender.SentryAppender.tags=tag1:value1,tag2:value2 +# Optional, provide tag names to be extracted from MDC when using SLF4J +log4j.appender.SentryAppender.extraTags=foo,bar,baz +``` + ### Additional data and information It's possible to add extra data to events, thanks to both [the MDC](https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/MDC.html) diff --git a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java index b220c5bdb33..72ea8c44523 100644 --- a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java @@ -2,6 +2,7 @@ import com.getsentry.raven.Raven; import com.getsentry.raven.RavenFactory; +import com.getsentry.raven.config.Lookup; import com.getsentry.raven.dsn.Dsn; import com.getsentry.raven.dsn.InvalidDsnException; import com.getsentry.raven.environment.RavenEnvironment; @@ -17,10 +18,8 @@ import org.apache.log4j.spi.LoggingEvent; import org.apache.log4j.spi.ThrowableInformation; -import java.util.Arrays; import java.util.Collections; import java.util.Date; -import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -89,6 +88,12 @@ public class SentryAppender extends AppenderSkeleton { * Creates an instance of SentryAppender. */ public SentryAppender() { + setRavenFactory(Lookup.lookup("ravenFactory")); + setRelease(Lookup.lookup("release")); + setEnvironment(Lookup.lookup("environment")); + setServerName(Lookup.lookup("serverName")); + setTags(Lookup.lookup("tags")); + setExtraTags(Lookup.lookup("extraTags")); } /** @@ -97,6 +102,7 @@ public SentryAppender() { * @param raven instance of Raven to use with this appender. */ public SentryAppender(Raven raven) { + this(); this.raven = raven; } @@ -281,7 +287,7 @@ public void setTags(String tags) { * @param extraTags A String of extraTags. extraTags are separated by commas(,). */ public void setExtraTags(String extraTags) { - this.extraTags = new HashSet<>(Arrays.asList(extraTags.split(","))); + this.extraTags = Util.parseExtraTags(extraTags); } @Override diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index e0df3912505..f1399b9caf9 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -29,7 +29,62 @@ Relies on: ## Usage ### Configuration -In the `log4j2.xml` file set: +Add the `SentryAppender` to your `log4j2.xml` file: + +```xml + + + + + + + + + + + + +``` + +Next, you'll need to configure your DSN (client key) and optionally other +values such as `environment` and `release`. See below for the two +ways you can do this. + +#### Configuration via runtime environment + +This is the most flexible method to configure the `SentryAppender`, +because it can be easily changed based on the environment you run your +application in. + +The following can be set as System Environment variables: + +```bash +SENTRY_EXAMPLE=xxx java -jar app.jar +``` + +or as Java System Properties: + +```bash +java -Dsentry.example=xxx -jar app.jar +``` + +Configuration parameters follow: + +| Environment variable | Java System Property | Example value | Description | +|---|---|---|---| +| `SENTRY_DSN` | `sentry.dsn` | `https://host:port/1?options` | Your Sentry DSN (client key), if left blank Raven will no-op | +| `SENTRY_RELEASE` | `sentry.release` | `1.0.0` | Optional, provide release version of your application | +| `SENTRY_ENVIRONMENT` | `sentry.environment` | `production` | Optional, provide environment your application is running in | +| `SENTRY_SERVERNAME` | `sentry.servername` | `server1` | Optional, override the server name (rather than looking it up dynamically) | +| `SENTRY_RAVENFACTORY` | `sentry.ravenfactory` | `com.foo.RavenFactory` | Optional, select the ravenFactory class | +| `SENTRY_TAGS` | `sentry.tags` | `tag1:value1,tag2:value2` | Optional, provide tags | +| `SENTRY_EXTRA_TAGS` | `sentry.extratags` | `foo,bar,baz` | Optional, provide tag names to be extracted from MDC when using SLF4J | + +#### Configuration via `log4j2.xml` + +You can also configure everything statically within the `log4j2.xml` file +itself. This is less flexible because it's harder to change when you run +your application in different environments. ```xml @@ -37,14 +92,8 @@ In the `log4j2.xml` file set: - https://publicKey:secretKey@host:port/1?options + https://host:port/1?options - - - tag1:value1,tag2:value2 - @@ -68,9 +117,21 @@ In the `log4j2.xml` file set: --> + + + tag1:value1,tag2:value2 + + + + foo,bar,baz + diff --git a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java index 17d78295f06..4e27d97f909 100644 --- a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java @@ -2,6 +2,7 @@ import com.getsentry.raven.Raven; import com.getsentry.raven.RavenFactory; +import com.getsentry.raven.config.Lookup; import com.getsentry.raven.dsn.Dsn; import com.getsentry.raven.dsn.InvalidDsnException; import com.getsentry.raven.environment.RavenEnvironment; @@ -22,10 +23,8 @@ import org.apache.logging.log4j.message.Message; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Date; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -106,6 +105,12 @@ public class SentryAppender extends AbstractAppender { */ public SentryAppender() { this(APPENDER_NAME, null); + setRavenFactory(Lookup.lookup("ravenFactory")); + setRelease(Lookup.lookup("release")); + setEnvironment(Lookup.lookup("environment")); + setServerName(Lookup.lookup("serverName")); + setTags(Lookup.lookup("tags")); + setExtraTags(Lookup.lookup("extraTags")); } /** @@ -114,7 +119,7 @@ public SentryAppender() { * @param raven instance of Raven to use with this appender. */ public SentryAppender(Raven raven) { - this(APPENDER_NAME, null); + this(); this.raven = raven; } @@ -154,6 +159,7 @@ public static SentryAppender createAppender(@PluginAttribute("name") final Strin } SentryAppender sentryAppender = new SentryAppender(name, filter); sentryAppender.setDsn(dsn); + if (release != null) sentryAppender.setRelease(release); if (environment != null) @@ -353,7 +359,7 @@ public void setTags(String tags) { * @param extraTags A String of extraTags. extraTags are separated by commas(,). */ public void setExtraTags(String extraTags) { - this.extraTags = new HashSet<>(Arrays.asList(extraTags.split(","))); + this.extraTags = Util.parseExtraTags(extraTags); } @Override diff --git a/raven-logback/README.md b/raven-logback/README.md index f27d91db893..7eebe8b1a8a 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -27,14 +27,61 @@ Relies on: ## Usage ### Configuration -In the `logback.xml` file set: +Add the `SentryAppender` to your `logback.xml` file: + +```xml + + + + + + +``` + +Next, you'll need to configure your DSN (client key) and optionally other +values such as `environment` and `release`. See below for the two +ways you can do this. + +#### Configuration via runtime environment + +This is the most flexible method to configure the `SentryAppender`, +because it can be easily changed based on the environment you run your +application in. + +The following can be set as System Environment variables: + +```bash +SENTRY_EXAMPLE=xxx java -jar app.jar +``` + +or as Java System Properties: + +```bash +java -Dsentry.example=xxx -jar app.jar +``` + +Configuration parameters follow: + +| Environment variable | Java System Property | Example value | Description | +|---|---|---|---| +| `SENTRY_DSN` | `sentry.dsn` | `https://host:port/1?options` | Your Sentry DSN (client key), if left blank Raven will no-op | +| `SENTRY_RELEASE` | `sentry.release` | `1.0.0` | Optional, provide release version of your application | +| `SENTRY_ENVIRONMENT` | `sentry.environment` | `production` | Optional, provide environment your application is running in | +| `SENTRY_SERVERNAME` | `sentry.servername` | `server1` | Optional, override the server name (rather than looking it up dynamically) | +| `SENTRY_RAVENFACTORY` | `sentry.ravenfactory` | `com.foo.RavenFactory` | Optional, select the ravenFactory class | +| `SENTRY_TAGS` | `sentry.tags` | `tag1:value1,tag2:value2` | Optional, provide tags | +| `SENTRY_EXTRA_TAGS` | `sentry.extratags` | `foo,bar,baz` | Optional, provide tag names to be extracted from MDC when using SLF4J | + +#### Configuration via `logback.xml` + +You can also configure everything statically within the `logback.xml` file +itself. This is less flexible because it's harder to change when you run +your application in different environments. ```xml - https://publicKey:secretKey@host:port/1?options - - tag1:value1,tag2:value2 + https://host:port/1?options 1.0.0 @@ -42,7 +89,11 @@ In the `logback.xml` file set: server1 - com.getsentry.raven.DefaultRavenFactory + com.foo.RavenFactory + + tag1:value1,tag2:value2 + + foo,bar,baz diff --git a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java index 20175c4b4fa..aaa297a4874 100644 --- a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java @@ -7,6 +7,7 @@ import ch.qos.logback.core.AppenderBase; import com.getsentry.raven.Raven; import com.getsentry.raven.RavenFactory; +import com.getsentry.raven.config.Lookup; import com.getsentry.raven.dsn.Dsn; import com.getsentry.raven.dsn.InvalidDsnException; import com.getsentry.raven.environment.RavenEnvironment; @@ -20,7 +21,6 @@ import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Deque; @@ -99,6 +99,12 @@ public class SentryAppender extends AppenderBase { * Creates an instance of SentryAppender. */ public SentryAppender() { + setRavenFactory(Lookup.lookup("ravenFactory")); + setRelease(Lookup.lookup("release")); + setEnvironment(Lookup.lookup("environment")); + setServerName(Lookup.lookup("serverName")); + setTags(Lookup.lookup("tags")); + setExtraTags(Lookup.lookup("extraTags")); } /** @@ -107,6 +113,7 @@ public SentryAppender() { * @param raven instance of Raven to use with this appender. */ public SentryAppender(Raven raven) { + this(); this.raven = raven; } @@ -154,16 +161,19 @@ protected static Event.Level formatLevel(Level level) { @Override protected void append(ILoggingEvent iLoggingEvent) { // Do not log the event if the current thread is managed by raven - if (RavenEnvironment.isManagingThread()) + if (RavenEnvironment.isManagingThread()) { return; + } RavenEnvironment.startManagingThread(); try { - if (raven == null) + if (raven == null) { initRaven(); + } - if (minLevel != null && !iLoggingEvent.getLevel().isGreaterOrEqual(minLevel)) + if (minLevel != null && !iLoggingEvent.getLevel().isGreaterOrEqual(minLevel)) { return; + } Event event = buildEvent(iLoggingEvent); raven.sendEvent(event); @@ -179,8 +189,9 @@ protected void append(ILoggingEvent iLoggingEvent) { */ protected void initRaven() { try { - if (dsn == null) + if (dsn == null) { dsn = Dsn.dsnLookup(); + } raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); } catch (InvalidDsnException e) { @@ -361,7 +372,7 @@ public void setTags(String tags) { * @param extraTags A String of extraTags. extraTags are separated by commas(,). */ public void setExtraTags(String extraTags) { - this.extraTags = new HashSet<>(Arrays.asList(extraTags.split(","))); + this.extraTags = Util.parseExtraTags(extraTags); } @Override diff --git a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderIT.java b/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderIT.java index a744c7ed47c..8358c980b8f 100644 --- a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderIT.java +++ b/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderIT.java @@ -17,6 +17,7 @@ public class SentryAppenderIT { @BeforeMethod public void setUp() throws Exception { sentryStub = new SentryStub(); + sentryStub.removeEvents(); } @AfterMethod diff --git a/raven/README.md b/raven/README.md index c3805eb2b93..5aba77e937a 100644 --- a/raven/README.md +++ b/raven/README.md @@ -29,12 +29,62 @@ Relies on: ## Usage (`java.util.logging`) ### Configuration -In the `logging.properties` file set: +Add the `SentryHandler` to the `logging.properties` file: ```properties .level=WARN handlers=com.getsentry.raven.jul.SentryHandler -com.getsentry.raven.jul.SentryHandler.dsn=https://publicKey:secretKey@host:port/1?options +``` + +When starting your application, add the `java.util.logging.config.file` to the +system properties, with the full path to the `logging.properties` as its value. + + $ java -Djava.util.logging.config.file=/path/to/app.properties MyClass + +Next, you'll need to configure your DSN (client key) and optionally other +values such as `environment` and `release`. See below for the two +ways you can do this. + +#### Configuration via runtime environment + +This is the most flexible method to configure the `SentryAppender`, +because it can be easily changed based on the environment you run your +application in. + +The following can be set as System Environment variables: + +```bash +SENTRY_EXAMPLE=xxx java -jar app.jar +``` + +or as Java System Properties: + +```bash +java -Dsentry.example=xxx -jar app.jar +``` + +Configuration parameters follow: + +| Environment variable | Java System Property | Example value | Description | +|---|---|---|---| +| `SENTRY_DSN` | `sentry.dsn` | `https://host:port/1?options` | Your Sentry DSN (client key), if left blank Raven will no-op | +| `SENTRY_RELEASE` | `sentry.release` | `1.0.0` | Optional, provide release version of your application | +| `SENTRY_ENVIRONMENT` | `sentry.environment` | `production` | Optional, provide environment your application is running in | +| `SENTRY_SERVERNAME` | `sentry.servername` | `server1` | Optional, override the server name (rather than looking it up dynamically) | +| `SENTRY_RAVENFACTORY` | `sentry.ravenfactory` | `com.foo.RavenFactory` | Optional, select the ravenFactory class | +| `SENTRY_TAGS` | `sentry.tags` | `tag1:value1,tag2:value2` | Optional, provide tags | +| `SENTRY_EXTRA_TAGS` | `sentry.extratags` | `foo,bar,baz` | Optional, provide tag names to be extracted from MDC when using SLF4J | + +#### Configuration via `logging.properties` + +You can also configure everything statically within the `logging.properties` file +itself. This is less flexible because it's harder to change when you run +your application in different environments. + +```properties +.level=WARN +handlers=com.getsentry.raven.jul.SentryHandler +com.getsentry.raven.jul.SentryHandler.dsn=https://host:port/1?options # Optional, provide tags com.getsentry.raven.jul.SentryHandler.tags=tag1:value1,tag2:value2 # Optional, provide release version of your application @@ -44,16 +94,11 @@ com.getsentry.raven.jul.SentryHandler.environment=production # Optional, override the server name (rather than looking it up dynamically) com.getsentry.raven.jul.SentryHandler.serverName=server1 # Optional, select the ravenFactory class -com.getsentry.raven.jul.SentryHandler.ravenFactory=com.getsentry.raven.DefaultRavenFactory +com.getsentry.raven.jul.SentryHandler.ravenFactory=com.foo.RavenFactory # Optional, provide tag names to be extracted from MDC when using SLF4J com.getsentry.raven.jul.SentryHandler.extraTags=foo,bar,baz ``` -When starting your application, add the `java.util.logging.config.file` to the -system properties, with the full path to the `logging.properties` as its value. - - $ java -Djava.util.logging.config.file=/path/to/app.properties MyClass - ### In practice ```java import java.util.logging.Level; diff --git a/raven/src/main/java/com/getsentry/raven/config/JndiLookup.java b/raven/src/main/java/com/getsentry/raven/config/JndiLookup.java new file mode 100644 index 00000000000..f2ec1949528 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/config/JndiLookup.java @@ -0,0 +1,45 @@ +package com.getsentry.raven.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.naming.NoInitialContextException; + +/** + * Handles JNDI lookup of a provided key within the Sentry namespace. + */ +public final class JndiLookup { + /** + * Lookup prefix for Sentry configuration in JNDI. + */ + private static final String JNDI_PREFIX = "java:comp/env/sentry/"; + private static final Logger logger = LoggerFactory.getLogger(JndiLookup.class); + + private JndiLookup() { + + } + + /** + * Looks up for a JNDI definition of the provided key. + * + * @param key name of configuration key, e.g. "dsn" + * @return the value as defined in JNDI or null if it isn't defined. + */ + public static String jndiLookup(String key) { + String value = null; + try { + Context c = new InitialContext(); + value = (String) c.lookup(JNDI_PREFIX + key); + } catch (NoInitialContextException e) { + logger.debug("JNDI not configured for Sentry (NoInitialContextEx)"); + } catch (NamingException e) { + logger.debug("No /sentry/" + key + " in JNDI"); + } catch (RuntimeException e) { + logger.warn("Odd RuntimeException while testing for JNDI", e); + } + return value; + } +} diff --git a/raven/src/main/java/com/getsentry/raven/config/Lookup.java b/raven/src/main/java/com/getsentry/raven/config/Lookup.java new file mode 100644 index 00000000000..ea71df9ef45 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/config/Lookup.java @@ -0,0 +1,52 @@ +package com.getsentry.raven.config; + +import com.getsentry.raven.dsn.Dsn; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handle lookup of configuration keys by trying JNDI, System Environment, and Java System Properties. + */ +public final class Lookup { + private static final Logger logger = LoggerFactory.getLogger(JndiLookup.class); + + private Lookup() { + + } + + /** + * Attempt to lookup a configuration key. + * + * @param key name of configuration key, e.g. "dsn" + * @return value of configuration key, if found, otherwise null + */ + public static String lookup(String key) { + String value = null; + + // Try to obtain from JNDI + try { + // Check that JNDI is available (not available on Android) by loading InitialContext + Class.forName("javax.naming.InitialContext", false, Dsn.class.getClassLoader()); + value = JndiLookup.jndiLookup(key); + } catch (ClassNotFoundException | NoClassDefFoundError e) { + logger.debug("JNDI not available", e); + } + + // Try to obtain from a Java System Property + if (value == null) { + value = System.getProperty("sentry." + key.toLowerCase()); + } + + // Try to obtain from a System Environment Variable + if (value == null) { + value = System.getenv("SENTRY_" + key.toUpperCase()); + } + + if (value != null) { + return value.trim(); + } else { + return null; + } + } + +} diff --git a/raven/src/main/java/com/getsentry/raven/dsn/Dsn.java b/raven/src/main/java/com/getsentry/raven/dsn/Dsn.java index dba8544d2a7..516ea661697 100644 --- a/raven/src/main/java/com/getsentry/raven/dsn/Dsn.java +++ b/raven/src/main/java/com/getsentry/raven/dsn/Dsn.java @@ -1,5 +1,6 @@ package com.getsentry.raven.dsn; +import com.getsentry.raven.config.Lookup; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,10 +14,6 @@ * Data Source name allowing a direct connection to a Sentry server. */ public class Dsn { - /** - * Name of the environment and system variables containing the DSN. - */ - public static final String DSN_VARIABLE = "SENTRY_DSN"; /** * Default DSN to use when auto detection fails. */ @@ -86,24 +83,7 @@ public Dsn(URI dsn) throws InvalidDsnException { * @return a DSN configuration or null if nothing could be found. */ public static String dsnLookup() { - String dsn = null; - - // Try to obtain the DSN from JNDI - try { - // Check that JNDI is available (not available on Android) by loading InitialContext - Class.forName("javax.naming.InitialContext", false, Dsn.class.getClassLoader()); - dsn = JndiLookup.jndiLookup(); - } catch (ClassNotFoundException | NoClassDefFoundError e) { - logger.debug("JNDI not available"); - } - - // Try to obtain the DSN from a System Environment Variable - if (dsn == null) - dsn = System.getenv(DSN_VARIABLE); - - // Try to obtain the DSN from a Java System Property - if (dsn == null) - dsn = System.getProperty(DSN_VARIABLE); + String dsn = Lookup.lookup("dsn"); if (dsn == null) { logger.warn("Couldn't find a suitable DSN, defaulting to a Noop one."); diff --git a/raven/src/main/java/com/getsentry/raven/dsn/JndiLookup.java b/raven/src/main/java/com/getsentry/raven/dsn/JndiLookup.java deleted file mode 100644 index f7ae6a3e273..00000000000 --- a/raven/src/main/java/com/getsentry/raven/dsn/JndiLookup.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.getsentry.raven.dsn; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.naming.Context; -import javax.naming.InitialContext; -import javax.naming.NamingException; -import javax.naming.NoInitialContextException; - -/** - * JNDI Lookup allows to do a JNDI lookup without tying {@link Dsn} to the JNDI libraries. - *

    - * Android does not support JNDI making the automatic lookup through JNDI illegal and fatal to the application. - * Having the lookup in a separate class allows the classloader to load {@link Dsn} without exceptions. - */ -final class JndiLookup { - /** - * Lookup name for the DSN in JNDI. - */ - private static final String JNDI_DSN_NAME = "java:comp/env/sentry/dsn"; - private static final Logger logger = LoggerFactory.getLogger(JndiLookup.class); - - private JndiLookup() { - } - - /** - * Looks up for a JNDI definition of the DSN. - * - * @return the DSN defined in JNDI or null if it isn't defined. - */ - public static String jndiLookup() { - String dsn = null; - try { - Context c = new InitialContext(); - dsn = (String) c.lookup(JNDI_DSN_NAME); - } catch (NoInitialContextException e) { - logger.debug("JNDI not configured for sentry (NoInitialContextEx)"); - } catch (NamingException e) { - logger.debug("No /sentry/dsn in JNDI"); - } catch (RuntimeException e) { - logger.warn("Odd RuntimeException while testing for JNDI", e); - } - return dsn; - } - - @Override - public String toString() { - return "JndiLookup{" - + "JNDI_DSN_NAME='" + JNDI_DSN_NAME + '\'' - + '}'; - } -} diff --git a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java index c9e9d82f4fe..219bad12954 100644 --- a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java +++ b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java @@ -2,6 +2,7 @@ import com.getsentry.raven.Raven; import com.getsentry.raven.RavenFactory; +import com.getsentry.raven.config.Lookup; import com.getsentry.raven.dsn.Dsn; import com.getsentry.raven.dsn.InvalidDsnException; import com.getsentry.raven.environment.RavenEnvironment; @@ -14,10 +15,8 @@ import java.text.MessageFormat; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Date; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -53,6 +52,7 @@ public class SentryHandler extends Handler { * false. */ protected boolean printfStyle; + /** * Name of the {@link RavenFactory} being used. *

    @@ -93,6 +93,13 @@ public class SentryHandler extends Handler { * Creates an instance of SentryHandler. */ public SentryHandler() { + setRavenFactory(Lookup.lookup("ravenFactory")); + setRelease(Lookup.lookup("release")); + setEnvironment(Lookup.lookup("environment")); + setServerName(Lookup.lookup("serverName")); + setTags(Lookup.lookup("tags")); + setExtraTags(Lookup.lookup("extraTags")); + retrieveProperties(); } @@ -102,30 +109,10 @@ public SentryHandler() { * @param raven instance of Raven to use with this appender. */ public SentryHandler(Raven raven) { + this(); this.raven = raven; } - public void setDsn(String dsn) { - this.dsn = dsn; - } - - public void setPrintfStyle(boolean printfStyle) { - this.printfStyle = printfStyle; - } - - /** - * Populates the tags map by parsing the given tags property string. - * @param tagsProperty comma-delimited key-value pairs, e.g. - * "tag1:value1,tag2:value2". - */ - public void setTags(String tagsProperty) { - this.tags = Util.parseTags(tagsProperty); - } - - public void setExtraTags(String extraTags) { - this.extraTags = new HashSet<>(Arrays.asList(extraTags.split(","))); - } - /** * Transforms a {@link Level} into an {@link Event.Level}. * @@ -165,17 +152,35 @@ protected static List formatMessageParameters(Object[] parameters) { protected void retrieveProperties() { LogManager manager = LogManager.getLogManager(); String className = SentryHandler.class.getName(); - dsn = manager.getProperty(className + ".dsn"); - printfStyle = Boolean.valueOf(manager.getProperty(className + ".printfStyle")); - ravenFactory = manager.getProperty(className + ".ravenFactory"); - release = manager.getProperty(className + ".release"); - environment = manager.getProperty(className + ".environment"); - serverName = manager.getProperty(className + ".serverName"); + String dsnProperty = manager.getProperty(className + ".dsn"); + if (dsnProperty != null) { + setDsn(dsnProperty); + } + String ravenFactoryProperty = manager.getProperty(className + ".ravenFactory"); + if (ravenFactoryProperty != null) { + setRavenFactory(ravenFactoryProperty); + } + String releaseProperty = manager.getProperty(className + ".release"); + if (releaseProperty != null) { + setRelease(releaseProperty); + } + String environmentProperty = manager.getProperty(className + ".environment"); + if (environmentProperty != null) { + setEnvironment(environmentProperty); + } + String serverNameProperty = manager.getProperty(className + ".serverName"); + if (serverNameProperty != null) { + setServerName(serverNameProperty); + } String tagsProperty = manager.getProperty(className + ".tags"); - tags = Util.parseTags(tagsProperty); + if (tagsProperty != null) { + setTags(tagsProperty); + } String extraTagsProperty = manager.getProperty(className + ".extraTags"); - if (extraTagsProperty != null) - extraTags = new HashSet<>(Arrays.asList(extraTagsProperty.split(","))); + if (extraTagsProperty != null) { + setExtraTags(extraTagsProperty); + } + setPrintfStyle(Boolean.valueOf(manager.getProperty(className + ".printfStyle"))); } @Override @@ -320,6 +325,18 @@ public void close() throws SecurityException { } } + public void setDsn(String dsn) { + this.dsn = dsn; + } + + public void setPrintfStyle(boolean printfStyle) { + this.printfStyle = printfStyle; + } + + public void setRavenFactory(String ravenFactory) { + this.ravenFactory = ravenFactory; + } + public void setRelease(String release) { this.release = release; } @@ -331,4 +348,18 @@ public void setEnvironment(String environment) { public void setServerName(String serverName) { this.serverName = serverName; } + + /** + * Populates the tags map by parsing the given tags property string. + * @param tags comma-delimited key-value pairs, e.g. + * "tag1:value1,tag2:value2". + */ + public void setTags(String tags) { + this.tags = Util.parseTags(tags); + } + + public void setExtraTags(String extraTags) { + this.extraTags = Util.parseExtraTags(extraTags); + } + } diff --git a/raven/src/main/java/com/getsentry/raven/util/Util.java b/raven/src/main/java/com/getsentry/raven/util/Util.java index a17addd54ed..fb95bb4482b 100644 --- a/raven/src/main/java/com/getsentry/raven/util/Util.java +++ b/raven/src/main/java/com/getsentry/raven/util/Util.java @@ -1,9 +1,12 @@ package com.getsentry.raven.util; import javax.annotation.Nullable; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; /** * Raven static Utility class. @@ -48,4 +51,18 @@ public static Map parseTags(String tagsString) { return map; } + /** + * Parses the provided extraTags string into a Set of Strings. + * + * @param extraTagsString comma-delimited tags + * @return Set of Strings representing extra tags + */ + public static Set parseExtraTags(String extraTagsString) { + if (isNullOrEmpty(extraTagsString)) { + return Collections.emptySet(); + } + + return new HashSet<>(Arrays.asList(extraTagsString.split(","))); + } + } diff --git a/raven/src/test/java/com/getsentry/raven/dsn/DsnTest.java b/raven/src/test/java/com/getsentry/raven/dsn/DsnTest.java index 4b7ed69a284..ed1ffcd176a 100644 --- a/raven/src/test/java/com/getsentry/raven/dsn/DsnTest.java +++ b/raven/src/test/java/com/getsentry/raven/dsn/DsnTest.java @@ -1,5 +1,6 @@ package com.getsentry.raven.dsn; +import com.getsentry.raven.config.JndiLookup; import mockit.Expectations; import mockit.Mocked; import mockit.NonStrictExpectations; @@ -64,7 +65,7 @@ public void testDsnLookupWithNothingSet() throws Exception { public void testJndiLookupFailsWithException( @SuppressWarnings("unused") @Mocked("jndiLookup") JndiLookup mockJndiLookup) throws Exception { new NonStrictExpectations() {{ - JndiLookup.jndiLookup(); + JndiLookup.jndiLookup("dsn"); result = new ClassNotFoundException("Couldn't find the JNDI classes"); }}; @@ -75,7 +76,7 @@ public void testJndiLookupFailsWithException( public void testJndiLookupFailsWithError( @SuppressWarnings("unused") @Mocked("jndiLookup") JndiLookup mockJndiLookup) throws Exception { new NonStrictExpectations() {{ - JndiLookup.jndiLookup(); + JndiLookup.jndiLookup("dsn"); result = new NoClassDefFoundError("Couldn't find the JNDI classes"); }}; @@ -96,11 +97,11 @@ public void testDsnLookupWithJndi() throws Exception { @Test public void testDsnLookupWithSystemProperty() throws Exception { String dsn = "aa9171a4-7e9b-4e3c-b3cc-fe537dc03527"; - System.setProperty("SENTRY_DSN", dsn); + System.setProperty("sentry.dsn", dsn); assertThat(Dsn.dsnLookup(), is(dsn)); - System.clearProperty("SENTRY_DSN"); + System.clearProperty("sentry.dsn"); } @Test From af5484b2fd019bb08a9278769f626085fba14cd4 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 6 Sep 2016 16:50:59 -0500 Subject: [PATCH 1453/2152] Send headers as an array of arrays, no longer wrapped in added apostrophes. (#249) --- CHANGES | 1 + .../marshaller/json/HttpInterfaceBinding.java | 19 ++---- .../json/HttpInterfaceBindingTest.java | 65 +++++++++++++++++++ .../raven/marshaller/json/Http1.json | 27 ++++++++ 4 files changed, 100 insertions(+), 12 deletions(-) create mode 100644 raven/src/test/java/com/getsentry/raven/marshaller/json/HttpInterfaceBindingTest.java create mode 100644 raven/src/test/resources/com/getsentry/raven/marshaller/json/Http1.json diff --git a/CHANGES b/CHANGES index cecefa76612..5ebb988ee68 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,7 @@ Version 7.7.0 - Add static ``Raven.capture`` methods that send to the most recently constructed Raven instance. - Add support for setting ``ravenFactory``, ``release``, ``environment`` and ``serverName`` via JNDI, System Environment or Java System Properties (like the DSN). +- Send headers as an array of arrays, no longer wrapped in added apostrophes. Version 7.6.0 ------------- diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/HttpInterfaceBinding.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/HttpInterfaceBinding.java index f4e60532511..4f0c9ff4b16 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/HttpInterfaceBinding.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/HttpInterfaceBinding.java @@ -5,7 +5,6 @@ import java.io.IOException; import java.util.Collection; -import java.util.Iterator; import java.util.Map; /** @@ -66,20 +65,16 @@ private void writeEnvironment(JsonGenerator generator, HttpInterface httpInterfa } private void writeHeaders(JsonGenerator generator, Map> headers) throws IOException { - generator.writeStartObject(); + generator.writeStartArray(); for (Map.Entry> headerEntry : headers.entrySet()) { - generator.writeFieldName(headerEntry.getKey()); - - StringBuilder sb = new StringBuilder(); - for (Iterator it = headerEntry.getValue().iterator(); it.hasNext(); ) { - sb.append("' ").append(it.next()).append(" '"); - if (it.hasNext()) { - sb.append(","); - } + for (String value : headerEntry.getValue()) { + generator.writeStartArray(); + generator.writeString(headerEntry.getKey()); + generator.writeString(value); + generator.writeEndArray(); } - generator.writeString(sb.toString()); } - generator.writeEndObject(); + generator.writeEndArray(); } private void writeCookies(JsonGenerator generator, Map cookies) throws IOException { diff --git a/raven/src/test/java/com/getsentry/raven/marshaller/json/HttpInterfaceBindingTest.java b/raven/src/test/java/com/getsentry/raven/marshaller/json/HttpInterfaceBindingTest.java new file mode 100644 index 00000000000..c5f16c9d05d --- /dev/null +++ b/raven/src/test/java/com/getsentry/raven/marshaller/json/HttpInterfaceBindingTest.java @@ -0,0 +1,65 @@ +package com.getsentry.raven.marshaller.json; + +import com.fasterxml.jackson.databind.JsonNode; +import com.getsentry.raven.event.interfaces.HttpInterface; +import mockit.Injectable; +import mockit.NonStrictExpectations; +import mockit.Tested; +import org.testng.annotations.Test; +import org.testng.collections.Lists; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import static com.getsentry.raven.marshaller.json.JsonComparisonUtil.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class HttpInterfaceBindingTest { + @Tested + private HttpInterfaceBinding interfaceBinding = null; + @Injectable + private HttpInterface mockMessageInterface = null; + + @Test + public void testHeaders() throws Exception { + final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); + final Map> headers = new HashMap<>(); + headers.put("Header1", Lists.newArrayList("Value1")); + headers.put("Header2", Lists.newArrayList("Value1", "Value2")); + final HashMap cookies = new HashMap<>(); + cookies.put("Cookie1", "Value1"); + new NonStrictExpectations() {{ + mockMessageInterface.getHeaders(); + result = headers; + mockMessageInterface.getRequestUrl(); + result = "http://host/url"; + mockMessageInterface.getMethod(); + result = "GET"; + mockMessageInterface.getQueryString(); + result = "query"; + mockMessageInterface.getCookies(); + result = cookies; + mockMessageInterface.getRemoteAddr(); + result = "1.2.3.4"; + mockMessageInterface.getServerName(); + result = "server-name"; + mockMessageInterface.getServerPort(); + result = 1234; + mockMessageInterface.getLocalPort(); + result = 5678; + mockMessageInterface.getProtocol(); + result = "HTTP"; + mockMessageInterface.getLocalAddr(); + result = "5.6.7.8"; + mockMessageInterface.getLocalName(); + result = "local-name"; + }}; + + interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockMessageInterface); + + JsonNode value = jsonGeneratorParser.value(); + assertThat(value, is(jsonResource("/com/getsentry/raven/marshaller/json/Http1.json"))); + } +} diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/Http1.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/Http1.json new file mode 100644 index 00000000000..d0bf64965cf --- /dev/null +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/Http1.json @@ -0,0 +1,27 @@ +{ + "url": "http://host/url", + "method": "GET", + "data": {}, + "query_string": "query", + "cookies": { + "Cookie1": "Value1" + }, + "headers": [ + ["Header1","Value1"], + ["Header2","Value1"], + ["Header2","Value2"] + ], + "env": { + "REMOTE_ADDR": "1.2.3.4", + "SERVER_NAME": "server-name", + "SERVER_PORT": 1234, + "LOCAL_ADDR": "5.6.7.8", + "LOCAL_NAME": "local-name", + "LOCAL_PORT": 5678, + "SERVER_PROTOCOL": "HTTP", + "REQUEST_SECURE": false, + "REQUEST_ASYNC": false, + "AUTH_TYPE": null, + "REMOTE_USER": null + } +} From f2a53e6e198300752ad210eb4b3f29784e1d9ccf Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 6 Sep 2016 16:51:41 -0500 Subject: [PATCH 1454/2152] Bump CHANGES and docs to 7.7.0 --- docs/modules/appengine.rst | 2 +- docs/modules/log4j.rst | 2 +- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 2 +- docs/modules/raven.rst | 2 +- raven-appengine/README.md | 4 ++-- raven-log4j/README.md | 4 ++-- raven-log4j2/README.md | 10 +++++----- raven-logback/README.md | 4 ++-- raven/README.md | 4 ++-- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index b198da2b917..778d0ffdb6a 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -25,7 +25,7 @@ If you want to use Maven you can install Raven-AppEngine as dependency: com.getsentry.raven raven-appengine - 7.6.0 + 7.7.0 Usage diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 2c95302e68c..c7b148dffbb 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven-Log4j as dependency: com.getsentry.raven raven-log4j - 7.6.0 + 7.7.0 If you manually want to manage your dependencies: diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index edb3ea69c30..e8b893dfaad 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,18 +19,18 @@ If you want to use Maven you can install Raven-Log4j2 as dependency: com.getsentry.raven raven-log4j2 - 7.6.0 + 7.7.0 If you manually want to manage your dependencies: - :doc:`raven dependencies ` - `log4j-api-2.1.jar - `_ + `_ - `log4j-core-2.0.jar - `_ + `_ - `log4j-slf4j-impl-2.1.jar - `_ + `_ is recommended as the implementation of slf4j (instead of slf4j-jdk14). Usage diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 057370d14db..33a85cc3661 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -16,7 +16,7 @@ If you want to use Maven you can install Raven-Logback as dependency: com.getsentry.raven raven-logback - 7.6.0 + 7.7.0 - :doc:`raven dependencies ` diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index 4906cbc8e70..6775ec1a31b 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven as dependency: com.getsentry.raven raven - 7.6.0 + 7.7.0 If you manually want to manage your dependencies: diff --git a/raven-appengine/README.md b/raven-appengine/README.md index 727e204a46c..a751211db1e 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -15,12 +15,12 @@ __This module is not useful outside of Google App Engine.__ com.getsentry.raven raven-appengine - 7.6.0 + 7.7.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.6.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.7.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 509accd66de..3a3604ed7cd 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,12 +10,12 @@ for log4j to send the logged events to Sentry. com.getsentry.raven raven-log4j - 7.6.0 + 7.7.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.6.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.7.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index f1399b9caf9..7847962cd6a 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,20 +10,20 @@ for Log4j 2 to send the logged events to Sentry. com.getsentry.raven raven-log4j2 - 7.6.0 + 7.7.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.6.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.7.0%7Cjar). ### Manual dependency management Relies on: - [raven dependencies](../raven) - - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.6.0%7Cjar) - - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.6.0%7Cjar) - - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.6.0%7Cjar) + - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.7.0%7Cjar) + - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.7.0%7Cjar) + - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.7.0%7Cjar) is recommended as the implementation of slf4j (instead of slf4j-jdk14). diff --git a/raven-logback/README.md b/raven-logback/README.md index 7eebe8b1a8a..d7caaca934c 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,12 +10,12 @@ for logback to send the logged events to Sentry. com.getsentry.raven raven-logback - 7.6.0 + 7.7.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.6.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.7.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven/README.md b/raven/README.md index 5aba77e937a..712c48036b1 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,12 +10,12 @@ for `java.util.logging`. com.getsentry.raven raven - 7.6.0 + 7.7.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.6.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.7.0%7Cjar). ### Manual dependency management Relies on: From 7d2fd7124a39b7fd2e8691f30340c8077719f225 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 6 Sep 2016 16:54:22 -0500 Subject: [PATCH 1455/2152] [maven-release-plugin] prepare release v7.7.0 --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index aeea54db305..9f4217b5e75 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.6.1-SNAPSHOT + 7.7.0 pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v7.7.0 https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 617cc1c072b..28252387215 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.6.1-SNAPSHOT + 7.7.0 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 95a904fae3c..3d6e56d1881 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.6.1-SNAPSHOT + 7.7.0 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index c9a7d9987be..acf15fc09c3 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.6.1-SNAPSHOT + 7.7.0 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index f239b750231..35bff866934 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.6.1-SNAPSHOT + 7.7.0 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index f4617b2ebbb..41ed97266a0 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.6.1-SNAPSHOT + 7.7.0 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index e0b0f6a9d02..1e1d3dcaf5b 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.6.1-SNAPSHOT + 7.7.0 sentry-stub From 8dd6b784ed062204f694ed98de55f28fd944eee1 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 6 Sep 2016 16:54:22 -0500 Subject: [PATCH 1456/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 9f4217b5e75..38cfb1173c5 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.7.0 + 7.7.1-SNAPSHOT pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v7.7.0 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 28252387215..27304abc22e 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.7.0 + 7.7.1-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 3d6e56d1881..1123a27c900 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.0 + 7.7.1-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index acf15fc09c3..6d21d073fc3 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.0 + 7.7.1-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 35bff866934..17dbd023c1f 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.0 + 7.7.1-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 41ed97266a0..44760effdb0 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.0 + 7.7.1-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 1e1d3dcaf5b..f88768ddc4f 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.0 + 7.7.1-SNAPSHOT sentry-stub From e122471ae6a186e9a6c433c04fc8e4a6f0458483 Mon Sep 17 00:00:00 2001 From: Victor Verbitsky Date: Sat, 24 Sep 2016 01:13:07 +0300 Subject: [PATCH 1457/2152] Add ``raven.maxmessagelength`` DSN option. (thanks vektory79) (#254) --- CHANGES | 5 +++ README.md | 6 ++++ .../getsentry/raven/DefaultRavenFactory.java | 12 +++++-- .../raven/marshaller/json/JsonMarshaller.java | 30 +++++++++++++++--- .../json/MessageInterfaceBinding.java | 31 ++++++++++++++++--- .../java/com/getsentry/raven/util/Util.java | 14 +++++++++ 6 files changed, 86 insertions(+), 12 deletions(-) diff --git a/CHANGES b/CHANGES index 5ebb988ee68..4b570efbe30 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 7.7.1 +------------- + +- Add ``raven.maxmessagelength`` DSN option. (thanks vektory79) + Version 7.7.0 ------------- diff --git a/README.md b/README.md index aad70e9fbb0..a792cfbb676 100644 --- a/README.md +++ b/README.md @@ -281,6 +281,12 @@ It's possible to manually enable/disable the compression with the option http://public:private@host:port/1?raven.compression=false +### Max message size +By default only the first 1000 characters of a message will be sent to +the server. This can be changed with the `raven.maxmessagelength` option. + + http://public:private@host:port/1?raven.maxmessagelength=1500 + ### Timeout (advanced) A timeout is set to avoid blocking Raven threads because establishing a connection is taking too long. diff --git a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java index 1fec0dd1eeb..ed27436caa3 100644 --- a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java @@ -7,6 +7,7 @@ import com.getsentry.raven.event.interfaces.*; import com.getsentry.raven.marshaller.Marshaller; import com.getsentry.raven.marshaller.json.*; +import com.getsentry.raven.util.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,6 +34,11 @@ public class DefaultRavenFactory extends RavenFactory { * Option specific to raven-java, allowing to enable/disable the compression of requests to the Sentry Server. */ public static final String COMPRESSION_OPTION = "raven.compression"; + /** + * Option specific to raven-java, allowing to set maximum length of the message body in the requests to the + * Sentry Server. + */ + public static final String MAX_MESSAGE_LENGTH_OPTION = "raven.maxmessagelength"; /** * Option specific to raven-java, allowing to set a timeout (in ms) for a request to the Sentry server. */ @@ -242,7 +248,9 @@ protected Connection createStdOutConnection(Dsn dsn) { * @return a {@link JsonMarshaller} to process the events. */ protected Marshaller createMarshaller(Dsn dsn) { - JsonMarshaller marshaller = new JsonMarshaller(); + int maxMessageLength = Util.parseInteger( + dsn.getOptions().get(MAX_MESSAGE_LENGTH_OPTION), JsonMarshaller.DEFAULT_MAX_MESSAGE_LENGTH); + JsonMarshaller marshaller = new JsonMarshaller(maxMessageLength); // Set JSON marshaller bindings StackTraceInterfaceBinding stackTraceBinding = new StackTraceInterfaceBinding(); @@ -253,7 +261,7 @@ protected Marshaller createMarshaller(Dsn dsn) { marshaller.addInterfaceBinding(StackTraceInterface.class, stackTraceBinding); marshaller.addInterfaceBinding(ExceptionInterface.class, new ExceptionInterfaceBinding(stackTraceBinding)); - marshaller.addInterfaceBinding(MessageInterface.class, new MessageInterfaceBinding()); + marshaller.addInterfaceBinding(MessageInterface.class, new MessageInterfaceBinding(maxMessageLength)); marshaller.addInterfaceBinding(UserInterface.class, new UserInterfaceBinding()); HttpInterfaceBinding httpBinding = new HttpInterfaceBinding(); //TODO: Add a way to clean the HttpRequest diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java index f871f529269..84a2f366df2 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java @@ -90,9 +90,9 @@ public class JsonMarshaller implements Marshaller { */ public static final String CHECKSUM = "checksum"; /** - * Maximum length for a message. + * Default maximum length for a message. */ - public static final int MAX_MESSAGE_LENGTH = 1000; + public static final int DEFAULT_MAX_MESSAGE_LENGTH = 1000; /** * Date format for ISO 8601. */ @@ -112,6 +112,26 @@ protected DateFormat initialValue() { * Enables disables the compression of JSON. */ private boolean compression = true; + /** + * Maximum length for a message. + */ + private final int maxMessageLength; + + /** + * Create instance of JsonMarshaller with default message length. + */ + public JsonMarshaller() { + maxMessageLength = DEFAULT_MAX_MESSAGE_LENGTH; + } + + /** + * Create instance of JsonMarshaller with provided the maximum length of the messages. + * + * @param maxMessageLength the maximum message length + */ + public JsonMarshaller(int maxMessageLength) { + this.maxMessageLength = maxMessageLength; + } @Override public void marshall(Event event, OutputStream destination) { @@ -280,7 +300,7 @@ private void writeBreadcumbs(JsonGenerator generator, List breadcrum } /** - * Trims a message, ensuring that the maximum length {@link #MAX_MESSAGE_LENGTH} isn't reached. + * Trims a message, ensuring that the maximum length {@link #maxMessageLength} isn't reached. * * @param message message to format. * @return trimmed message (shortened if necessary). @@ -288,8 +308,8 @@ private void writeBreadcumbs(JsonGenerator generator, List breadcrum private String trimMessage(String message) { if (message == null) return null; - else if (message.length() > MAX_MESSAGE_LENGTH) - return message.substring(0, MAX_MESSAGE_LENGTH); + else if (message.length() > maxMessageLength) + return message.substring(0, maxMessageLength); else return message; } diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java index 74c3059bae1..b5c52e03e66 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java @@ -10,15 +10,36 @@ */ public class MessageInterfaceBinding implements InterfaceBinding { /** - * Maximum length for a message. + * Default maximum length for a message. */ - public static final int MAX_MESSAGE_LENGTH = 1000; + public static final int DEFAULT_MAX_MESSAGE_LENGTH = 1000; private static final String MESSAGE_PARAMETER = "message"; private static final String PARAMS_PARAMETER = "params"; private static final String FORMATTED_PARAMETER = "formatted"; /** - * Trims a message, ensuring that the maximum length {@link #MAX_MESSAGE_LENGTH} isn't reached. + * Maximum length for a message. + */ + private final int maxMessageLength; + + /** + * Create instance of MessageInterfaceBinding with default message length. + */ + public MessageInterfaceBinding() { + maxMessageLength = DEFAULT_MAX_MESSAGE_LENGTH; + } + + /** + * Create instance of MessageInterfaceBinding with provided the maximum length of the messages. + * + * @param maxMessageLength the maximum message length + */ + public MessageInterfaceBinding(int maxMessageLength) { + this.maxMessageLength = maxMessageLength; + } + + /** + * Trims a message, ensuring that the maximum length {@link #maxMessageLength} isn't reached. * * @param message message to format. * @return trimmed message (shortened if necessary). @@ -26,8 +47,8 @@ public class MessageInterfaceBinding implements InterfaceBinding MAX_MESSAGE_LENGTH) - return message.substring(0, MAX_MESSAGE_LENGTH); + else if (message.length() > maxMessageLength) + return message.substring(0, maxMessageLength); else return message; } diff --git a/raven/src/main/java/com/getsentry/raven/util/Util.java b/raven/src/main/java/com/getsentry/raven/util/Util.java index fb95bb4482b..6e5ae2dcae8 100644 --- a/raven/src/main/java/com/getsentry/raven/util/Util.java +++ b/raven/src/main/java/com/getsentry/raven/util/Util.java @@ -65,4 +65,18 @@ public static Set parseExtraTags(String extraTagsString) { return new HashSet<>(Arrays.asList(extraTagsString.split(","))); } + /** + * Parses the provided string value into an integer value. + *

    If the string is null or empty this returns the default value.

    + * + * @param value value to parse + * @param defaultValue default value + * @return integer representation of provided value or default value. + */ + public static int parseInteger(String value, int defaultValue) { + if (isNullOrEmpty(value)) { + return defaultValue; + } + return Integer.parseInt(value); + } } From 9a5aba6a4fa15894e216fbd6e447327b568d7e71 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 30 Sep 2016 14:36:55 -0500 Subject: [PATCH 1458/2152] =?UTF-8?q?Add=20EventBuffer,=20BufferedConnecti?= =?UTF-8?q?on=20to=20temporarily=20buffer=20Events=20that=E2=80=A6=20(#252?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add EventBuffer, BufferedConnection to temporarily buffer Events that failed to be sent to the Sentry server. * Fix ordering. * Rough take on new Buffer logic. * Add some explaination. * Add more commentary. * Move early return. * Move/cleanup Flusher. * Comments, file suffix, cleanup. * Fix line lengths, add logging. * Complete? * Drop NoopBuffer. * Document. * Exception, not logs. * Add CHANGES --- CHANGES | 2 + README.md | 43 ++++ .../getsentry/raven/DefaultRavenFactory.java | 76 ++++-- .../com/getsentry/raven/buffer/Buffer.java | 34 +++ .../getsentry/raven/buffer/DiskBuffer.java | 200 ++++++++++++++++ .../raven/connection/AbstractConnection.java | 4 +- .../raven/connection/AsyncConnection.java | 6 +- .../raven/connection/BufferedConnection.java | 221 ++++++++++++++++++ .../raven/connection/Connection.java | 3 +- .../raven/connection/HttpConnection.java | 2 +- .../java/com/getsentry/raven/util/Util.java | 15 ++ .../java/com/getsentry/raven/BaseTest.java | 28 +++ .../getsentry/raven/buffer/BufferTest.java | 20 ++ .../raven/buffer/DiskBufferTest.java | 98 ++++++++ .../connection/AbstractConnectionTest.java | 25 +- .../connection/BufferedConnectionTest.java | 104 +++++++++ 16 files changed, 858 insertions(+), 23 deletions(-) create mode 100644 raven/src/main/java/com/getsentry/raven/buffer/Buffer.java create mode 100644 raven/src/main/java/com/getsentry/raven/buffer/DiskBuffer.java create mode 100644 raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java create mode 100644 raven/src/test/java/com/getsentry/raven/BaseTest.java create mode 100644 raven/src/test/java/com/getsentry/raven/buffer/BufferTest.java create mode 100644 raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java create mode 100644 raven/src/test/java/com/getsentry/raven/connection/BufferedConnectionTest.java diff --git a/CHANGES b/CHANGES index 4b570efbe30..939dc914453 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,8 @@ Version 7.7.1 ------------- - Add ``raven.maxmessagelength`` DSN option. (thanks vektory79) +- Add ``Buffer`` interface, used to store events that fail to be sent to the Sentry server. + Includes a DiskBuffer implementation and related ``raven.buffer.*`` options. Version 7.7.0 ------------- diff --git a/README.md b/README.md index a792cfbb676..ae9f69d49b9 100644 --- a/README.md +++ b/README.md @@ -217,6 +217,49 @@ with the option `raven.async.priority`: http://public:private@host:port/1?raven.async.priority=10 +### Buffering to disk upon network error +Raven can be configured to write events to a specified directory on disk +anytime communication with the Sentry server fails with the `raven.buffer.dir` +option. If the directory doesn't exist, Raven will attempt to create it +on startup and may therefore need write permission on the parent directory. +Raven always requires write permission on the buffer directory itself. + + http://public:private@host:port/1?raven.buffer.dir=raven-events + +The maximum number of events that will be stored on disk defaults to 50, +but can also be configured with the option `raven.buffer.size`: + + http://public:private@host:port/1?raven.buffer.size=100 + +If a buffer directory is provided, a background thread will periodically +attempt to re-send the events that are found on disk. By default it will +attempt to send events every 60 seconds. You can change this with the +`raven.buffer.flushtime` option (in milliseconds): + + http://public:private@host:port/1?raven.buffer.flushtime=10000 + +#### Graceful Shutdown (advanced) +In order to shutdown the buffer flushing thread gracefully, a `ShutdownHook` +is created. By default, the buffer flushing thread is given 1 second +to shutdown gracefully, but this can be adjusted via +`raven.buffer.shutdowntimeout` (represented in milliseconds): + + http://public:private@host:port/1?raven.buffer.shutdowntimeout=5000 + +The special value `-1` can be used to disable the timeout and wait +indefinitely for the executor to terminate. + +The `ShutdownHook` could lead to memory leaks in an environment where +the life cycle of Raven doesn't match the life cycle of the JVM. + +An example would be in a JEE environment where the application using Raven +could be deployed and undeployed regularly. + +To avoid this behaviour, it is possible to disable the graceful shutdown +by setting the `raven.buffer.gracefulshutdown` option: + + http://public:private@host:port/1?raven.buffer.gracefulshutdown=false + ### Inapp classes Sentry differentiate `in_app` stack frames (which are directly related to your application) and the "not `in_app`" ones. diff --git a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java index ed27436caa3..e6e39d6522b 100644 --- a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java @@ -1,5 +1,7 @@ package com.getsentry.raven; +import com.getsentry.raven.buffer.Buffer; +import com.getsentry.raven.buffer.DiskBuffer; import com.getsentry.raven.connection.*; import com.getsentry.raven.dsn.Dsn; import com.getsentry.raven.event.helper.ContextBuilderHelper; @@ -11,6 +13,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.URL; @@ -43,30 +46,58 @@ public class DefaultRavenFactory extends RavenFactory { * Option specific to raven-java, allowing to set a timeout (in ms) for a request to the Sentry server. */ public static final String TIMEOUT_OPTION = "raven.timeout"; + /** + * Option to buffer events to disk when network is down. + */ + public static final String BUFFER_DIR_OPTION = "raven.buffer.dir"; + /** + * Option for maximum number of events to cache offline when network is down. + */ + public static final String BUFFER_SIZE_OPTION = "raven.buffer.size"; + /** + * Default number of events to cache offline when network is down. + */ + public static final int BUFFER_SIZE_DEFAULT = 50; + /** + * Option for how long to wait between attempts to flush the disk buffer, in milliseconds. + */ + public static final String BUFFER_FLUSHTIME_OPTION = "raven.buffer.flushtime"; + /** + * Default number of milliseconds between attempts to flush buffered events. + */ + public static final int BUFFER_FLUSHTIME_DEFAULT = 60000; + /** + * Option to disable the graceful shutdown of the buffer flusher. + */ + public static final String BUFFER_GRACEFUL_SHUTDOWN_OPTION = "raven.buffer.gracefulshutdown"; + /** + * Option for the graceful shutdown timeout of the buffer flushing executor, in milliseconds. + */ + public static final String BUFFER_SHUTDOWN_TIMEOUT_OPTION = "raven.buffer.shutdowntimeout"; /** * Option to send events asynchronously. */ public static final String ASYNC_OPTION = "raven.async"; /** - * Option to disable the graceful shutdown. + * Option to disable the graceful shutdown of the async connection. */ - public static final String GRACEFUL_SHUTDOWN_OPTION = "raven.async.gracefulshutdown"; + public static final String ASYNC_GRACEFUL_SHUTDOWN_OPTION = "raven.async.gracefulshutdown"; /** * Option for the number of threads assigned for the connection. */ - public static final String MAX_THREADS_OPTION = "raven.async.threads"; + public static final String ASYNC_THREADS_OPTION = "raven.async.threads"; /** * Option for the priority of threads assigned for the connection. */ - public static final String PRIORITY_OPTION = "raven.async.priority"; + public static final String ASYNC_PRIORITY_OPTION = "raven.async.priority"; /** * Option for the maximum size of the queue. */ - public static final String QUEUE_SIZE_OPTION = "raven.async.queuesize"; + public static final String ASYNC_QUEUE_SIZE_OPTION = "raven.async.queuesize"; /** * Option for the graceful shutdown timeout of the async executor, in milliseconds. */ - public static final String SHUTDOWN_TIMEOUT_OPTION = "raven.async.shutdowntimeout"; + public static final String ASYNC_SHUTDOWN_TIMEOUT_OPTION = "raven.async.shutdowntimeout"; /** * Option to hide common stackframes with enclosing exceptions. */ @@ -131,6 +162,23 @@ protected Connection createConnection(Dsn dsn) { throw new IllegalStateException("Couldn't create a connection for the protocol '" + protocol + "'"); } + String bufferDir = dsn.getOptions().get(BUFFER_DIR_OPTION); + if (bufferDir != null) { + int bufferSize = Util.parseInteger(dsn.getOptions().get(BUFFER_SIZE_OPTION), BUFFER_SIZE_DEFAULT); + long flushtime = Util.parseLong(dsn.getOptions().get(BUFFER_FLUSHTIME_OPTION), BUFFER_FLUSHTIME_DEFAULT); + boolean gracefulShutdown = !FALSE.equalsIgnoreCase(dsn.getOptions().get(BUFFER_GRACEFUL_SHUTDOWN_OPTION)); + Buffer eventBuffer = new DiskBuffer(new File(bufferDir), bufferSize); + + String shutdownTimeoutStr = dsn.getOptions().get(BUFFER_SHUTDOWN_TIMEOUT_OPTION); + if (shutdownTimeoutStr != null) { + long shutdownTimeout = Long.parseLong(shutdownTimeoutStr); + connection = new BufferedConnection(connection, eventBuffer, flushtime, gracefulShutdown, + shutdownTimeout); + } else { + connection = new BufferedConnection(connection, eventBuffer, flushtime, gracefulShutdown); + } + } + // Enable async unless its value is 'false'. if (!FALSE.equalsIgnoreCase(dsn.getOptions().get(ASYNC_OPTION))) { connection = createAsyncConnection(dsn, connection); @@ -150,22 +198,22 @@ protected Connection createConnection(Dsn dsn) { protected Connection createAsyncConnection(Dsn dsn, Connection connection) { int maxThreads; - if (dsn.getOptions().containsKey(MAX_THREADS_OPTION)) { - maxThreads = Integer.parseInt(dsn.getOptions().get(MAX_THREADS_OPTION)); + if (dsn.getOptions().containsKey(ASYNC_THREADS_OPTION)) { + maxThreads = Integer.parseInt(dsn.getOptions().get(ASYNC_THREADS_OPTION)); } else { maxThreads = Runtime.getRuntime().availableProcessors(); } int priority; - if (dsn.getOptions().containsKey(PRIORITY_OPTION)) { - priority = Integer.parseInt(dsn.getOptions().get(PRIORITY_OPTION)); + if (dsn.getOptions().containsKey(ASYNC_PRIORITY_OPTION)) { + priority = Integer.parseInt(dsn.getOptions().get(ASYNC_PRIORITY_OPTION)); } else { priority = Thread.MIN_PRIORITY; } BlockingDeque queue; - if (dsn.getOptions().containsKey(QUEUE_SIZE_OPTION)) { - int queueSize = Integer.parseInt(dsn.getOptions().get(QUEUE_SIZE_OPTION)); + if (dsn.getOptions().containsKey(ASYNC_QUEUE_SIZE_OPTION)) { + int queueSize = Integer.parseInt(dsn.getOptions().get(ASYNC_QUEUE_SIZE_OPTION)); if (queueSize == -1) { queue = new LinkedBlockingDeque<>(); } else { @@ -179,9 +227,9 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { maxThreads, maxThreads, 0L, TimeUnit.MILLISECONDS, queue, new DaemonThreadFactory(priority), new ThreadPoolExecutor.DiscardOldestPolicy()); - boolean gracefulShutdown = !FALSE.equalsIgnoreCase(dsn.getOptions().get(GRACEFUL_SHUTDOWN_OPTION)); + boolean gracefulShutdown = !FALSE.equalsIgnoreCase(dsn.getOptions().get(ASYNC_GRACEFUL_SHUTDOWN_OPTION)); - String shutdownTimeoutStr = dsn.getOptions().get(SHUTDOWN_TIMEOUT_OPTION); + String shutdownTimeoutStr = dsn.getOptions().get(ASYNC_SHUTDOWN_TIMEOUT_OPTION); if (shutdownTimeoutStr != null) { long shutdownTimeout = Long.parseLong(shutdownTimeoutStr); return new AsyncConnection(connection, executorService, gracefulShutdown, shutdownTimeout); diff --git a/raven/src/main/java/com/getsentry/raven/buffer/Buffer.java b/raven/src/main/java/com/getsentry/raven/buffer/Buffer.java new file mode 100644 index 00000000000..ec0ec34153f --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/buffer/Buffer.java @@ -0,0 +1,34 @@ +package com.getsentry.raven.buffer; + +import com.getsentry.raven.event.Event; + +import java.util.Iterator; + +/** + * Buffer that is called by a {@link com.getsentry.raven.connection.BufferedConnection} when an {@link Event} send + * fails with a {@link com.getsentry.raven.connection.ConnectionException}. + */ +public interface Buffer { + /** + * Buffer the {@link Event} so that it can be flushed to the Sentry server at a later + * point in time. + * + * @param event Event object that should be buffered. + */ + void add(Event event); + + /** + * Discard and {@link Event} from the buffer. Note: the {@link Event} may or may not exist in + * the buffer. + * + * @param event Event to discard from the buffer. + */ + void discard(Event event); + + /** + * Returns an Iterator of {@link Event}s in the buffer. + * + * @return Iterator of Events in the buffer. + */ + Iterator getEvents(); +} diff --git a/raven/src/main/java/com/getsentry/raven/buffer/DiskBuffer.java b/raven/src/main/java/com/getsentry/raven/buffer/DiskBuffer.java new file mode 100644 index 00000000000..26edb6a8a8a --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/buffer/DiskBuffer.java @@ -0,0 +1,200 @@ +package com.getsentry.raven.buffer; + +import com.getsentry.raven.event.Event; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import java.util.Iterator; + +/** + * Stores {@link Event} objects to a directory on the filesystem and allows + * them to be flushed to Sentry (and deleted) at a later time. + */ +public class DiskBuffer implements Buffer { + + /** + * File suffix added to all serialized event files. + */ + public static final String FILE_SUFFIX = ".raven-event"; + + private static final Logger logger = LoggerFactory.getLogger(DiskBuffer.class); + + private int maxEvents; + private final File bufferDir; + + /** + * Construct an DiskBuffer which stores errors in the specified directory on disk. + * + * @param bufferDir File representing directory to store buffered Events in + * @param maxEvents The maximum number of events to store offline + */ + public DiskBuffer(File bufferDir, int maxEvents) { + super(); + + this.bufferDir = bufferDir; + this.maxEvents = maxEvents; + + String errMsg = "Could not create or write to disk buffer dir: " + bufferDir.getAbsolutePath(); + try { + bufferDir.mkdirs(); + if (!bufferDir.isDirectory() || !bufferDir.canWrite()) { + throw new RuntimeException(errMsg); + } + } catch (Exception e) { + throw new RuntimeException(errMsg, e); + } + + logger.debug(Integer.toString(getNumStoredEvents()) + + " stored events found in dir: " + + bufferDir.getAbsolutePath()); + } + + /** + * Store a single event to the add directory. Java serialization is used and each + * event is stored in a file named by its UUID. + * + * @param event Event to store in add directory + */ + @Override + public void add(Event event) { + if (getNumStoredEvents() >= maxEvents) { + logger.warn("Not adding Event because at least " + + Integer.toString(maxEvents) + " events are already stored: " + event.getId()); + return; + } + + File eventFile = new File(bufferDir.getAbsolutePath(), event.getId().toString() + FILE_SUFFIX); + logger.debug("Adding Event to offline storage: " + eventFile.getAbsolutePath()); + + try (FileOutputStream fileOutputStream = new FileOutputStream(eventFile); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) { + objectOutputStream.writeObject(event); + } catch (Exception e) { + logger.error("Error writing Event to offline storage: " + event.getId(), e); + } + + logger.debug(Integer.toString(getNumStoredEvents()) + + " stored events are now in dir: " + + bufferDir.getAbsolutePath()); + } + + /** + * Deletes a buffered {@link Event{ from disk. + * + * @param event Event to delete from the disk. + */ + @Override + public void discard(Event event) { + File eventFile = new File(bufferDir, event.getId().toString() + FILE_SUFFIX); + if (eventFile.exists()) { + logger.debug("Discarding Event from offline storage: " + eventFile.getAbsolutePath()); + eventFile.delete(); + } + } + + /** + * Attempts to open and deserialize a single {@link Event} from a {@link File}. + * + * @param eventFile File to deserialize into an Event + * @return Event from the File, or null + */ + private Event fileToEvent(File eventFile) { + FileInputStream fileInputStream; + Object eventObj; + + try { + fileInputStream = new FileInputStream(new File(eventFile.getAbsolutePath())); + ObjectInputStream ois = new ObjectInputStream(fileInputStream); + eventObj = ois.readObject(); + } catch (Exception e) { + logger.error("Error reading Event file: " + eventFile.getAbsolutePath(), e); + eventFile.delete(); + return null; + } + + try { + return (Event) eventObj; + } catch (Exception e) { + logger.error("Error casting Object to Event: " + eventFile.getAbsolutePath(), e); + eventFile.delete(); + return null; + } + } + + /** + * Returns the next *valid* {@link Event} found in an Iterator of Files. + * + * @param files Iterator of Files to deserialize + * @return The next Event found, or null if there are none + */ + private Event getNextEvent(Iterator files) { + while (files.hasNext()) { + File file = files.next(); + + // only consider files that end with FILE_SUFFIX + if (!file.getAbsolutePath().endsWith(FILE_SUFFIX)) { + continue; + } + + Event event = fileToEvent(file); + if (event != null) { + return event; + } + } + + return null; + } + + /** + * Returns an Iterator of Events that are stored on disk at the point in time this method + * is called. Note that files may not deserialize correctly, may be corrupted, + * or may be missing on disk by the time we attempt to open them - so some care is taken to + * only return valid {@link Event}s. + * + * If Events are written to disk after this Iterator is created they will not be returned + * by this Iterator. + * + * @return Iterator of Events on disk + */ + @Override + public Iterator getEvents() { + final Iterator files = Arrays.asList(bufferDir.listFiles()).iterator(); + + return new Iterator() { + private Event next = getNextEvent(files); + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public Event next() { + Event toReturn = next; + next = getNextEvent(files); + return toReturn; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + private int getNumStoredEvents() { + int count = 0; + for (File file : bufferDir.listFiles()) { + if (file.getAbsolutePath().endsWith(FILE_SUFFIX)) { + count += 1; + } + } + return count; + } +} diff --git a/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java b/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java index 90171fc574d..2268e7767c5 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java @@ -79,7 +79,7 @@ protected String getAuthHeader() { } @Override - public final void send(Event event) { + public final void send(Event event) throws ConnectionException { try { waitIfLockedDown(); @@ -97,6 +97,8 @@ public final void send(Event event) { + eventSendFailureCallback.getClass().getName(), exc); } } + + throw e; } } diff --git a/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java b/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java index f8dc670eb98..c32a358f6d2 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java @@ -46,7 +46,7 @@ public class AsyncConnection implements Connection { /** * Boolean used to check whether the connection is still open or not. */ - private boolean closed; + private volatile boolean closed; /** * Creates a connection which will rely on an executor to send events. @@ -121,8 +121,10 @@ public void addEventSendFailureCallback(EventSendFailureCallback eventSendFailur */ @Override public void close() throws IOException { - if (gracefulShutdown) + if (gracefulShutdown) { shutDownHook.enabled = false; + } + doClose(); } diff --git a/raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java b/raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java new file mode 100644 index 00000000000..2c1c17cc3e0 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java @@ -0,0 +1,221 @@ +package com.getsentry.raven.connection; + +import com.getsentry.raven.buffer.Buffer; +import com.getsentry.raven.environment.RavenEnvironment; +import com.getsentry.raven.event.Event; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +/** + * Connection wrapper that sends Events to an Buffer when send fails. + */ +public class BufferedConnection implements Connection { + + private static final Logger logger = LoggerFactory.getLogger(BufferedConnection.class); + + /** + * Default timeout of the {@link #executorService}, in milliseconds. + */ + private static final long DEFAULT_SHUTDOWN_TIMEOUT = TimeUnit.SECONDS.toMillis(1); + /** + * Shutdown hook used to stop the buffered connection properly when the JVM quits. + */ + private final BufferedConnection.ShutDownHook shutDownHook = new BufferedConnection.ShutDownHook(); + /** + * Flusher ExecutorService, created in a verbose way to use daemon threads + * so that it doesn't keep the JVM running after main() exits. + */ + private final ScheduledExecutorService executorService = + Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setDaemon(true); + return thread; + } + }); + /** + * Connection used to actually send the events. + */ + private Connection actualConnection; + /** + * Buffer used to store and retrieve events from. + */ + private Buffer buffer; + /** + * Boolean that represents if graceful shutdown is enabled. + */ + private boolean gracefulShutdown; + /** + * Timeout of the {@link #executorService}, in milliseconds. + */ + private long shutdownTimeout; + /** + * Boolean used to check whether the connection is still open or not. + */ + private volatile boolean closed = false; + + /** + * Construct a BufferedConnection that will store events that failed to send to the provided + * {@link Buffer} and attempt to flush them to the underlying connection later. + * + * @param actualConnection Connection to wrap. + * @param buffer Buffer to be used when {@link Connection#send(Event)}s fail. + * @param flushtime Time to wait between flush attempts, in milliseconds. + * @param gracefulShutdown Indicates whether or not the shutdown operation should be managed by a ShutdownHook. + * @param shutdownTimeout Timeout for graceful shutdown of the executor, in milliseconds. + */ + public BufferedConnection(Connection actualConnection, Buffer buffer, long flushtime, boolean gracefulShutdown, + long shutdownTimeout) { + + this.actualConnection = actualConnection; + this.buffer = buffer; + this.gracefulShutdown = gracefulShutdown; + this.shutdownTimeout = shutdownTimeout; + + if (gracefulShutdown) { + Runtime.getRuntime().addShutdownHook(shutDownHook); + } + + Flusher flusher = new BufferedConnection.Flusher(); + executorService.scheduleWithFixedDelay(flusher, flushtime, flushtime, TimeUnit.MILLISECONDS); + } + + /** + * Construct a BufferedConnection that will store events that failed to send to the provided + * {@link Buffer} and attempt to flush them to the underlying connection later. + * + * @param actualConnection Connection to wrap. + * @param buffer Buffer to be used when {@link Connection#send(Event)}s fail. + * @param flushtime Time to wait between flush attempts, in milliseconds. + * @param gracefulShutdown Indicates whether or not the shutdown operation should be managed by a ShutdownHook. + */ + public BufferedConnection(Connection actualConnection, Buffer buffer, long flushtime, boolean gracefulShutdown) { + this(actualConnection, buffer, flushtime, gracefulShutdown, DEFAULT_SHUTDOWN_TIMEOUT); + } + + @Override + public void send(Event event) { + try { + actualConnection.send(event); + // success: ensure the even isn't buffered + buffer.discard(event); + } catch (Exception e) { + // failure: buffer the event + buffer.add(event); + throw e; + } + } + + @Override + public void addEventSendFailureCallback(EventSendFailureCallback eventSendFailureCallback) { + actualConnection.addEventSendFailureCallback(eventSendFailureCallback); + } + + @Override + @SuppressWarnings("checkstyle:magicnumber") + public void close() throws IOException { + if (gracefulShutdown) { + shutDownHook.enabled = false; + } + + closed = true; + executorService.shutdown(); + try { + if (shutdownTimeout == -1L) { + // Block until the executor terminates, but log periodically. + long waitBetweenLoggingMs = 5000L; + while (true) { + if (executorService.awaitTermination(waitBetweenLoggingMs, TimeUnit.MILLISECONDS)) { + break; + } + logger.info("Still waiting on buffer flusher executor to terminate."); + } + } else if (!executorService.awaitTermination(shutdownTimeout, TimeUnit.MILLISECONDS)) { + logger.warn("Graceful shutdown took too much time, forcing the shutdown."); + List tasks = executorService.shutdownNow(); + logger.info("{} tasks failed to execute before the shutdown.", tasks.size()); + } + logger.info("Shutdown finished."); + } catch (InterruptedException e) { + logger.error("Graceful shutdown interrupted, forcing the shutdown."); + List tasks = executorService.shutdownNow(); + logger.info("{} tasks failed to execute before the shutdown.", tasks.size()); + } finally { + actualConnection.close(); + } + } + + /** + * Flusher is scheduled to periodically run by the BufferedConnection. It retrieves + * an Iterator of Events from the underlying {@link Buffer} and attempts to send + * one at time, discarding from the buffer on success and re-adding to the buffer + * on failure. + * + * Upon the first failure, Flusher will return and wait to be run again in the futre, + * under the assumption that the network is now/still down. + */ + private class Flusher implements Runnable { + @Override + public void run() { + logger.trace("Running Flusher"); + + RavenEnvironment.startManagingThread(); + try { + Iterator events = buffer.getEvents(); + while (events.hasNext() && !closed) { + Event event = events.next(); + + try { + logger.trace("Flusher attempting to send Event: " + event.getId()); + send(event); + logger.trace("Flusher successfully sent Event: " + event.getId()); + } catch (Exception e) { + logger.debug("Flusher failed to send Event: " + event.getId(), e); + + // Connectivity issues, give up until next Flusher run. + logger.trace("Flusher run exiting early."); + return; + } + } + logger.trace("Flusher run exiting, no more events to send."); + } finally { + RavenEnvironment.stopManagingThread(); + } + } + } + + private final class ShutDownHook extends Thread { + + /** + * Whether or not this ShutDownHook instance will do anything when run. + */ + private volatile boolean enabled = true; + + @Override + public void run() { + if (!enabled) { + return; + } + + RavenEnvironment.startManagingThread(); + try { + // The current thread is managed by raven + logger.info("Automatic shutdown of the buffered connection"); + BufferedConnection.this.close(); + } catch (Exception e) { + logger.error("An exception occurred while closing the connection.", e); + } finally { + RavenEnvironment.stopManagingThread(); + } + } + } +} diff --git a/raven/src/main/java/com/getsentry/raven/connection/Connection.java b/raven/src/main/java/com/getsentry/raven/connection/Connection.java index 7a8f97a765e..bd163159e13 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/Connection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/Connection.java @@ -12,8 +12,9 @@ public interface Connection extends Closeable { * Sends an event to the Sentry server. * * @param event captured event to add in Sentry. + * @throws ConnectionException Thrown when an Event send fails. */ - void send(Event event); + void send(Event event) throws ConnectionException; /** * Add a callback that is called when an exception occurs while attempting to diff --git a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java b/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java index b4356fd5cea..13d9bc24bcb 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java @@ -138,7 +138,7 @@ protected HttpURLConnection getConnection() { } @Override - protected void doSend(Event event) { + protected void doSend(Event event) throws ConnectionException { HttpURLConnection connection = getConnection(); try { connection.connect(); diff --git a/raven/src/main/java/com/getsentry/raven/util/Util.java b/raven/src/main/java/com/getsentry/raven/util/Util.java index 6e5ae2dcae8..1351fa9a3ef 100644 --- a/raven/src/main/java/com/getsentry/raven/util/Util.java +++ b/raven/src/main/java/com/getsentry/raven/util/Util.java @@ -79,4 +79,19 @@ public static int parseInteger(String value, int defaultValue) { } return Integer.parseInt(value); } + + /** + * Parses the provided string value into a long value. + *

    If the string is null or empty this returns the default value.

    + * + * @param value value to parse + * @param defaultValue default value + * @return long representation of provided value or default value. + */ + public static long parseLong(String value, long defaultValue) { + if (isNullOrEmpty(value)) { + return defaultValue; + } + return Long.parseLong(value); + } } diff --git a/raven/src/test/java/com/getsentry/raven/BaseTest.java b/raven/src/test/java/com/getsentry/raven/BaseTest.java new file mode 100644 index 00000000000..2816bb1d7d8 --- /dev/null +++ b/raven/src/test/java/com/getsentry/raven/BaseTest.java @@ -0,0 +1,28 @@ +package com.getsentry.raven; + +import java.util.concurrent.Callable; + +public class BaseTest { + /** + * To avoid littering tests with static Thread.sleep calls (because Android code must do async I/O), + * we use this method to repeatedly test a predicate with a maximum wait time, returning as + * soon as we see that it's true so that tests can move along promptly. + * + * @param maxWaitMs maximum time to wait in milliseconds + * @param predicate Callable that returns a boolean + * @throws Exception thrown is maximum time is used up, or Callable throws its own exception + */ + public void waitUntilTrue(int maxWaitMs, Callable predicate) throws Exception { + long until = System.currentTimeMillis() + maxWaitMs; + // pause one one-hundredths of the max delay between predicate checks + int pauseMs = maxWaitMs / 100; + while (System.currentTimeMillis() < until) { + boolean response = predicate.call(); + if (response) { + return; + } + Thread.sleep(pauseMs); + } + throw new RuntimeException("Waited too long for predicate to come true."); + } +} diff --git a/raven/src/test/java/com/getsentry/raven/buffer/BufferTest.java b/raven/src/test/java/com/getsentry/raven/buffer/BufferTest.java new file mode 100644 index 00000000000..316b3f7d0d4 --- /dev/null +++ b/raven/src/test/java/com/getsentry/raven/buffer/BufferTest.java @@ -0,0 +1,20 @@ +package com.getsentry.raven.buffer; + +import java.io.File; + +public class BufferTest { + protected void delete(File dir) { + if (!dir.exists()) { + return; + } + + if (dir.isDirectory()) { + for (File c : dir.listFiles()) { + delete(c); + } + } + if (!dir.delete()) { + throw new RuntimeException("Failed to delete dir: " + dir); + } + } +} diff --git a/raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java b/raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java new file mode 100644 index 00000000000..2d79369a5df --- /dev/null +++ b/raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java @@ -0,0 +1,98 @@ +package com.getsentry.raven.buffer; + +import com.getsentry.raven.event.Event; +import com.getsentry.raven.event.EventBuilder; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.IOException; +import java.util.Iterator; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +public class DiskBufferTest extends BufferTest { + + private static File BUFFER_DIR = new File("./raven-test-buffer-dir"); + private int maxEvents = 2; + + private DiskBuffer buffer; + + @BeforeMethod + public void setup() { + buffer = new DiskBuffer(BUFFER_DIR, maxEvents); + } + + @AfterMethod + public void teardown() { + delete(BUFFER_DIR); + } + + @Test + public void testAddAndDiscard() throws IOException { + Event event1 = new EventBuilder().build(); + buffer.add(event1); + // 1 event is buffered + assertThat(eventCount(buffer.getEvents()), equalTo(1)); + + Event event2 = new EventBuilder().build(); + buffer.add(event2); + // 2 events are buffered + assertThat(eventCount(buffer.getEvents()), equalTo(2)); + + Event event3 = new EventBuilder().build(); + buffer.add(event3); + // still 2 events, because we hit maxEvents (2) + assertThat(eventCount(buffer.getEvents()), equalTo(2)); + + buffer.discard(event1); + assertThat(eventCount(buffer.getEvents()), equalTo(1)); + + buffer.discard(event2); + assertThat(eventCount(buffer.getEvents()), equalTo(0)); + + // noop, because event3 isn't in the buffer + buffer.discard(event3); + assertThat(eventCount(buffer.getEvents()), equalTo(0)); + } + + @Test + public void testNonEvent() throws IOException { + // create a file in the buffer dir that dosn't match the event pattern + File nonEventFile = new File(BUFFER_DIR, "not-an-event"); + assertThat(nonEventFile.createNewFile(), equalTo(true)); + + // still 2 events, because only event files are returned + assertThat(eventCount(buffer.getEvents()), equalTo(0)); + + // but the non-event file is definitely still there + assertThat(BUFFER_DIR.listFiles().length, equalTo(1)); + } + + @Test + public void testCorruptEventFile() throws IOException { + // create a file that can't be deserialized for whatever reason + File corruptEventFile = new File(BUFFER_DIR, "corrupt-event" + DiskBuffer.FILE_SUFFIX); + assertThat(corruptEventFile.createNewFile(), equalTo(true)); + + // the file is there + assertThat(BUFFER_DIR.listFiles().length, equalTo(1)); + + // the file isn't returned because it is invalid + assertThat(eventCount(buffer.getEvents()), equalTo(0)); + + // the file has been deleted so we don't retry it forevr + assertThat(BUFFER_DIR.listFiles().length, equalTo(0)); + } + + private int eventCount(Iterator events) { + int count = 0; + while (events.hasNext()) { + events.next(); + count += 1; + } + return count; + } +} diff --git a/raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java b/raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java index 741b2b1df42..0243a702b18 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java +++ b/raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java @@ -62,7 +62,11 @@ public void testExceptionOnSendStartLockDown(@Injectable final Event mockEvent) result = new ConnectionException(); }}; - abstractConnection.send(mockEvent); + try { + abstractConnection.send(mockEvent); + } catch (Exception e) { + // ignore + } new Verifications() {{ mockLock.lock(); @@ -78,7 +82,11 @@ public void testLockDownDoublesTheWaitingTime(@Injectable final Event mockEvent) result = new ConnectionException(); }}; - abstractConnection.send(mockEvent); + try { + abstractConnection.send(mockEvent); + } catch (Exception e) { + // ignore + } long waitingTimeAfter = getField(abstractConnection, "waitingTime"); assertThat(waitingTimeAfter, is(AbstractConnection.DEFAULT_BASE_WAITING_TIME * 2)); @@ -95,7 +103,11 @@ public void testLockDownDoesntDoubleItAtMax(@Injectable final Event mockEvent) t result = new ConnectionException(); }}; - abstractConnection.send(mockEvent); + try { + abstractConnection.send(mockEvent); + } catch (Exception e) { + // ignore + } long waitingTimeAfter = getField(abstractConnection, "waitingTime"); assertThat(waitingTimeAfter, is(AbstractConnection.DEFAULT_MAX_WAITING_TIME)); @@ -124,7 +136,12 @@ public void onFailure(Event event, Exception exception) { result = new ConnectionException(); }}; - abstractConnection.send(mockEvent); + try { + abstractConnection.send(mockEvent); + } catch (Exception e) { + // ignore + } + assertThat(callbackCalled.get(), is(true)); } diff --git a/raven/src/test/java/com/getsentry/raven/connection/BufferedConnectionTest.java b/raven/src/test/java/com/getsentry/raven/connection/BufferedConnectionTest.java new file mode 100644 index 00000000000..6e6a4b5ab96 --- /dev/null +++ b/raven/src/test/java/com/getsentry/raven/connection/BufferedConnectionTest.java @@ -0,0 +1,104 @@ +package com.getsentry.raven.connection; + +import com.getsentry.raven.BaseTest; +import com.getsentry.raven.buffer.Buffer; +import com.getsentry.raven.event.Event; +import com.getsentry.raven.event.EventBuilder; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.testng.collections.Lists; + +import java.io.IOException; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Callable; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +public class BufferedConnectionTest extends BaseTest { + private List bufferedEvents; + private List sentEvents; + private Buffer mockBuffer; + private Connection mockConnection; + private BufferedConnection bufferedConnection; + private volatile boolean connectionUp; + + @BeforeMethod + public void setup() { + bufferedEvents = Lists.newArrayList(); + sentEvents = Lists.newArrayList(); + connectionUp = true; + + mockConnection = new Connection() { + @Override + public void send(Event event) throws ConnectionException { + if (connectionUp) { + sentEvents.add(event); + } else { + throw new ConnectionException("Connection is down."); + } + } + + @Override + public void addEventSendFailureCallback(EventSendFailureCallback eventSendFailureCallback) { + + } + + @Override + public void close() throws IOException { + + } + }; + + mockBuffer = new Buffer() { + @Override + public void add(Event event) { + bufferedEvents.add(event); + } + + @Override + public void discard(Event event) { + bufferedEvents.remove(event); + } + + @Override + public Iterator getEvents() { + return Lists.newArrayList(bufferedEvents).iterator(); + } + }; + + int flushtime = 10; + int shutdownTimeout = 0; + bufferedConnection = new BufferedConnection(mockConnection, mockBuffer, flushtime, false, shutdownTimeout); + } + + @AfterMethod + public void teardown() throws IOException { + bufferedConnection.close(); + } + + @Test + public void test() throws Exception { + Event event = new EventBuilder().build(); + connectionUp = false; + try { + bufferedConnection.send(event); + } catch (Exception e) { + + } + assertThat(bufferedEvents.size(), equalTo(1)); + assertThat(bufferedEvents.get(0), equalTo(event)); + + connectionUp = true; + waitUntilTrue(1000, new Callable() { + @Override + public Boolean call() throws Exception { + return bufferedEvents.size() == 0; + } + }); + assertThat(bufferedEvents.size(), equalTo(0)); + assertThat(sentEvents.get(0), equalTo(event)); + } +} From ae4dd804557a4c5a1b31750bd651177b8f9653c2 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 3 Oct 2016 18:21:37 -0500 Subject: [PATCH 1459/2152] Store last event sent in RavenContext. (#255) --- CHANGES | 2 ++ .../main/java/com/getsentry/raven/Raven.java | 10 +++++++- .../com/getsentry/raven/RavenContext.java | 25 +++++++++++++++++++ .../servlet/RavenServletRequestListener.java | 15 ++++++++++- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 939dc914453..c7c09a90bd3 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,8 @@ Version 7.7.1 - Add ``raven.maxmessagelength`` DSN option. (thanks vektory79) - Add ``Buffer`` interface, used to store events that fail to be sent to the Sentry server. Includes a DiskBuffer implementation and related ``raven.buffer.*`` options. +- Add a way to retrieve the thread's last sent Event ID, if any. Useful for integrating + with the user feedback feature. Version 7.7.0 ------------- diff --git a/raven/src/main/java/com/getsentry/raven/Raven.java b/raven/src/main/java/com/getsentry/raven/Raven.java index d2ef66e9bfb..3ff9fc7769a 100644 --- a/raven/src/main/java/com/getsentry/raven/Raven.java +++ b/raven/src/main/java/com/getsentry/raven/Raven.java @@ -95,6 +95,8 @@ public void sendEvent(Event event) { connection.send(event); } catch (Exception e) { logger.error("An exception occurred while sending the event to Sentry.", e); + } finally { + context.get().setLastEventId(event.getId()); } } @@ -195,7 +197,13 @@ public String toString() { // Static helper methods follow // -------------------------------------------------------- - private static Raven getStoredInstance() { + /** + * Returns the last statically stored Raven instance or throws a {@link NullPointerException} + * if one hasn't been constructed and stored yet. + * + * @return statically stored {@link Raven} instance + */ + public static Raven getStoredInstance() { if (stored == null) { throw new NullPointerException("No stored Raven instance is available to use." + " You must construct a Raven instance before using the static Raven methods."); diff --git a/raven/src/main/java/com/getsentry/raven/RavenContext.java b/raven/src/main/java/com/getsentry/raven/RavenContext.java index 2848d547e99..7438984a887 100644 --- a/raven/src/main/java/com/getsentry/raven/RavenContext.java +++ b/raven/src/main/java/com/getsentry/raven/RavenContext.java @@ -8,6 +8,7 @@ import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; +import java.util.UUID; /** * RavenContext is used to hold {@link ThreadLocal} context data (such as @@ -42,6 +43,8 @@ protected IdentityHashMap initialValue() { */ private static final int DEFAULT_BREADCRUMB_LIMIT = 100; + private UUID lastEventId; + /** * Ring buffer of {@link Breadcrumb} objects. */ @@ -82,6 +85,7 @@ public void deactivate() { */ public void clear() { breadcrumbs.clear(); + lastEventId = null; } /** @@ -122,4 +126,25 @@ public void recordBreadcrumb(Breadcrumb breadcrumb) { breadcrumbs.add(breadcrumb); } + /** + * Store the UUID of the last sent event by this thread, useful for handling user feedback. + * + * @param id UUID of the last event sent by this thread. + */ + public void setLastEventId(UUID id) { + lastEventId = id; + } + + /** + * Get the UUID of the last event sent by this thread, useful for handling user feedback. + * + * Returns null if no event has been sent by this thread or if the event has been + * cleared. For example the RavenServletRequestListener clears the thread's RavenContext + * at the end of each request. + * + * @return UUID of the last event sent by this thread. + */ + public UUID getLastEventId() { + return lastEventId; + } } diff --git a/raven/src/main/java/com/getsentry/raven/servlet/RavenServletRequestListener.java b/raven/src/main/java/com/getsentry/raven/servlet/RavenServletRequestListener.java index 1ec6536f2e3..1922f9fb92b 100644 --- a/raven/src/main/java/com/getsentry/raven/servlet/RavenServletRequestListener.java +++ b/raven/src/main/java/com/getsentry/raven/servlet/RavenServletRequestListener.java @@ -1,5 +1,9 @@ package com.getsentry.raven.servlet; +import com.getsentry.raven.Raven; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import javax.servlet.ServletRequest; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; @@ -11,6 +15,8 @@ * in the event sent to Sentry. */ public class RavenServletRequestListener implements ServletRequestListener { + private static final Logger logger = LoggerFactory.getLogger(RavenServletRequestListener.class); + private static final ThreadLocal THREAD_REQUEST = new ThreadLocal<>(); public static HttpServletRequest getServletRequest() { @@ -20,12 +26,19 @@ public static HttpServletRequest getServletRequest() { @Override public void requestDestroyed(ServletRequestEvent servletRequestEvent) { THREAD_REQUEST.remove(); + + try { + Raven.getStoredInstance().getContext().clear(); + } catch (Exception e) { + logger.error("Error clearing RavenContext state.", e); + } } @Override public void requestInitialized(ServletRequestEvent servletRequestEvent) { ServletRequest servletRequest = servletRequestEvent.getServletRequest(); - if (servletRequest instanceof HttpServletRequest) + if (servletRequest instanceof HttpServletRequest) { THREAD_REQUEST.set((HttpServletRequest) servletRequest); + } } } From 41823159577cdfb41394bc02b7bf8762ebe2edca Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 7 Oct 2016 15:56:02 -0500 Subject: [PATCH 1460/2152] =?UTF-8?q?Broke=20out=20many=20helper=20methods?= =?UTF-8?q?=20inside=20of=20``DefaultRavenFactory``=20to=20al=E2=80=A6=20(?= =?UTF-8?q?#256)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES | 2 + .../getsentry/raven/DefaultRavenFactory.java | 313 +++++++++++++----- .../raven/connection/AsyncConnection.java | 18 - .../raven/connection/BufferedConnection.java | 17 - .../raven/connection/HttpConnection.java | 3 +- .../raven/connection/AsyncConnectionTest.java | 10 +- .../raven/connection/HttpConnectionTest.java | 2 + 7 files changed, 251 insertions(+), 114 deletions(-) diff --git a/CHANGES b/CHANGES index c7c09a90bd3..dbf29e9dc8e 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,8 @@ Version 7.7.1 - Add ``raven.maxmessagelength`` DSN option. (thanks vektory79) - Add ``Buffer`` interface, used to store events that fail to be sent to the Sentry server. Includes a DiskBuffer implementation and related ``raven.buffer.*`` options. +- Broke out many helper methods inside of ``DefaultRavenFactory`` to allow for easier + overrides. - Add a way to retrieve the thread's last sent Event ID, if any. Useful for integrating with the user feedback feature. diff --git a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java index e6e39d6522b..bbd973d48c8 100644 --- a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java @@ -34,18 +34,22 @@ public class DefaultRavenFactory extends RavenFactory { */ public static final String NAIVE_PROTOCOL = "naive"; /** - * Option specific to raven-java, allowing to enable/disable the compression of requests to the Sentry Server. + * Option for whether to compress requests sent to the Sentry Server. */ public static final String COMPRESSION_OPTION = "raven.compression"; /** - * Option specific to raven-java, allowing to set maximum length of the message body in the requests to the + * Option to set the maximum length of the message body in the requests to the * Sentry Server. */ public static final String MAX_MESSAGE_LENGTH_OPTION = "raven.maxmessagelength"; /** - * Option specific to raven-java, allowing to set a timeout (in ms) for a request to the Sentry server. + * Option to set a timeout for requests to the Sentry server, in milliseconds. */ public static final String TIMEOUT_OPTION = "raven.timeout"; + /** + * Default timeout of an HTTP connection to Sentry. + */ + public static final int TIMEOUT_DEFAULT = (int) TimeUnit.SECONDS.toMillis(1); /** * Option to buffer events to disk when network is down. */ @@ -75,7 +79,11 @@ public class DefaultRavenFactory extends RavenFactory { */ public static final String BUFFER_SHUTDOWN_TIMEOUT_OPTION = "raven.buffer.shutdowntimeout"; /** - * Option to send events asynchronously. + * Default timeout of the {@link BufferedConnection} shutdown, in milliseconds. + */ + public static final long BUFFER_SHUTDOWN_TIMEOUT_DEFAULT = TimeUnit.SECONDS.toMillis(1); + /** + * Option for whether to send events asynchronously. */ public static final String ASYNC_OPTION = "raven.async"; /** @@ -83,15 +91,15 @@ public class DefaultRavenFactory extends RavenFactory { */ public static final String ASYNC_GRACEFUL_SHUTDOWN_OPTION = "raven.async.gracefulshutdown"; /** - * Option for the number of threads assigned for the connection. + * Option for the number of threads used for the async connection. */ public static final String ASYNC_THREADS_OPTION = "raven.async.threads"; /** - * Option for the priority of threads assigned for the connection. + * Option for the priority of threads used for the async connection. */ public static final String ASYNC_PRIORITY_OPTION = "raven.async.priority"; /** - * Option for the maximum size of the queue. + * Option for the maximum size of the async send queue. */ public static final String ASYNC_QUEUE_SIZE_OPTION = "raven.async.queuesize"; /** @@ -99,7 +107,11 @@ public class DefaultRavenFactory extends RavenFactory { */ public static final String ASYNC_SHUTDOWN_TIMEOUT_OPTION = "raven.async.shutdowntimeout"; /** - * Option to hide common stackframes with enclosing exceptions. + * Default timeout of the {@link AsyncConnection} executor, in milliseconds. + */ + public static final long ASYNC_SHUTDOWN_TIMEOUT_DEFAULT = TimeUnit.SECONDS.toMillis(1); + /** + * Option for whether to hide common stackframes with enclosing exceptions. */ public static final String HIDE_COMMON_FRAMES_OPTION = "raven.stacktrace.hidecommon"; /** @@ -162,25 +174,17 @@ protected Connection createConnection(Dsn dsn) { throw new IllegalStateException("Couldn't create a connection for the protocol '" + protocol + "'"); } - String bufferDir = dsn.getOptions().get(BUFFER_DIR_OPTION); - if (bufferDir != null) { - int bufferSize = Util.parseInteger(dsn.getOptions().get(BUFFER_SIZE_OPTION), BUFFER_SIZE_DEFAULT); - long flushtime = Util.parseLong(dsn.getOptions().get(BUFFER_FLUSHTIME_OPTION), BUFFER_FLUSHTIME_DEFAULT); - boolean gracefulShutdown = !FALSE.equalsIgnoreCase(dsn.getOptions().get(BUFFER_GRACEFUL_SHUTDOWN_OPTION)); - Buffer eventBuffer = new DiskBuffer(new File(bufferDir), bufferSize); - - String shutdownTimeoutStr = dsn.getOptions().get(BUFFER_SHUTDOWN_TIMEOUT_OPTION); - if (shutdownTimeoutStr != null) { - long shutdownTimeout = Long.parseLong(shutdownTimeoutStr); - connection = new BufferedConnection(connection, eventBuffer, flushtime, gracefulShutdown, - shutdownTimeout); - } else { - connection = new BufferedConnection(connection, eventBuffer, flushtime, gracefulShutdown); - } + Buffer eventBuffer = getBuffer(dsn); + if (eventBuffer != null) { + long flushtime = getBufferFlushtime(dsn); + boolean gracefulShutdown = getBufferedConnectionGracefulShutdownEnabled(dsn); + Long shutdownTimeout = getBufferedConnectionShutdownTimeout(dsn); + connection = new BufferedConnection(connection, eventBuffer, flushtime, gracefulShutdown, + shutdownTimeout); } // Enable async unless its value is 'false'. - if (!FALSE.equalsIgnoreCase(dsn.getOptions().get(ASYNC_OPTION))) { + if (getAsyncEnabled(dsn)) { connection = createAsyncConnection(dsn, connection); } @@ -197,45 +201,25 @@ protected Connection createConnection(Dsn dsn) { */ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { - int maxThreads; - if (dsn.getOptions().containsKey(ASYNC_THREADS_OPTION)) { - maxThreads = Integer.parseInt(dsn.getOptions().get(ASYNC_THREADS_OPTION)); - } else { - maxThreads = Runtime.getRuntime().availableProcessors(); - } - - int priority; - if (dsn.getOptions().containsKey(ASYNC_PRIORITY_OPTION)) { - priority = Integer.parseInt(dsn.getOptions().get(ASYNC_PRIORITY_OPTION)); - } else { - priority = Thread.MIN_PRIORITY; - } + int maxThreads = getAsyncThreads(dsn); + int priority = getAsyncPriority(dsn); BlockingDeque queue; - if (dsn.getOptions().containsKey(ASYNC_QUEUE_SIZE_OPTION)) { - int queueSize = Integer.parseInt(dsn.getOptions().get(ASYNC_QUEUE_SIZE_OPTION)); - if (queueSize == -1) { - queue = new LinkedBlockingDeque<>(); - } else { - queue = new LinkedBlockingDeque<>(queueSize); - } + int queueSize = getAsyncQueueSize(dsn); + if (queueSize == -1) { + queue = new LinkedBlockingDeque<>(); } else { - queue = new LinkedBlockingDeque<>(QUEUE_SIZE_DEFAULT); + queue = new LinkedBlockingDeque<>(queueSize); } ExecutorService executorService = new ThreadPoolExecutor( maxThreads, maxThreads, 0L, TimeUnit.MILLISECONDS, queue, new DaemonThreadFactory(priority), new ThreadPoolExecutor.DiscardOldestPolicy()); - boolean gracefulShutdown = !FALSE.equalsIgnoreCase(dsn.getOptions().get(ASYNC_GRACEFUL_SHUTDOWN_OPTION)); + boolean gracefulShutdown = getAsyncGracefulShutdownEnabled(dsn); - String shutdownTimeoutStr = dsn.getOptions().get(ASYNC_SHUTDOWN_TIMEOUT_OPTION); - if (shutdownTimeoutStr != null) { - long shutdownTimeout = Long.parseLong(shutdownTimeoutStr); - return new AsyncConnection(connection, executorService, gracefulShutdown, shutdownTimeout); - } else { - return new AsyncConnection(connection, executorService, gracefulShutdown); - } + long shutdownTimeout = getAsyncShutdownTimeout(dsn); + return new AsyncConnection(connection, executorService, gracefulShutdown, shutdownTimeout); } /** @@ -247,15 +231,8 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { protected Connection createHttpConnection(Dsn dsn) { URL sentryApiUrl = HttpConnection.getSentryApiUrl(dsn.getUri(), dsn.getProjectId()); - String proxyHost = null; - if (dsn.getOptions().containsKey(HTTP_PROXY_HOST_OPTION)) { - proxyHost = dsn.getOptions().get(HTTP_PROXY_HOST_OPTION); - } - - int proxyPort = HTTP_PROXY_PORT_DEFAULT; - if (dsn.getOptions().containsKey(HTTP_PROXY_PORT_OPTION)) { - proxyPort = Integer.parseInt(dsn.getOptions().get(HTTP_PROXY_PORT_OPTION)); - } + String proxyHost = getProxyHost(dsn); + int proxyPort = getProxyPort(dsn); Proxy proxy = null; if (proxyHost != null) { @@ -263,14 +240,18 @@ protected Connection createHttpConnection(Dsn dsn) { proxy = new Proxy(Proxy.Type.HTTP, proxyAddr); } - HttpConnection httpConnection = new HttpConnection(sentryApiUrl, dsn.getPublicKey(), dsn.getSecretKey(), proxy); - httpConnection.setMarshaller(createMarshaller(dsn)); + HttpConnection httpConnection = new HttpConnection(sentryApiUrl, dsn.getPublicKey(), + dsn.getSecretKey(), proxy); + + Marshaller marshaller = createMarshaller(dsn); + httpConnection.setMarshaller(marshaller); + + int timeout = getTimeout(dsn); + httpConnection.setTimeout(timeout); + + boolean bypassSecurityEnabled = getBypassSecurityEnabled(dsn); + httpConnection.setBypassSecurity(bypassSecurityEnabled); - // Set the naive mode - httpConnection.setBypassSecurity(dsn.getProtocolSettings().contains(NAIVE_PROTOCOL)); - // Set the HTTP timeout - if (dsn.getOptions().containsKey(TIMEOUT_OPTION)) - httpConnection.setTimeout(Integer.parseInt(dsn.getOptions().get(TIMEOUT_OPTION))); return httpConnection; } @@ -296,15 +277,13 @@ protected Connection createStdOutConnection(Dsn dsn) { * @return a {@link JsonMarshaller} to process the events. */ protected Marshaller createMarshaller(Dsn dsn) { - int maxMessageLength = Util.parseInteger( - dsn.getOptions().get(MAX_MESSAGE_LENGTH_OPTION), JsonMarshaller.DEFAULT_MAX_MESSAGE_LENGTH); + int maxMessageLength = getMaxMessageLength(dsn); JsonMarshaller marshaller = new JsonMarshaller(maxMessageLength); // Set JSON marshaller bindings StackTraceInterfaceBinding stackTraceBinding = new StackTraceInterfaceBinding(); // Enable common frames hiding unless its value is 'false'. - stackTraceBinding.setRemoveCommonFramesWithEnclosing( - !FALSE.equalsIgnoreCase(dsn.getOptions().get(HIDE_COMMON_FRAMES_OPTION))); + stackTraceBinding.setRemoveCommonFramesWithEnclosing(getHideCommonFramesEnabled(dsn)); stackTraceBinding.setNotInAppFrames(getNotInAppFrames()); marshaller.addInterfaceBinding(StackTraceInterface.class, stackTraceBinding); @@ -317,7 +296,7 @@ protected Marshaller createMarshaller(Dsn dsn) { marshaller.addInterfaceBinding(HttpInterface.class, httpBinding); // Enable compression unless the option is set to false - marshaller.setCompression(!FALSE.equalsIgnoreCase(dsn.getOptions().get(COMPRESSION_OPTION))); + marshaller.setCompression(getCompressionEnabled(dsn)); return marshaller; } @@ -340,6 +319,192 @@ protected Collection getNotInAppFrames() { "com.intellij.rt."); } + /** + * Whether or not to wrap the underlying connection in an {@link AsyncConnection}. + * + * @param dsn Sentry server DSN which may contain options. + * @return Whether or not to wrap the underlying connection in an {@link AsyncConnection}. + */ + protected boolean getAsyncEnabled(Dsn dsn) { + return !FALSE.equalsIgnoreCase(dsn.getOptions().get(ASYNC_OPTION)); + } + + /** + * Maximum time to wait for {@link BufferedConnection} shutdown when closed, in milliseconds. + * + * @param dsn Sentry server DSN which may contain options. + * @return Maximum time to wait for {@link BufferedConnection} shutdown when closed, in milliseconds. + */ + protected long getBufferedConnectionShutdownTimeout(Dsn dsn) { + return Util.parseLong(dsn.getOptions().get(BUFFER_SHUTDOWN_TIMEOUT_OPTION), BUFFER_SHUTDOWN_TIMEOUT_DEFAULT); + } + + /** + * Whether or not to attempt a graceful shutdown of the {@link BufferedConnection} upon close. + * + * @param dsn Sentry server DSN which may contain options. + * @return Whether or not to attempt a graceful shutdown of the {@link BufferedConnection} upon close. + */ + protected boolean getBufferedConnectionGracefulShutdownEnabled(Dsn dsn) { + return !FALSE.equalsIgnoreCase(dsn.getOptions().get(BUFFER_GRACEFUL_SHUTDOWN_OPTION)); + } + + /** + * How long to wait between attempts to flush the disk buffer, in milliseconds. + * + * @param dsn Sentry server DSN which may contain options. + * @return ow long to wait between attempts to flush the disk buffer, in milliseconds. + */ + protected long getBufferFlushtime(Dsn dsn) { + return Util.parseLong(dsn.getOptions().get(BUFFER_FLUSHTIME_OPTION), BUFFER_FLUSHTIME_DEFAULT); + } + + /** + * The graceful shutdown timeout of the async executor, in milliseconds. + * + * @param dsn Sentry server DSN which may contain options. + * @return The graceful shutdown timeout of the async executor, in milliseconds. + */ + protected long getAsyncShutdownTimeout(Dsn dsn) { + return Util.parseLong(dsn.getOptions().get(ASYNC_SHUTDOWN_TIMEOUT_OPTION), ASYNC_SHUTDOWN_TIMEOUT_DEFAULT); + } + + /** + * Whether or not to attempt the graceful shutdown of the {@link AsyncConnection} upon close. + * + * @param dsn Sentry server DSN which may contain options. + * @return Whether or not to attempt the graceful shutdown of the {@link AsyncConnection} upon close. + */ + protected boolean getAsyncGracefulShutdownEnabled(Dsn dsn) { + return !FALSE.equalsIgnoreCase(dsn.getOptions().get(ASYNC_GRACEFUL_SHUTDOWN_OPTION)); + } + + /** + * Maximum size of the async send queue. + * + * @param dsn Sentry server DSN which may contain options. + * @return Maximum size of the async send queue. + */ + protected int getAsyncQueueSize(Dsn dsn) { + return Util.parseInteger(dsn.getOptions().get(ASYNC_QUEUE_SIZE_OPTION), QUEUE_SIZE_DEFAULT); + } + + /** + * Priority of threads used for the async connection. + * + * @param dsn Sentry server DSN which may contain options. + * @return Priority of threads used for the async connection. + */ + protected int getAsyncPriority(Dsn dsn) { + return Util.parseInteger(dsn.getOptions().get(ASYNC_PRIORITY_OPTION), Thread.MIN_PRIORITY); + } + + /** + * The number of threads used for the async connection. + * + * @param dsn Sentry server DSN which may contain options. + * @return The number of threads used for the async connection. + */ + protected int getAsyncThreads(Dsn dsn) { + return Util.parseInteger(dsn.getOptions().get(ASYNC_THREADS_OPTION), + Runtime.getRuntime().availableProcessors()); + } + + /** + * Whether to disable security checks over an SSL connection. + * + * @param dsn Sentry server DSN which may contain options. + * @return Whether to disable security checks over an SSL connection. + */ + protected boolean getBypassSecurityEnabled(Dsn dsn) { + return dsn.getProtocolSettings().contains(NAIVE_PROTOCOL); + } + + /** + * HTTP proxy port for Sentry connections. + * + * @param dsn Sentry server DSN which may contain options. + * @return HTTP proxy port for Sentry connections. + */ + protected int getProxyPort(Dsn dsn) { + return Util.parseInteger(dsn.getOptions().get(HTTP_PROXY_PORT_OPTION), HTTP_PROXY_PORT_DEFAULT); + } + + /** + * HTTP proxy hostname for Sentry connections. + * + * @param dsn Sentry server DSN which may contain options. + * @return HTTP proxy hostname for Sentry connections. + */ + protected String getProxyHost(Dsn dsn) { + return dsn.getOptions().get(HTTP_PROXY_HOST_OPTION); + } + + /** + * Whether to compress requests sent to the Sentry Server. + * + * @param dsn Sentry server DSN which may contain options. + * @return Whether to compress requests sent to the Sentry Server. + */ + protected boolean getCompressionEnabled(Dsn dsn) { + return !FALSE.equalsIgnoreCase(dsn.getOptions().get(COMPRESSION_OPTION)); + } + + /** + * Whether to hide common stackframes with enclosing exceptions. + * + * @param dsn Sentry server DSN which may contain options. + * @return Whether to hide common stackframes with enclosing exceptions. + */ + protected boolean getHideCommonFramesEnabled(Dsn dsn) { + return !FALSE.equalsIgnoreCase(dsn.getOptions().get(HIDE_COMMON_FRAMES_OPTION)); + } + + /** + * The maximum length of the message body in the requests to the Sentry Server. + * + * @param dsn Sentry server DSN which may contain options. + * @return The maximum length of the message body in the requests to the Sentry Server. + */ + protected int getMaxMessageLength(Dsn dsn) { + return Util.parseInteger( + dsn.getOptions().get(MAX_MESSAGE_LENGTH_OPTION), JsonMarshaller.DEFAULT_MAX_MESSAGE_LENGTH); + } + + /** + * Timeout for requests to the Sentry server, in milliseconds. + * + * @param dsn Sentry server DSN which may contain options. + * @return Timeout for requests to the Sentry server, in milliseconds. + */ + protected int getTimeout(Dsn dsn) { + return Util.parseInteger(dsn.getOptions().get(TIMEOUT_OPTION), TIMEOUT_DEFAULT); + } + + /** + * Get the {@link Buffer} where events are stored when network is down. + * + * @param dsn Dsn passed in by the user. + * @return the {@link Buffer} where events are stored when network is down. + */ + protected Buffer getBuffer(Dsn dsn) { + String bufferDir = dsn.getOptions().get(BUFFER_DIR_OPTION); + if (bufferDir != null) { + return new DiskBuffer(new File(bufferDir), getBufferSize(dsn)); + } + return null; + } + + /** + * Get the maximum number of events to cache offline when network is down. + * + * @param dsn Dsn passed in by the user. + * @return the maximum number of events to cache offline when network is down. + */ + protected int getBufferSize(Dsn dsn) { + return Util.parseInteger(dsn.getOptions().get(BUFFER_SIZE_OPTION), BUFFER_SIZE_DEFAULT); + } + /** * Thread factory generating daemon threads with a custom priority. *

    diff --git a/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java b/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java index c32a358f6d2..1717ab2b68c 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java @@ -19,10 +19,6 @@ */ public class AsyncConnection implements Connection { private static final Logger logger = LoggerFactory.getLogger(AsyncConnection.class); - /** - * Default timeout of the {@link #executorService}, in milliseconds. - */ - private static final long DEFAULT_SHUTDOWN_TIMEOUT = TimeUnit.SECONDS.toMillis(1); /** * Timeout of the {@link #executorService}, in milliseconds. */ @@ -73,20 +69,6 @@ public AsyncConnection(Connection actualConnection, ExecutorService executorServ this.shutdownTimeout = shutdownTimeout; } - /** - * Creates a connection which will rely on an executor to send events. - *

    - * Will propagate the {@link #close()} operation. - * - * @param actualConnection connection used to send the events. - * @param executorService executorService used to process events, if null, the executorService will automatically - * be set to {@code Executors.newSingleThreadExecutor()} - * @param gracefulShutdown Indicates whether or not the shutdown operation should be managed by a ShutdownHook. - */ - public AsyncConnection(Connection actualConnection, ExecutorService executorService, boolean gracefulShutdown) { - this(actualConnection, executorService, gracefulShutdown, DEFAULT_SHUTDOWN_TIMEOUT); - } - /** * Adds a hook to shutdown the {@link #executorService} gracefully when the JVM shuts down. */ diff --git a/raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java b/raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java index 2c1c17cc3e0..c4773a29150 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java @@ -21,10 +21,6 @@ public class BufferedConnection implements Connection { private static final Logger logger = LoggerFactory.getLogger(BufferedConnection.class); - /** - * Default timeout of the {@link #executorService}, in milliseconds. - */ - private static final long DEFAULT_SHUTDOWN_TIMEOUT = TimeUnit.SECONDS.toMillis(1); /** * Shutdown hook used to stop the buffered connection properly when the JVM quits. */ @@ -89,19 +85,6 @@ public BufferedConnection(Connection actualConnection, Buffer buffer, long flush executorService.scheduleWithFixedDelay(flusher, flushtime, flushtime, TimeUnit.MILLISECONDS); } - /** - * Construct a BufferedConnection that will store events that failed to send to the provided - * {@link Buffer} and attempt to flush them to the underlying connection later. - * - * @param actualConnection Connection to wrap. - * @param buffer Buffer to be used when {@link Connection#send(Event)}s fail. - * @param flushtime Time to wait between flush attempts, in milliseconds. - * @param gracefulShutdown Indicates whether or not the shutdown operation should be managed by a ShutdownHook. - */ - public BufferedConnection(Connection actualConnection, Buffer buffer, long flushtime, boolean gracefulShutdown) { - this(actualConnection, buffer, flushtime, gracefulShutdown, DEFAULT_SHUTDOWN_TIMEOUT); - } - @Override public void send(Event event) { try { diff --git a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java b/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java index 13d9bc24bcb..cab8fa06b01 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java @@ -82,7 +82,8 @@ public HttpConnection(URL sentryUrl, String publicKey, String secretKey) { /** * Creates an HTTP connection to a Sentry server. - * @param sentryUrl URL to the Sentry API. + * + * @param sentryUrl URL to the Sentry API. * @param publicKey public key of the current project. * @param secretKey private key of the current project. * @param proxy address of HTTP proxy or null if using direct connections. diff --git a/raven/src/test/java/com/getsentry/raven/connection/AsyncConnectionTest.java b/raven/src/test/java/com/getsentry/raven/connection/AsyncConnectionTest.java index d09e6f9d720..3bce0673e6b 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/AsyncConnectionTest.java +++ b/raven/src/test/java/com/getsentry/raven/connection/AsyncConnectionTest.java @@ -19,6 +19,8 @@ public class AsyncConnectionTest { private ExecutorService mockExecutorService = null; @Injectable("false") private boolean mockGracefulShutdown = false; + @Injectable + private long mockTimeout = 10000L; @SuppressWarnings("unused") @Mocked("addShutdownHook") private Runtime mockRuntime = null; @@ -36,7 +38,7 @@ public void verifyShutdownHookIsAddedWhenGraceful() throws Exception { // Ensure that the shutdown hooks for the unused @Tested instance are removed asyncConnection.close(); - new AsyncConnection(mockConnection, mockExecutorService, true); + new AsyncConnection(mockConnection, mockExecutorService, true, mockTimeout); new Verifications() {{ mockRuntime.addShutdownHook((Thread) any); @@ -48,7 +50,7 @@ public void verifyShutdownHookNotAddedWhenNotGraceful() throws Exception { // Ensure that the shutdown hooks for the unused @Tested instance are removed asyncConnection.close(); - new AsyncConnection(mockConnection, mockExecutorService, false); + new AsyncConnection(mockConnection, mockExecutorService, false, mockTimeout); new Verifications() {{ mockRuntime.addShutdownHook((Thread) any); @@ -73,7 +75,7 @@ public void addShutdownHook(Thread hook) { }; }}; - new AsyncConnection(mockConnection, mockExecutorService, true); + new AsyncConnection(mockConnection, mockExecutorService, true, mockTimeout); new VerificationsInOrder() {{ RavenEnvironment.startManagingThread(); @@ -101,7 +103,7 @@ public void addShutdownHook(Thread hook) { result = new RuntimeException("Close operation failed"); }}; - new AsyncConnection(mockConnection, mockExecutorService, true); + new AsyncConnection(mockConnection, mockExecutorService, true, mockTimeout); new Verifications() {{ RavenEnvironment.stopManagingThread(); diff --git a/raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java b/raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java index f49890c2910..5a301237b5f 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java @@ -34,6 +34,8 @@ public class HttpConnectionTest { @Injectable private Marshaller mockMarshaller = null; @Injectable + private int timeout = 12; + @Injectable private URL mockUrl = null; @Injectable private OutputStream mockOutputStream = null; From cd55661ac830e3590a9216bb965c38f67710a6d7 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 7 Oct 2016 15:56:33 -0500 Subject: [PATCH 1461/2152] Add ``raven.async.queue.overflow`` option (#260) --- CHANGES | 2 + .../getsentry/raven/DefaultRavenFactory.java | 55 ++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index dbf29e9dc8e..ab3116373dc 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,8 @@ Version 7.7.1 overrides. - Add a way to retrieve the thread's last sent Event ID, if any. Useful for integrating with the user feedback feature. +- Add ``raven.async.queue.overflow`` option for controlling what to do when the async + executor queue is full. (thanks barogi) Version 7.7.0 ------------- diff --git a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java index bbd973d48c8..b14b6c32d46 100644 --- a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java @@ -19,6 +19,8 @@ import java.net.URL; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; @@ -102,6 +104,28 @@ public class DefaultRavenFactory extends RavenFactory { * Option for the maximum size of the async send queue. */ public static final String ASYNC_QUEUE_SIZE_OPTION = "raven.async.queuesize"; + /** + * Option for what to do when the async executor queue is full. + */ + public static final String ASYNC_QUEUE_OVERFLOW_OPTION = "raven.async.queue.overflow"; + /** + * Async executor overflow behavior that will discard old events in the queue. + */ + public static final String ASYNC_QUEUE_DISCARDOLD = "discardold"; + /** + * Async executor overflow behavior that will discard the new event that was attempting + * to be sent. + */ + public static final String ASYNC_QUEUE_DISCARDNEW = "discardnew"; + /** + * Async executor overflow behavior that will cause a synchronous send to occur on the + * current thread. + */ + public static final String ASYNC_QUEUE_SYNC = "sync"; + /** + * Default behavior to use when the async executor queue is full. + */ + public static final String ASYNC_QUEUE_OVERFLOW_DEFAULT = ASYNC_QUEUE_DISCARDOLD; /** * Option for the graceful shutdown timeout of the async executor, in milliseconds. */ @@ -134,6 +158,13 @@ public class DefaultRavenFactory extends RavenFactory { private static final Logger logger = LoggerFactory.getLogger(DefaultRavenFactory.class); private static final String FALSE = Boolean.FALSE.toString(); + private static final Map REJECT_EXECUTION_HANDLERS = new HashMap<>(); + static { + REJECT_EXECUTION_HANDLERS.put(ASYNC_QUEUE_SYNC, new ThreadPoolExecutor.CallerRunsPolicy()); + REJECT_EXECUTION_HANDLERS.put(ASYNC_QUEUE_DISCARDNEW, new ThreadPoolExecutor.DiscardPolicy()); + REJECT_EXECUTION_HANDLERS.put(ASYNC_QUEUE_DISCARDOLD, new ThreadPoolExecutor.DiscardOldestPolicy()); + } + @Override public Raven createRavenInstance(Dsn dsn) { Raven raven = new Raven(createConnection(dsn)); @@ -214,7 +245,7 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { ExecutorService executorService = new ThreadPoolExecutor( maxThreads, maxThreads, 0L, TimeUnit.MILLISECONDS, queue, - new DaemonThreadFactory(priority), new ThreadPoolExecutor.DiscardOldestPolicy()); + new DaemonThreadFactory(priority), getRejectedExecutionHandler(dsn)); boolean gracefulShutdown = getAsyncGracefulShutdownEnabled(dsn); @@ -329,6 +360,28 @@ protected boolean getAsyncEnabled(Dsn dsn) { return !FALSE.equalsIgnoreCase(dsn.getOptions().get(ASYNC_OPTION)); } + /** + * Handler for tasks that cannot be immediately queued by a {@link ThreadPoolExecutor}. + * + * @param dsn Sentry server DSN which may contain options. + * @return Handler for tasks that cannot be immediately queued by a {@link ThreadPoolExecutor}. + */ + protected RejectedExecutionHandler getRejectedExecutionHandler(Dsn dsn) { + String overflowName = ASYNC_QUEUE_OVERFLOW_DEFAULT; + if (dsn.getOptions().containsKey(ASYNC_QUEUE_OVERFLOW_OPTION)) { + overflowName = dsn.getOptions().get(ASYNC_QUEUE_OVERFLOW_OPTION).toLowerCase(); + } + + RejectedExecutionHandler handler = REJECT_EXECUTION_HANDLERS.get(overflowName); + if (handler == null) { + String options = Arrays.toString(REJECT_EXECUTION_HANDLERS.keySet().toArray()); + throw new RuntimeException("RejectedExecutionHandler not found: '" + overflowName + + "', valid choices are: " + options); + } + + return handler; + } + /** * Maximum time to wait for {@link BufferedConnection} shutdown when closed, in milliseconds. * From 46e2335152cbb6a42ec8290281fb1099eba5a6a7 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 7 Oct 2016 16:40:57 -0500 Subject: [PATCH 1462/2152] Bump CHANGES and docs to 7.7.1 --- docs/modules/appengine.rst | 2 +- docs/modules/log4j.rst | 2 +- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 2 +- docs/modules/raven.rst | 2 +- raven-appengine/README.md | 4 ++-- raven-log4j/README.md | 4 ++-- raven-log4j2/README.md | 10 +++++----- raven-logback/README.md | 4 ++-- raven/README.md | 4 ++-- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 778d0ffdb6a..12fae605e39 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -25,7 +25,7 @@ If you want to use Maven you can install Raven-AppEngine as dependency: com.getsentry.raven raven-appengine - 7.7.0 + 7.7.1 Usage diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index c7b148dffbb..ca0644ca84b 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven-Log4j as dependency: com.getsentry.raven raven-log4j - 7.7.0 + 7.7.1 If you manually want to manage your dependencies: diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index e8b893dfaad..c6d7f20723d 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,18 +19,18 @@ If you want to use Maven you can install Raven-Log4j2 as dependency: com.getsentry.raven raven-log4j2 - 7.7.0 + 7.7.1 If you manually want to manage your dependencies: - :doc:`raven dependencies ` - `log4j-api-2.1.jar - `_ + `_ - `log4j-core-2.0.jar - `_ + `_ - `log4j-slf4j-impl-2.1.jar - `_ + `_ is recommended as the implementation of slf4j (instead of slf4j-jdk14). Usage diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 33a85cc3661..bf7ec8f6150 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -16,7 +16,7 @@ If you want to use Maven you can install Raven-Logback as dependency: com.getsentry.raven raven-logback - 7.7.0 + 7.7.1 - :doc:`raven dependencies ` diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index 6775ec1a31b..db33099f081 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven as dependency: com.getsentry.raven raven - 7.7.0 + 7.7.1 If you manually want to manage your dependencies: diff --git a/raven-appengine/README.md b/raven-appengine/README.md index a751211db1e..c78fc389471 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -15,12 +15,12 @@ __This module is not useful outside of Google App Engine.__ com.getsentry.raven raven-appengine - 7.7.0 + 7.7.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.7.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.7.1%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 3a3604ed7cd..9eb302b385e 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,12 +10,12 @@ for log4j to send the logged events to Sentry. com.getsentry.raven raven-log4j - 7.7.0 + 7.7.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.7.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.7.1%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index 7847962cd6a..291e6366808 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,20 +10,20 @@ for Log4j 2 to send the logged events to Sentry. com.getsentry.raven raven-log4j2 - 7.7.0 + 7.7.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.7.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.7.1%7Cjar). ### Manual dependency management Relies on: - [raven dependencies](../raven) - - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.7.0%7Cjar) - - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.7.0%7Cjar) - - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.7.0%7Cjar) + - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.7.1%7Cjar) + - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.7.1%7Cjar) + - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.7.1%7Cjar) is recommended as the implementation of slf4j (instead of slf4j-jdk14). diff --git a/raven-logback/README.md b/raven-logback/README.md index d7caaca934c..7d4e100849e 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,12 +10,12 @@ for logback to send the logged events to Sentry. com.getsentry.raven raven-logback - 7.7.0 + 7.7.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.7.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.7.1%7Cjar). ### Manual dependency management Relies on: diff --git a/raven/README.md b/raven/README.md index 712c48036b1..e87acfcd334 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,12 +10,12 @@ for `java.util.logging`. com.getsentry.raven raven - 7.7.0 + 7.7.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.7.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.7.1%7Cjar). ### Manual dependency management Relies on: From 1932d8d7598fc77664ee6c274c7dde556c951c89 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 7 Oct 2016 16:42:52 -0500 Subject: [PATCH 1463/2152] Fix JavaDoc syntax. --- raven/src/main/java/com/getsentry/raven/buffer/DiskBuffer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/main/java/com/getsentry/raven/buffer/DiskBuffer.java b/raven/src/main/java/com/getsentry/raven/buffer/DiskBuffer.java index 26edb6a8a8a..9e578181f28 100644 --- a/raven/src/main/java/com/getsentry/raven/buffer/DiskBuffer.java +++ b/raven/src/main/java/com/getsentry/raven/buffer/DiskBuffer.java @@ -85,7 +85,7 @@ public void add(Event event) { } /** - * Deletes a buffered {@link Event{ from disk. + * Deletes a buffered {@link Event} from disk. * * @param event Event to delete from the disk. */ From a43002f5613809023dc51c0c6bc1a096a1f967b2 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 7 Oct 2016 16:44:17 -0500 Subject: [PATCH 1464/2152] [maven-release-plugin] prepare release v7.7.1 --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 38cfb1173c5..0701d965904 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.7.1-SNAPSHOT + 7.7.1 pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v7.7.1 https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 27304abc22e..8d2ab27723e 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.7.1-SNAPSHOT + 7.7.1 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 1123a27c900..cd02144ded3 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.1-SNAPSHOT + 7.7.1 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 6d21d073fc3..babded60c97 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.1-SNAPSHOT + 7.7.1 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 17dbd023c1f..7190f750555 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.1-SNAPSHOT + 7.7.1 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 44760effdb0..265d863d66f 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.1-SNAPSHOT + 7.7.1 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index f88768ddc4f..c3210040247 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.1-SNAPSHOT + 7.7.1 sentry-stub From 2bfb7f4379c634e31ffddf9399cca6c65f3fca5e Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 7 Oct 2016 16:44:18 -0500 Subject: [PATCH 1465/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 0701d965904..524ea5d9979 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.7.1 + 7.7.2-SNAPSHOT pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v7.7.1 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 8d2ab27723e..42d2cba5cf1 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.7.1 + 7.7.2-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index cd02144ded3..e26802c1d17 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.1 + 7.7.2-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index babded60c97..3429f77890a 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.1 + 7.7.2-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 7190f750555..d8ede8cadec 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.1 + 7.7.2-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 265d863d66f..87cc5292d72 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.1 + 7.7.2-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index c3210040247..0f029820de4 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.1 + 7.7.2-SNAPSHOT sentry-stub From 3d7b0788bcb7ced980935681617471e7f4aa767f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 13 Oct 2016 17:08:29 -0500 Subject: [PATCH 1466/2152] Loosen permission on some Appender/Handler methods to allow for easier overriding. (#264) --- CHANGES | 5 +++ .../raven/log4j2/SentryAppender.java | 10 ++++- .../raven/logback/SentryAppender.java | 41 +++++++++++++++++-- .../getsentry/raven/jul/SentryHandler.java | 12 +++++- 4 files changed, 60 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index ab3116373dc..fb6ec77c063 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 7.7.2 +------------- + +- Loosen permission on some Appender/Handler methods to allow for easier overriding. (thanks briprowe) + Version 7.7.1 ------------- diff --git a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java index 4e27d97f909..b9f499c17ad 100644 --- a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java @@ -98,7 +98,7 @@ public class SentryAppender extends AbstractAppender { * Might be empty in which case no mapped tags are set. *

    */ - private Set extraTags = Collections.emptySet(); + protected Set extraTags = Collections.emptySet(); /** * Creates an instance of SentryAppender. @@ -123,7 +123,13 @@ public SentryAppender(Raven raven) { this.raven = raven; } - private SentryAppender(String name, Filter filter) { + /** + * Creates an instance of SentryAppender. + * + * @param name The Appender name. + * @param filter The Filter to associate with the Appender. + */ + protected SentryAppender(String name, Filter filter) { super(name, filter, null, true); } diff --git a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java index aaa297a4874..28efd3a0e3d 100644 --- a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java @@ -269,7 +269,14 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { return eventBuilder.build(); } - private Deque extractExceptionQueue(ILoggingEvent iLoggingEvent) { + /** + * Creates a sequence of {@link SentryExceptions} given a particular {@link ILoggingEvent}. + * + * @param iLoggingEvent Information detailing a particular logging event + * + * @return A {@link Deque} of {@link SentryExceptions} detailing the exception chain + */ + protected Deque extractExceptionQueue(ILoggingEvent iLoggingEvent) { IThrowableProxy throwableProxy = iLoggingEvent.getThrowableProxy(); Deque exceptions = new ArrayDeque<>(); Set circularityDetector = new HashSet<>(); @@ -292,7 +299,17 @@ private Deque extractExceptionQueue(ILoggingEvent iLoggingEvent return exceptions; } - private SentryException createSentryExceptionFrom(IThrowableProxy throwableProxy, StackTraceInterface stackTrace) { + /** + * Given a {@link IThrowableProxy} and a {@link StackTraceInterface} return + * a {@link SentryException} to be reported to Sentry. + * + * @param throwableProxy Information detailing a Throwable + * @param stackTrace The stacktrace associated with the Throwable. + * + * @return A {@link SentryException} object ready to be sent to Sentry. + */ + protected SentryException createSentryExceptionFrom(IThrowableProxy throwableProxy, + StackTraceInterface stackTrace) { String exceptionMessage = throwableProxy.getMessage(); String[] packageNameSimpleName = extractPackageSimpleClassName(throwableProxy.getClassName()); String exceptionPackageName = packageNameSimpleName[0]; @@ -301,7 +318,15 @@ private SentryException createSentryExceptionFrom(IThrowableProxy throwableProxy return new SentryException(exceptionMessage, exceptionClassName, exceptionPackageName, stackTrace); } - private String[] extractPackageSimpleClassName(String canonicalClassName) { + /** + * Given a {@link String} representing a classname, return Strings + * representing the package name and the class name individually. + * + * @param canonicalClassName A dotted-notation string representing a class name (eg. "java.util.Date") + * + * @return An array of {@link Strings}. The first of which is the package name. The second is the class name. + */ + protected String[] extractPackageSimpleClassName(String canonicalClassName) { String[] packageNameSimpleName = new String[2]; try { Class exceptionClass = Class.forName(canonicalClassName); @@ -322,7 +347,15 @@ private String[] extractPackageSimpleClassName(String canonicalClassName) { return packageNameSimpleName; } - private StackTraceElement[] toStackTraceElements(IThrowableProxy throwableProxy) { + /** + * Given a {@link IThrowableProxy} return an array of {@link StackTraceElement}s + * associated with the underlying {@link Throwable}. + * + * @param throwableProxy Information detailing a Throwable. + * + * @return The {@link StackTraceElement}s associated w/the underlying {@link Throwable} + */ + protected StackTraceElement[] toStackTraceElements(IThrowableProxy throwableProxy) { StackTraceElementProxy[] stackTraceElementProxies = throwableProxy.getStackTraceElementProxyArray(); StackTraceElement[] stackTraceElements = new StackTraceElement[stackTraceElementProxies.length]; diff --git a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java index 219bad12954..6548c2f7632 100644 --- a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java +++ b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java @@ -87,7 +87,7 @@ public class SentryHandler extends Handler { *

    * Might be empty in which case no mapped tags are set. */ - private Set extraTags = Collections.emptySet(); + protected Set extraTags = Collections.emptySet(); /** * Creates an instance of SentryHandler. @@ -298,7 +298,15 @@ protected Event buildEvent(LogRecord record) { return eventBuilder.build(); } - private String formatMessage(String message, Object[] parameters) { + /** + * Returns formatted Event message when provided the message template and + * parameters. + * + * @param message Message template body. + * @param parameters Array of parameters for the message. + * @return Formatted message. + */ + protected String formatMessage(String message, Object[] parameters) { String formatted; if (printfStyle) { formatted = String.format(message, parameters); From feed36b692dd1dd57e8c3072044639da245fe7d5 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 14 Oct 2016 13:17:59 -0500 Subject: [PATCH 1467/2152] Add raven-android submodule. (#261) --- README.md | 23 +-- pom.xml | 1 + raven-android/README.md | 61 ++++++ raven-android/pom.xml | 91 +++++++++ .../raven/android/AndroidRavenFactory.java | 61 ++++++ .../com/getsentry/raven/android/Raven.java | 178 ++++++++++++++++++ .../RavenUncaughtExceptionHandler.java | 54 ++++++ .../com/getsentry/raven/android/Util.java | 61 ++++++ .../helper/AndroidEventBuilderHelper.java | 40 ++++ .../getsentry/raven/android/AndroidTest.java | 24 +++ .../com/getsentry/raven/android/RavenIT.java | 31 +++ .../raven/android/RavenITActivity.java | 20 ++ 12 files changed, 623 insertions(+), 22 deletions(-) create mode 100644 raven-android/README.md create mode 100644 raven-android/pom.xml create mode 100644 raven-android/src/main/java/com/getsentry/raven/android/AndroidRavenFactory.java create mode 100644 raven-android/src/main/java/com/getsentry/raven/android/Raven.java create mode 100644 raven-android/src/main/java/com/getsentry/raven/android/RavenUncaughtExceptionHandler.java create mode 100644 raven-android/src/main/java/com/getsentry/raven/android/Util.java create mode 100644 raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java create mode 100644 raven-android/src/test/java/com/getsentry/raven/android/AndroidTest.java create mode 100644 raven-android/src/test/java/com/getsentry/raven/android/RavenIT.java create mode 100644 raven-android/src/test/java/com/getsentry/raven/android/RavenITActivity.java diff --git a/README.md b/README.md index ae9f69d49b9..906394f2eb3 100644 --- a/README.md +++ b/README.md @@ -49,28 +49,7 @@ To use it with maven, add the following repository: ``` ## Android -Raven works on Android, and relies on the -[ServiceLoader](https://developer.android.com/reference/java/util/ServiceLoader.html) -system which uses the content of `META-INF/services`. -This is used to declare the `RavenFactory` implementations (to allow more -control over the automatically generated instances of `Raven`) in -`META-INF/services/com.getsentry.raven.RavenFactory`. - -Unfortunately, when the APK is built, the content of `META-INF/services` in -the dependencies is lost, this prevents Raven from working properly. Some -solutions exist: - - - Use [maven-android-plugin](http://simpligility.github.io/android-maven-plugin/) - which has already solved this -[problem](https://web.archive.org/web/20150523160437/http://code.google.com/p/maven-android-plugin/issues/detail?id=97) - - Manually create a `META-INF/services/com.getsentry.raven.RavenFactory` for - the project which will contain the canonical name of the implementation of - `RavenFactory` (ie. `com.getsentry.raven.DefaultRavenFactory`). - - Manually register the `RavenFactory` when the application starts: - - ```java - RavenFactory.registerFactory(new DefaultRavenFactory()); - ``` +Raven works on Android, please see the [Android README](https://github.com/getsentry/raven-java/blob/raven-android-start/raven-android/README.md). ## HTTP Request Context If the runtime environment utilizes Servlets, events that are created during diff --git a/pom.xml b/pom.xml index 524ea5d9979..5f8e96d395b 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,7 @@ raven + raven-android raven-appengine raven-log4j raven-logback diff --git a/raven-android/README.md b/raven-android/README.md new file mode 100644 index 00000000000..4154b644b96 --- /dev/null +++ b/raven-android/README.md @@ -0,0 +1,61 @@ +# Raven-Android + +## Installation + +### Gradle (Android Studio) + +In your `app/build.gradle` add: `compile 'com.getsentry.raven:raven-android:7.6.0'` + +### Other dependency managers +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-android%7C7.6.0%7Cjar). + +## Usage + +### Configuration + +Configure your Sentry DSN (client key) in `AndroidManifest.xml`: + +```xml + + + +``` + +Your application must also have permission to access the internet in order to send +event to the Sentry server. In `AndroidManifest.xml`: + +```xml + + +``` + +Then, in your application's `onCreate`, initialize the Raven client: + +```java +import com.getsentry.raven.android.Raven; + +// `this` is your main Activity +Raven.init(this.getApplicationContext()); +``` + +Now you can use `Raven` to capture events in your application: + +```java +// Pass a String event +Raven.capture("Error message"); + +// Or pass it a throwable +try { + something() +} catch (Exception e) { + Raven.capture(e); +} + +// Or build an event yourself +EventBuilder eventBuilder = new EventBuilder() + .withMessage("Exception caught") + .withLevel(Event.Level.ERROR); +Raven.capture(eventBuilder.build()); +``` diff --git a/raven-android/pom.xml b/raven-android/pom.xml new file mode 100644 index 00000000000..d5f2a3f95a2 --- /dev/null +++ b/raven-android/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + + com.getsentry.raven + raven-all + 7.7.1-SNAPSHOT + + + raven-android + jar + + Raven-Java for Android + Android Raven-Java client. + + + 4.1.1.4 + 4.12 + 2.4 + 2.1.3 + + + + + ${project.groupId} + raven + + + com.google.android + android + ${android.version} + provided + + + + + junit + junit + ${junit.version} + test + + + org.robolectric + robolectric + ${robolectric.version} + test + + + org.apache.maven + maven-ant-tasks + ${maven-ant.version} + test + + + + + + ${project.groupId} + raven + test-jar + test + + + com.fasterxml.jackson.core + jackson-databind + test + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.apache.maven.plugins + maven-dependency-plugin + + + org.eclipse.jetty + jetty-maven-plugin + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + + diff --git a/raven-android/src/main/java/com/getsentry/raven/android/AndroidRavenFactory.java b/raven-android/src/main/java/com/getsentry/raven/android/AndroidRavenFactory.java new file mode 100644 index 00000000000..c69e9e75c17 --- /dev/null +++ b/raven-android/src/main/java/com/getsentry/raven/android/AndroidRavenFactory.java @@ -0,0 +1,61 @@ +package com.getsentry.raven.android; + +import android.content.Context; +import android.util.Log; +import com.getsentry.raven.*; +import com.getsentry.raven.android.event.helper.AndroidEventBuilderHelper; +import com.getsentry.raven.buffer.Buffer; +import com.getsentry.raven.buffer.DiskBuffer; +import com.getsentry.raven.dsn.Dsn; + +import java.io.File; + +/** + * RavenFactory that handles Android-specific construction, like taking advantage + * of the Android Context instance. + */ +public class AndroidRavenFactory extends DefaultRavenFactory { + + /** + * Logger tag. + */ + public static final String TAG = AndroidRavenFactory.class.getName(); + /** + * Default Buffer directory name. + */ + private static final String DEFAULT_BUFFER_DIR = "raven-buffered-events"; + + private Context ctx; + + /** + * Construct an AndroidRavenFactory using the specified Android Context. + * + * @param ctx Android Context. + */ + public AndroidRavenFactory(Context ctx) { + this.ctx = ctx; + + Log.d(TAG, "Construction of Android Raven."); + } + + @Override + public com.getsentry.raven.Raven createRavenInstance(Dsn dsn) { + com.getsentry.raven.Raven ravenInstance = super.createRavenInstance(dsn); + ravenInstance.addBuilderHelper(new AndroidEventBuilderHelper(ctx)); + return ravenInstance; + } + + @Override + protected Buffer getBuffer(Dsn dsn) { + File bufferDir; + if (dsn.getOptions().get(BUFFER_DIR_OPTION) != null) { + bufferDir = new File(dsn.getOptions().get(BUFFER_DIR_OPTION)); + } else { + bufferDir = new File(ctx.getCacheDir().getAbsolutePath(), DEFAULT_BUFFER_DIR); + } + + Log.d(TAG, "Using buffer dir: " + bufferDir.getAbsolutePath()); + return new DiskBuffer(bufferDir, getBufferSize(dsn)); + } + +} diff --git a/raven-android/src/main/java/com/getsentry/raven/android/Raven.java b/raven-android/src/main/java/com/getsentry/raven/android/Raven.java new file mode 100644 index 00000000000..24b85ba7c06 --- /dev/null +++ b/raven-android/src/main/java/com/getsentry/raven/android/Raven.java @@ -0,0 +1,178 @@ +package com.getsentry.raven.android; + +import android.Manifest; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.text.TextUtils; +import android.util.Log; +import com.getsentry.raven.DefaultRavenFactory; +import com.getsentry.raven.RavenFactory; +import com.getsentry.raven.dsn.Dsn; +import com.getsentry.raven.event.Event; +import com.getsentry.raven.event.EventBuilder; + +/** + * Android specific class to interface with Raven. Supplements the default Java classes + * with Android specific state and features. + */ +public final class Raven { + + /** + * Logger tag. + */ + public static final String TAG = Raven.class.getName(); + + private static volatile com.getsentry.raven.Raven raven; + + /** + * Hide constructor. + */ + private Raven() { + + } + + /** + * Initialize Raven using a DSN set in the AndroidManifest. + * + * @param ctx Android application ctx + */ + public static void init(Context ctx) { + ctx = ctx.getApplicationContext(); + String dsn = ""; + + // attempt to get DSN from AndroidManifest + ApplicationInfo appInfo = null; + try { + PackageManager packageManager = ctx.getPackageManager(); + appInfo = packageManager.getApplicationInfo(ctx.getPackageName(), PackageManager.GET_META_DATA); + dsn = appInfo.metaData.getString("com.getsentry.raven.android.DSN"); + } catch (PackageManager.NameNotFoundException e) { + // skip + } + + if (TextUtils.isEmpty(dsn)) { + throw new NullPointerException("Raven DSN is not set, you must provide it via" + + "the constructor or AndroidManifest."); + } + + init(ctx, new Dsn(dsn)); + } + + /** + * Initialize Raven using a string DSN. + * + * @param ctx Android application ctx + * @param dsn Sentry DSN string + */ + public static void init(Context ctx, String dsn) { + init(ctx, new Dsn(dsn)); + } + + /** + * Initialize Raven using a DSN object. This is the 'main' initializer that other methods + * eventually call. + * + * @param ctx Android application ctx + * @param dsn Sentry DSN object + */ + public static void init(Context ctx, Dsn dsn) { + if (raven != null) { + Log.e(TAG, "Initializing Raven multiple times."); + // cleanup existing connections + raven.closeConnection(); + } + + // Ensure we have the application context + Context context = ctx.getApplicationContext(); + + if (!Util.checkPermission(context, Manifest.permission.INTERNET)) { + Log.e(TAG, Manifest.permission.INTERNET + " is required to connect to the Sentry server," + + " please add it to your AndroidManifest.xml"); + } + + Log.d(TAG, "Raven init with ctx='" + ctx.toString() + "' and dsn='" + dsn + "'"); + + String protocol = dsn.getProtocol(); + if (!(protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https"))) { + throw new IllegalArgumentException("Only 'http' or 'https' connections are supported in" + + " Raven Android, but received: " + protocol); + } + + if ("false".equalsIgnoreCase(dsn.getOptions().get(DefaultRavenFactory.ASYNC_OPTION))) { + throw new IllegalArgumentException("Raven Android cannot use synchronous connections, remove '" + + DefaultRavenFactory.ASYNC_OPTION + "=false' from your DSN."); + } + + RavenFactory.registerFactory(new AndroidRavenFactory(ctx)); + raven = RavenFactory.ravenInstance(dsn); + + setupUncaughtExceptionHandler(); + } + + /** + * Configures an Android uncaught exception handler which sends events to + * Sentry, then calls the preexisting uncaught exception handler. + */ + private static void setupUncaughtExceptionHandler() { + Thread.UncaughtExceptionHandler currentHandler = Thread.getDefaultUncaughtExceptionHandler(); + if (currentHandler != null) { + Log.d(TAG, "default UncaughtExceptionHandler class='" + currentHandler.getClass().getName() + "'"); + } + + // don't double register + if (!(currentHandler instanceof RavenUncaughtExceptionHandler)) { + // register as default exception handler + Thread.setDefaultUncaughtExceptionHandler( + new RavenUncaughtExceptionHandler(currentHandler)); + } + } + + /** + * Send an Event using the statically stored Raven instance. + * + * @param event Event to send to the Sentry server + */ + public static void capture(Event event) { + raven.sendEvent(event); + } + + /** + * Sends an exception (or throwable) to the Sentry server using the statically stored Raven instance. + *

    + * The exception will be logged at the {@link Event.Level#ERROR} level. + * + * @param throwable exception to send to Sentry. + */ + public static void capture(Throwable throwable) { + raven.sendException(throwable); + } + + /** + * Sends a message to the Sentry server using the statically stored Raven instance. + *

    + * The message will be logged at the {@link Event.Level#INFO} level. + * + * @param message message to send to Sentry. + */ + public static void capture(String message) { + raven.sendMessage(message); + } + + /** + * Builds and sends an {@link Event} to the Sentry server using the statically stored Raven instance. + * + * @param eventBuilder {@link EventBuilder} to send to Sentry. + */ + public static void capture(EventBuilder eventBuilder) { + raven.sendEvent(eventBuilder); + } + + /** + * Clear statically stored Raven instance. Useful for tests. + */ + public static void clearStoredRaven() { + raven = null; + } + +} diff --git a/raven-android/src/main/java/com/getsentry/raven/android/RavenUncaughtExceptionHandler.java b/raven-android/src/main/java/com/getsentry/raven/android/RavenUncaughtExceptionHandler.java new file mode 100644 index 00000000000..6adbb82e7d0 --- /dev/null +++ b/raven-android/src/main/java/com/getsentry/raven/android/RavenUncaughtExceptionHandler.java @@ -0,0 +1,54 @@ +package com.getsentry.raven.android; + +import android.util.Log; + +/** + * Sends any uncaught exception to Sentry, then passes the exception on to the pre-existing + * uncaught exception handler. + */ +class RavenUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { + + /** + * Logger tag. + */ + private static final String TAG = RavenUncaughtExceptionHandler.class.getName(); + + /** + * Reference to the pre-existing uncaught exception handler. + */ + private Thread.UncaughtExceptionHandler defaultExceptionHandler; + + /** + * Construct the {@link RavenUncaughtExceptionHandler}, storing the pre-existing uncaught exception + * handler. + * + * @param defaultExceptionHandler pre-existing uncaught exception handler + */ + RavenUncaughtExceptionHandler(Thread.UncaughtExceptionHandler defaultExceptionHandler) { + this.defaultExceptionHandler = defaultExceptionHandler; + } + + /** + * Sends any uncaught exception to Sentry, then passes the exception on to the pre-existing + * uncaught exception handler. + * + * @param thread thread that threw the error + * @param thrown the uncaught throwable + */ + @Override + public void uncaughtException(Thread thread, Throwable thrown) { + Log.d(TAG, "Uncaught exception received."); + + try { + com.getsentry.raven.Raven.capture(thrown); + } catch (Exception e) { + Log.e(TAG, "Error sending excepting to Sentry.", e); + } + + if (defaultExceptionHandler != null) { + // call the original handler + defaultExceptionHandler.uncaughtException(thread, thrown); + } + } + +} diff --git a/raven-android/src/main/java/com/getsentry/raven/android/Util.java b/raven-android/src/main/java/com/getsentry/raven/android/Util.java new file mode 100644 index 00000000000..65fa6b1192e --- /dev/null +++ b/raven-android/src/main/java/com/getsentry/raven/android/Util.java @@ -0,0 +1,61 @@ +package com.getsentry.raven.android; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; + +/** + * Raven Android utility methods. + */ +public final class Util { + + /** + * Hide constructor. + */ + private Util() { + + } + + /** + * Check whether the application has been granted a certain permission. + * + * @param ctx Android application context + * @param permission Permission as a string + * @return true if permissions is granted + */ + public static boolean checkPermission(Context ctx, String permission) { + int res = ctx.checkCallingOrSelfPermission(permission); + return (res == PackageManager.PERMISSION_GRANTED); + } + + /** + * Check whether the application has internet access at a point in time. + * + * @param ctx Android application context + * @return true if the application has internet access + */ + public static boolean isConnected(Context ctx) { + ConnectivityManager connectivityManager = + (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); + return activeNetworkInfo != null && activeNetworkInfo.isConnected(); + } + + /** + * Check whether Raven should attempt to send an event, or just immediately store it. + * + * @param ctx Android application context + * @return true if Raven should attempt to send an event + */ + public static boolean shouldAttemptToSend(Context ctx) { + if (!checkPermission(ctx, android.Manifest.permission.ACCESS_NETWORK_STATE)) { + // we can't check whether the connection is up, so the + // best we can do is try + return true; + } + + return isConnected(ctx); + } + +} diff --git a/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java b/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java new file mode 100644 index 00000000000..e9e47072f64 --- /dev/null +++ b/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java @@ -0,0 +1,40 @@ +package com.getsentry.raven.android.event.helper; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.util.Log; +import com.getsentry.raven.event.EventBuilder; +import com.getsentry.raven.event.helper.EventBuilderHelper; + +/** + * EventBuilderHelper that makes use of Android Context to populate some Event fields. + */ +public class AndroidEventBuilderHelper implements EventBuilderHelper { + + /** + * Logger tag. + */ + public static final String TAG = AndroidEventBuilderHelper.class.getName(); + + private Context ctx; + + /** + * Construct given the provided Android {@link Context}. + * + * @param ctx Android application context. + */ + public AndroidEventBuilderHelper(Context ctx) { + this.ctx = ctx; + } + + @Override + public void helpBuildingEvent(EventBuilder eventBuilder) { + try { + int versionCode = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0).versionCode; + eventBuilder.withRelease(Integer.toString(versionCode)); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Couldn't find package version: " + e); + } + } + +} diff --git a/raven-android/src/test/java/com/getsentry/raven/android/AndroidTest.java b/raven-android/src/test/java/com/getsentry/raven/android/AndroidTest.java new file mode 100644 index 00000000000..4393cedc183 --- /dev/null +++ b/raven-android/src/test/java/com/getsentry/raven/android/AndroidTest.java @@ -0,0 +1,24 @@ +package com.getsentry.raven.android; + +import com.getsentry.raven.BaseTest; +import com.getsentry.raven.stub.SentryStub; +import org.junit.After; +import org.junit.Before; + +public class AndroidTest extends BaseTest { + + protected SentryStub sentryStub; + + @Before + public void setUp() throws Exception { + sentryStub = new SentryStub(); + sentryStub.removeEvents(); + } + + @After + public void tearDown() throws Exception { + Raven.clearStoredRaven(); + sentryStub.removeEvents(); + } + +} \ No newline at end of file diff --git a/raven-android/src/test/java/com/getsentry/raven/android/RavenIT.java b/raven-android/src/test/java/com/getsentry/raven/android/RavenIT.java new file mode 100644 index 00000000000..1ccf3b5408c --- /dev/null +++ b/raven-android/src/test/java/com/getsentry/raven/android/RavenIT.java @@ -0,0 +1,31 @@ +package com.getsentry.raven.android; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; + +import java.util.concurrent.Callable; + +@RunWith(RobolectricTestRunner.class) +public class RavenIT extends AndroidTest { + + @Test + public void test() throws Exception { + Assert.assertEquals(sentryStub.getEventCount(), 0); + + RavenITActivity activity = Robolectric.setupActivity(RavenITActivity.class); + activity.sendEvent(); + + waitUntilTrue(1000, new Callable() { + @Override + public Boolean call() throws Exception { + return sentryStub.getEventCount() == 1; + } + }); + + Assert.assertEquals(sentryStub.getEventCount(), 1); + } + +} diff --git a/raven-android/src/test/java/com/getsentry/raven/android/RavenITActivity.java b/raven-android/src/test/java/com/getsentry/raven/android/RavenITActivity.java new file mode 100644 index 00000000000..4164b5b2ef4 --- /dev/null +++ b/raven-android/src/test/java/com/getsentry/raven/android/RavenITActivity.java @@ -0,0 +1,20 @@ +package com.getsentry.raven.android; + +import android.app.Activity; +import android.os.Bundle; + +public class RavenITActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Raven.init( + this.getApplicationContext(), + "http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1"); + } + + public void sendEvent() { + Raven.capture("sendEvent()"); + } + +} From e657fc5568f892d3e63b173d5a1a00f77722a40a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 14 Oct 2016 13:19:42 -0500 Subject: [PATCH 1468/2152] Fix raven-android version. --- raven-android/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven-android/pom.xml b/raven-android/pom.xml index d5f2a3f95a2..d51fb8877e5 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.1-SNAPSHOT + 7.7.2-SNAPSHOT raven-android From 6eabc32b0b65afef6857266d2b1f54375b3f67a6 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Sat, 15 Oct 2016 08:09:35 -0500 Subject: [PATCH 1469/2152] Add Filters to drop all Raven logs before they go to the Sentry server. (#265) --- CHANGES | 1 + .../getsentry/raven/log4j/SentryAppender.java | 14 ++++++ .../raven/log4j/SentryAppenderIT.java | 14 +++++- .../raven/log4j2/SentryAppender.java | 46 ++++++++++++++++--- .../raven/log4j2/SentryAppenderIT.java | 14 +++++- .../raven/logback/SentryAppender.java | 15 ++++++ .../raven/logback/SentryAppenderIT.java | 14 +++++- raven/README.md | 4 +- .../getsentry/raven/jul/SentryHandler.java | 9 ++++ .../getsentry/raven/jul/SentryHandlerIT.java | 14 +++++- 10 files changed, 133 insertions(+), 12 deletions(-) diff --git a/CHANGES b/CHANGES index fb6ec77c063..b0c2bee63a3 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 7.7.2 ------------- - Loosen permission on some Appender/Handler methods to allow for easier overriding. (thanks briprowe) +- Add Filters to drop all Raven logs before they go to the Sentry server. Version 7.7.1 ------------- diff --git a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java index 72ea8c44523..2bb0d0d8994 100644 --- a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java @@ -14,6 +14,7 @@ import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; import org.apache.log4j.spi.ErrorCode; +import org.apache.log4j.spi.Filter; import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; import org.apache.log4j.spi.ThrowableInformation; @@ -94,6 +95,8 @@ public SentryAppender() { setServerName(Lookup.lookup("serverName")); setTags(Lookup.lookup("tags")); setExtraTags(Lookup.lookup("extraTags")); + + this.addFilter(new DropRavenFilter()); } /** @@ -311,4 +314,15 @@ public void close() { public boolean requiresLayout() { return false; } + + private class DropRavenFilter extends Filter { + @Override + public int decide(LoggingEvent event) { + String loggerName = event.getLoggerName(); + if (loggerName != null && loggerName.startsWith("com.getsentry.raven")) { + return Filter.DENY; + } + return Filter.NEUTRAL; + } + } } diff --git a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderIT.java b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderIT.java index 2796374e4df..aec04c04ca0 100644 --- a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderIT.java +++ b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderIT.java @@ -10,7 +10,12 @@ import static org.hamcrest.Matchers.is; public class SentryAppenderIT { - private static final Logger logger = Logger.getLogger(SentryAppenderIT.class); + /* + We filter out loggers that start with `com.getsentry.raven`, so we deliberately + use a custom logger name here. + */ + private static final Logger logger = Logger.getLogger("SentryAppenderIT: log4j"); + private static final Logger ravenLogger = Logger.getLogger(SentryAppenderIT.class); private SentryStub sentryStub; @BeforeMethod @@ -37,4 +42,11 @@ public void testChainedExceptions() throws Exception { new UnsupportedOperationException("Test", new UnsupportedOperationException())); assertThat(sentryStub.getEventCount(), is(1)); } + + @Test + public void testNoRavenLogging() throws Exception { + assertThat(sentryStub.getEventCount(), is(0)); + ravenLogger.error("This is a test"); + assertThat(sentryStub.getEventCount(), is(0)); + } } diff --git a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java index b9f499c17ad..de5e2ea8a4a 100644 --- a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java @@ -13,13 +13,16 @@ import com.getsentry.raven.event.interfaces.StackTraceInterface; import com.getsentry.raven.util.Util; import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginAttribute; import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.filter.AbstractFilter; import org.apache.logging.log4j.message.Message; import java.util.ArrayList; @@ -105,12 +108,6 @@ public class SentryAppender extends AbstractAppender { */ public SentryAppender() { this(APPENDER_NAME, null); - setRavenFactory(Lookup.lookup("ravenFactory")); - setRelease(Lookup.lookup("release")); - setEnvironment(Lookup.lookup("environment")); - setServerName(Lookup.lookup("serverName")); - setTags(Lookup.lookup("tags")); - setExtraTags(Lookup.lookup("extraTags")); } /** @@ -131,6 +128,13 @@ public SentryAppender(Raven raven) { */ protected SentryAppender(String name, Filter filter) { super(name, filter, null, true); + setRavenFactory(Lookup.lookup("ravenFactory")); + setRelease(Lookup.lookup("release")); + setEnvironment(Lookup.lookup("environment")); + setServerName(Lookup.lookup("serverName")); + setTags(Lookup.lookup("tags")); + setExtraTags(Lookup.lookup("extraTags")); + this.addFilter(new DropRavenFilter()); } /** @@ -383,4 +387,34 @@ public void stop() { RavenEnvironment.stopManagingThread(); } } + + private class DropRavenFilter extends AbstractFilter { + + @Override + public Result filter(Logger logger, Level level, Marker marker, String msg, Object... params) { + return filter(logger.getName()); + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) { + return filter(logger.getName()); + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) { + return filter(logger.getName()); + } + + @Override + public Result filter(LogEvent event) { + return filter(event.getLoggerName()); + } + + private Result filter(String loggerName) { + if (loggerName != null && loggerName.startsWith("com.getsentry.raven")) { + return Result.DENY; + } + return Result.NEUTRAL; + } + } } diff --git a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderIT.java b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderIT.java index 5d0af7d24c2..bb77ab7db29 100644 --- a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderIT.java +++ b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderIT.java @@ -11,7 +11,12 @@ import static org.hamcrest.Matchers.is; public class SentryAppenderIT { - private static final Logger logger = LogManager.getLogger(SentryAppenderIT.class); + /* + We filter out loggers that start with `com.getsentry.raven`, so we deliberately + use a custom logger name here. + */ + private static final Logger logger = LogManager.getLogger("SentryAppenderIT: log4j2"); + private static final Logger ravenLogger = LogManager.getLogger(SentryAppenderIT.class); private SentryStub sentryStub; @BeforeMethod @@ -38,4 +43,11 @@ public void testChainedExceptions() throws Exception { new UnsupportedOperationException("Test", new UnsupportedOperationException())); assertThat(sentryStub.getEventCount(), is(1)); } + + @Test + public void testNoRavenLogging() throws Exception { + assertThat(sentryStub.getEventCount(), is(0)); + ravenLogger.error("This is a test"); + assertThat(sentryStub.getEventCount(), is(0)); + } } diff --git a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java index 28efd3a0e3d..1cf994a62e0 100644 --- a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java @@ -5,6 +5,8 @@ import ch.qos.logback.classic.spi.IThrowableProxy; import ch.qos.logback.classic.spi.StackTraceElementProxy; import ch.qos.logback.core.AppenderBase; +import ch.qos.logback.core.filter.Filter; +import ch.qos.logback.core.spi.FilterReply; import com.getsentry.raven.Raven; import com.getsentry.raven.RavenFactory; import com.getsentry.raven.config.Lookup; @@ -105,6 +107,8 @@ public SentryAppender() { setServerName(Lookup.lookup("serverName")); setTags(Lookup.lookup("tags")); setExtraTags(Lookup.lookup("extraTags")); + + this.addFilter(new DropRavenFilter()); } /** @@ -423,4 +427,15 @@ public void stop() { RavenEnvironment.stopManagingThread(); } } + + private class DropRavenFilter extends Filter { + @Override + public FilterReply decide(ILoggingEvent event) { + String loggerName = event.getLoggerName(); + if (loggerName != null && loggerName.startsWith("com.getsentry.raven")) { + return FilterReply.DENY; + } + return FilterReply.NEUTRAL; + } + } } diff --git a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderIT.java b/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderIT.java index 8358c980b8f..49819f91ad5 100644 --- a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderIT.java +++ b/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderIT.java @@ -11,7 +11,12 @@ import static org.hamcrest.Matchers.is; public class SentryAppenderIT { - private static final Logger logger = LoggerFactory.getLogger(SentryAppenderIT.class); + /* + We filter out loggers that start with `com.getsentry.raven`, so we deliberately + use a custom logger name here. + */ + private static final Logger logger = LoggerFactory.getLogger("SentryAppenderIT: logback"); + private static final Logger ravenLogger = LoggerFactory.getLogger(SentryAppenderIT.class); private SentryStub sentryStub; @BeforeMethod @@ -39,4 +44,11 @@ public void testChainedExceptions() throws Exception { new UnsupportedOperationException("Test", new UnsupportedOperationException())); assertThat(sentryStub.getEventCount(), is(1)); } + + @Test + public void testNoRavenLogging() throws Exception { + assertThat(sentryStub.getEventCount(), is(0)); + ravenLogger.error("This is a test"); + assertThat(sentryStub.getEventCount(), is(0)); + } } diff --git a/raven/README.md b/raven/README.md index e87acfcd334..7c90d337d6f 100644 --- a/raven/README.md +++ b/raven/README.md @@ -32,7 +32,7 @@ Relies on: Add the `SentryHandler` to the `logging.properties` file: ```properties -.level=WARN +.level=WARNING handlers=com.getsentry.raven.jul.SentryHandler ``` @@ -82,7 +82,7 @@ itself. This is less flexible because it's harder to change when you run your application in different environments. ```properties -.level=WARN +.level=WARNING handlers=com.getsentry.raven.jul.SentryHandler com.getsentry.raven.jul.SentryHandler.dsn=https://host:port/1?options # Optional, provide tags diff --git a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java index 6548c2f7632..d68d5810210 100644 --- a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java +++ b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java @@ -21,6 +21,7 @@ import java.util.Map; import java.util.Set; import java.util.logging.ErrorManager; +import java.util.logging.Filter; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogManager; @@ -101,6 +102,7 @@ public SentryHandler() { setExtraTags(Lookup.lookup("extraTags")); retrieveProperties(); + this.setFilter(new DropRavenFilter()); } /** @@ -370,4 +372,11 @@ public void setExtraTags(String extraTags) { this.extraTags = Util.parseExtraTags(extraTags); } + private class DropRavenFilter implements Filter { + @Override + public boolean isLoggable(LogRecord record) { + String loggerName = record.getLoggerName(); + return loggerName == null || !loggerName.startsWith("com.getsentry.raven"); + } + } } diff --git a/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerIT.java b/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerIT.java index 1d406d597c9..c5118522fb5 100644 --- a/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerIT.java +++ b/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerIT.java @@ -12,7 +12,12 @@ import static org.hamcrest.Matchers.is; public class SentryHandlerIT { - private static final Logger logger = Logger.getLogger(SentryHandlerIT.class.getName()); + /* + We filter out loggers that start with `com.getsentry.raven`, so we deliberately + use a custom logger name here. + */ + private static final Logger logger = Logger.getLogger("SentryHandlerIT: jul"); + private static final Logger ravenLogger = Logger.getLogger(SentryHandlerIT.class.getName()); private SentryStub sentryStub; @BeforeMethod @@ -39,4 +44,11 @@ public void testChainedExceptions() throws Exception { new UnsupportedOperationException("Test", new UnsupportedOperationException())); assertThat(sentryStub.getEventCount(), is(1)); } + + @Test + public void testNoRavenLogging() throws Exception { + assertThat(sentryStub.getEventCount(), is(0)); + ravenLogger.log(Level.SEVERE, "This is a test"); + assertThat(sentryStub.getEventCount(), is(0)); + } } From b5d870873271815fbe62f055e2b3c1d5fba7a84c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Sat, 15 Oct 2016 09:03:26 -0500 Subject: [PATCH 1470/2152] Bump CHANGES and docs to 7.8.0 --- CHANGES | 5 +++-- docs/modules/appengine.rst | 2 +- docs/modules/log4j.rst | 2 +- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 2 +- docs/modules/raven.rst | 2 +- raven-android/README.md | 4 ++-- raven-appengine/README.md | 4 ++-- raven-log4j/README.md | 4 ++-- raven-log4j2/README.md | 10 +++++----- raven-logback/README.md | 4 ++-- raven/README.md | 4 ++-- 12 files changed, 26 insertions(+), 25 deletions(-) diff --git a/CHANGES b/CHANGES index b0c2bee63a3..93cfdcb2607 100644 --- a/CHANGES +++ b/CHANGES @@ -1,8 +1,9 @@ -Version 7.7.2 +Version 7.8.0 ------------- -- Loosen permission on some Appender/Handler methods to allow for easier overriding. (thanks briprowe) +- Add Android support under the ``raven-android`` subproject. - Add Filters to drop all Raven logs before they go to the Sentry server. +- Loosen permission on some Appender/Handler methods to allow for easier overriding. (thanks briprowe) Version 7.7.1 ------------- diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 12fae605e39..45f95fca2e1 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -25,7 +25,7 @@ If you want to use Maven you can install Raven-AppEngine as dependency: com.getsentry.raven raven-appengine - 7.7.1 + 7.8.0 Usage diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index ca0644ca84b..1e45a5681ff 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven-Log4j as dependency: com.getsentry.raven raven-log4j - 7.7.1 + 7.8.0 If you manually want to manage your dependencies: diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index c6d7f20723d..976fc33372c 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,18 +19,18 @@ If you want to use Maven you can install Raven-Log4j2 as dependency: com.getsentry.raven raven-log4j2 - 7.7.1 + 7.8.0 If you manually want to manage your dependencies: - :doc:`raven dependencies ` - `log4j-api-2.1.jar - `_ + `_ - `log4j-core-2.0.jar - `_ + `_ - `log4j-slf4j-impl-2.1.jar - `_ + `_ is recommended as the implementation of slf4j (instead of slf4j-jdk14). Usage diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index bf7ec8f6150..b8cc2a92f49 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -16,7 +16,7 @@ If you want to use Maven you can install Raven-Logback as dependency: com.getsentry.raven raven-logback - 7.7.1 + 7.8.0 - :doc:`raven dependencies ` diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index db33099f081..b25e807e4ad 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven as dependency: com.getsentry.raven raven - 7.7.1 + 7.8.0 If you manually want to manage your dependencies: diff --git a/raven-android/README.md b/raven-android/README.md index 4154b644b96..eaed2e27548 100644 --- a/raven-android/README.md +++ b/raven-android/README.md @@ -4,10 +4,10 @@ ### Gradle (Android Studio) -In your `app/build.gradle` add: `compile 'com.getsentry.raven:raven-android:7.6.0'` +In your `app/build.gradle` add: `compile 'com.getsentry.raven:raven-android:7.8.0'` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-android%7C7.6.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-android%7C7.8.0%7Cjar). ## Usage diff --git a/raven-appengine/README.md b/raven-appengine/README.md index c78fc389471..1634a40d392 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -15,12 +15,12 @@ __This module is not useful outside of Google App Engine.__ com.getsentry.raven raven-appengine - 7.7.1 + 7.8.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.7.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.8.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 9eb302b385e..36f94e27b77 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,12 +10,12 @@ for log4j to send the logged events to Sentry. com.getsentry.raven raven-log4j - 7.7.1 + 7.8.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.7.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.8.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index 291e6366808..d8d9ca6fe04 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,20 +10,20 @@ for Log4j 2 to send the logged events to Sentry. com.getsentry.raven raven-log4j2 - 7.7.1 + 7.8.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.7.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.8.0%7Cjar). ### Manual dependency management Relies on: - [raven dependencies](../raven) - - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.7.1%7Cjar) - - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.7.1%7Cjar) - - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.7.1%7Cjar) + - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.8.0%7Cjar) + - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.8.0%7Cjar) + - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.8.0%7Cjar) is recommended as the implementation of slf4j (instead of slf4j-jdk14). diff --git a/raven-logback/README.md b/raven-logback/README.md index 7d4e100849e..ed68fb49307 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,12 +10,12 @@ for logback to send the logged events to Sentry. com.getsentry.raven raven-logback - 7.7.1 + 7.8.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.7.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.8.0%7Cjar). ### Manual dependency management Relies on: diff --git a/raven/README.md b/raven/README.md index 7c90d337d6f..7153ee7754e 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,12 +10,12 @@ for `java.util.logging`. com.getsentry.raven raven - 7.7.1 + 7.8.0 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.7.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.8.0%7Cjar). ### Manual dependency management Relies on: From df9703b3297a7951593c612b51271904573e050d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Sat, 15 Oct 2016 09:06:44 -0500 Subject: [PATCH 1471/2152] Fix JavaDocs. --- .../java/com/getsentry/raven/logback/SentryAppender.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java index 1cf994a62e0..9641174db33 100644 --- a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java @@ -274,11 +274,11 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { } /** - * Creates a sequence of {@link SentryExceptions} given a particular {@link ILoggingEvent}. + * Creates a sequence of {@link SentryException}s given a particular {@link ILoggingEvent}. * * @param iLoggingEvent Information detailing a particular logging event * - * @return A {@link Deque} of {@link SentryExceptions} detailing the exception chain + * @return A {@link Deque} of {@link SentryException}s detailing the exception chain */ protected Deque extractExceptionQueue(ILoggingEvent iLoggingEvent) { IThrowableProxy throwableProxy = iLoggingEvent.getThrowableProxy(); @@ -328,7 +328,7 @@ protected SentryException createSentryExceptionFrom(IThrowableProxy throwablePro * * @param canonicalClassName A dotted-notation string representing a class name (eg. "java.util.Date") * - * @return An array of {@link Strings}. The first of which is the package name. The second is the class name. + * @return An array of {@link String}s. The first of which is the package name. The second is the class name. */ protected String[] extractPackageSimpleClassName(String canonicalClassName) { String[] packageNameSimpleName = new String[2]; From 31c269b02fa27d1c9b9a902bc8c6b65ed7c278a1 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Sat, 15 Oct 2016 09:15:42 -0500 Subject: [PATCH 1472/2152] [maven-release-plugin] prepare release v7.8.0 --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 5f8e96d395b..8ad3cbfc3e7 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.7.2-SNAPSHOT + 7.8.0 pom Raven-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v7.8.0 https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index d51fb8877e5..be47ac477d6 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.2-SNAPSHOT + 7.8.0 raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 42d2cba5cf1..61431eedee3 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.7.2-SNAPSHOT + 7.8.0 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index e26802c1d17..15b3265a1f7 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.2-SNAPSHOT + 7.8.0 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 3429f77890a..fa4cab65dc9 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.2-SNAPSHOT + 7.8.0 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index d8ede8cadec..8125be8d1a2 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.2-SNAPSHOT + 7.8.0 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 87cc5292d72..d9c66b72537 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.2-SNAPSHOT + 7.8.0 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 0f029820de4..a2bc2b63060 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.7.2-SNAPSHOT + 7.8.0 sentry-stub From 2ddae94497bd48191eebdd1a44b149ef64b7cfa9 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Sat, 15 Oct 2016 09:15:42 -0500 Subject: [PATCH 1473/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 8ad3cbfc3e7..30705f72651 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.8.0 + 7.8.1-SNAPSHOT pom Raven-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v7.8.0 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index be47ac477d6..44450d600ae 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.0 + 7.8.1-SNAPSHOT raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 61431eedee3..810adcb835e 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.8.0 + 7.8.1-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 15b3265a1f7..81651fe3f42 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.0 + 7.8.1-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index fa4cab65dc9..84babb87677 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.0 + 7.8.1-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 8125be8d1a2..b002e44b33d 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.0 + 7.8.1-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index d9c66b72537..e9eea633c3f 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.0 + 7.8.1-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index a2bc2b63060..c9cc159ee49 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.0 + 7.8.1-SNAPSHOT sentry-stub From 99a36a54f593809fb68863304498964c87e71d89 Mon Sep 17 00:00:00 2001 From: ted kaemming Date: Tue, 1 Nov 2016 11:08:46 -0700 Subject: [PATCH 1474/2152] Fix dead link in README. Fixes GH-271. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 906394f2eb3..e7a0295c0fe 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ To use it with maven, add the following repository: ``` ## Android -Raven works on Android, please see the [Android README](https://github.com/getsentry/raven-java/blob/raven-android-start/raven-android/README.md). +Raven also works on Android. For integration details, see the [raven-android README](https://github.com/getsentry/raven-java/blob/master/raven-android/README.md). ## HTTP Request Context If the runtime environment utilizes Servlets, events that are created during From 9c85fe81ec43493f43b786a92ae30385ff4fef8a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 4 Nov 2016 12:40:43 -0500 Subject: [PATCH 1475/2152] Don't log errors in RavenServletRequestListener if Raven hasn't been initialized. (#270) --- .../src/main/java/com/getsentry/raven/Raven.java | 16 +++++++++++----- .../servlet/RavenServletRequestListener.java | 5 ++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/raven/src/main/java/com/getsentry/raven/Raven.java b/raven/src/main/java/com/getsentry/raven/Raven.java index 3ff9fc7769a..0623ee1e518 100644 --- a/raven/src/main/java/com/getsentry/raven/Raven.java +++ b/raven/src/main/java/com/getsentry/raven/Raven.java @@ -198,17 +198,20 @@ public String toString() { // -------------------------------------------------------- /** - * Returns the last statically stored Raven instance or throws a {@link NullPointerException} - * if one hasn't been constructed and stored yet. + * Returns the last statically stored Raven instance or null if one has + * never been stored. * * @return statically stored {@link Raven} instance */ public static Raven getStoredInstance() { + return stored; + } + + private static void verifyStoredInstance() { if (stored == null) { throw new NullPointerException("No stored Raven instance is available to use." + " You must construct a Raven instance before using the static Raven methods."); } - return stored; } /** @@ -217,6 +220,7 @@ public static Raven getStoredInstance() { * @param event Event to send to the Sentry server */ public static void capture(Event event) { + verifyStoredInstance(); getStoredInstance().sendEvent(event); } @@ -228,6 +232,7 @@ public static void capture(Event event) { * @param throwable exception to send to Sentry. */ public static void capture(Throwable throwable) { + verifyStoredInstance(); getStoredInstance().sendException(throwable); } @@ -239,6 +244,7 @@ public static void capture(Throwable throwable) { * @param message message to send to Sentry. */ public static void capture(String message) { + verifyStoredInstance(); getStoredInstance().sendMessage(message); } @@ -248,8 +254,8 @@ public static void capture(String message) { * @param eventBuilder {@link EventBuilder} to send to Sentry. */ public static void capture(EventBuilder eventBuilder) { - getStoredInstance(); - stored.sendEvent(eventBuilder); + verifyStoredInstance(); + getStoredInstance().sendEvent(eventBuilder); } } diff --git a/raven/src/main/java/com/getsentry/raven/servlet/RavenServletRequestListener.java b/raven/src/main/java/com/getsentry/raven/servlet/RavenServletRequestListener.java index 1922f9fb92b..60f3004e504 100644 --- a/raven/src/main/java/com/getsentry/raven/servlet/RavenServletRequestListener.java +++ b/raven/src/main/java/com/getsentry/raven/servlet/RavenServletRequestListener.java @@ -28,7 +28,10 @@ public void requestDestroyed(ServletRequestEvent servletRequestEvent) { THREAD_REQUEST.remove(); try { - Raven.getStoredInstance().getContext().clear(); + Raven raven = Raven.getStoredInstance(); + if (raven != null) { + raven.getContext().clear(); + } } catch (Exception e) { logger.error("Error clearing RavenContext state.", e); } From fa976688595388633f1b9dee48e2bb6482012ccf Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 11 Nov 2016 10:55:38 -0600 Subject: [PATCH 1476/2152] Make Breadcrumbs serializable. --- raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java b/raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java index 45e9e725809..60e5e91696c 100644 --- a/raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java +++ b/raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java @@ -1,5 +1,6 @@ package com.getsentry.raven.event; +import java.io.Serializable; import java.util.Date; import java.util.Map; @@ -8,7 +9,7 @@ * of breadcrumbs that help users re-create the path of actions that occurred * which lead to the Event happening. */ -public class Breadcrumb { +public class Breadcrumb implements Serializable { /** * (Optional) Type of the breadcrumb. From df816d46552b33354ea0d70f2038e4e89a5af478 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 11 Nov 2016 14:03:02 -0600 Subject: [PATCH 1477/2152] Serialize a Breadcrumb in tests. --- .../com/getsentry/raven/buffer/DiskBufferTest.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java b/raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java index 2d79369a5df..5e25017422e 100644 --- a/raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java +++ b/raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java @@ -1,5 +1,8 @@ package com.getsentry.raven.buffer; +import com.beust.jcommander.internal.Lists; +import com.getsentry.raven.event.Breadcrumb; +import com.getsentry.raven.event.BreadcrumbBuilder; import com.getsentry.raven.event.Event; import com.getsentry.raven.event.EventBuilder; import org.testng.annotations.AfterMethod; @@ -9,6 +12,7 @@ import java.io.File; import java.io.IOException; import java.util.Iterator; +import java.util.List; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -32,7 +36,15 @@ public void teardown() { @Test public void testAddAndDiscard() throws IOException { - Event event1 = new EventBuilder().build(); + Breadcrumb breadcrumb = new BreadcrumbBuilder() + .setLevel("INFO") + .setCategory("CATEGORY") + .setMessage("MESSAGE") + .build(); + List breadcrumbs = Lists.newArrayList(); + breadcrumbs.add(breadcrumb); + + Event event1 = new EventBuilder().withBreadcrumbs(breadcrumbs).build(); buffer.add(event1); // 1 event is buffered assertThat(eventCount(buffer.getEvents()), equalTo(1)); From 868e6b85926aada16f5f4605ac18e4c015cfabe3 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 11 Nov 2016 14:14:26 -0600 Subject: [PATCH 1478/2152] Fix import. --- .../test/java/com/getsentry/raven/buffer/DiskBufferTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java b/raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java index 5e25017422e..86f395a9be5 100644 --- a/raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java +++ b/raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java @@ -1,6 +1,5 @@ package com.getsentry.raven.buffer; -import com.beust.jcommander.internal.Lists; import com.getsentry.raven.event.Breadcrumb; import com.getsentry.raven.event.BreadcrumbBuilder; import com.getsentry.raven.event.Event; @@ -8,6 +7,7 @@ import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import org.testng.collections.Lists; import java.io.File; import java.io.IOException; From 8b782e8705c9d3a51970109863bb748f98a8737b Mon Sep 17 00:00:00 2001 From: mozillazg Date: Sun, 20 Nov 2016 19:38:56 +0800 Subject: [PATCH 1479/2152] update docs about .level fix "Bad level value for property: .level" --- docs/modules/raven.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index b25e807e4ad..7266255b7f6 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -39,7 +39,7 @@ following configuration (``logging.properties``) gets you started: .. sourcecode:: ini - .level=WARN + .level=WARNING handlers=com.getsentry.raven.jul.SentryHandler com.getsentry.raven.jul.SentryHandler.dsn=___DSN___ com.getsentry.raven.jul.SentryHandler.tags=tag1:value1,tag2:value2 From 9bd827bce0a74a9c5ea85c8a114869a4805ef450 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 2 Dec 2016 08:47:15 -0600 Subject: [PATCH 1480/2152] Update CHANGES. --- CHANGES | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES b/CHANGES index 93cfdcb2607..0574889a528 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +Version 7.8.1 +------------- + +- Make Breadcrumbs serializable. +- Don't log errors in RavenServletRequestListener if Raven hasn't been initialized. + Version 7.8.0 ------------- From 361cd2d37b0dbab1fb75710ff762e8bf17b0673e Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 2 Dec 2016 08:47:40 -0600 Subject: [PATCH 1481/2152] Bump CHANGES and docs to 7.8.1 --- docs/modules/appengine.rst | 2 +- docs/modules/log4j.rst | 2 +- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 2 +- docs/modules/raven.rst | 2 +- raven-android/README.md | 4 ++-- raven-appengine/README.md | 4 ++-- raven-log4j/README.md | 4 ++-- raven-log4j2/README.md | 10 +++++----- raven-logback/README.md | 4 ++-- raven/README.md | 4 ++-- 11 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 45f95fca2e1..aa0c6a7ae92 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -25,7 +25,7 @@ If you want to use Maven you can install Raven-AppEngine as dependency: com.getsentry.raven raven-appengine - 7.8.0 + 7.8.1 Usage diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 1e45a5681ff..b11e0d5edbb 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven-Log4j as dependency: com.getsentry.raven raven-log4j - 7.8.0 + 7.8.1 If you manually want to manage your dependencies: diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 976fc33372c..643755aae5e 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,18 +19,18 @@ If you want to use Maven you can install Raven-Log4j2 as dependency: com.getsentry.raven raven-log4j2 - 7.8.0 + 7.8.1 If you manually want to manage your dependencies: - :doc:`raven dependencies ` - `log4j-api-2.1.jar - `_ + `_ - `log4j-core-2.0.jar - `_ + `_ - `log4j-slf4j-impl-2.1.jar - `_ + `_ is recommended as the implementation of slf4j (instead of slf4j-jdk14). Usage diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index b8cc2a92f49..9f0ff67e785 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -16,7 +16,7 @@ If you want to use Maven you can install Raven-Logback as dependency: com.getsentry.raven raven-logback - 7.8.0 + 7.8.1 - :doc:`raven dependencies ` diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index 7266255b7f6..8b3a7e144c4 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven as dependency: com.getsentry.raven raven - 7.8.0 + 7.8.1 If you manually want to manage your dependencies: diff --git a/raven-android/README.md b/raven-android/README.md index eaed2e27548..b8f6ff8b083 100644 --- a/raven-android/README.md +++ b/raven-android/README.md @@ -4,10 +4,10 @@ ### Gradle (Android Studio) -In your `app/build.gradle` add: `compile 'com.getsentry.raven:raven-android:7.8.0'` +In your `app/build.gradle` add: `compile 'com.getsentry.raven:raven-android:7.8.1'` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-android%7C7.8.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-android%7C7.8.1%7Cjar). ## Usage diff --git a/raven-appengine/README.md b/raven-appengine/README.md index 1634a40d392..e0ede41417a 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -15,12 +15,12 @@ __This module is not useful outside of Google App Engine.__ com.getsentry.raven raven-appengine - 7.8.0 + 7.8.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.8.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.8.1%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 36f94e27b77..d9dd9047744 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,12 +10,12 @@ for log4j to send the logged events to Sentry. com.getsentry.raven raven-log4j - 7.8.0 + 7.8.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.8.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.8.1%7Cjar). ### Manual dependency management Relies on: diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index d8d9ca6fe04..f698db5a5a5 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,20 +10,20 @@ for Log4j 2 to send the logged events to Sentry. com.getsentry.raven raven-log4j2 - 7.8.0 + 7.8.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.8.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.8.1%7Cjar). ### Manual dependency management Relies on: - [raven dependencies](../raven) - - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.8.0%7Cjar) - - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.8.0%7Cjar) - - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.8.0%7Cjar) + - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.8.1%7Cjar) + - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.8.1%7Cjar) + - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.8.1%7Cjar) is recommended as the implementation of slf4j (instead of slf4j-jdk14). diff --git a/raven-logback/README.md b/raven-logback/README.md index ed68fb49307..cd1195013af 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,12 +10,12 @@ for logback to send the logged events to Sentry. com.getsentry.raven raven-logback - 7.8.0 + 7.8.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.8.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.8.1%7Cjar). ### Manual dependency management Relies on: diff --git a/raven/README.md b/raven/README.md index 7153ee7754e..d7fcb6371b7 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,12 +10,12 @@ for `java.util.logging`. com.getsentry.raven raven - 7.8.0 + 7.8.1 ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.8.0%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.8.1%7Cjar). ### Manual dependency management Relies on: From 41a175c859722a11c04b9809b22aa04e9aab41d9 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 2 Dec 2016 08:58:45 -0600 Subject: [PATCH 1482/2152] [maven-release-plugin] prepare release v7.8.1 --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 30705f72651..9e5a861a306 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.8.1-SNAPSHOT + 7.8.1 pom Raven-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v7.8.1 https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index 44450d600ae..c302405e39c 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.1-SNAPSHOT + 7.8.1 raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 810adcb835e..385716ff686 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.8.1-SNAPSHOT + 7.8.1 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 81651fe3f42..e27bf3ba91e 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.1-SNAPSHOT + 7.8.1 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 84babb87677..ceaa1bacf96 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.1-SNAPSHOT + 7.8.1 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index b002e44b33d..1d4e62b8c93 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.1-SNAPSHOT + 7.8.1 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index e9eea633c3f..7f02f5bb7ff 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.1-SNAPSHOT + 7.8.1 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index c9cc159ee49..ab70e43cbeb 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.1-SNAPSHOT + 7.8.1 sentry-stub From 8972d80d88eb6bc5a71c069ad35e3bdc041c96b0 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 2 Dec 2016 08:58:46 -0600 Subject: [PATCH 1483/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 9e5a861a306..4347897ef89 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.8.1 + 7.8.2-SNAPSHOT pom Raven-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v7.8.1 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index c302405e39c..360caffbf4a 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.1 + 7.8.2-SNAPSHOT raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 385716ff686..ac7cf70e45a 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.8.1 + 7.8.2-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index e27bf3ba91e..1acb9fe2969 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.1 + 7.8.2-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index ceaa1bacf96..46df5349fa0 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.1 + 7.8.2-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 1d4e62b8c93..57957471279 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.1 + 7.8.2-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 7f02f5bb7ff..8119d095f62 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.1 + 7.8.2-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index ab70e43cbeb..407e131859f 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.1 + 7.8.2-SNAPSHOT sentry-stub From 2ca173cea1e594d6ad3295e6cbdb595452c92b1c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 2 Dec 2016 15:27:10 -0600 Subject: [PATCH 1484/2152] =?UTF-8?q?Document=20ServiceLoader=20provider?= =?UTF-8?q?=20file=20requirement=20for=20custom=20RavenFact=E2=80=A6=20(#2?= =?UTF-8?q?80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e7a0295c0fe..d9c5b0bd59e 100644 --- a/README.md +++ b/README.md @@ -344,5 +344,10 @@ public class MyRavenFactory extends DefaultRavenFactory { } ``` -See the README for the logger integration you use to find out how to -make use of your own RavenFactory. +You'll need to add a `ServiceLoader` provider file to your project at +`src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory` that contains +the name of your class so that it will be considered as a candidate `RavenFactory`. For an example, see +[how we configure the DefaultRavenFactory itself](https://github.com/getsentry/raven-java/blob/master/raven/src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory). + +Finally, see the `README` for the logger integration you use to find out how to +configure it to use your custom `RavenFactory`. From a6a9edfa3de1ba73fa1abad7ba8dd817820b3da5 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 2 Dec 2016 15:27:21 -0600 Subject: [PATCH 1485/2152] Drop existing contexts before running context test. (#279) --- raven/src/main/java/com/getsentry/raven/Raven.java | 2 +- .../src/test/java/com/getsentry/raven/RavenContextTest.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/raven/src/main/java/com/getsentry/raven/Raven.java b/raven/src/main/java/com/getsentry/raven/Raven.java index 0623ee1e518..8116f6ae3ba 100644 --- a/raven/src/main/java/com/getsentry/raven/Raven.java +++ b/raven/src/main/java/com/getsentry/raven/Raven.java @@ -96,7 +96,7 @@ public void sendEvent(Event event) { } catch (Exception e) { logger.error("An exception occurred while sending the event to Sentry.", e); } finally { - context.get().setLastEventId(event.getId()); + getContext().setLastEventId(event.getId()); } } diff --git a/raven/src/test/java/com/getsentry/raven/RavenContextTest.java b/raven/src/test/java/com/getsentry/raven/RavenContextTest.java index adef64068b6..b9b6aa0c8c8 100644 --- a/raven/src/test/java/com/getsentry/raven/RavenContextTest.java +++ b/raven/src/test/java/com/getsentry/raven/RavenContextTest.java @@ -13,10 +13,15 @@ import static org.hamcrest.Matchers.emptyCollectionOf; import static org.hamcrest.Matchers.equalTo; +@Test(singleThreaded = true) public class RavenContextTest { @Test public void testActivateDeactivate() { + for (RavenContext context : RavenContext.getActiveContexts()) { + context.deactivate(); + } + RavenContext context = new RavenContext(); assertThat(RavenContext.getActiveContexts(), emptyCollectionOf(RavenContext.class)); From 97793d19d696d757ca56a65067cd22b39a2d7fc6 Mon Sep 17 00:00:00 2001 From: Rob van der Linden Vooren Date: Mon, 30 Jan 2017 17:13:30 +0100 Subject: [PATCH 1486/2152] Restore thread interrupt status upon InterruptedException (#289) --- .../java/com/getsentry/raven/connection/AbstractConnection.java | 1 + .../java/com/getsentry/raven/connection/AsyncConnection.java | 1 + .../java/com/getsentry/raven/connection/BufferedConnection.java | 1 + 3 files changed, 3 insertions(+) diff --git a/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java b/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java index 2268e7767c5..3af63b524c0 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java @@ -112,6 +112,7 @@ private void waitIfLockedDown() { if (lockdown.get()) condition.await(); } catch (InterruptedException e) { + Thread.currentThread().interrupt(); logger.warn("An exception occurred during the lockdown.", e); } finally { lock.unlock(); diff --git a/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java b/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java index 1717ab2b68c..7e5aacd1e2f 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java @@ -137,6 +137,7 @@ private void doClose() throws IOException { } logger.info("Shutdown finished."); } catch (InterruptedException e) { + Thread.currentThread().interrupt(); logger.error("Graceful shutdown interrupted, forcing the shutdown."); List tasks = executorService.shutdownNow(); logger.info("{} tasks failed to execute before the shutdown.", tasks.size()); diff --git a/raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java b/raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java index c4773a29150..2c08fe99fb8 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java @@ -129,6 +129,7 @@ public void close() throws IOException { } logger.info("Shutdown finished."); } catch (InterruptedException e) { + Thread.currentThread().interrupt(); logger.error("Graceful shutdown interrupted, forcing the shutdown."); List tasks = executorService.shutdownNow(); logger.info("{} tasks failed to execute before the shutdown.", tasks.size()); From 8d8230eb9f28e4f6cdd5f1f48206576fd177b405 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 31 Jan 2017 17:09:06 -0600 Subject: [PATCH 1487/2152] Allow use of custom AndroidRavenFactory (thanks kassim) --- CHANGES | 5 +++ .../com/getsentry/raven/android/Raven.java | 44 +++++++++++++++++-- .../com/getsentry/raven/android/RavenIT.java | 3 +- .../raven/android/RavenITActivity.java | 24 +++++++++- 4 files changed, 70 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 0574889a528..40108be75e0 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 7.8.2 +------------- + +- Allow use of custom AndroidRavenFactory. (thanks kassim) + Version 7.8.1 ------------- diff --git a/raven-android/src/main/java/com/getsentry/raven/android/Raven.java b/raven-android/src/main/java/com/getsentry/raven/android/Raven.java index 24b85ba7c06..575606dbf00 100644 --- a/raven-android/src/main/java/com/getsentry/raven/android/Raven.java +++ b/raven-android/src/main/java/com/getsentry/raven/android/Raven.java @@ -38,6 +38,16 @@ private Raven() { * @param ctx Android application ctx */ public static void init(Context ctx) { + init(ctx, getDefaultRavenFactory(ctx)); + } + + /** + * Initialize Raven using a DSN set in the AndroidManifest. + * + * @param ctx Android application ctx + * @param ravenFactory the RavenFactory to be used to generate the Raven instance + */ + public static void init(Context ctx, AndroidRavenFactory ravenFactory) { ctx = ctx.getApplicationContext(); String dsn = ""; @@ -56,7 +66,7 @@ public static void init(Context ctx) { + "the constructor or AndroidManifest."); } - init(ctx, new Dsn(dsn)); + init(ctx, dsn, ravenFactory); } /** @@ -66,7 +76,28 @@ public static void init(Context ctx) { * @param dsn Sentry DSN string */ public static void init(Context ctx, String dsn) { - init(ctx, new Dsn(dsn)); + init(ctx, new Dsn(dsn), getDefaultRavenFactory(ctx)); + } + + /** + * Initialize Raven using a string DSN. + * + * @param ctx Android application ctx + * @param dsn Sentry DSN string + * @param ravenFactory the RavenFactory to be used to generate the Raven instance + */ + public static void init(Context ctx, String dsn, AndroidRavenFactory ravenFactory) { + init(ctx, new Dsn(dsn), ravenFactory); + } + + /** + * Initialize Raven using a DSN object. + * + * @param ctx Android application ctx + * @param dsn Sentry DSN object + */ + public static void init(Context ctx, Dsn dsn) { + init(ctx, dsn, getDefaultRavenFactory(ctx)); } /** @@ -75,8 +106,9 @@ public static void init(Context ctx, String dsn) { * * @param ctx Android application ctx * @param dsn Sentry DSN object + * @param ravenFactory the RavenFactory to be used to generate the Raven instance */ - public static void init(Context ctx, Dsn dsn) { + public static void init(Context ctx, Dsn dsn, AndroidRavenFactory ravenFactory) { if (raven != null) { Log.e(TAG, "Initializing Raven multiple times."); // cleanup existing connections @@ -104,12 +136,16 @@ public static void init(Context ctx, Dsn dsn) { + DefaultRavenFactory.ASYNC_OPTION + "=false' from your DSN."); } - RavenFactory.registerFactory(new AndroidRavenFactory(ctx)); + RavenFactory.registerFactory(ravenFactory); raven = RavenFactory.ravenInstance(dsn); setupUncaughtExceptionHandler(); } + private static AndroidRavenFactory getDefaultRavenFactory(Context ctx) { + return new AndroidRavenFactory(ctx); + } + /** * Configures an Android uncaught exception handler which sends events to * Sentry, then calls the preexisting uncaught exception handler. diff --git a/raven-android/src/test/java/com/getsentry/raven/android/RavenIT.java b/raven-android/src/test/java/com/getsentry/raven/android/RavenIT.java index 1ccf3b5408c..332c838a830 100644 --- a/raven-android/src/test/java/com/getsentry/raven/android/RavenIT.java +++ b/raven-android/src/test/java/com/getsentry/raven/android/RavenIT.java @@ -16,8 +16,9 @@ public void test() throws Exception { Assert.assertEquals(sentryStub.getEventCount(), 0); RavenITActivity activity = Robolectric.setupActivity(RavenITActivity.class); - activity.sendEvent(); + Assert.assertEquals(activity.getCustomFactoryUsed(), true); + activity.sendEvent(); waitUntilTrue(1000, new Callable() { @Override public Boolean call() throws Exception { diff --git a/raven-android/src/test/java/com/getsentry/raven/android/RavenITActivity.java b/raven-android/src/test/java/com/getsentry/raven/android/RavenITActivity.java index 4164b5b2ef4..99c1af96b96 100644 --- a/raven-android/src/test/java/com/getsentry/raven/android/RavenITActivity.java +++ b/raven-android/src/test/java/com/getsentry/raven/android/RavenITActivity.java @@ -1,20 +1,42 @@ package com.getsentry.raven.android; import android.app.Activity; +import android.content.Context; import android.os.Bundle; +import java.util.concurrent.atomic.AtomicBoolean; + public class RavenITActivity extends Activity { + private AtomicBoolean customFactoryUsed = new AtomicBoolean(false); + + class CustomAndroidRavenFactory extends AndroidRavenFactory { + /** + * Construct an AndroidRavenFactory using the specified Android Context. + * + * @param ctx Android Context. + */ + public CustomAndroidRavenFactory(Context ctx) { + super(ctx); + customFactoryUsed.set(true); + } + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Raven.init( this.getApplicationContext(), - "http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1"); + "http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1", + new CustomAndroidRavenFactory(getApplicationContext())); } public void sendEvent() { Raven.capture("sendEvent()"); } + public boolean getCustomFactoryUsed() { + return customFactoryUsed.get(); + } + } From 1f1a33ec9626f5657eb4ccde603dc79ce3445a44 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 31 Jan 2017 17:09:38 -0600 Subject: [PATCH 1488/2152] =?UTF-8?q?Bump=20Maven=20plugins=20and=20deps,?= =?UTF-8?q?=20wait=20for=20Jetty=20test=20server=20to=20stop=20betwe?= =?UTF-8?q?=E2=80=A6=20(#295)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 53 +++++++++++++++++++++-------------------- raven-appengine/pom.xml | 9 +------ raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- 4 files changed, 30 insertions(+), 36 deletions(-) diff --git a/pom.xml b/pom.xml index 4347897ef89..17f475cbb6f 100644 --- a/pom.xml +++ b/pom.xml @@ -45,9 +45,9 @@ bretthoerner Brett Hoerner - brett@sentry.com + brett@sentry.io Sentry - https://getsentry.com/ + https://sentry.io/ @@ -117,9 +117,9 @@ 7 - 1.7.21 + 1.7.22 3.0.1 - 2.7.3 + 2.7.6 1.14 6.9.10 1.3 @@ -192,7 +192,7 @@ org.apache.maven.plugins maven-release-plugin - 2.5.1 + 2.5.3 v@{project.version} true @@ -209,7 +209,7 @@ com.github.github site-maven-plugin - 0.10 + 0.12 Creating site for ${project.artifactId} ${project.version} ${project.distributionManagement.site.url} @@ -228,7 +228,7 @@ org.apache.maven.plugins maven-site-plugin - 3.4 + 3.6 true @@ -236,7 +236,7 @@ org.codehaus.mojo animal-sniffer-maven-plugin - 1.13 + 1.15 org.codehaus.mojo.signature @@ -260,10 +260,11 @@ org.eclipse.jetty jetty-maven-plugin - 9.3.0.M1 + 9.2.21.v20170120 10 foo + 10 9999 ${project.build.directory}/webapps/sentry-stub.war @@ -292,12 +293,12 @@ org.apache.maven.plugins maven-surefire-plugin - 2.18.1 + 2.19.1 org.apache.maven.plugins maven-failsafe-plugin - 2.18.1 + 2.19.1 integration-tests @@ -311,7 +312,7 @@ org.apache.maven.plugins maven-dependency-plugin - 2.9 + 3.0.0 analyze @@ -377,11 +378,11 @@ maven-clean-plugin - 2.6.1 + 3.0.0 maven-compiler-plugin - 3.2 + 3.6.1 maven-deploy-plugin @@ -393,19 +394,19 @@ maven-jar-plugin - 2.5 + 3.0.2 maven-resources-plugin - 2.7 + 3.0.2 maven-site-plugin - 3.4 + 3.6 maven-war-plugin - 2.5 + 3.0.0 @@ -416,12 +417,12 @@ org.apache.maven.plugins maven-project-info-reports-plugin - 2.7 + 2.9 org.apache.maven.plugins maven-javadoc-plugin - 2.10.3 + 2.10.4 com.getsentry.raven.util.base64*:com.getsentry.raven.sentrystub.util.base64 @@ -429,7 +430,7 @@ org.apache.maven.plugins maven-surefire-report-plugin - 2.18.1 + 2.19.1 org.apache.maven.plugins @@ -456,7 +457,7 @@ org.apache.maven.plugins maven-source-plugin - 2.4 + 3.0.1 attach-sources @@ -469,7 +470,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.10.1 + 2.10.4 attach-javadocs @@ -482,7 +483,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.5 + 1.6 sign-artifacts @@ -496,7 +497,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.5 + 1.6.7 true sonatype-nexus-staging @@ -527,7 +528,7 @@ org.pitest pitest-maven - 1.1.3 + 1.1.11 *Test diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index ac7cf70e45a..ea4f137d5eb 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -14,8 +14,7 @@ Raven module allowing to use GoogleApp Engine and its task queueing system. - 1.9.34 - 1.1.7 + 1.9.49 @@ -33,12 +32,6 @@ slf4j-api - - ch.qos.logback - logback-classic - ${logback.version} - test - org.jmockit jmockit diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 46df5349fa0..4eb983661ae 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -15,7 +15,7 @@ log4j2 appender allowing to send logs to the Raven-Java client. - 2.5 + 2.8 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 57957471279..1d84040bcbe 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -15,7 +15,7 @@ Logback appender allowing to send logs to the Raven-Java client. - 1.1.7 + 1.1.9 From dcb8eba84eb183d57847e4b6d0cf1f7f8c9589f2 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 1 Feb 2017 13:34:15 -0600 Subject: [PATCH 1489/2152] =?UTF-8?q?Add=20``raven.sample.rate``=20DSN=20o?= =?UTF-8?q?ption=20to=20optionally=20reduce=20the=20number=20=E2=80=A6=20(?= =?UTF-8?q?#301)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES | 1 + README.md | 47 ++++++++++------- .../getsentry/raven/DefaultRavenFactory.java | 24 ++++++++- .../raven/connection/EventSampler.java | 17 +++++++ .../raven/connection/HttpConnection.java | 18 +++++-- .../raven/connection/RandomEventSampler.java | 51 +++++++++++++++++++ .../java/com/getsentry/raven/util/Util.java | 19 ++++++- .../connection/RandomEventSamplerTest.java | 36 +++++++++++++ 8 files changed, 187 insertions(+), 26 deletions(-) create mode 100644 raven/src/main/java/com/getsentry/raven/connection/EventSampler.java create mode 100644 raven/src/main/java/com/getsentry/raven/connection/RandomEventSampler.java create mode 100644 raven/src/test/java/com/getsentry/raven/connection/RandomEventSamplerTest.java diff --git a/CHANGES b/CHANGES index 40108be75e0..e723e7c7cbf 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 7.8.2 ------------- - Allow use of custom AndroidRavenFactory. (thanks kassim) +- Add ``raven.sample.rate`` DSN option to optionally reduce the number of events sent to the server. Version 7.8.1 ------------- diff --git a/README.md b/README.md index d9c5b0bd59e..51cc58b05c7 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,28 @@ with the option `raven.async.priority`: http://public:private@host:port/1?raven.async.priority=10 +#### Graceful Shutdown (advanced) +In order to shutdown the buffer flushing thread gracefully, a `ShutdownHook` +is created. By default, the buffer flushing thread is given 1 second +to shutdown gracefully, but this can be adjusted via +`raven.buffer.shutdowntimeout` (represented in milliseconds): + + http://public:private@host:port/1?raven.buffer.shutdowntimeout=5000 + +The special value `-1` can be used to disable the timeout and wait +indefinitely for the executor to terminate. + +The `ShutdownHook` could lead to memory leaks in an environment where +the life cycle of Raven doesn't match the life cycle of the JVM. + +An example would be in a JEE environment where the application using Raven +could be deployed and undeployed regularly. + +To avoid this behaviour, it is possible to disable the graceful shutdown +by setting the `raven.buffer.gracefulshutdown` option: + + http://public:private@host:port/1?raven.buffer.gracefulshutdown=false + ### Buffering to disk upon network error Raven can be configured to write events to a specified directory on disk anytime communication with the Sentry server fails with the `raven.buffer.dir` @@ -217,27 +239,14 @@ attempt to send events every 60 seconds. You can change this with the http://public:private@host:port/1?raven.buffer.flushtime=10000 -#### Graceful Shutdown (advanced) -In order to shutdown the buffer flushing thread gracefully, a `ShutdownHook` -is created. By default, the buffer flushing thread is given 1 second -to shutdown gracefully, but this can be adjusted via -`raven.buffer.shutdowntimeout` (represented in milliseconds): - - http://public:private@host:port/1?raven.buffer.shutdowntimeout=5000 - -The special value `-1` can be used to disable the timeout and wait -indefinitely for the executor to terminate. - -The `ShutdownHook` could lead to memory leaks in an environment where -the life cycle of Raven doesn't match the life cycle of the JVM. - -An example would be in a JEE environment where the application using Raven -could be deployed and undeployed regularly. +### Event sampling +Raven can be configured to sample events with the `raven.sample.rate` option: -To avoid this behaviour, it is possible to disable the graceful shutdown -by setting the `raven.buffer.gracefulshutdown` option: + http://public:private@host:port/1?raven.sample.rate=0.75 - http://public:private@host:port/1?raven.buffer.gracefulshutdown=false +This option takes a number from 0.0 to 1.0, representing the percent of +events to allow through to server (from 0% to 100%). By default all +events will be sent to the Sentry server. ### Inapp classes Sentry differentiate `in_app` stack frames (which are directly related to your application) diff --git a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java index b14b6c32d46..a70755b2e5d 100644 --- a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java @@ -71,7 +71,7 @@ public class DefaultRavenFactory extends RavenFactory { /** * Default number of milliseconds between attempts to flush buffered events. */ - public static final int BUFFER_FLUSHTIME_DEFAULT = 60000; + public static final long BUFFER_FLUSHTIME_DEFAULT = 60000; /** * Option to disable the graceful shutdown of the buffer flusher. */ @@ -138,6 +138,10 @@ public class DefaultRavenFactory extends RavenFactory { * Option for whether to hide common stackframes with enclosing exceptions. */ public static final String HIDE_COMMON_FRAMES_OPTION = "raven.stacktrace.hidecommon"; + /** + * Option for whether to sample events, allowing from 0.0 to 1.0 (0 to 100%) to be sent to the server. + */ + public static final String SAMPLE_RATE_OPTION = "raven.sample.rate"; /** * Option to set an HTTP proxy hostname for Sentry connections. */ @@ -271,8 +275,14 @@ protected Connection createHttpConnection(Dsn dsn) { proxy = new Proxy(Proxy.Type.HTTP, proxyAddr); } + Double sampleRate = getSampleRate(dsn); + EventSampler eventSampler = null; + if (sampleRate != null) { + eventSampler = new RandomEventSampler(sampleRate); + } + HttpConnection httpConnection = new HttpConnection(sentryApiUrl, dsn.getPublicKey(), - dsn.getSecretKey(), proxy); + dsn.getSecretKey(), proxy, eventSampler); Marshaller marshaller = createMarshaller(dsn); httpConnection.setMarshaller(marshaller); @@ -473,6 +483,16 @@ protected boolean getBypassSecurityEnabled(Dsn dsn) { return dsn.getProtocolSettings().contains(NAIVE_PROTOCOL); } + /** + * Whether to sample events, and if so how much to allow through to the server (from 0.0 to 1.0). + * + * @param dsn Sentry server DSN which may contain options. + * @return The ratio of events to allow through to server, or null if sampling is disabled. + */ + protected Double getSampleRate(Dsn dsn) { + return Util.parseDouble(dsn.getOptions().get(SAMPLE_RATE_OPTION), null); + } + /** * HTTP proxy port for Sentry connections. * diff --git a/raven/src/main/java/com/getsentry/raven/connection/EventSampler.java b/raven/src/main/java/com/getsentry/raven/connection/EventSampler.java new file mode 100644 index 00000000000..e5c573015a2 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/connection/EventSampler.java @@ -0,0 +1,17 @@ +package com.getsentry.raven.connection; + +import com.getsentry.raven.event.Event; + +/** + * Used by {@link HttpConnection} to decide whether a specific event should actually be + * send to the server or not. + */ +public interface EventSampler { + /** + * Decides whether a specific event should be sent to the server or not. + * + * @param event Event to be checked against the sampling logic. + * @return True if the event should be sent to the server, else False. + */ + boolean shouldSendEvent(Event event); +} diff --git a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java b/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java index cab8fa06b01..c69c28c5f47 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java @@ -55,6 +55,10 @@ public boolean verify(String hostname, SSLSession sslSession) { * Optional instance of an HTTP proxy server to use. */ private final Proxy proxy; + /** + * Optional instance of an EventSampler to use. + */ + private EventSampler eventSampler; /** * Marshaller used to transform and send the {@link Event} over a stream. */ @@ -75,23 +79,27 @@ public boolean verify(String hostname, SSLSession sslSession) { * @param sentryUrl URL to the Sentry API. * @param publicKey public key of the current project. * @param secretKey private key of the current project. + * @deprecated use the more explicit constructor below */ + @Deprecated public HttpConnection(URL sentryUrl, String publicKey, String secretKey) { - this(sentryUrl, publicKey, secretKey, null); + this(sentryUrl, publicKey, secretKey, null, null); } - /** + /** * Creates an HTTP connection to a Sentry server. * * @param sentryUrl URL to the Sentry API. * @param publicKey public key of the current project. * @param secretKey private key of the current project. * @param proxy address of HTTP proxy or null if using direct connections. + * @param eventSampler EventSampler instance to use, or null to not sample events. */ - public HttpConnection(URL sentryUrl, String publicKey, String secretKey, Proxy proxy) { + public HttpConnection(URL sentryUrl, String publicKey, String secretKey, Proxy proxy, EventSampler eventSampler) { super(publicKey, secretKey); this.sentryUrl = sentryUrl; this.proxy = proxy; + this.eventSampler = eventSampler; } /** @@ -140,6 +148,10 @@ protected HttpURLConnection getConnection() { @Override protected void doSend(Event event) throws ConnectionException { + if (eventSampler != null && !eventSampler.shouldSendEvent(event)) { + return; + } + HttpURLConnection connection = getConnection(); try { connection.connect(); diff --git a/raven/src/main/java/com/getsentry/raven/connection/RandomEventSampler.java b/raven/src/main/java/com/getsentry/raven/connection/RandomEventSampler.java new file mode 100644 index 00000000000..b509d8ca7b2 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/connection/RandomEventSampler.java @@ -0,0 +1,51 @@ +package com.getsentry.raven.connection; + +import com.getsentry.raven.event.Event; + +import java.util.Random; + +/** + * Decide whether {@link Event}s should be sent to the server by applying a + * sample rate to random bits from the Event's ID. + */ +public class RandomEventSampler implements EventSampler { + private static final int RATE_MULTIPLIER = 100; + + private int sampleRate; + private Random random; + + /** + * Construct a RandomEventSampler with the given sampleRate (from 0.0 to 1.0). + * + * @param sampleRate ratio of events to allow through to the server (from 0.0 to 1.0). + */ + public RandomEventSampler(double sampleRate) { + this(sampleRate, new Random()); + } + + /** + * Construct a RandomEventSampler with the given sampleRate (from 0.0 to 1.0) + * and Random instance. + * + * This constructor is primarily visible for testing, you should use + * {@link RandomEventSampler#RandomEventSampler(double)}. + * + * @param sampleRate ratio of events to allow through to the server (from 0.0 to 1.0). + * @param random Random instance to use for sampling, useful for testing. + */ + public RandomEventSampler(double sampleRate, Random random) { + this.sampleRate = (int) (sampleRate * RATE_MULTIPLIER); + this.random = random; + } + + /** + * Handles event sampling logic. + * + * @param event Event to be checked against the sampling logic. + * @return True if the event should be sent to the server, else False. + */ + @Override + public boolean shouldSendEvent(Event event) { + return Math.abs(random.nextInt()) % RATE_MULTIPLIER < sampleRate; + } +} diff --git a/raven/src/main/java/com/getsentry/raven/util/Util.java b/raven/src/main/java/com/getsentry/raven/util/Util.java index 1351fa9a3ef..209266759b2 100644 --- a/raven/src/main/java/com/getsentry/raven/util/Util.java +++ b/raven/src/main/java/com/getsentry/raven/util/Util.java @@ -73,7 +73,7 @@ public static Set parseExtraTags(String extraTagsString) { * @param defaultValue default value * @return integer representation of provided value or default value. */ - public static int parseInteger(String value, int defaultValue) { + public static Integer parseInteger(String value, Integer defaultValue) { if (isNullOrEmpty(value)) { return defaultValue; } @@ -88,10 +88,25 @@ public static int parseInteger(String value, int defaultValue) { * @param defaultValue default value * @return long representation of provided value or default value. */ - public static long parseLong(String value, long defaultValue) { + public static Long parseLong(String value, Long defaultValue) { if (isNullOrEmpty(value)) { return defaultValue; } return Long.parseLong(value); } + + /** + * Parses the provided string value into a double value. + *

    If the string is null or empty this returns the default value.

    + * + * @param value value to parse + * @param defaultValue default value + * @return double representation of provided value or default value. + */ + public static Double parseDouble(String value, Double defaultValue) { + if (isNullOrEmpty(value)) { + return defaultValue; + } + return Double.parseDouble(value); + } } diff --git a/raven/src/test/java/com/getsentry/raven/connection/RandomEventSamplerTest.java b/raven/src/test/java/com/getsentry/raven/connection/RandomEventSamplerTest.java new file mode 100644 index 00000000000..624a6299356 --- /dev/null +++ b/raven/src/test/java/com/getsentry/raven/connection/RandomEventSamplerTest.java @@ -0,0 +1,36 @@ +package com.getsentry.raven.connection; + +import com.getsentry.raven.event.Event; +import com.getsentry.raven.event.EventBuilder; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.Random; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + + +public class RandomEventSamplerTest { + private Event event = new EventBuilder().build(); + private Random seededRandom = new Random(); + + @BeforeMethod + public void setup() { + // set our Random to a known seed such that nextInt % 100 == -25 + seededRandom.setSeed(1); + } + + @Test + public void testShouldSend() { + RandomEventSampler randomEventSampler = new RandomEventSampler(0.5, seededRandom); + assertThat(randomEventSampler.shouldSendEvent(event), is(true)); + } + + @Test + public void testShouldNotSend() { + RandomEventSampler randomEventSampler = new RandomEventSampler(0.1, seededRandom); + assertThat(randomEventSampler.shouldSendEvent(event), is(false)); + } + +} From 6fe2a6ce122b0bc887f5120f66fe2223ccee9a72 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 1 Feb 2017 13:34:28 -0600 Subject: [PATCH 1490/2152] =?UTF-8?q?Better=20error=20messages=20when=20cu?= =?UTF-8?q?stom=20RavenFactory=20isn't=20found=20or=20is=20miss=E2=80=A6?= =?UTF-8?q?=20(#300)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 18 ++++++++++++++ .../com/getsentry/raven/RavenFactory.java | 24 +++++++++++++++---- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 51cc58b05c7..b3d0e88ad30 100644 --- a/README.md +++ b/README.md @@ -353,10 +353,28 @@ public class MyRavenFactory extends DefaultRavenFactory { } ``` +### Registration +Next, you'll need to make your class known to Raven in one of two ways. + +#### Java ServiceLoader provider (recommended) You'll need to add a `ServiceLoader` provider file to your project at `src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory` that contains the name of your class so that it will be considered as a candidate `RavenFactory`. For an example, see [how we configure the DefaultRavenFactory itself](https://github.com/getsentry/raven-java/blob/master/raven/src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory). +#### Manual registration +You can also manually register your `RavenFactory` instance. Note that this should be done +early in your application lifecycle so that your factory is available the first time +you attempt to send an event to the Sentry server. +```java +class MyApp { + public static void main(String[] args) { + RavenFactory.registerFactory(new MyRavenFactory()); + // ... your app code ... + } +} +``` + +### Configuration Finally, see the `README` for the logger integration you use to find out how to configure it to use your custom `RavenFactory`. diff --git a/raven/src/main/java/com/getsentry/raven/RavenFactory.java b/raven/src/main/java/com/getsentry/raven/RavenFactory.java index be1c5c5eefd..a77e73b7f23 100644 --- a/raven/src/main/java/com/getsentry/raven/RavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/RavenFactory.java @@ -77,12 +77,12 @@ public static Raven ravenInstance(Dsn dsn) { * Creates an instance of Raven using the provided DSN and the specified factory. * * @param dsn Data Source Name of the Sentry server. - * @param ravenFactoryName name of the raven factory to use to generate an instance of Raven. + * @param ravenFactoryName name of the RavenFactory to use to generate an instance of Raven. * @return an instance of Raven. * @throws IllegalStateException when no instance of Raven has been created. */ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { - logger.debug("Attempting to find a working Raven factory"); + logger.debug("Attempting to find a working RavenFactory"); // Loop through registered factories, keeping track of which classes we skip, which we try to instantiate, // and the last exception thrown. @@ -97,15 +97,29 @@ public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { continue; } - logger.debug("Attempting to use '{}' as a Raven factory.", ravenFactory); + logger.debug("Attempting to use '{}' as a RavenFactory.", ravenFactory); triedFactories.add(name); try { Raven ravenInstance = ravenFactory.createRavenInstance(dsn); - logger.debug("The raven factory '{}' created an instance of Raven.", ravenFactory); + logger.debug("The RavenFactory '{}' created an instance of Raven.", ravenFactory); return ravenInstance; } catch (RuntimeException e) { lastExc = e; - logger.debug("The raven factory '{}' couldn't create an instance of Raven.", ravenFactory, e); + logger.debug("The RavenFactory '{}' couldn't create an instance of Raven.", ravenFactory, e); + } + } + + if (ravenFactoryName != null && triedFactories.isEmpty()) { + try { + // see if the provided class exists on the classpath at all + Class.forName(ravenFactoryName); + logger.error( + "The RavenFactory class '{}' was found on your classpath but was not " + + "registered with Raven, see: " + + "https://github.com/getsentry/raven-java/#custom-ravenfactory", ravenFactoryName); + } catch (ClassNotFoundException e) { + logger.error("The RavenFactory class name '{}' was specified but " + + "the class was not found on your classpath.", ravenFactoryName); } } From 10ad5c9497ff5e292db506ac851a29d3e39e465d Mon Sep 17 00:00:00 2001 From: ted kaemming Date: Thu, 2 Feb 2017 08:30:38 -0800 Subject: [PATCH 1491/2152] Capture uncaught exceptions as FATAL level for Android. (#284) --- .../raven/android/RavenUncaughtExceptionHandler.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/raven-android/src/main/java/com/getsentry/raven/android/RavenUncaughtExceptionHandler.java b/raven-android/src/main/java/com/getsentry/raven/android/RavenUncaughtExceptionHandler.java index 6adbb82e7d0..7ab51ff9dc1 100644 --- a/raven-android/src/main/java/com/getsentry/raven/android/RavenUncaughtExceptionHandler.java +++ b/raven-android/src/main/java/com/getsentry/raven/android/RavenUncaughtExceptionHandler.java @@ -1,6 +1,9 @@ package com.getsentry.raven.android; import android.util.Log; +import com.getsentry.raven.event.Event; +import com.getsentry.raven.event.EventBuilder; +import com.getsentry.raven.event.interfaces.ExceptionInterface; /** * Sends any uncaught exception to Sentry, then passes the exception on to the pre-existing @@ -39,8 +42,13 @@ class RavenUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { public void uncaughtException(Thread thread, Throwable thrown) { Log.d(TAG, "Uncaught exception received."); + EventBuilder eventBuilder = new EventBuilder() + .withMessage(thrown.getMessage()) + .withLevel(Event.Level.FATAL) + .withSentryInterface(new ExceptionInterface(thrown)); + try { - com.getsentry.raven.Raven.capture(thrown); + com.getsentry.raven.Raven.capture(eventBuilder); } catch (Exception e) { Log.e(TAG, "Error sending excepting to Sentry.", e); } From b3dbae7121be319c5e41b31e83ec3d05ff48446d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 7 Feb 2017 17:01:32 -0600 Subject: [PATCH 1492/2152] Update logging configuration examples to show non-Sentry appender and set Sentry to WARN level. (#306) --- raven-log4j/README.md | 62 ++++++++++++++++++++++------------- raven-log4j2/README.md | 72 ++++++++++++++++------------------------- raven-logback/README.md | 31 ++++++++++++++++-- raven/README.md | 22 ++++++++++--- 4 files changed, 113 insertions(+), 74 deletions(-) diff --git a/raven-log4j/README.md b/raven-log4j/README.md index d9dd9047744..23bc339de31 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -30,27 +30,36 @@ Relies on: Add the `SentryAppender` to the `log4j.properties` file: ```properties -log4j.rootLogger=WARN, SentryAppender -log4j.appender.SentryAppender=com.getsentry.raven.log4j.SentryAppender +# Enable the Console and Sentry appenders +log4j.rootLogger=INFO, Console, Sentry + +# Configure the Console appender +log4j.appender.Console=org.apache.log4j.ConsoleAppender +log4j.appender.Console.layout=org.apache.log4j.PatternLayout +log4j.appender.Console.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5p: %m%n + +# Configure the Sentry appender, overriding the logging threshold to the WARN level +log4j.appender.Sentry=com.getsentry.raven.log4j.SentryAppender +log4j.appender.Sentry.threshold=WARN ``` -Alternatively in the `log4j.xml` file set: +Alternatively in the `log4j.xml` file add: ``` - - - - + + + + + ``` -You'll also need to associate the `sentry` appender with your root logger, like so: +You'll also need to associate the `Sentry` appender with your root logger, like so: ``` - - - - + --> + ``` @@ -88,25 +97,34 @@ Configuration parameters follow: | `SENTRY_TAGS` | `sentry.tags` | `tag1:value1,tag2:value2` | Optional, provide tags | | `SENTRY_EXTRA_TAGS` | `sentry.extratags` | `foo,bar,baz` | Optional, provide tag names to be extracted from MDC when using SLF4J | -#### Configuration via `log4j.properties` +#### Configuration via `log4j.properties` (or `log4j.xml`) -You can also configure everything statically within the `log4j.properties` file -itself. This is less flexible because it's harder to change when you run +You can also configure everything statically within the `log4j.properties` (or `log4j.xml`) +file itself. This is less flexible because it's harder to change when you run your application in different environments. ```properties -log4j.rootLogger=WARN, SentryAppender -log4j.appender.SentryAppender=com.getsentry.raven.log4j.SentryAppender +# Enable the Console and Sentry appenders +log4j.rootLogger=INFO, Console, Sentry + +# Configure the Console appender +log4j.appender.Console=org.apache.log4j.ConsoleAppender + +# Configure the Sentry appender, overriding the logging threshold to the WARN level +log4j.appender.Sentry=com.getsentry.raven.log4j.SentryAppender +log4j.appender.Sentry.threshold=WARN + +# Set Sentry DSN log4j.appender.SentryAppender.dsn=https://host:port/1?options -// Optional, provide release version of your application +# Optional, provide release version of your application log4j.appender.SentryAppender.release=1.0.0 -// Optional, provide environment your application is running in +# Optional, provide environment your application is running in log4j.appender.SentryAppender.environment=production -// Optional, override the server name (rather than looking it up dynamically) +# Optional, override the server name (rather than looking it up dynamically) log4j.appender.SentryAppender.serverName=server1 # Optional, select the ravenFactory class -#log4j.appender.SentryAppender.ravenFactory=com.foo.RavenFactory -// Optional, provide tags +log4j.appender.SentryAppender.ravenFactory=com.foo.RavenFactory +# Optional, provide tags log4j.appender.SentryAppender.tags=tag1:value1,tag2:value2 # Optional, provide tag names to be extracted from MDC when using SLF4J log4j.appender.SentryAppender.extraTags=foo,bar,baz diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index f698db5a5a5..12851421cc7 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -35,12 +35,17 @@ Add the `SentryAppender` to your `log4j2.xml` file: + + + + - - + + + @@ -90,54 +95,31 @@ your application in different environments. + + + + - - https://host:port/1?options - - - - 1.0.0 - - - - production - - - - server1 - - - - - - tag1:value1,tag2:value2 - - - - foo,bar,baz - + https://host:port/1?options + + 1.0.0 + + production + + server1 + + com.foo.RavenFactory + + tag1:value1,tag2:value2 + + foo,bar,baz - - + + + diff --git a/raven-logback/README.md b/raven-logback/README.md index cd1195013af..8c8ac8d7d2c 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -31,8 +31,20 @@ Add the `SentryAppender` to your `logback.xml` file: ```xml - - + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + WARN + + + + + @@ -80,7 +92,18 @@ your application in different environments. ```xml + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + WARN + + + https://host:port/1?options 1.0.0 @@ -95,7 +118,9 @@ your application in different environments. foo,bar,baz - + + + diff --git a/raven/README.md b/raven/README.md index d7fcb6371b7..f514e824875 100644 --- a/raven/README.md +++ b/raven/README.md @@ -32,8 +32,14 @@ Relies on: Add the `SentryHandler` to the `logging.properties` file: ```properties -.level=WARNING -handlers=com.getsentry.raven.jul.SentryHandler +# Enable the Console and Sentry handlers +handlers=java.util.logging.ConsoleHandler,com.getsentry.raven.jul.SentryHandler + +# Set the default log level to INFO +.level=INFO + +# Override the Sentry handler log level to WARNING +com.getsentry.raven.jul.SentryHandler.level=WARNING ``` When starting your application, add the `java.util.logging.config.file` to the @@ -82,8 +88,16 @@ itself. This is less flexible because it's harder to change when you run your application in different environments. ```properties -.level=WARNING -handlers=com.getsentry.raven.jul.SentryHandler +# Enable the Console and Sentry handlers +handlers=java.util.logging.ConsoleHandler,com.getsentry.raven.jul.SentryHandler + +# Set the default log level to INFO +.level=INFO + +# Override the Sentry handler log level to WARNING +com.getsentry.raven.jul.SentryHandler.level=WARNING + +# Set Sentry DSN com.getsentry.raven.jul.SentryHandler.dsn=https://host:port/1?options # Optional, provide tags com.getsentry.raven.jul.SentryHandler.tags=tag1:value1,tag2:value2 From 6d3cd2aad19a0ee37d4fcbd245b539be29711814 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 7 Feb 2017 17:52:35 -0600 Subject: [PATCH 1493/2152] Add makefile. (#304) --- Makefile | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 00000000000..bf56117ab34 --- /dev/null +++ b/Makefile @@ -0,0 +1,56 @@ +.PHONY: checkstyle test install clean prepare prepareDocs prepareMvn prepareChanges perform + +MVN=mvn -e + +all: checkstyle test install + +checkstyle: + $(MVN) checkstyle:check + +test: + $(MVN) verify + +install: + $(MVN) install -Dcheckstyle.skip=true -DskipTests + +clean: + $(MVN) clean + +prepareDocs: +# Store previously released version + $(eval PREVIOUS_RELEASE=$(shell grep -E "^Version" CHANGES | head -2 | tail -1 | sed -e 's/Version //')) + @echo Previous release: $(PREVIOUS_RELEASE) +# Store release project version + $(eval RELEASE_VERSION=$(shell mvn help:evaluate -Dexpression=project.version | grep -Ev '^\[' | sed -e 's/-SNAPSHOT//')) + @echo This release: $(RELEASE_VERSION) +# Fix released version in documentation + @echo Fixing documentation versions + find . -name '*.md' -or -name '*.rst' -exec sed -i -e 's/$(PREVIOUS_RELEASE)/$(RELEASE_VERSION)/g' {} \; +# Commit documentation changes + @echo Committing documentation version changes + git commit -a -m 'Bump docs to $(RELEASE_VERSION)' + +prepareMvn: +# Prepare release (interactive) + $(MVN) release:prepare + +prepareChanges: +# Store new project version + $(eval DEV_VERSION=$(shell mvn help:evaluate -Dexpression=project.version | grep -Ev '^\[' | sed -e 's/-SNAPSHOT//')) + @echo Development version: $(DEV_VERSION) +# Store enough dashes to go under "Version X.Y.Z", accounting for changes in the $VERSION length + $(eval DASHES=$(shell python -c 'print("-" * (8 + len("$(DEV_VERSION)")))')) +# Add new Version section to the top of the CHANGES file + @echo Updating CHANGES file + echo -e "Version $(DEV_VERSION)\n$(DASHES)\n\n-\n" > CHANGES.new && cat CHANGES >> CHANGES.new && mv CHANGES.new CHANGES + git add CHANGES + git commit -m "Bump CHANGES to $(DEV_VERSION)" + +# Prepare is broken into stages because otherwise `make` will run things out of order +prepare: prepareDocs prepareMvn prepareChanges + +perform: + $(MVN) release:perform + +rollback: + $(MVN) release:rollback From 08c45e38a859e222b56d21570d00ad6a1411ebdd Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 7 Feb 2017 17:52:50 -0600 Subject: [PATCH 1494/2152] Add Gradle instructions and drop the manual dependency docs. (#307) --- raven-appengine/README.md | 10 +++++----- raven-log4j/README.md | 13 +++++-------- raven-log4j2/README.md | 15 +++++---------- raven-logback/README.md | 13 +++++-------- raven/README.md | 15 +++++---------- 5 files changed, 25 insertions(+), 41 deletions(-) diff --git a/raven-appengine/README.md b/raven-appengine/README.md index e0ede41417a..ea74c1e10cf 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -19,14 +19,14 @@ __This module is not useful outside of Google App Engine.__ ``` +### Gradle +``` +compile 'com.getsentry.raven:raven-appengine:7.8.1' +``` + ### Other dependency managers Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.8.1%7Cjar). -### Manual dependency management -Relies on: - - - [raven dependencies](../raven) - ## Usage This module provides a new `RavenFactory` which replaces the default async system with a GAE compatible one. diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 23bc339de31..f645a4abaeb 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -14,17 +14,14 @@ for log4j to send the logged events to Sentry. ``` +### Gradle +``` +compile 'com.getsentry.raven:raven-log4j:7.8.1' +``` + ### Other dependency managers Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.8.1%7Cjar). -### Manual dependency management -Relies on: - - - [raven dependencies](../raven) - - [log4j-1.2.17.jar](https://search.maven.org/#artifactdetails%7Clog4j%7Clog4j%7C1.2.17%7Cjar) - - [slf4j-log4j12-1.7.7.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-log4j12%7C1.7.7%7Cjar) - is recommended as the implementation of slf4j (instead of slf4j-jdk14). - ## Usage ### Configuration Add the `SentryAppender` to the `log4j.properties` file: diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index 12851421cc7..2da9c1d12a7 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -14,19 +14,14 @@ for Log4j 2 to send the logged events to Sentry. ``` +### Gradle +``` +compile 'com.getsentry.raven:raven-log4j2:7.8.1' +``` + ### Other dependency managers Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.8.1%7Cjar). -### Manual dependency management -Relies on: - - - [raven dependencies](../raven) - - [log4j-api-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-api%7.8.1%7Cjar) - - [log4j-core-2.1.jar](https://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-core%7.8.1%7Cjar) - - [log4j-slf4j-impl-2.1.jar](http://search.maven.org/#artifactdetails%7Corg.apache.logging.log4j%7Clog4j-slf4j-impl%7.8.1%7Cjar) - is recommended as the implementation of slf4j (instead of slf4j-jdk14). - - ## Usage ### Configuration Add the `SentryAppender` to your `log4j2.xml` file: diff --git a/raven-logback/README.md b/raven-logback/README.md index 8c8ac8d7d2c..16f9beae7af 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -14,17 +14,14 @@ for logback to send the logged events to Sentry. ``` +### Gradle +``` +compile 'com.getsentry.raven:raven-logback:7.8.1' +``` + ### Other dependency managers Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.8.1%7Cjar). -### Manual dependency management -Relies on: - - - [raven dependencies](../raven) - - [logback-core-1.1.2.jar](https://search.maven.org/#artifactdetails%7Cch.qos.logback%7Clogback-core%7C1.1.2%7Cjar) - - [logback-classic-1.1.2.jar](https://search.maven.org/#artifactdetails%7Cch.qos.logback%7Clogback-classic%7C1.1.2%7Cjar) - will act as the implementation of slf4j (instead of slf4j-jdk14). - ## Usage ### Configuration Add the `SentryAppender` to your `logback.xml` file: diff --git a/raven/README.md b/raven/README.md index f514e824875..7b0484fd4ff 100644 --- a/raven/README.md +++ b/raven/README.md @@ -14,19 +14,14 @@ for `java.util.logging`. ``` +### Gradle +``` +compile 'com.getsentry.raven:raven:7.8.1' +``` + ### Other dependency managers Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.8.1%7Cjar). -### Manual dependency management -Relies on: - - - [jackson-core-2.5.0.jar](https://search.maven.org/#artifactdetails%7Ccom.fasterxml.jackson.core%7Cjackson-core%7C2.5.0%7Cjar) - - [slf4j-api-1.7.9.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-api%7C1.7.9%7Cjar) - - [slf4j-jdk14-1.7.9.jar](https://search.maven.org/#artifactdetails%7Corg.slf4j%7Cslf4j-jdk14%7C1.7.9%7Cjar) - is recommended as the implementation of slf4j to capture the log events - generated by Raven (connection errors, ...) if `java.util.logging` is used. - - ## Usage (`java.util.logging`) ### Configuration Add the `SentryHandler` to the `logging.properties` file: From 1f77e919e386d93750d6d2b3d42c4661c016e075 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 7 Feb 2017 18:17:14 -0600 Subject: [PATCH 1495/2152] Fix race condition, ensuring Raven initialization is synchronized in appenders. (thanks OutOfBrain) (#288) --- CHANGES | 1 + .../com/getsentry/raven/log4j/SentryAppender.java | 11 ++++++++--- .../com/getsentry/raven/log4j2/SentryAppender.java | 8 ++++---- .../com/getsentry/raven/logback/SentryAppender.java | 11 +++++------ .../java/com/getsentry/raven/jul/SentryHandler.java | 7 ++++--- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index e723e7c7cbf..e07331429dc 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ Version 7.8.2 ------------- +- Fix race condition, ensuring Raven initialization is synchronized in appenders. (thanks OutOfBrain) - Allow use of custom AndroidRavenFactory. (thanks kassim) - Add ``raven.sample.rate`` DSN option to optionally reduce the number of events sent to the server. diff --git a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java index 2bb0d0d8994..79b8d217365 100644 --- a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java @@ -41,7 +41,7 @@ public class SentryAppender extends AppenderSkeleton { * * @see #initRaven() */ - protected Raven raven; + protected volatile Raven raven; /** * DSN property of the appender. *

    @@ -144,14 +144,16 @@ protected static StackTraceElement asStackTraceElement(LocationInfo location) { @Override public void activateOptions() { super.activateOptions(); - if (raven == null) + + if (raven == null) { initRaven(); + } } /** * Initialises the Raven instance. */ - protected void initRaven() { + protected synchronized void initRaven() { try { if (dsn == null) dsn = Dsn.dsnLookup(); @@ -174,6 +176,9 @@ protected void append(LoggingEvent loggingEvent) { RavenEnvironment.startManagingThread(); try { + if (raven == null) { + initRaven(); + } Event event = buildEvent(loggingEvent); raven.sendEvent(event); } catch (Exception e) { diff --git a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java index de5e2ea8a4a..baa3e77076c 100644 --- a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java @@ -58,7 +58,7 @@ public class SentryAppender extends AbstractAppender { * * @see #initRaven() */ - protected Raven raven; + protected volatile Raven raven; /** * DSN property of the appender. *

    @@ -234,9 +234,9 @@ public void append(LogEvent logEvent) { RavenEnvironment.startManagingThread(); try { - if (raven == null) + if (raven == null) { initRaven(); - + } Event event = buildEvent(logEvent); raven.sendEvent(event); } catch (Exception e) { @@ -249,7 +249,7 @@ public void append(LogEvent logEvent) { /** * Initialises the Raven instance. */ - protected void initRaven() { + protected synchronized void initRaven() { try { if (dsn == null) dsn = Dsn.dsnLookup(); diff --git a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java index 9641174db33..32e73b42aa2 100644 --- a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java @@ -49,7 +49,7 @@ public class SentryAppender extends AppenderBase { * * @see #initRaven() */ - protected Raven raven; + protected volatile Raven raven; /** * DSN property of the appender. *

    @@ -171,14 +171,13 @@ protected void append(ILoggingEvent iLoggingEvent) { RavenEnvironment.startManagingThread(); try { - if (raven == null) { - initRaven(); - } - if (minLevel != null && !iLoggingEvent.getLevel().isGreaterOrEqual(minLevel)) { return; } + if (raven == null) { + initRaven(); + } Event event = buildEvent(iLoggingEvent); raven.sendEvent(event); } catch (Exception e) { @@ -191,7 +190,7 @@ protected void append(ILoggingEvent iLoggingEvent) { /** * Initialises the Raven instance. */ - protected void initRaven() { + protected synchronized void initRaven() { try { if (dsn == null) { dsn = Dsn.dsnLookup(); diff --git a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java index d68d5810210..79de0342816 100644 --- a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java +++ b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java @@ -40,7 +40,7 @@ public class SentryHandler extends Handler { * * @see #initRaven() */ - protected Raven raven; + protected volatile Raven raven; /** * DSN property of the appender. *

    @@ -193,8 +193,9 @@ public void publish(LogRecord record) { RavenEnvironment.startManagingThread(); try { - if (raven == null) + if (raven == null) { initRaven(); + } Event event = buildEvent(record); raven.sendEvent(event); } catch (Exception e) { @@ -207,7 +208,7 @@ public void publish(LogRecord record) { /** * Initialises the Raven instance. */ - protected void initRaven() { + protected synchronized void initRaven() { try { if (dsn == null) dsn = Dsn.dsnLookup(); From 8ed6ac6e2e621ab04431a25ba95c92251ca7e068 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 7 Feb 2017 17:07:47 -0800 Subject: [PATCH 1496/2152] Fix missing 'formatted' field in stub. --- .../raven/sentrystub/event/interfaces/MessageInterface.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/interfaces/MessageInterface.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/interfaces/MessageInterface.java index 22f5938cdc3..b2b07a1c01b 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/interfaces/MessageInterface.java +++ b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/interfaces/MessageInterface.java @@ -9,4 +9,6 @@ public class MessageInterface { private String message; @JsonProperty(value = "params") private List params; + @JsonProperty(value = "formatted") + private String formatted; } From 6133b2d1c7bbce8806446fbb0385d5fe46874579 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 7 Feb 2017 19:17:39 -0600 Subject: [PATCH 1497/2152] Add logging integration name to Event platform field. (#281) --- CHANGES | 1 + .../helper/AndroidEventBuilderHelper.java | 2 ++ .../getsentry/raven/log4j/SentryAppender.java | 11 +++---- .../SentryAppenderEventBuildingTest.java | 2 ++ .../raven/log4j2/SentryAppender.java | 11 +++---- .../SentryAppenderEventBuildingTest.java | 2 ++ .../raven/logback/SentryAppender.java | 11 +++---- .../SentryAppenderEventBuildingTest.java | 2 ++ raven/pom.xml | 4 +-- .../main/java/com/getsentry/raven/Raven.java | 2 +- .../raven/connection/AbstractConnection.java | 2 +- .../raven/connection/HttpConnection.java | 2 +- .../raven/environment/RavenEnvironment.java | 18 ++++++++++-- .../java/com/getsentry/raven/event/Event.java | 24 +++++++++++++++ .../getsentry/raven/event/EventBuilder.java | 29 +++++++++++++++++++ .../getsentry/raven/jul/SentryHandler.java | 7 +++-- .../raven/marshaller/json/JsonMarshaller.java | 12 ++++++++ .../connection/AbstractConnectionTest.java | 2 +- .../raven/connection/HttpConnectionTest.java | 2 +- .../jul/SentryHandlerEventBuildingTest.java | 2 ++ .../marshaller/json/JsonMarshallerTest.java | 26 +++++++++++++---- .../jsonmarshallertest/testBreadcrumbs.json | 4 +++ .../json/jsonmarshallertest/testChecksum.json | 6 +++- .../json/jsonmarshallertest/testCulprit.json | 6 +++- .../jsonmarshallertest/testEnvironment.json | 6 +++- .../json/jsonmarshallertest/testEventId.json | 6 +++- .../jsonmarshallertest/testExtraArray.json | 6 +++- .../jsonmarshallertest/testExtraBoolean.json | 6 +++- .../testExtraCustomValue.json | 6 +++- .../jsonmarshallertest/testExtraIterable.json | 6 +++- .../json/jsonmarshallertest/testExtraMap.json | 6 +++- .../jsonmarshallertest/testExtraNull.json | 6 +++- .../testExtraNullKeyMap.json | 6 +++- .../jsonmarshallertest/testExtraNumber.json | 6 +++- .../testExtraObjectKeyMap.json | 6 +++- .../testExtraRecursiveArray.json | 6 +++- .../testExtraRecursiveMap.json | 6 +++- .../jsonmarshallertest/testExtraString.json | 6 +++- .../jsonmarshallertest/testFingerprint.json | 6 +++- .../testInterfaceBinding.json | 6 +++- .../jsonmarshallertest/testLevelDebug.json | 6 +++- .../jsonmarshallertest/testLevelError.json | 6 +++- .../jsonmarshallertest/testLevelFatal.json | 6 +++- .../jsonmarshallertest/testLevelInfo.json | 6 +++- .../jsonmarshallertest/testLevelWarning.json | 6 +++- .../json/jsonmarshallertest/testLogger.json | 6 +++- .../json/jsonmarshallertest/testMessage.json | 6 +++- .../json/jsonmarshallertest/testPlatform.json | 6 +++- .../json/jsonmarshallertest/testRelease.json | 6 +++- .../json/jsonmarshallertest/testSdk.json | 19 ++++++++++++ .../jsonmarshallertest/testServerName.json | 6 +++- .../json/jsonmarshallertest/testTags.json | 6 +++- .../jsonmarshallertest/testTimestamp.json | 6 +++- .../src/test/resources/raven-build.properties | 2 +- .../raven/sentrystub/event/Event.java | 2 ++ 55 files changed, 317 insertions(+), 64 deletions(-) create mode 100644 raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testSdk.json diff --git a/CHANGES b/CHANGES index e07331429dc..729606bd8a6 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ Version 7.8.2 - Fix race condition, ensuring Raven initialization is synchronized in appenders. (thanks OutOfBrain) - Allow use of custom AndroidRavenFactory. (thanks kassim) +- Add ``sdk`` field to payload, identify subprojects in SDK name. - Add ``raven.sample.rate`` DSN option to optionally reduce the number of events sent to the server. Version 7.8.1 diff --git a/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java b/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java index e9e47072f64..299218ce4f0 100644 --- a/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java +++ b/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java @@ -3,6 +3,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.util.Log; +import com.getsentry.raven.environment.RavenEnvironment; import com.getsentry.raven.event.EventBuilder; import com.getsentry.raven.event.helper.EventBuilderHelper; @@ -29,6 +30,7 @@ public AndroidEventBuilderHelper(Context ctx) { @Override public void helpBuildingEvent(EventBuilder eventBuilder) { + eventBuilder.withSdkName(RavenEnvironment.SDK_NAME + ":android"); try { int versionCode = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0).versionCode; eventBuilder.withRelease(Integer.toString(versionCode)); diff --git a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java index 79b8d217365..0b4cd187e80 100644 --- a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java @@ -197,11 +197,12 @@ protected void append(LoggingEvent loggingEvent) { */ protected Event buildEvent(LoggingEvent loggingEvent) { EventBuilder eventBuilder = new EventBuilder() - .withTimestamp(new Date(loggingEvent.getTimeStamp())) - .withMessage(loggingEvent.getRenderedMessage()) - .withLogger(loggingEvent.getLoggerName()) - .withLevel(formatLevel(loggingEvent.getLevel())) - .withExtra(THREAD_NAME, loggingEvent.getThreadName()); + .withSdkName(RavenEnvironment.SDK_NAME + ":log4j") + .withTimestamp(new Date(loggingEvent.getTimeStamp())) + .withMessage(loggingEvent.getRenderedMessage()) + .withLogger(loggingEvent.getLoggerName()) + .withLevel(formatLevel(loggingEvent.getLevel())) + .withExtra(THREAD_NAME, loggingEvent.getThreadName()); if (!Util.isNullOrEmpty(serverName)) { eventBuilder.withServerName(serverName.trim()); diff --git a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderEventBuildingTest.java b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderEventBuildingTest.java index 10dc9bf74e2..62d48172382 100644 --- a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderEventBuildingTest.java +++ b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderEventBuildingTest.java @@ -1,5 +1,6 @@ package com.getsentry.raven.log4j; +import com.getsentry.raven.environment.RavenEnvironment; import mockit.*; import com.getsentry.raven.Raven; import com.getsentry.raven.event.Event; @@ -70,6 +71,7 @@ public void testSimpleMessageLogging() throws Exception { assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); + assertThat(event.getSdkName(), is(RavenEnvironment.SDK_NAME + ":log4j")); }}; assertNoErrorsInErrorHandler(); } diff --git a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java index baa3e77076c..d7841721db5 100644 --- a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java @@ -271,11 +271,12 @@ protected synchronized void initRaven() { protected Event buildEvent(LogEvent event) { Message eventMessage = event.getMessage(); EventBuilder eventBuilder = new EventBuilder() - .withTimestamp(new Date(event.getTimeMillis())) - .withMessage(eventMessage.getFormattedMessage()) - .withLogger(event.getLoggerName()) - .withLevel(formatLevel(event.getLevel())) - .withExtra(THREAD_NAME, event.getThreadName()); + .withSdkName(RavenEnvironment.SDK_NAME + ":log4j2") + .withTimestamp(new Date(event.getTimeMillis())) + .withMessage(eventMessage.getFormattedMessage()) + .withLogger(event.getLoggerName()) + .withLevel(formatLevel(event.getLevel())) + .withExtra(THREAD_NAME, event.getThreadName()); if (!Util.isNullOrEmpty(serverName)) { eventBuilder.withServerName(serverName.trim()); diff --git a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderEventBuildingTest.java b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderEventBuildingTest.java index f77ad920345..d621cf504d0 100644 --- a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderEventBuildingTest.java +++ b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderEventBuildingTest.java @@ -1,5 +1,6 @@ package com.getsentry.raven.log4j2; +import com.getsentry.raven.environment.RavenEnvironment; import mockit.Injectable; import mockit.Tested; import mockit.Verifications; @@ -65,6 +66,7 @@ public void testSimpleMessageLogging() throws Exception { assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); + assertThat(event.getSdkName(), is(RavenEnvironment.SDK_NAME + ":log4j2")); }}; assertNoErrorsInErrorHandler(); } diff --git a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java index 32e73b42aa2..0d50858b67b 100644 --- a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java @@ -212,11 +212,12 @@ protected synchronized void initRaven() { */ protected Event buildEvent(ILoggingEvent iLoggingEvent) { EventBuilder eventBuilder = new EventBuilder() - .withTimestamp(new Date(iLoggingEvent.getTimeStamp())) - .withMessage(iLoggingEvent.getFormattedMessage()) - .withLogger(iLoggingEvent.getLoggerName()) - .withLevel(formatLevel(iLoggingEvent.getLevel())) - .withExtra(THREAD_NAME, iLoggingEvent.getThreadName()); + .withSdkName(RavenEnvironment.SDK_NAME + ":logback") + .withTimestamp(new Date(iLoggingEvent.getTimeStamp())) + .withMessage(iLoggingEvent.getFormattedMessage()) + .withLogger(iLoggingEvent.getLoggerName()) + .withLevel(formatLevel(iLoggingEvent.getLevel())) + .withExtra(THREAD_NAME, iLoggingEvent.getThreadName()); if (!Util.isNullOrEmpty(serverName)) { diff --git a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderEventBuildingTest.java b/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderEventBuildingTest.java index 143b3ecdf22..8b0d87c675c 100644 --- a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderEventBuildingTest.java +++ b/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderEventBuildingTest.java @@ -5,6 +5,7 @@ import ch.qos.logback.core.BasicStatusManager; import ch.qos.logback.core.Context; import ch.qos.logback.core.status.OnConsoleStatusListener; +import com.getsentry.raven.environment.RavenEnvironment; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; @@ -79,6 +80,7 @@ public void testSimpleMessageLogging() throws Exception { assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); + assertThat(event.getSdkName(), is(RavenEnvironment.SDK_NAME + ":logback")); }}; assertNoErrorsInStatusManager(); } diff --git a/raven/pom.xml b/raven/pom.xml index 8119d095f62..680db7410cf 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -91,7 +91,7 @@ org.codehaus.mojo buildnumber-maven-plugin - 1.3 + 1.4 @@ -102,7 +102,7 @@ - Raven-Java/{0}-{1} + {0}-{1} ${project.version} scmVersion diff --git a/raven/src/main/java/com/getsentry/raven/Raven.java b/raven/src/main/java/com/getsentry/raven/Raven.java index 8116f6ae3ba..a4045065a07 100644 --- a/raven/src/main/java/com/getsentry/raven/Raven.java +++ b/raven/src/main/java/com/getsentry/raven/Raven.java @@ -188,7 +188,7 @@ public RavenContext getContext() { @Override public String toString() { return "Raven{" - + "name=" + RavenEnvironment.NAME + + "name=" + RavenEnvironment.getRavenName() + ", connection=" + connection + '}'; } diff --git a/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java b/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java index 3af63b524c0..8c7db974062 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java @@ -64,7 +64,7 @@ public abstract class AbstractConnection implements Connection { protected AbstractConnection(String publicKey, String secretKey) { eventSendFailureCallbacks = new HashSet<>(); authHeader = "Sentry sentry_version=" + SENTRY_PROTOCOL_VERSION + "," - + "sentry_client=" + RavenEnvironment.NAME + "," + + "sentry_client=" + RavenEnvironment.getRavenName() + "," + "sentry_key=" + publicKey + "," + "sentry_secret=" + secretKey; } diff --git a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java b/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java index c69c28c5f47..358447fcd6b 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java @@ -138,7 +138,7 @@ protected HttpURLConnection getConnection() { connection.setRequestMethod("POST"); connection.setDoOutput(true); connection.setConnectTimeout(timeout); - connection.setRequestProperty(USER_AGENT, RavenEnvironment.NAME); + connection.setRequestProperty(USER_AGENT, RavenEnvironment.getRavenName()); connection.setRequestProperty(SENTRY_AUTH, getAuthHeader()); return connection; } catch (IOException e) { diff --git a/raven/src/main/java/com/getsentry/raven/environment/RavenEnvironment.java b/raven/src/main/java/com/getsentry/raven/environment/RavenEnvironment.java index c6a6f7dff5f..6be3539981c 100644 --- a/raven/src/main/java/com/getsentry/raven/environment/RavenEnvironment.java +++ b/raven/src/main/java/com/getsentry/raven/environment/RavenEnvironment.java @@ -14,10 +14,13 @@ */ public final class RavenEnvironment { /** - * Version of this client, the major version is the current supported Sentry protocol, the minor version changes - * for each release of this project. + * Name of this SDK. */ - public static final String NAME = ResourceBundle.getBundle("raven-build").getString("build.name"); + public static final String SDK_NAME = "raven-java"; + /** + * Version of this SDK. + */ + public static final String SDK_VERSION = ResourceBundle.getBundle("raven-build").getString("build.name"); /** * Indicates whether the current thread is managed by raven or not. */ @@ -81,4 +84,13 @@ public static void stopManagingThread() { public static boolean isManagingThread() { return RAVEN_THREAD.get().get() > 0; } + + /** + * Returns sdk name+version string, used for HTTP User Agent, sentry_client, etc. + * + * @return Raven sdk string + */ + public static String getRavenName() { + return SDK_NAME + "/" + SDK_VERSION; + } } diff --git a/raven/src/main/java/com/getsentry/raven/event/Event.java b/raven/src/main/java/com/getsentry/raven/event/Event.java index d96618362cd..81d97ff40fc 100644 --- a/raven/src/main/java/com/getsentry/raven/event/Event.java +++ b/raven/src/main/java/com/getsentry/raven/event/Event.java @@ -54,6 +54,14 @@ public class Event implements Serializable { * A string representing the currently used platform (java/python). */ private String platform; + /** + * A string representing the name of the SDK used to create the event. + */ + private String sdkName; + /** + * A string representing the version of the SDK used to create the event. + */ + private String sdkVersion; /** * Function call which was the primary perpetrator of this event. */ @@ -160,6 +168,22 @@ void setPlatform(String platform) { this.platform = platform; } + public String getSdkName() { + return sdkName; + } + + public void setSdkName(String sdkName) { + this.sdkName = sdkName; + } + + public String getSdkVersion() { + return sdkVersion; + } + + public void setSdkVersion(String sdkVersion) { + this.sdkVersion = sdkVersion; + } + public String getCulprit() { return culprit; } diff --git a/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java b/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java index 9d0dd4b3a58..81d5fec49fe 100644 --- a/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java +++ b/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java @@ -1,5 +1,6 @@ package com.getsentry.raven.event; +import com.getsentry.raven.environment.RavenEnvironment; import com.getsentry.raven.event.interfaces.SentryInterface; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -85,6 +86,12 @@ private static void autoSetMissingValues(Event event) { if (event.getPlatform() == null) event.setPlatform(DEFAULT_PLATFORM); + // Ensure that an SDK is set + if (event.getSdkName() == null) + event.setSdkName(RavenEnvironment.SDK_NAME); + if (event.getSdkVersion() == null) + event.setSdkVersion(RavenEnvironment.SDK_VERSION); + // Ensure that a hostname is set if (event.getServerName() == null) event.setServerName(HOSTNAME_CACHE.getHostname()); @@ -186,6 +193,28 @@ public EventBuilder withPlatform(String platform) { return this; } + /** + * Sets the SDK name in the event. + * + * @param sdkName name of the SDK that created the event. + * @return the current {@code EventBuilder} for chained calls. + */ + public EventBuilder withSdkName(String sdkName) { + event.setSdkName(sdkName); + return this; + } + + /** + * Sets the SDK version in the event. + * + * @param sdkVersion version of the SDK that created the event. + * @return the current {@code EventBuilder} for chained calls. + */ + public EventBuilder withSdkVersion(String sdkVersion) { + event.setSdkVersion(sdkVersion); + return this; + } + /** * Sets the culprit in the event based on a {@link StackTraceElement}. * diff --git a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java index 79de0342816..b7b1cb1897a 100644 --- a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java +++ b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java @@ -230,9 +230,10 @@ protected synchronized void initRaven() { */ protected Event buildEvent(LogRecord record) { EventBuilder eventBuilder = new EventBuilder() - .withLevel(getLevel(record.getLevel())) - .withTimestamp(new Date(record.getMillis())) - .withLogger(record.getLoggerName()); + .withSdkName(RavenEnvironment.SDK_NAME + ":jul") + .withLevel(getLevel(record.getLevel())) + .withTimestamp(new Date(record.getMillis())) + .withLogger(record.getLoggerName()); String message = record.getMessage(); if (record.getResourceBundle() != null && record.getResourceBundle().containsKey(record.getMessage())) { diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java index 84a2f366df2..3cf2155a214 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java @@ -53,6 +53,10 @@ public class JsonMarshaller implements Marshaller { * Function call which was the primary perpetrator of this event. */ public static final String CULPRIT = "culprit"; + /** + * An object representing the SDK name and version. + */ + public static final String SDK = "sdk"; /** * A map or list of tags for this event. */ @@ -165,6 +169,7 @@ private void writeContent(JsonGenerator generator, Event event) throws IOExcepti generator.writeStringField(LOGGER, event.getLogger()); generator.writeStringField(PLATFORM, event.getPlatform()); generator.writeStringField(CULPRIT, event.getCulprit()); + writeSdk(generator, event.getSdkName(), event.getSdkVersion()); writeTags(generator, event.getTags()); writeBreadcumbs(generator, event.getBreadcrumbs()); generator.writeStringField(SERVER_NAME, event.getServerName()); @@ -253,6 +258,13 @@ private void safelyWriteObject(JsonGenerator generator, Object value) throws IOE } } + private void writeSdk(JsonGenerator generator, String sdkName, String sdkVersion) throws IOException { + generator.writeObjectFieldStart(SDK); + generator.writeStringField("name", sdkName); + generator.writeStringField("version", sdkVersion); + generator.writeEndObject(); + } + private void writeTags(JsonGenerator generator, Map tags) throws IOException { generator.writeObjectFieldStart(TAGS); for (Map.Entry tag : tags.entrySet()) { diff --git a/raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java b/raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java index 0243a702b18..130f79866c9 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java +++ b/raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java @@ -36,7 +36,7 @@ public void testAuthHeader() throws Exception { String authHeader = abstractConnection.getAuthHeader(); assertThat(authHeader, is("Sentry sentry_version=6," - + "sentry_client=Raven-Java/Test," + + "sentry_client=raven-java/test," + "sentry_key=" + publicKey + "," + "sentry_secret=" + secretKey)); } diff --git a/raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java b/raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java index 5a301237b5f..c81fd4e0c54 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java @@ -118,7 +118,7 @@ public void testAuthHeaderSent(@Injectable final Event mockEvent) throws Excepti new Verifications() {{ - mockUrlConnection.setRequestProperty("User-Agent", RavenEnvironment.NAME); + mockUrlConnection.setRequestProperty("User-Agent", RavenEnvironment.getRavenName()); mockUrlConnection.setRequestProperty("X-Sentry-Auth", httpConnection.getAuthHeader()); }}; } diff --git a/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerEventBuildingTest.java b/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerEventBuildingTest.java index 8316a23c10f..c840c1c9d9d 100644 --- a/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerEventBuildingTest.java +++ b/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerEventBuildingTest.java @@ -1,5 +1,6 @@ package com.getsentry.raven.jul; +import com.getsentry.raven.environment.RavenEnvironment; import mockit.Injectable; import mockit.Tested; import mockit.Verifications; @@ -60,6 +61,7 @@ public void testSimpleMessageLogging() throws Exception { assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryHandler.THREAD_ID, (int) threadId)); assertThat(event.getTimestamp(), is(date)); + assertThat(event.getSdkName(), is(RavenEnvironment.SDK_NAME + ":jul")); }}; assertNoErrorsInErrorManager(); } diff --git a/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java index 754b01d715a..b88c40893d6 100644 --- a/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java @@ -139,6 +139,22 @@ public void testEventPlaftormWrittenProperly(@Injectable("platform") final Strin assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testPlatform.json"))); } + @Test + public void testEventPlaftormWrittenProperly(@Injectable("sdkName") final String mockSdkName, + @Injectable("sdkVersion") final String mockSdkVersion) throws Exception { + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); + new NonStrictExpectations() {{ + mockEvent.getSdkName(); + result = mockSdkName; + mockEvent.getSdkVersion(); + result = mockSdkVersion; + }}; + + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); + + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testSdk.json"))); + } + @Test public void testEventCulpritWrittenProperly(@Injectable("culprit") final String mockCulprit) throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); @@ -344,10 +360,10 @@ public void testCompressedDataIsWorking() throws Exception { jsonMarshaller.marshall(mockEvent, outputStream); assertThat(new String(outputStream.toByteArray(), "UTF-8"), is("" - + "eJyFT8sKAjEQ+5c5V+iexH6H92WoYy1OH0zbRVj23" - + "y1qe3XIJWGSkB1oo1hXfwMD+s+BgkCloCMwsTErqL" - + "4LFUPu7uVy1ie9dFy1Nh90A/d8Hu+cnCMZLDPWe5I" - + "wuG2cxdeZja6A2Q8FhWQjWSOGWSzEhGVSipuXFEOf" - + "MqVXFfz67YPss7Rf0fEGRZpNqQ==")); + + "eJyFj8EKwyAMht8l5w7saczn2L1Imzlp1JLYMih994V1ym6T/+AX/z+JO" + + "+CGqQxhAgvmz4EOIoo4j2DTStRBCVooLi6a7m9XczG96m6M/UgDpP2p2i" + + "l7j1xpIVcemWPlcaWFQ6ko0wx2h+RiG7chS8jpxEPHOy/q0Zsg6+Pwa2Y" + + "kdNIQ0xY4p6i/baVXYXfmxyeOs6zfXY43jPBZ0g==" + )); } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testBreadcrumbs.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testBreadcrumbs.json index 0ec60fab7d5..907572b7874 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testBreadcrumbs.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testBreadcrumbs.json @@ -17,5 +17,9 @@ {"timestamp":1463169342,"level":"info","message":"test1","category":"foo"}, {"timestamp":1463169343,"level":"info","message":"test2","category":"foo"} ] + }, + "sdk": { + "name": null, + "version": null } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testChecksum.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testChecksum.json index 62d660e2ca8..0f539d28757 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testChecksum.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testChecksum.json @@ -11,5 +11,9 @@ "release": null, "environment": null, "extra": {}, - "checksum": "1234567890abcdef" + "checksum": "1234567890abcdef", + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testCulprit.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testCulprit.json index 3447f32969b..c87935fc74f 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testCulprit.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testCulprit.json @@ -11,5 +11,9 @@ "release": null, "environment": null, "extra": {}, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEnvironment.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEnvironment.json index f11fd125cde..1384c8b466d 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEnvironment.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEnvironment.json @@ -11,5 +11,9 @@ "release": null, "environment": "environment", "extra": {}, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEventId.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEventId.json index f5bbb0cb6c0..8b82c8cde91 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEventId.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEventId.json @@ -11,5 +11,9 @@ "release": null, "environment": null, "extra": {}, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraArray.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraArray.json index e7eca2117e0..e24df5371bc 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraArray.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraArray.json @@ -18,5 +18,9 @@ true ] }, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json index 6434aa97b3c..0cda8102b77 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json @@ -13,5 +13,9 @@ "extra": { "key": true }, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json index a209f7ca707..8d6ce33adbe 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json @@ -13,5 +13,9 @@ "extra": { "key": "test" }, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json index 40b2cae5eab..b4dbfeb734f 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json @@ -18,5 +18,9 @@ "string" ] }, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraMap.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraMap.json index 90f5ac1bdb1..94a5c005e9a 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraMap.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraMap.json @@ -15,5 +15,9 @@ "key": "value" } }, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNull.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNull.json index bff59b32319..f3781b86c77 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNull.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNull.json @@ -13,5 +13,9 @@ "extra": { "key": null }, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json index 90e017ab898..3f2c420d408 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json @@ -15,5 +15,9 @@ "null": "value" } }, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json index 8357bfeefb3..a401012a901 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json @@ -13,5 +13,9 @@ "extra": { "key": 1 }, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json index 2a6e3d2e172..8fa35ba341e 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json @@ -15,5 +15,9 @@ "true": "value" } }, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json index 231cfd9913a..71afb16f4e8 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json @@ -20,5 +20,9 @@ ] ] }, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json index 3397d14d396..46472bd6d1f 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json @@ -20,5 +20,9 @@ ] } }, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraString.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraString.json index 84f4f750da6..b10994f3224 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraString.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraString.json @@ -13,5 +13,9 @@ "extra": { "key": "string" }, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testFingerprint.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testFingerprint.json index 0269baf4838..7b0b1088112 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testFingerprint.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testFingerprint.json @@ -15,5 +15,9 @@ "fingerprint1", "fingerprint2" ], - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json index 176e6bd4170..2446742d4dc 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json @@ -12,5 +12,9 @@ "environment": null, "extra": {}, "checksum": null, - "interfaceKey": null + "interfaceKey": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json index d2d48f38834..7e9f7514e52 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json @@ -11,5 +11,9 @@ "release": null, "environment": null, "extra": {}, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelError.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelError.json index bfd520094e9..1ba7f9751ad 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelError.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelError.json @@ -11,5 +11,9 @@ "release": null, "environment": null, "extra": {}, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json index 10f5044afd6..9c8f894b166 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json @@ -11,5 +11,9 @@ "release": null, "environment": null, "extra": {}, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json index 191983c5363..986886bca6b 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json @@ -11,5 +11,9 @@ "release": null, "environment": null, "extra": {}, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json index 636e8a0789c..2af16e2d25b 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json @@ -11,5 +11,9 @@ "release": null, "environment": null, "extra": {}, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLogger.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLogger.json index 7c4d2acfb48..1f9cabdaabb 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLogger.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLogger.json @@ -11,5 +11,9 @@ "release": null, "environment": null, "extra": {}, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testMessage.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testMessage.json index 4eb0681b5e4..44d559372c1 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testMessage.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testMessage.json @@ -11,5 +11,9 @@ "release": null, "environment": null, "extra": {}, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testPlatform.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testPlatform.json index 96ea413a8ea..978829a5522 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testPlatform.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testPlatform.json @@ -11,5 +11,9 @@ "release": null, "environment": null, "extra": {}, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testRelease.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testRelease.json index d1447f907d7..ca43ee78933 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testRelease.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testRelease.json @@ -11,5 +11,9 @@ "release": "release", "environment": null, "extra": {}, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testSdk.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testSdk.json new file mode 100644 index 00000000000..3d292f6db55 --- /dev/null +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testSdk.json @@ -0,0 +1,19 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "release": null, + "environment": null, + "extra": {}, + "checksum": null, + "sdk": { + "name": "sdkName", + "version": "sdkVersion" + } +} diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testServerName.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testServerName.json index f3a57076375..25bf5705f0f 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testServerName.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testServerName.json @@ -11,5 +11,9 @@ "release": null, "environment": null, "extra": {}, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTags.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTags.json index 0e7ce261855..47787fc1413 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTags.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTags.json @@ -13,5 +13,9 @@ "release": null, "environment": null, "extra": {}, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTimestamp.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTimestamp.json index f9cb01c46db..9a954e27b28 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTimestamp.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTimestamp.json @@ -11,5 +11,9 @@ "release": null, "extra": {}, "environment": null, - "checksum": null + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/raven/src/test/resources/raven-build.properties b/raven/src/test/resources/raven-build.properties index 4b4cf051ab6..db75953b62f 100644 --- a/raven/src/test/resources/raven-build.properties +++ b/raven/src/test/resources/raven-build.properties @@ -1 +1 @@ -build.name=Raven-Java/Test +build.name=test diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/Event.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/Event.java index 83663e21ca9..44556df47af 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/Event.java +++ b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/Event.java @@ -24,6 +24,8 @@ public class Event { private String logger; @JsonProperty(value = "platform") private String platform; + @JsonProperty(value = "sdk") + private Map sdk; @JsonProperty(value = "culprit") private String culprit; @JsonProperty(value = "tags") From bef9835f69fb38049edd929e1945d9006cd20850 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 7 Feb 2017 17:19:35 -0800 Subject: [PATCH 1498/2152] Bump docs to 7.8.2 --- docs/modules/appengine.rst | 2 +- docs/modules/log4j.rst | 2 +- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 2 +- docs/modules/raven.rst | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index aa0c6a7ae92..0108b53732a 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -25,7 +25,7 @@ If you want to use Maven you can install Raven-AppEngine as dependency: com.getsentry.raven raven-appengine - 7.8.1 + 7.8.2 Usage diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index b11e0d5edbb..f1f0a0d12f1 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven-Log4j as dependency: com.getsentry.raven raven-log4j - 7.8.1 + 7.8.2 If you manually want to manage your dependencies: diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 643755aae5e..ea28817ba7d 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,18 +19,18 @@ If you want to use Maven you can install Raven-Log4j2 as dependency: com.getsentry.raven raven-log4j2 - 7.8.1 + 7.8.2 If you manually want to manage your dependencies: - :doc:`raven dependencies ` - `log4j-api-2.1.jar - `_ + `_ - `log4j-core-2.0.jar - `_ + `_ - `log4j-slf4j-impl-2.1.jar - `_ + `_ is recommended as the implementation of slf4j (instead of slf4j-jdk14). Usage diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 9f0ff67e785..fd523d5e92a 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -16,7 +16,7 @@ If you want to use Maven you can install Raven-Logback as dependency: com.getsentry.raven raven-logback - 7.8.1 + 7.8.2 - :doc:`raven dependencies ` diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index 8b3a7e144c4..22ebc9ab83d 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -19,7 +19,7 @@ If you want to use Maven you can install Raven as dependency: com.getsentry.raven raven - 7.8.1 + 7.8.2 If you manually want to manage your dependencies: From 5e71d04f5d6626cfdf996e6606be50b08a6a9148 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 7 Feb 2017 17:20:44 -0800 Subject: [PATCH 1499/2152] [maven-release-plugin] prepare release v7.8.2 --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 17f475cbb6f..f6e7b25f219 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.8.2-SNAPSHOT + 7.8.2 pom Raven-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v7.8.2 https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index 360caffbf4a..d81a7e080d8 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.2-SNAPSHOT + 7.8.2 raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index ea4f137d5eb..404abc437d0 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.8.2-SNAPSHOT + 7.8.2 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 1acb9fe2969..9cdde480673 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.2-SNAPSHOT + 7.8.2 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 4eb983661ae..9de1ae455cb 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.2-SNAPSHOT + 7.8.2 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 1d84040bcbe..892f0fd05bf 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.2-SNAPSHOT + 7.8.2 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 680db7410cf..224c4f3a416 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.2-SNAPSHOT + 7.8.2 raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index 407e131859f..a77c6549797 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.2-SNAPSHOT + 7.8.2 sentry-stub From 54b68b38ebf4bd770b0934cc03e17587fb649cad Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 7 Feb 2017 17:20:44 -0800 Subject: [PATCH 1500/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- sentry-stub/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index f6e7b25f219..478fb69ada7 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.8.2 + 7.8.3-SNAPSHOT pom Raven-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v7.8.2 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index d81a7e080d8..07b64f707c6 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.2 + 7.8.3-SNAPSHOT raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 404abc437d0..9c9922a5f30 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.8.2 + 7.8.3-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 9cdde480673..2436d8ab02c 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.2 + 7.8.3-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 9de1ae455cb..1f4344d6a01 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.2 + 7.8.3-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 892f0fd05bf..52a8f3b8d67 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.2 + 7.8.3-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 224c4f3a416..87ce92582cb 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.2 + 7.8.3-SNAPSHOT raven diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml index a77c6549797..4436f0ddfa5 100644 --- a/sentry-stub/pom.xml +++ b/sentry-stub/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.2 + 7.8.3-SNAPSHOT sentry-stub From cbd3dd5df4de41d659ed395dacc4d1e127104fb2 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 7 Feb 2017 17:20:46 -0800 Subject: [PATCH 1501/2152] Bump CHANGES to 7.8.3 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 729606bd8a6..d67651fd39b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 7.8.3 +------------- + +- + Version 7.8.2 ------------- From 25adfd3c31c2412e82376aef329c824ac5b01121 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 8 Feb 2017 11:31:08 -0800 Subject: [PATCH 1502/2152] Checkstyle: require block braces, standardize indentation. (#299) --- pom.xml | 8 ++ .../connection/AppEngineAsyncConnection.java | 3 +- .../getsentry/raven/log4j/SentryAppender.java | 22 +++-- .../raven/log4j2/SentryAppender.java | 50 +++++++---- .../raven/logback/SentryAppender.java | 16 ++-- .../getsentry/raven/DefaultRavenFactory.java | 24 +++--- .../raven/connection/AbstractConnection.java | 9 +- .../raven/connection/AsyncConnection.java | 18 ++-- .../raven/connection/HttpConnection.java | 6 +- .../java/com/getsentry/raven/dsn/Dsn.java | 77 ++++++++++++----- .../raven/environment/RavenEnvironment.java | 3 +- .../java/com/getsentry/raven/event/Event.java | 28 +++--- .../getsentry/raven/event/EventBuilder.java | 21 +++-- .../event/helper/HttpEventBuilderHelper.java | 3 +- .../event/interfaces/ExceptionInterface.java | 8 +- .../raven/event/interfaces/HttpInterface.java | 85 ++++++++++++++----- .../event/interfaces/SentryException.java | 32 ++++--- .../event/interfaces/StackTraceInterface.java | 8 +- .../raven/event/interfaces/UserInterface.java | 8 +- .../getsentry/raven/jul/SentryHandler.java | 31 ++++--- .../raven/marshaller/json/JsonMarshaller.java | 24 +++--- .../json/MessageInterfaceBinding.java | 8 +- .../json/StackTraceInterfaceBinding.java | 4 +- .../java/com/getsentry/raven/util/Base64.java | 28 ++++-- .../raven/util/Base64OutputStream.java | 4 +- .../SentryAuthenticationFilter.java | 3 +- .../raven/sentrystub/auth/AuthValidator.java | 37 +++++--- .../raven/sentrystub/util/Base64.java | 28 ++++-- .../sentrystub/util/Base64InputStream.java | 4 +- src/checkstyle/checkstyle.xml | 9 +- 30 files changed, 407 insertions(+), 202 deletions(-) diff --git a/pom.xml b/pom.xml index 478fb69ada7..db4fa1a24b8 100644 --- a/pom.xml +++ b/pom.xml @@ -364,6 +364,14 @@ src/checkstyle/checkstyle.xml true + + + com.puppycrawl.tools + checkstyle + + 6.19 + + checkstyle diff --git a/raven-appengine/src/main/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnection.java b/raven-appengine/src/main/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnection.java index 48d534f1129..bd29e133d3d 100644 --- a/raven-appengine/src/main/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnection.java +++ b/raven-appengine/src/main/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnection.java @@ -72,8 +72,9 @@ public AppEngineAsyncConnection(String id, Connection actualConnection) { */ @Override public void send(Event event) { - if (!closed) + if (!closed) { queue.add(withPayload(new EventSubmitter(id, event))); + } } @Override diff --git a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java index 0b4cd187e80..fca09daaf8b 100644 --- a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java @@ -126,7 +126,9 @@ protected static Event.Level formatLevel(Level level) { return Event.Level.INFO; } else if (level.isGreaterOrEqual(Level.ALL)) { return Event.Level.DEBUG; - } else return null; + } else { + return null; + } } /** @@ -155,8 +157,9 @@ public void activateOptions() { */ protected synchronized void initRaven() { try { - if (dsn == null) + if (dsn == null) { dsn = Dsn.dsnLookup(); + } raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); } catch (InvalidDsnException e) { @@ -171,8 +174,9 @@ protected synchronized void initRaven() { @Override protected void append(LoggingEvent loggingEvent) { // Do not log the event if the current thread is managed by raven - if (RavenEnvironment.isManagingThread()) + if (RavenEnvironment.isManagingThread()) { return; + } RavenEnvironment.startManagingThread(); try { @@ -241,8 +245,9 @@ protected Event buildEvent(LoggingEvent loggingEvent) { eventBuilder.withCulprit(loggingEvent.getLoggerName()); } - if (loggingEvent.getNDC() != null) + if (loggingEvent.getNDC() != null) { eventBuilder.withExtra(LOG4J_NDC, loggingEvent.getNDC()); + } @SuppressWarnings("unchecked") Map properties = (Map) loggingEvent.getProperties(); @@ -254,8 +259,9 @@ protected Event buildEvent(LoggingEvent loggingEvent) { } } - for (Map.Entry tagEntry : tags.entrySet()) + for (Map.Entry tagEntry : tags.entrySet()) { eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); + } raven.runBuilderHelpers(eventBuilder); return eventBuilder.build(); @@ -303,11 +309,13 @@ public void setExtraTags(String extraTags) { public void close() { RavenEnvironment.startManagingThread(); try { - if (this.closed) + if (this.closed) { return; + } this.closed = true; - if (raven != null) + if (raven != null) { raven.closeConnection(); + } } catch (Exception e) { getErrorHandler().error("An exception occurred while closing the Raven connection", e, ErrorCode.CLOSE_FAILURE); diff --git a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java index d7841721db5..fe18c8a3ab5 100644 --- a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java @@ -170,16 +170,21 @@ public static SentryAppender createAppender(@PluginAttribute("name") final Strin SentryAppender sentryAppender = new SentryAppender(name, filter); sentryAppender.setDsn(dsn); - if (release != null) + if (release != null) { sentryAppender.setRelease(release); - if (environment != null) + } + if (environment != null) { sentryAppender.setEnvironment(environment); - if (serverName != null) + } + if (serverName != null) { sentryAppender.setServerName(serverName); - if (tags != null) + } + if (tags != null) { sentryAppender.setTags(tags); - if (extraTags != null) + } + if (extraTags != null) { sentryAppender.setExtraTags(extraTags); + } sentryAppender.setRavenFactory(ravenFactory); return sentryAppender; } @@ -191,16 +196,17 @@ public static SentryAppender createAppender(@PluginAttribute("name") final Strin * @return log level used within raven. */ protected static Event.Level formatLevel(Level level) { - if (level.isMoreSpecificThan(Level.FATAL)) + if (level.isMoreSpecificThan(Level.FATAL)) { return Event.Level.FATAL; - else if (level.isMoreSpecificThan(Level.ERROR)) + } else if (level.isMoreSpecificThan(Level.ERROR)) { return Event.Level.ERROR; - else if (level.isMoreSpecificThan(Level.WARN)) + } else if (level.isMoreSpecificThan(Level.WARN)) { return Event.Level.WARNING; - else if (level.isMoreSpecificThan(Level.INFO)) + } else if (level.isMoreSpecificThan(Level.INFO)) { return Event.Level.INFO; - else + } else { return Event.Level.DEBUG; + } } /** @@ -213,8 +219,9 @@ else if (level.isMoreSpecificThan(Level.INFO)) */ protected static List formatMessageParameters(Object[] parameters) { List stringParameters = new ArrayList<>(parameters.length); - for (Object parameter : parameters) + for (Object parameter : parameters) { stringParameters.add((parameter != null) ? parameter.toString() : null); + } return stringParameters; } @@ -229,8 +236,9 @@ protected static List formatMessageParameters(Object[] parameters) { @Override public void append(LogEvent logEvent) { // Do not log the event if the current thread is managed by raven - if (RavenEnvironment.isManagingThread()) + if (RavenEnvironment.isManagingThread()) { return; + } RavenEnvironment.startManagingThread(); try { @@ -251,8 +259,9 @@ public void append(LogEvent logEvent) { */ protected synchronized void initRaven() { try { - if (dsn == null) + if (dsn == null) { dsn = Dsn.dsnLookup(); + } raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); } catch (InvalidDsnException e) { @@ -311,8 +320,9 @@ protected Event buildEvent(LogEvent event) { eventBuilder.withCulprit(event.getLoggerName()); } - if (event.getContextStack() != null) + if (event.getContextStack() != null) { eventBuilder.withExtra(LOG4J_NDC, event.getContextStack().asList()); + } if (event.getContextMap() != null) { for (Map.Entry contextEntry : event.getContextMap().entrySet()) { @@ -324,11 +334,13 @@ protected Event buildEvent(LogEvent event) { } } - if (event.getMarker() != null) + if (event.getMarker() != null) { eventBuilder.withTag(LOG4J_MARKER, event.getMarker().getName()); + } - for (Map.Entry tagEntry : tags.entrySet()) + for (Map.Entry tagEntry : tags.entrySet()) { eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); + } raven.runBuilderHelpers(eventBuilder); return eventBuilder.build(); @@ -377,11 +389,13 @@ public void setExtraTags(String extraTags) { public void stop() { RavenEnvironment.startManagingThread(); try { - if (!isStarted()) + if (!isStarted()) { return; + } super.stop(); - if (raven != null) + if (raven != null) { raven.closeConnection(); + } } catch (Exception e) { error("An exception occurred while closing the Raven connection", e); } finally { diff --git a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java index 0d50858b67b..21162ef3df4 100644 --- a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java @@ -152,7 +152,9 @@ protected static Event.Level formatLevel(Level level) { return Event.Level.INFO; } else if (level.isGreaterOrEqual(Level.ALL)) { return Event.Level.DEBUG; - } else return null; + } else { + return null; + } } /** @@ -263,11 +265,13 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { } } - if (iLoggingEvent.getMarker() != null) + if (iLoggingEvent.getMarker() != null) { eventBuilder.withTag(LOGBACK_MARKER, iLoggingEvent.getMarker().getName()); + } - for (Map.Entry tagEntry : tags.entrySet()) + for (Map.Entry tagEntry : tags.entrySet()) { eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); + } raven.runBuilderHelpers(eventBuilder); return eventBuilder.build(); @@ -416,11 +420,13 @@ public void setExtraTags(String extraTags) { public void stop() { RavenEnvironment.startManagingThread(); try { - if (!isStarted()) + if (!isStarted()) { return; + } super.stop(); - if (raven != null) + if (raven != null) { raven.closeConnection(); + } } catch (Exception e) { addError("An exception occurred while closing the Raven connection", e); } finally { diff --git a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java index a70755b2e5d..49a85a66032 100644 --- a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java @@ -180,7 +180,7 @@ public Raven createRavenInstance(Dsn dsn) { raven.addBuilderHelper(new HttpEventBuilderHelper()); } catch (ClassNotFoundException e) { logger.debug("The current environment doesn't provide access to servlets," - + "or provides an unsupported version."); + + "or provides an unsupported version."); } raven.addBuilderHelper(new ContextBuilderHelper(raven)); return raven; @@ -248,8 +248,8 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { } ExecutorService executorService = new ThreadPoolExecutor( - maxThreads, maxThreads, 0L, TimeUnit.MILLISECONDS, queue, - new DaemonThreadFactory(priority), getRejectedExecutionHandler(dsn)); + maxThreads, maxThreads, 0L, TimeUnit.MILLISECONDS, queue, + new DaemonThreadFactory(priority), getRejectedExecutionHandler(dsn)); boolean gracefulShutdown = getAsyncGracefulShutdownEnabled(dsn); @@ -352,12 +352,12 @@ protected Marshaller createMarshaller(Dsn dsn) { */ protected Collection getNotInAppFrames() { return Arrays.asList("com.sun.", - "java.", - "javax.", - "org.omg.", - "sun.", - "junit.", - "com.intellij.rt."); + "java.", + "javax.", + "org.omg.", + "sun.", + "junit.", + "com.intellij.rt."); } /** @@ -602,10 +602,12 @@ private DaemonThreadFactory(int priority) { @Override public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); - if (!t.isDaemon()) + if (!t.isDaemon()) { t.setDaemon(true); - if (t.getPriority() != priority) + } + if (t.getPriority() != priority) { t.setPriority(priority); + } return t; } } diff --git a/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java b/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java index 8c7db974062..328910d78fa 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java @@ -109,8 +109,9 @@ private void waitIfLockedDown() { while (lockdown.get()) { lock.lock(); try { - if (lockdown.get()) + if (lockdown.get()) { condition.await(); + } } catch (InterruptedException e) { Thread.currentThread().interrupt(); logger.warn("An exception occurred during the lockdown.", e); @@ -124,16 +125,18 @@ private void waitIfLockedDown() { * Initiates a lockdown for {@link #waitingTime}ms and resume the paused threads once the lockdown ends. */ private void lockDown() { - if (!lockdown.compareAndSet(false, true)) + if (!lockdown.compareAndSet(false, true)) { return; + } try { logger.warn("Lockdown started for {}ms.", waitingTime); Thread.sleep(waitingTime); // Double the wait until the maximum is reached - if (waitingTime < maxWaitingTime) + if (waitingTime < maxWaitingTime) { waitingTime <<= 1; + } } catch (Exception e) { logger.warn("An exception occurred during the lockdown.", e); } finally { diff --git a/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java b/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java index 7e5aacd1e2f..81cd1dcc18e 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java @@ -56,12 +56,13 @@ public class AsyncConnection implements Connection { * @param shutdownTimeout timeout for graceful shutdown of the executor, in milliseconds. */ public AsyncConnection(Connection actualConnection, ExecutorService executorService, boolean gracefulShutdown, - long shutdownTimeout) { + long shutdownTimeout) { this.actualConnection = actualConnection; - if (executorService == null) + if (executorService == null) { this.executorService = Executors.newSingleThreadExecutor(); - else + } else { this.executorService = executorService; + } if (gracefulShutdown) { this.gracefulShutdown = gracefulShutdown; addShutdownHook(); @@ -84,8 +85,9 @@ private void addShutdownHook() { */ @Override public void send(Event event) { - if (!closed) + if (!closed) { executorService.execute(new EventSubmitter(event)); + } } @Override @@ -173,10 +175,10 @@ public void run() { private final class ShutDownHook extends Thread { - /** - * Whether or not this ShutDownHook instance will do anything when run. - */ - private volatile boolean enabled = true; + /** + * Whether or not this ShutDownHook instance will do anything when run. + */ + private volatile boolean enabled = true; @Override public void run() { diff --git a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java b/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java index 358447fcd6b..166e3c7e9ef 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java @@ -162,10 +162,12 @@ protected void doSend(Event event) throws ConnectionException { } catch (IOException e) { String errorMessage = null; final InputStream errorStream = connection.getErrorStream(); - if (errorStream != null) + if (errorStream != null) { errorMessage = getErrorMessageFromStream(errorStream); - if (null == errorMessage || errorMessage.isEmpty()) + } + if (null == errorMessage || errorMessage.isEmpty()) { errorMessage = "An exception occurred while submitting the event to the sentry server."; + } throw new ConnectionException(errorMessage, e); } finally { connection.disconnect(); diff --git a/raven/src/main/java/com/getsentry/raven/dsn/Dsn.java b/raven/src/main/java/com/getsentry/raven/dsn/Dsn.java index 516ea661697..3d7ab7ed2d1 100644 --- a/raven/src/main/java/com/getsentry/raven/dsn/Dsn.java +++ b/raven/src/main/java/com/getsentry/raven/dsn/Dsn.java @@ -54,8 +54,9 @@ public Dsn(String dsn) throws InvalidDsnException { * @throws InvalidDsnException the given DSN is not valid. */ public Dsn(URI dsn) throws InvalidDsnException { - if (dsn == null) + if (dsn == null) { throw new InvalidDsnException("The sentry DSN must be provided and not be null"); + } options = new HashMap<>(); protocolSettings = new HashSet<>(); @@ -100,8 +101,9 @@ public static String dsnLookup() { */ private void extractPathInfo(URI dsnUri) { String uriPath = dsnUri.getPath(); - if (uriPath == null) + if (uriPath == null) { return; + } int projectIdStart = uriPath.lastIndexOf("/") + 1; path = uriPath.substring(0, projectIdStart); projectId = uriPath.substring(projectIdStart); @@ -124,8 +126,9 @@ private void extractHostInfo(URI dsnUri) { */ private void extractProtocolInfo(URI dsnUri) { String scheme = dsnUri.getScheme(); - if (scheme == null) + if (scheme == null) { return; + } String[] schemeDetails = scheme.split("\\+"); protocolSettings.addAll(Arrays.asList(schemeDetails).subList(0, schemeDetails.length - 1)); protocol = schemeDetails[schemeDetails.length - 1]; @@ -138,12 +141,14 @@ private void extractProtocolInfo(URI dsnUri) { */ private void extractUserKeys(URI dsnUri) { String userInfo = dsnUri.getUserInfo(); - if (userInfo == null) + if (userInfo == null) { return; + } String[] userDetails = userInfo.split(":"); publicKey = userDetails[0]; - if (userDetails.length > 1) + if (userDetails.length > 1) { secretKey = userDetails[1]; + } } /** @@ -153,8 +158,9 @@ private void extractUserKeys(URI dsnUri) { */ private void extractOptions(URI dsnUri) { String query = dsnUri.getQuery(); - if (query == null || query.isEmpty()) + if (query == null || query.isEmpty()) { return; + } for (String optionPair : query.split("&")) { try { String[] pairDetails = optionPair.split("="); @@ -183,17 +189,22 @@ private void makeOptionsImmutable() { */ private void validate() { List missingElements = new LinkedList<>(); - if (host == null) + if (host == null) { missingElements.add("host"); - if (publicKey == null) + } + if (publicKey == null) { missingElements.add("public key"); - if (secretKey == null) + } + if (secretKey == null) { missingElements.add("secret key"); - if (projectId == null || projectId.isEmpty()) + } + if (projectId == null || projectId.isEmpty()) { missingElements.add("project ID"); + } - if (!missingElements.isEmpty()) + if (!missingElements.isEmpty()) { throw new InvalidDsnException("Invalid DSN, the following properties aren't set '" + missingElements + "'"); + } } public String getSecretKey() { @@ -243,20 +254,42 @@ public URI getUri() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } Dsn dsn = (Dsn) o; - if (port != dsn.port) return false; - if (!host.equals(dsn.host)) return false; - if (!options.equals(dsn.options)) return false; - if (!path.equals(dsn.path)) return false; - if (!projectId.equals(dsn.projectId)) return false; - if (protocol != null ? !protocol.equals(dsn.protocol) : dsn.protocol != null) return false; - if (!protocolSettings.equals(dsn.protocolSettings)) return false; - if (!publicKey.equals(dsn.publicKey)) return false; - if (!secretKey.equals(dsn.secretKey)) return false; + if (port != dsn.port) { + return false; + } + if (!host.equals(dsn.host)) { + return false; + } + if (!options.equals(dsn.options)) { + return false; + } + if (!path.equals(dsn.path)) { + return false; + } + if (!projectId.equals(dsn.projectId)) { + return false; + } + if (protocol != null ? !protocol.equals(dsn.protocol) : dsn.protocol != null) { + return false; + } + if (!protocolSettings.equals(dsn.protocolSettings)) { + return false; + } + if (!publicKey.equals(dsn.publicKey)) { + return false; + } + if (!secretKey.equals(dsn.secretKey)) { + return false; + } return true; } diff --git a/raven/src/main/java/com/getsentry/raven/environment/RavenEnvironment.java b/raven/src/main/java/com/getsentry/raven/environment/RavenEnvironment.java index 6be3539981c..62ddd1e5a6f 100644 --- a/raven/src/main/java/com/getsentry/raven/environment/RavenEnvironment.java +++ b/raven/src/main/java/com/getsentry/raven/environment/RavenEnvironment.java @@ -52,8 +52,9 @@ private RavenEnvironment() { */ public static void startManagingThread() { try { - if (isManagingThread()) + if (isManagingThread()) { logger.warn("Thread already managed by Raven"); + } } finally { RAVEN_THREAD.get().incrementAndGet(); } diff --git a/raven/src/main/java/com/getsentry/raven/event/Event.java b/raven/src/main/java/com/getsentry/raven/event/Event.java index 81d97ff40fc..d8f7a682ec6 100644 --- a/raven/src/main/java/com/getsentry/raven/event/Event.java +++ b/raven/src/main/java/com/getsentry/raven/event/Event.java @@ -119,8 +119,9 @@ public class Event implements Serializable { * @param id unique identifier of the event. */ Event(UUID id) { - if (id == null) + if (id == null) { throw new IllegalArgumentException("The id can't be null"); + } this.id = id; } @@ -266,13 +267,13 @@ void setSentryInterfaces(Map sentryInterfaces) { @SuppressWarnings("unchecked") private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException { + throws IOException, ClassNotFoundException { stream.defaultReadObject(); extra = (Map) stream.readObject(); } private void writeObject(ObjectOutputStream stream) - throws IOException { + throws IOException { stream.defaultWriteObject(); stream.writeObject(convertToSerializable(extra)); } @@ -290,10 +291,11 @@ private void writeObject(ObjectOutputStream stream) private static HashMap convertToSerializable(Map objectMap) { HashMap serializableMap = new HashMap<>(objectMap.size()); for (Map.Entry objectEntry : objectMap.entrySet()) { - if (objectEntry.getValue() instanceof Serializable) + if (objectEntry.getValue() instanceof Serializable) { serializableMap.put(objectEntry.getKey(), (Serializable) objectEntry.getValue()); - else + } else { serializableMap.put(objectEntry.getKey(), objectEntry.getValue().toString()); + } } return serializableMap; } @@ -301,8 +303,12 @@ private void writeObject(ObjectOutputStream stream) @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } return id.equals(((Event) o).id); } @@ -315,10 +321,10 @@ public int hashCode() { @Override public String toString() { return "Event{" - + "level=" + level - + ", message='" + message + '\'' - + ", logger='" + logger + '\'' - + '}'; + + "level=" + level + + ", message='" + message + '\'' + + ", logger='" + logger + '\'' + + '}'; } /** diff --git a/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java b/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java index 81d5fec49fe..212a429518c 100644 --- a/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java +++ b/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java @@ -79,22 +79,27 @@ private static String calculateChecksum(String string) { */ private static void autoSetMissingValues(Event event) { // Ensure that a timestamp is set (to now at least!) - if (event.getTimestamp() == null) + if (event.getTimestamp() == null) { event.setTimestamp(new Date()); + } // Ensure that a platform is set - if (event.getPlatform() == null) + if (event.getPlatform() == null) { event.setPlatform(DEFAULT_PLATFORM); + } // Ensure that an SDK is set - if (event.getSdkName() == null) + if (event.getSdkName() == null) { event.setSdkName(RavenEnvironment.SDK_NAME); - if (event.getSdkVersion() == null) + } + if (event.getSdkVersion() == null) { event.setSdkVersion(RavenEnvironment.SDK_VERSION); + } // Ensure that a hostname is set - if (event.getServerName() == null) + if (event.getServerName() == null) { event.setServerName(HOSTNAME_CACHE.getHostname()); + } } /** @@ -370,8 +375,9 @@ public EventBuilder withSentryInterface(SentryInterface sentryInterface) { * @return the current {@code EventBuilder} for chained calls. */ public EventBuilder withSentryInterface(SentryInterface sentryInterface, boolean replace) { - if (replace || !event.getSentryInterfaces().containsKey(sentryInterface.getInterfaceName())) + if (replace || !event.getSentryInterfaces().containsKey(sentryInterface.getInterfaceName())) { event.getSentryInterfaces().put(sentryInterface.getInterfaceName(), sentryInterface); + } return this; } @@ -383,8 +389,9 @@ public EventBuilder withSentryInterface(SentryInterface sentryInterface, boolean * @return an immutable event. */ public synchronized Event build() { - if (alreadyBuilt) + if (alreadyBuilt) { throw new IllegalStateException("A message can't be built twice"); + } autoSetMissingValues(event); makeImmutable(event); diff --git a/raven/src/main/java/com/getsentry/raven/event/helper/HttpEventBuilderHelper.java b/raven/src/main/java/com/getsentry/raven/event/helper/HttpEventBuilderHelper.java index 32106de85fc..22dfd235692 100644 --- a/raven/src/main/java/com/getsentry/raven/event/helper/HttpEventBuilderHelper.java +++ b/raven/src/main/java/com/getsentry/raven/event/helper/HttpEventBuilderHelper.java @@ -36,8 +36,9 @@ public HttpEventBuilderHelper(RemoteAddressResolver remoteAddressResolver) { @Override public void helpBuildingEvent(EventBuilder eventBuilder) { HttpServletRequest servletRequest = RavenServletRequestListener.getServletRequest(); - if (servletRequest == null) + if (servletRequest == null) { return; + } addHttpInterface(eventBuilder, servletRequest); addUserInterface(eventBuilder, servletRequest); diff --git a/raven/src/main/java/com/getsentry/raven/event/interfaces/ExceptionInterface.java b/raven/src/main/java/com/getsentry/raven/event/interfaces/ExceptionInterface.java index 2d6647615c8..8b8815d0fb5 100644 --- a/raven/src/main/java/com/getsentry/raven/event/interfaces/ExceptionInterface.java +++ b/raven/src/main/java/com/getsentry/raven/event/interfaces/ExceptionInterface.java @@ -48,8 +48,12 @@ public String toString() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } ExceptionInterface that = (ExceptionInterface) o; diff --git a/raven/src/main/java/com/getsentry/raven/event/interfaces/HttpInterface.java b/raven/src/main/java/com/getsentry/raven/event/interfaces/HttpInterface.java index e476a1e9ee5..2df8c4c3e4f 100644 --- a/raven/src/main/java/com/getsentry/raven/event/interfaces/HttpInterface.java +++ b/raven/src/main/java/com/getsentry/raven/event/interfaces/HttpInterface.java @@ -52,13 +52,15 @@ public HttpInterface(HttpServletRequest request, RemoteAddressResolver remoteAdd this.requestUrl = request.getRequestURL().toString(); this.method = request.getMethod(); this.parameters = new HashMap<>(); - for (Map.Entry parameterMapEntry : request.getParameterMap().entrySet()) + for (Map.Entry parameterMapEntry : request.getParameterMap().entrySet()) { this.parameters.put(parameterMapEntry.getKey(), Arrays.asList(parameterMapEntry.getValue())); + } this.queryString = request.getQueryString(); if (request.getCookies() != null) { this.cookies = new HashMap<>(); - for (Cookie cookie : request.getCookies()) + for (Cookie cookie : request.getCookies()) { this.cookies.put(cookie.getName(), cookie.getValue()); + } } else { this.cookies = Collections.emptyMap(); } @@ -74,8 +76,9 @@ public HttpInterface(HttpServletRequest request, RemoteAddressResolver remoteAdd this.authType = request.getAuthType(); this.remoteUser = request.getRemoteUser(); this.headers = new HashMap<>(); - for (String headerName : Collections.list(request.getHeaderNames())) + for (String headerName : Collections.list(request.getHeaderNames())) { this.headers.put(headerName, Collections.list(request.getHeaders(headerName))); + } } @Override @@ -163,28 +166,66 @@ public String toString() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } HttpInterface that = (HttpInterface) o; - if (asyncStarted != that.asyncStarted) return false; - if (localPort != that.localPort) return false; - if (secure != that.secure) return false; - if (serverPort != that.serverPort) return false; - if (authType != null ? !authType.equals(that.authType) : that.authType != null) return false; - if (!cookies.equals(that.cookies)) return false; - if (!headers.equals(that.headers)) return false; - if (localAddr != null ? !localAddr.equals(that.localAddr) : that.localAddr != null) return false; - if (localName != null ? !localName.equals(that.localName) : that.localName != null) return false; - if (method != null ? !method.equals(that.method) : that.method != null) return false; - if (!parameters.equals(that.parameters)) return false; - if (protocol != null ? !protocol.equals(that.protocol) : that.protocol != null) return false; - if (queryString != null ? !queryString.equals(that.queryString) : that.queryString != null) return false; - if (remoteAddr != null ? !remoteAddr.equals(that.remoteAddr) : that.remoteAddr != null) return false; - if (remoteUser != null ? !remoteUser.equals(that.remoteUser) : that.remoteUser != null) return false; - if (!requestUrl.equals(that.requestUrl)) return false; - if (serverName != null ? !serverName.equals(that.serverName) : that.serverName != null) return false; + if (asyncStarted != that.asyncStarted) { + return false; + } + if (localPort != that.localPort) { + return false; + } + if (secure != that.secure) { + return false; + } + if (serverPort != that.serverPort) { + return false; + } + if (authType != null ? !authType.equals(that.authType) : that.authType != null) { + return false; + } + if (!cookies.equals(that.cookies)) { + return false; + } + if (!headers.equals(that.headers)) { + return false; + } + if (localAddr != null ? !localAddr.equals(that.localAddr) : that.localAddr != null) { + return false; + } + if (localName != null ? !localName.equals(that.localName) : that.localName != null) { + return false; + } + if (method != null ? !method.equals(that.method) : that.method != null) { + return false; + } + if (!parameters.equals(that.parameters)) { + return false; + } + if (protocol != null ? !protocol.equals(that.protocol) : that.protocol != null) { + return false; + } + if (queryString != null ? !queryString.equals(that.queryString) : that.queryString != null) { + return false; + } + if (remoteAddr != null ? !remoteAddr.equals(that.remoteAddr) : that.remoteAddr != null) { + return false; + } + if (remoteUser != null ? !remoteUser.equals(that.remoteUser) : that.remoteUser != null) { + return false; + } + if (!requestUrl.equals(that.requestUrl)) { + return false; + } + if (serverName != null ? !serverName.equals(that.serverName) : that.serverName != null) { + return false; + } return true; } diff --git a/raven/src/main/java/com/getsentry/raven/event/interfaces/SentryException.java b/raven/src/main/java/com/getsentry/raven/event/interfaces/SentryException.java index f5de66130f0..0a4bdff8153 100644 --- a/raven/src/main/java/com/getsentry/raven/event/interfaces/SentryException.java +++ b/raven/src/main/java/com/getsentry/raven/event/interfaces/SentryException.java @@ -103,29 +103,37 @@ public StackTraceInterface getStackTraceInterface() { @Override public String toString() { return "SentryException{" - + "exceptionMessage='" + exceptionMessage + '\'' - + ", exceptionClassName='" + exceptionClassName + '\'' - + ", exceptionPackageName='" + exceptionPackageName + '\'' - + ", stackTraceInterface=" + stackTraceInterface - + '}'; + + "exceptionMessage='" + exceptionMessage + '\'' + + ", exceptionClassName='" + exceptionClassName + '\'' + + ", exceptionPackageName='" + exceptionPackageName + '\'' + + ", stackTraceInterface=" + stackTraceInterface + + '}'; } @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } SentryException that = (SentryException) o; - if (!exceptionClassName.equals(that.exceptionClassName)) return false; - if (exceptionMessage != null ? !exceptionMessage.equals(that.exceptionMessage) : that.exceptionMessage != null) + if (!exceptionClassName.equals(that.exceptionClassName)) { return false; + } + if (exceptionMessage != null ? !exceptionMessage.equals(that.exceptionMessage) + : that.exceptionMessage != null) { + return false; + } if (exceptionPackageName != null ? !exceptionPackageName.equals(that.exceptionPackageName) - : that.exceptionPackageName != null) + : that.exceptionPackageName != null) { return false; - if (!stackTraceInterface.equals(that.stackTraceInterface)) return false; + } - return true; + return stackTraceInterface.equals(that.stackTraceInterface); } @Override diff --git a/raven/src/main/java/com/getsentry/raven/event/interfaces/StackTraceInterface.java b/raven/src/main/java/com/getsentry/raven/event/interfaces/StackTraceInterface.java index 490dc4f532c..5a1e23a44d6 100644 --- a/raven/src/main/java/com/getsentry/raven/event/interfaces/StackTraceInterface.java +++ b/raven/src/main/java/com/getsentry/raven/event/interfaces/StackTraceInterface.java @@ -59,8 +59,12 @@ public int getFramesCommonWithEnclosing() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } StackTraceInterface that = (StackTraceInterface) o; diff --git a/raven/src/main/java/com/getsentry/raven/event/interfaces/UserInterface.java b/raven/src/main/java/com/getsentry/raven/event/interfaces/UserInterface.java index c88ca114b71..7674bdf4f00 100644 --- a/raven/src/main/java/com/getsentry/raven/event/interfaces/UserInterface.java +++ b/raven/src/main/java/com/getsentry/raven/event/interfaces/UserInterface.java @@ -51,8 +51,12 @@ public String getEmail() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } UserInterface that = (UserInterface) o; diff --git a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java index b7b1cb1897a..ca2352bb783 100644 --- a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java +++ b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java @@ -122,15 +122,17 @@ public SentryHandler(Raven raven) { * @return log level used within raven. */ protected static Event.Level getLevel(Level level) { - if (level.intValue() >= Level.SEVERE.intValue()) + if (level.intValue() >= Level.SEVERE.intValue()) { return Event.Level.ERROR; - else if (level.intValue() >= Level.WARNING.intValue()) + } else if (level.intValue() >= Level.WARNING.intValue()) { return Event.Level.WARNING; - else if (level.intValue() >= Level.INFO.intValue()) + } else if (level.intValue() >= Level.INFO.intValue()) { return Event.Level.INFO; - else if (level.intValue() >= Level.ALL.intValue()) + } else if (level.intValue() >= Level.ALL.intValue()) { return Event.Level.DEBUG; - else return null; + } else { + return null; + } } /** @@ -143,8 +145,9 @@ else if (level.intValue() >= Level.ALL.intValue()) */ protected static List formatMessageParameters(Object[] parameters) { List formattedParameters = new ArrayList<>(parameters.length); - for (Object parameter : parameters) + for (Object parameter : parameters) { formattedParameters.add((parameter != null) ? parameter.toString() : null); + } return formattedParameters; } @@ -188,8 +191,9 @@ protected void retrieveProperties() { @Override public void publish(LogRecord record) { // Do not log the event if the current thread is managed by raven - if (!isLoggable(record) || RavenEnvironment.isManagingThread()) + if (!isLoggable(record) || RavenEnvironment.isManagingThread()) { return; + } RavenEnvironment.startManagingThread(); try { @@ -210,13 +214,14 @@ public void publish(LogRecord record) { */ protected synchronized void initRaven() { try { - if (dsn == null) + if (dsn == null) { dsn = Dsn.dsnLookup(); + } raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); } catch (InvalidDsnException e) { reportError("An exception occurred during the retrieval of the DSN for Raven", - e, ErrorManager.OPEN_FAILURE); + e, ErrorManager.OPEN_FAILURE); } catch (Exception e) { reportError("An exception occurred during the creation of a Raven instance", e, ErrorManager.OPEN_FAILURE); } @@ -258,12 +263,13 @@ protected Event buildEvent(LogRecord record) { eventBuilder.withMessage(topLevelMessage); Throwable throwable = record.getThrown(); - if (throwable != null) + if (throwable != null) { eventBuilder.withSentryInterface(new ExceptionInterface(throwable)); + } if (record.getSourceClassName() != null && record.getSourceMethodName() != null) { StackTraceElement fakeFrame = new StackTraceElement(record.getSourceClassName(), - record.getSourceMethodName(), null, -1); + record.getSourceMethodName(), null, -1); eventBuilder.withCulprit(fakeFrame); } else { eventBuilder.withCulprit(record.getLoggerName()); @@ -328,8 +334,9 @@ public void flush() { public void close() throws SecurityException { RavenEnvironment.startManagingThread(); try { - if (raven != null) + if (raven != null) { raven.closeConnection(); + } } catch (Exception e) { reportError("An exception occurred while closing the Raven connection", e, ErrorManager.CLOSE_FAILURE); } finally { diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java index 3cf2155a214..6ae5c03535d 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java @@ -184,7 +184,7 @@ private void writeContent(JsonGenerator generator, Event event) throws IOExcepti } private void writeInterfaces(JsonGenerator generator, Map sentryInterfaces) - throws IOException { + throws IOException { for (Map.Entry interfaceEntry : sentryInterfaces.entrySet()) { SentryInterface sentryInterface = interfaceEntry.getValue(); @@ -193,7 +193,7 @@ private void writeInterfaces(JsonGenerator generator, Map entry : ((Map) value).entrySet()) { - if (entry.getKey() == null) + if (entry.getKey() == null) { generator.writeFieldName("null"); - else + } else { generator.writeFieldName(entry.getKey().toString()); + } safelyWriteObject(generator, entry.getValue()); } generator.writeEndObject(); @@ -252,7 +253,7 @@ private void safelyWriteObject(JsonGenerator generator, Object value) throws IOE generator.writeObject(value); } catch (IllegalStateException e) { logger.debug("Couldn't marshal '{}' of type '{}', had to be converted into a String", - value, value.getClass()); + value, value.getClass()); generator.writeString(value.toString()); } } @@ -318,11 +319,13 @@ private void writeBreadcumbs(JsonGenerator generator, List breadcrum * @return trimmed message (shortened if necessary). */ private String trimMessage(String message) { - if (message == null) + if (message == null) { return null; - else if (message.length() > maxMessageLength) + } else if (message.length() > maxMessageLength) { return message.substring(0, maxMessageLength); - else return message; + } else { + return message; + } } /** @@ -342,8 +345,9 @@ private String formatId(UUID id) { * @return log level as a String. */ private String formatLevel(Event.Level level) { - if (level == null) + if (level == null) { return null; + } switch (level) { case DEBUG: @@ -358,7 +362,7 @@ private String formatLevel(Event.Level level) { return "error"; default: logger.error("The level '{}' isn't supported, this should NEVER happen, contact Raven developers", - level.name()); + level.name()); return null; } } diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java index b5c52e03e66..57efedcea50 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java @@ -45,11 +45,13 @@ public MessageInterfaceBinding(int maxMessageLength) { * @return trimmed message (shortened if necessary). */ private String trimMessage(String message) { - if (message == null) + if (message == null) { return null; - else if (message.length() > maxMessageLength) + } else if (message.length() > maxMessageLength) { return message.substring(0, maxMessageLength); - else return message; + } else { + return message; + } } @Override diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/StackTraceInterfaceBinding.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/StackTraceInterfaceBinding.java index 5f34da19aac..80c36e2f375 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/StackTraceInterfaceBinding.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/StackTraceInterfaceBinding.java @@ -31,12 +31,12 @@ public class StackTraceInterfaceBinding implements InterfaceBinding= len) break; + if (p >= len) { + break; + } } // The fast path isn't available -- either we've read a // partial tuple, or the next four input bytes aren't all @@ -593,7 +597,9 @@ public boolean process(byte[] input, int offset, int len, boolean finish) { output[op++] = alphabet[(v >> 6) & 0x3f]; output[op++] = alphabet[v & 0x3f]; if (--count == 0) { - if (do_cr) output[op++] = '\r'; + if (do_cr) { + output[op++] = '\r'; + } output[op++] = '\n'; count = LINE_GROUPS; } @@ -613,7 +619,9 @@ public boolean process(byte[] input, int offset, int len, boolean finish) { p += 3; op += 4; if (--count == 0) { - if (do_cr) output[op++] = '\r'; + if (do_cr) { + output[op++] = '\r'; + } output[op++] = '\n'; count = LINE_GROUPS; } @@ -634,7 +642,9 @@ public boolean process(byte[] input, int offset, int len, boolean finish) { output[op++] = '='; } if (do_newline) { - if (do_cr) output[op++] = '\r'; + if (do_cr) { + output[op++] = '\r'; + } output[op++] = '\n'; } } else if (p-tailLen == len-2) { @@ -649,11 +659,15 @@ public boolean process(byte[] input, int offset, int len, boolean finish) { output[op++] = '='; } if (do_newline) { - if (do_cr) output[op++] = '\r'; + if (do_cr) { + output[op++] = '\r'; + } output[op++] = '\n'; } } else if (do_newline && op > 0 && count != LINE_GROUPS) { - if (do_cr) output[op++] = '\r'; + if (do_cr) { + output[op++] = '\r'; + } output[op++] = '\n'; } assert tailLen == 0; diff --git a/raven/src/main/java/com/getsentry/raven/util/Base64OutputStream.java b/raven/src/main/java/com/getsentry/raven/util/Base64OutputStream.java index 61612f6ac20..217cfd559a9 100644 --- a/raven/src/main/java/com/getsentry/raven/util/Base64OutputStream.java +++ b/raven/src/main/java/com/getsentry/raven/util/Base64OutputStream.java @@ -86,7 +86,9 @@ private void flushBuffer() throws IOException { } } public void write(byte[] b, int off, int len) throws IOException { - if (len <= 0) return; + if (len <= 0) { + return; + } flushBuffer(); internalWrite(b, off, len, false); } diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryAuthenticationFilter.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryAuthenticationFilter.java index e74eb01a6eb..b648da67a29 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryAuthenticationFilter.java +++ b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryAuthenticationFilter.java @@ -26,8 +26,9 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; - if (!validateAuth(req, resp)) + if (!validateAuth(req, resp)) { return; + } chain.doFilter(request, response); } diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/auth/AuthValidator.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/auth/AuthValidator.java index ad23651c4b1..c1b18f327f0 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/auth/AuthValidator.java +++ b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/auth/AuthValidator.java @@ -50,8 +50,9 @@ public void validateSentryAuth(Map authParameters) { invalidAuthException); validateClient(authParameters.get(SENTRY_CLIENT_PARAMETER), invalidAuthException); - if (!invalidAuthException.isEmpty()) + if (!invalidAuthException.isEmpty()) { throw invalidAuthException; + } } /** @@ -71,8 +72,9 @@ public void validateSentryAuth(Map authParameters, String projec validateProject(authParameters.get(PUBLIC_KEY_PARAMETER), projectId, invalidAuthException); - if (!invalidAuthException.isEmpty()) + if (!invalidAuthException.isEmpty()) { throw invalidAuthException; + } } /** @@ -84,9 +86,10 @@ public void validateSentryAuth(Map authParameters, String projec * @param invalidAuthException exception thrown if the auth header is invalid. */ private void validateVersion(String authSentryVersion, InvalidAuthException invalidAuthException) { - if (authSentryVersion == null || !SENTRY_PROTOCOL_VERSIONS.contains(authSentryVersion)) + if (authSentryVersion == null || !SENTRY_PROTOCOL_VERSIONS.contains(authSentryVersion)) { invalidAuthException.addDetailedMessage("The version '" + authSentryVersion + "' isn't valid, " - + "only those " + SENTRY_PROTOCOL_VERSIONS + " are supported."); + + "only those " + SENTRY_PROTOCOL_VERSIONS + " are supported."); + } } /** @@ -101,18 +104,21 @@ private void validateVersion(String authSentryVersion, InvalidAuthException inva */ private void validateKeys(String publicKey, String secretKey, InvalidAuthException invalidAuthException) { - if (publicKey == null) + if (publicKey == null) { invalidAuthException.addDetailedMessage("No public key provided"); - else if (!publicKeySecretKey.containsKey(publicKey)) + } else if (!publicKeySecretKey.containsKey(publicKey)) { invalidAuthException.addDetailedMessage("The public key '" + publicKey + "' isn't associated " - + "with a secret key."); + + "with a secret key."); + } - if (secretKey == null) + if (secretKey == null) { invalidAuthException.addDetailedMessage("No secret key provided"); + } - if (secretKey != null && publicKey != null && !secretKey.equals(publicKeySecretKey.get(publicKey))) + if (secretKey != null && publicKey != null && !secretKey.equals(publicKeySecretKey.get(publicKey))) { invalidAuthException.addDetailedMessage("The secret key '" + secretKey + "' " - + "isn't valid for '" + publicKey + "'"); + + "isn't valid for '" + publicKey + "'"); + } } /** @@ -124,12 +130,14 @@ else if (!publicKeySecretKey.containsKey(publicKey)) * @see #addUser(String, String, String) */ private void validateProject(String publicKey, String projectId, InvalidAuthException invalidAuthException) { - if (projectId == null) + if (projectId == null) { invalidAuthException.addDetailedMessage("No project ID provided"); + } - if (projectId != null && publicKey != null && !projectId.equals(publicKeyProjectId.get(publicKey))) + if (projectId != null && publicKey != null && !projectId.equals(publicKeyProjectId.get(publicKey))) { invalidAuthException.addDetailedMessage("The project '" + projectId + "' " - + "can't be accessed by ' " + publicKey + " '"); + + "can't be accessed by ' " + publicKey + " '"); + } } /** @@ -141,8 +149,9 @@ private void validateProject(String publicKey, String projectId, InvalidAuthExce * @param invalidAuthException exception thrown if the auth header is invalid. */ private void validateClient(String client, InvalidAuthException invalidAuthException) { - if (client == null) + if (client == null) { invalidAuthException.addDetailedMessage("The client name is mandatory."); + } } public void loadSentryUsers(String resourceName) { diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64.java index ab3a5da0f35..03b5c612301 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64.java +++ b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64.java @@ -235,7 +235,9 @@ public int maxOutputSize(int len) { * bad base-64 data has been detected in the input stream. */ public boolean process(byte[] input, int offset, int len, boolean finish) { - if (this.state == 6) return false; + if (this.state == 6) { + return false; + } int p = offset; len += offset; // Using local variables makes the decoder about 12% @@ -275,7 +277,9 @@ public boolean process(byte[] input, int offset, int len, boolean finish) { op += 3; p += 4; } - if (p >= len) break; + if (p >= len) { + break; + } } // The fast path isn't available -- either we've read a // partial tuple, or the next four input bytes aren't all @@ -587,7 +591,9 @@ public boolean process(byte[] input, int offset, int len, boolean finish) { output[op++] = alphabet[(v >> 6) & 0x3f]; output[op++] = alphabet[v & 0x3f]; if (--count == 0) { - if (do_cr) output[op++] = '\r'; + if (do_cr) { + output[op++] = '\r'; + } output[op++] = '\n'; count = LINE_GROUPS; } @@ -607,7 +613,9 @@ public boolean process(byte[] input, int offset, int len, boolean finish) { p += 3; op += 4; if (--count == 0) { - if (do_cr) output[op++] = '\r'; + if (do_cr) { + output[op++] = '\r'; + } output[op++] = '\n'; count = LINE_GROUPS; } @@ -628,7 +636,9 @@ public boolean process(byte[] input, int offset, int len, boolean finish) { output[op++] = '='; } if (do_newline) { - if (do_cr) output[op++] = '\r'; + if (do_cr) { + output[op++] = '\r'; + } output[op++] = '\n'; } } else if (p-tailLen == len-2) { @@ -643,11 +653,15 @@ public boolean process(byte[] input, int offset, int len, boolean finish) { output[op++] = '='; } if (do_newline) { - if (do_cr) output[op++] = '\r'; + if (do_cr) { + output[op++] = '\r'; + } output[op++] = '\n'; } } else if (do_newline && op > 0 && count != LINE_GROUPS) { - if (do_cr) output[op++] = '\r'; + if (do_cr) { + output[op++] = '\r'; + } output[op++] = '\n'; } assert tailLen == 0; diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64InputStream.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64InputStream.java index 1e6adf67448..65f8b1fe730 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64InputStream.java +++ b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64InputStream.java @@ -120,7 +120,9 @@ public int read(byte[] b, int off, int len) throws IOException { * outputStart and outputEnd pointers. */ private void refill() throws IOException { - if (eof) return; + if (eof) { + return; + } int bytesRead = in.read(inputBuffer); boolean success; if (bytesRead == -1) { diff --git a/src/checkstyle/checkstyle.xml b/src/checkstyle/checkstyle.xml index cfda47df861..43690dd2879 100644 --- a/src/checkstyle/checkstyle.xml +++ b/src/checkstyle/checkstyle.xml @@ -90,7 +90,6 @@ - @@ -111,7 +110,6 @@ - @@ -126,7 +124,6 @@ - @@ -142,13 +139,11 @@ - - @@ -158,7 +153,7 @@ - + @@ -206,9 +201,9 @@ - + From 2ae97dc2e532cb238282a58cbb5c621d378d00a5 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 8 Feb 2017 13:44:00 -0800 Subject: [PATCH 1503/2152] Fix version in READMEs. --- raven-android/README.md | 4 ++-- raven-appengine/README.md | 6 +++--- raven-log4j/README.md | 6 +++--- raven-log4j2/README.md | 6 +++--- raven-logback/README.md | 6 +++--- raven/README.md | 6 +++--- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/raven-android/README.md b/raven-android/README.md index b8f6ff8b083..1397bf1b6ca 100644 --- a/raven-android/README.md +++ b/raven-android/README.md @@ -4,10 +4,10 @@ ### Gradle (Android Studio) -In your `app/build.gradle` add: `compile 'com.getsentry.raven:raven-android:7.8.1'` +In your `app/build.gradle` add: `compile 'com.getsentry.raven:raven-android:7.8.2'` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-android%7C7.8.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-android%7C7.8.2%7Cjar). ## Usage diff --git a/raven-appengine/README.md b/raven-appengine/README.md index ea74c1e10cf..40be559f24a 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -15,17 +15,17 @@ __This module is not useful outside of Google App Engine.__ com.getsentry.raven raven-appengine - 7.8.1 + 7.8.2 ``` ### Gradle ``` -compile 'com.getsentry.raven:raven-appengine:7.8.1' +compile 'com.getsentry.raven:raven-appengine:7.8.2' ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.8.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.8.2%7Cjar). ## Usage diff --git a/raven-log4j/README.md b/raven-log4j/README.md index f645a4abaeb..47655083cf2 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -10,17 +10,17 @@ for log4j to send the logged events to Sentry. com.getsentry.raven raven-log4j - 7.8.1 + 7.8.2 ``` ### Gradle ``` -compile 'com.getsentry.raven:raven-log4j:7.8.1' +compile 'com.getsentry.raven:raven-log4j:7.8.2' ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.8.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.8.2%7Cjar). ## Usage ### Configuration diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index 2da9c1d12a7..54445bc1a2b 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -10,17 +10,17 @@ for Log4j 2 to send the logged events to Sentry. com.getsentry.raven raven-log4j2 - 7.8.1 + 7.8.2 ``` ### Gradle ``` -compile 'com.getsentry.raven:raven-log4j2:7.8.1' +compile 'com.getsentry.raven:raven-log4j2:7.8.2' ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.8.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.8.2%7Cjar). ## Usage ### Configuration diff --git a/raven-logback/README.md b/raven-logback/README.md index 16f9beae7af..035e3c579c4 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -10,17 +10,17 @@ for logback to send the logged events to Sentry. com.getsentry.raven raven-logback - 7.8.1 + 7.8.2 ``` ### Gradle ``` -compile 'com.getsentry.raven:raven-logback:7.8.1' +compile 'com.getsentry.raven:raven-logback:7.8.2' ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.8.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.8.2%7Cjar). ## Usage ### Configuration diff --git a/raven/README.md b/raven/README.md index 7b0484fd4ff..838ee94ad68 100644 --- a/raven/README.md +++ b/raven/README.md @@ -10,17 +10,17 @@ for `java.util.logging`. com.getsentry.raven raven - 7.8.1 + 7.8.2 ``` ### Gradle ``` -compile 'com.getsentry.raven:raven:7.8.1' +compile 'com.getsentry.raven:raven:7.8.2' ``` ### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.8.1%7Cjar). +Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.8.2%7Cjar). ## Usage (`java.util.logging`) ### Configuration From 537980dfba1e0081fd855e169f2b0eaa3beb73ee Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 8 Feb 2017 13:44:25 -0800 Subject: [PATCH 1504/2152] Fix find command for upgrading version in docs. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index bf56117ab34..b79d5739654 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ prepareDocs: @echo This release: $(RELEASE_VERSION) # Fix released version in documentation @echo Fixing documentation versions - find . -name '*.md' -or -name '*.rst' -exec sed -i -e 's/$(PREVIOUS_RELEASE)/$(RELEASE_VERSION)/g' {} \; + find . \( -name '*.md' -or -name '*.rst' \) -exec sed -i -e 's/$(PREVIOUS_RELEASE)/$(RELEASE_VERSION)/g' {} \; # Commit documentation changes @echo Committing documentation version changes git commit -a -m 'Bump docs to $(RELEASE_VERSION)' From 0691791999ffe0cf745b953553aa61c694f7128a Mon Sep 17 00:00:00 2001 From: JSH Date: Mon, 13 Feb 2017 11:55:43 -0500 Subject: [PATCH 1505/2152] Support for basic authentication to proxy servers (thanks subfxnet) --- .../getsentry/raven/DefaultRavenFactory.java | 34 +++++++++++++++++++ .../raven/connection/ProxyAuthenticator.java | 31 +++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 raven/src/main/java/com/getsentry/raven/connection/ProxyAuthenticator.java diff --git a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java index 49a85a66032..8fc4c0ce431 100644 --- a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java @@ -15,6 +15,7 @@ import java.io.File; import java.net.InetSocketAddress; +import java.net.Authenticator; import java.net.Proxy; import java.net.URL; import java.util.Arrays; @@ -150,6 +151,14 @@ public class DefaultRavenFactory extends RavenFactory { * Option to set an HTTP proxy port for Sentry connections. */ public static final String HTTP_PROXY_PORT_OPTION = "raven.http.proxy.port"; + /** + * Option to set an HTTP proxy username for Sentry connections. + */ + public static final String HTTP_PROXY_USER_OPTION = "raven.http.proxy.user"; + /** + * Option to set an HTTP proxy password for Sentry connections. + */ + public static final String HTTP_PROXY_PASS_OPTION = "raven.http.proxy.password"; /** * The default async queue size if none is provided. */ @@ -267,12 +276,17 @@ protected Connection createHttpConnection(Dsn dsn) { URL sentryApiUrl = HttpConnection.getSentryApiUrl(dsn.getUri(), dsn.getProjectId()); String proxyHost = getProxyHost(dsn); + String proxyUser = getProxyUser(dsn); + String proxyPass = getProxyPass(dsn); int proxyPort = getProxyPort(dsn); Proxy proxy = null; if (proxyHost != null) { InetSocketAddress proxyAddr = new InetSocketAddress(proxyHost, proxyPort); proxy = new Proxy(Proxy.Type.HTTP, proxyAddr); + if (proxyUser != null && proxyPass != null) { + Authenticator.setDefault(new ProxyAuthenticator(proxyUser, proxyPass)); + } } Double sampleRate = getSampleRate(dsn); @@ -513,6 +527,26 @@ protected String getProxyHost(Dsn dsn) { return dsn.getOptions().get(HTTP_PROXY_HOST_OPTION); } + /** + * HTTP proxy username for Sentry connections. + * + * @param dsn Sentry server DSN which may contain options. + * @return HTTP proxy username for Sentry connections. + */ + protected String getProxyUser(Dsn dsn) { + return dsn.getOptions().get(HTTP_PROXY_USER_OPTION); + } + + /** + * HTTP proxy password for Sentry connections. + * + * @param dsn Sentry server DSN which may contain options. + * @return HTTP proxy password for Sentry connections. + */ + protected String getProxyPass(Dsn dsn) { + return dsn.getOptions().get(HTTP_PROXY_PASS_OPTION); + } + /** * Whether to compress requests sent to the Sentry Server. * diff --git a/raven/src/main/java/com/getsentry/raven/connection/ProxyAuthenticator.java b/raven/src/main/java/com/getsentry/raven/connection/ProxyAuthenticator.java new file mode 100644 index 00000000000..08b787e3e91 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/connection/ProxyAuthenticator.java @@ -0,0 +1,31 @@ +package com.getsentry.raven.connection; + +import java.net.Authenticator; +import java.net.PasswordAuthentication; + +/** + * Proxy authenticator. + */ +public class ProxyAuthenticator extends Authenticator { + private String user; + private String pass; + + /** + * Proxy authenticator. + * + * @param user proxy username + * @param pass proxy password + */ + public ProxyAuthenticator(String user, String pass) { + this.user = user; + this.pass = pass; + } + + @Override + protected PasswordAuthentication getPasswordAuthentication() { + if (getRequestorType() == RequestorType.PROXY) { + return new PasswordAuthentication(user, pass.toCharArray()); + } + return null; + } +} From 4245119741bacf3b40383d7df204190dcc98d859 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 13 Feb 2017 12:06:33 -0600 Subject: [PATCH 1506/2152] Add User to RavenContext and ContextBuilderHelper. (thanks mpecan) (#310) --- CHANGES | 2 +- .../com/getsentry/raven/RavenContext.java | 41 ++++++++++-- .../java/com/getsentry/raven/event/User.java | 47 ++++++++++++++ .../getsentry/raven/event/UserBuilder.java | 64 +++++++++++++++++++ .../event/helper/ContextBuilderHelper.java | 24 ++++++- .../com/getsentry/raven/RavenContextTest.java | 20 +++++- .../com/getsentry/raven/event/UserTest.java | 48 ++++++++++++++ 7 files changed, 236 insertions(+), 10 deletions(-) create mode 100644 raven/src/main/java/com/getsentry/raven/event/User.java create mode 100644 raven/src/main/java/com/getsentry/raven/event/UserBuilder.java create mode 100644 raven/src/test/java/com/getsentry/raven/event/UserTest.java diff --git a/CHANGES b/CHANGES index d67651fd39b..ac16fdcdc52 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 7.8.3 ------------- -- +- Add User to RavenContext and ContextBuilderHelper. (thanks mpecan) Version 7.8.2 ------------- diff --git a/raven/src/main/java/com/getsentry/raven/RavenContext.java b/raven/src/main/java/com/getsentry/raven/RavenContext.java index 7438984a887..2bccccee94b 100644 --- a/raven/src/main/java/com/getsentry/raven/RavenContext.java +++ b/raven/src/main/java/com/getsentry/raven/RavenContext.java @@ -1,22 +1,19 @@ package com.getsentry.raven; - import com.getsentry.raven.event.Breadcrumb; +import com.getsentry.raven.event.User; import com.getsentry.raven.util.CircularFifoQueue; - import java.util.ArrayList; import java.util.Collection; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.UUID; - /** * RavenContext is used to hold {@link ThreadLocal} context data (such as * {@link Breadcrumb}s) so that data may be collected in different parts * of an application and then sent together when an exception occurs. */ public class RavenContext implements AutoCloseable { - /** * Thread local set of active context objects. Note that an {@link IdentityHashMap} * is used instead of a Set because there is no identity-set in the Java @@ -36,13 +33,16 @@ public class RavenContext implements AutoCloseable { protected IdentityHashMap initialValue() { return new IdentityHashMap<>(); } - }; + }; /** * The number of {@link Breadcrumb}s to keep in the ring buffer by default. */ private static final int DEFAULT_BREADCRUMB_LIMIT = 100; + /** + * UUID of the last event sent to the Sentry server, if any. + */ private UUID lastEventId; /** @@ -50,6 +50,11 @@ protected IdentityHashMap initialValue() { */ private CircularFifoQueue breadcrumbs; + /** + * User active in the current context, if any. + */ + private User user; + /** * Create a new (empty) RavenContext object with the default Breadcrumb limit. */ @@ -86,6 +91,7 @@ public void deactivate() { public void clear() { breadcrumbs.clear(); lastEventId = null; + user = null; } /** @@ -147,4 +153,29 @@ public void setLastEventId(UUID id) { public UUID getLastEventId() { return lastEventId; } + + /** + * Store the current user in the context. + * + * @param user User to store in context. + */ + public void setUser(User user) { + this.user = user; + } + + /** + * Clears the current user set on this context. + */ + public void clearUser() { + setUser(null); + } + + /** + * Gets the current user from the context. + * + * @return User currently stored in context. + */ + public User getUser() { + return user; + } } diff --git a/raven/src/main/java/com/getsentry/raven/event/User.java b/raven/src/main/java/com/getsentry/raven/event/User.java new file mode 100644 index 00000000000..08962994144 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/event/User.java @@ -0,0 +1,47 @@ +package com.getsentry.raven.event; + + +/** + * An object that represents a user. Typically used to represent + * the user in the current context, for whatever a context means + * in your application (typically a web request). + */ +public class User { + + private final String id; + private final String username; + private final String ipAddress; + private final String email; + + /** + * Create an immutable User object. + * + * @param id String (optional) + * @param username String (optional) + * @param ipAddress String (optional) + * @param email String (optional) + */ + public User(String id, String username, String ipAddress, String email) { + this.id = id; + this.username = username; + this.ipAddress = ipAddress; + this.email = email; + } + + public String getId() { + return id; + } + + public String getUsername() { + return username; + } + + public String getIpAddress() { + return ipAddress; + } + + public String getEmail() { + return email; + } + +} diff --git a/raven/src/main/java/com/getsentry/raven/event/UserBuilder.java b/raven/src/main/java/com/getsentry/raven/event/UserBuilder.java new file mode 100644 index 00000000000..8b2dbdffd35 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/event/UserBuilder.java @@ -0,0 +1,64 @@ +package com.getsentry.raven.event; + +/** + * Builder to assist with the creation of {@link User}s. + */ +public class UserBuilder { + private String id; + private String username; + private String ipAddress; + private String email; + + /** + * Sets the Id for the user. + * + * @param value String + * @return current instance of UserBuilder + */ + public UserBuilder setId(String value) { + this.id = value; + return this; + } + + /** + * Sets the username for the user. + * + * @param value String + * @return current instance of UserBuilder + */ + public UserBuilder setUsername(String value) { + this.username = value; + return this; + } + + /** + * Sets the ipAddress for the user. + * + * @param value String + * @return current instance of UserBuilder + */ + public UserBuilder setIpAddress(String value) { + this.ipAddress = value; + return this; + } + + /** + * Sets the email for the user. + * + * @param value String + * @return current instance of UserBuilder + */ + public UserBuilder setEmail(String value) { + this.email = value; + return this; + } + + /** + * Build and return the {@link User} object. + * + * @return User + */ + public User build() { + return new User(id, username, ipAddress, email); + } +} diff --git a/raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java b/raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java index 7bb60043ee1..a3b4ee44aee 100644 --- a/raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java +++ b/raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java @@ -1,8 +1,11 @@ package com.getsentry.raven.event.helper; import com.getsentry.raven.Raven; +import com.getsentry.raven.RavenContext; import com.getsentry.raven.event.Breadcrumb; import com.getsentry.raven.event.EventBuilder; +import com.getsentry.raven.event.User; +import com.getsentry.raven.event.interfaces.UserInterface; import java.util.ArrayList; import java.util.Iterator; @@ -31,11 +34,26 @@ public ContextBuilderHelper(Raven raven) { @Override public void helpBuildingEvent(EventBuilder eventBuilder) { List breadcrumbs = new ArrayList<>(); - Iterator iter = raven.getContext().getBreadcrumbs(); - while (iter.hasNext()) { - breadcrumbs.add(iter.next()); + RavenContext context = raven.getContext(); + + Iterator breadcrumbIterator = context.getBreadcrumbs(); + while (breadcrumbIterator.hasNext()) { + breadcrumbs.add(breadcrumbIterator.next()); } eventBuilder.withBreadcrumbs(breadcrumbs); + + if (context.getUser() != null) { + eventBuilder.withSentryInterface(fromUser(context.getUser())); + } + } + + /** + * Builds a {@link UserInterface} object from a {@link User} object. + * @param user User + * @return UserInterface + */ + private UserInterface fromUser(User user) { + return new UserInterface(user.getId(), user.getUsername(), user.getIpAddress(), user.getEmail()); } } diff --git a/raven/src/test/java/com/getsentry/raven/RavenContextTest.java b/raven/src/test/java/com/getsentry/raven/RavenContextTest.java index b9b6aa0c8c8..a175e2b7a86 100644 --- a/raven/src/test/java/com/getsentry/raven/RavenContextTest.java +++ b/raven/src/test/java/com/getsentry/raven/RavenContextTest.java @@ -2,7 +2,8 @@ import com.getsentry.raven.event.Breadcrumb; import com.getsentry.raven.event.BreadcrumbBuilder; -import org.hamcrest.Matchers; +import com.getsentry.raven.event.User; +import com.getsentry.raven.event.UserBuilder; import org.testng.annotations.Test; import java.util.ArrayList; @@ -91,4 +92,21 @@ public void breadcrumbLimit() { } + @Test + public void testUser() { + RavenContext context = new RavenContext(); + + User user = new UserBuilder() + .setEmail("test@example.com") + .setId("1234") + .setIpAddress("192.168.0.1") + .setUsername("testUser_123").build(); + + context.setUser(user); + assertThat(context.getUser(), equalTo(user)); + + context.clearUser(); + assertThat(context.getUser(), equalTo(null)); + } + } diff --git a/raven/src/test/java/com/getsentry/raven/event/UserTest.java b/raven/src/test/java/com/getsentry/raven/event/UserTest.java new file mode 100644 index 00000000000..25dddcdadd2 --- /dev/null +++ b/raven/src/test/java/com/getsentry/raven/event/UserTest.java @@ -0,0 +1,48 @@ +package com.getsentry.raven.event; + +import com.getsentry.raven.Raven; +import com.getsentry.raven.connection.Connection; +import com.getsentry.raven.event.helper.ContextBuilderHelper; +import com.getsentry.raven.event.interfaces.UserInterface; +import mockit.Injectable; +import mockit.Tested; +import mockit.Verifications; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; + +public class UserTest { + @Tested + private Raven raven = null; + + @Injectable + private Connection mockConnection = null; + + @Test + public void testUserPropagation() { + raven.addBuilderHelper(new ContextBuilderHelper(raven)); + + final User user = new UserBuilder() + .setEmail("test@example.com") + .setId("1234") + .setIpAddress("192.168.0.1") + .setUsername("testUser_123").build(); + raven.getContext().setUser(user); + + raven.sendEvent(new EventBuilder() + .withMessage("Some random message") + .withLevel(Event.Level.INFO)); + + new Verifications() {{ + Event event; + mockConnection.send(event = withCapture()); + UserInterface userInterface = (UserInterface) event.getSentryInterfaces().get(UserInterface.USER_INTERFACE); + assertThat(userInterface.getId(), equalTo(user.getId())); + assertThat(userInterface.getEmail(), equalTo(user.getEmail())); + assertThat(userInterface.getIpAddress(), equalTo(user.getIpAddress())); + assertThat(userInterface.getUsername(), equalTo(user.getUsername())); + }}; + } + +} From 72ec8dcdab966e355d21f713bd82cde41cc9657d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 13 Feb 2017 13:30:55 -0600 Subject: [PATCH 1507/2152] Add more make commands. --- Makefile | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index b79d5739654..47a6a91d413 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,20 @@ -.PHONY: checkstyle test install clean prepare prepareDocs prepareMvn prepareChanges perform +.PHONY: checkstyle compile test install clean prepare prepareDocs prepareMvn prepareChanges perform verify MVN=mvn -e all: checkstyle test install +compile: + $(MVN) compile + checkstyle: $(MVN) checkstyle:check -test: +verify: $(MVN) verify +test: verify + install: $(MVN) install -Dcheckstyle.skip=true -DskipTests From 775916b92ad681cbb16d2d1958ba90f668b30123 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 17 Feb 2017 16:19:21 -0600 Subject: [PATCH 1508/2152] Migrate Markdown docs to ReST and general documentation improvements. (#311) --- README.md | 388 +------------ docs/config.rst | 509 +++++++++++++----- docs/index.rst | 28 +- docs/installation.rst | 71 --- docs/modules/android.rst | 63 +++ docs/modules/appengine.rst | 51 +- docs/modules/index.rst | 9 +- docs/modules/log4j.rst | 305 ++++++++--- docs/modules/log4j2.rst | 223 +++++--- docs/modules/logback.rst | 205 +++++-- docs/modules/raven.rst | 222 ++++---- docs/sentry-doc-config.json | 24 +- docs/usage.rst | 177 ++++++ raven-android/README.md | 62 +-- raven-appengine/README.md | 53 +- .../connection/AppEngineAsyncConnection.java | 6 +- raven-log4j/README.md | 198 +------ raven-log4j2/README.md | 195 +------ raven-logback/README.md | 194 +------ raven/README.md | 285 +--------- 20 files changed, 1360 insertions(+), 1908 deletions(-) delete mode 100644 docs/installation.rst create mode 100644 docs/modules/android.rst create mode 100644 docs/usage.rst diff --git a/README.md b/README.md index b3d0e88ad30..f232acc3a3b 100644 --- a/README.md +++ b/README.md @@ -1,380 +1,18 @@ -# Raven +# raven-java -[![Build Status](https://travis-ci.org/getsentry/raven-java.svg?branch=master)](https://travis-ci.org/getsentry/raven-java) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.getsentry.raven/raven-all/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.getsentry.raven/raven-all) +Raven is the Java SDK for [Sentry](https://sentry.io/). It provides out-of-the-box support for +many popular JVM based frameworks and libraries, including Android, Log4j, Logback, and more. -Raven is the Java client for [Sentry](https://www.getsentry.com/). -Raven relies on the most popular logging libraries to capture and convert logs -before sending details to a Sentry instance. +In most cases using one of the existing integrations is preferred, but Raven additionally provides +a low level client for manually building and sending events to Sentry that can be used in any JVM +based application. - - [`java.util.logging`](http://docs.oracle.com/javase/7/docs/technotes/guides/logging/index.html) - support is provided by the main project [raven](raven) - - [log4j](https://logging.apache.org/log4j/1.2/) support is provided in [raven-log4j](raven-log4j) - - [log4j2](https://logging.apache.org/log4j/2.x/) can be used with [raven-log4j2](raven-log4j2) - - [logback](http://logback.qos.ch/) support is provided in [raven-logback](raven-logback) +## Resources -While it's **strongly recommended to use one of the supported logging -frameworks** to capture and send messages to Sentry, it is also possible to do so -manually with the main project [raven](raven). +* [Documentation](https://docs.sentry.io/clients/java/) +* [Bug Tracker](http://github.com/getsentry/raven-java/issues) +* [Code](http://github.com/getsentry/raven-java) +* [Mailing List](https://groups.google.com/group/getsentry) +* [IRC](irc://irc.freenode.net/sentry) (irc.freenode.net, #sentry) +* [Travis CI](http://travis-ci.org/getsentry/raven-java) -Raven supports both HTTP and HTTPS as transport protocols to the Sentry -instance. - -Support for [Google App Engine](https://appengine.google.com/) is provided in [raven-appengine](raven-appengine) - -## Maven -Stable versions of Raven are available on the -[central Maven Repository](https://search.maven.org) under the `com.getsentry` -groupId. (NOTE: This is change from the previous `net.kencochrane` groupId) - -Please see individual module `README`s for more information. - -### Snapshot versions -Newer (but less stable) versions (AKA snapshots) are available in Sonatype's -snapshot repository. - -To use it with maven, add the following repository: - -```xml - - sonatype-nexus-snapshots - Sonatype Nexus Snapshots - https://oss.sonatype.org/content/repositories/snapshots - - false - - - true - - -``` - -## Android -Raven also works on Android. For integration details, see the [raven-android README](https://github.com/getsentry/raven-java/blob/master/raven-android/README.md). - -## HTTP Request Context -If the runtime environment utilizes Servlets, events that are created during -the processing of an HTTP request will include additional contextual data about -that active request, such as the URL, method, parameters, and other data. (This -feature requires version 2.4 the Servlet API.) - -## Connection and protocol -It is possible to send events to Sentry over different protocols, depending -on the security and performance requirements. - -### HTTP -The most common way to send events to Sentry is via HTTP, this can be done by -using a DSN of this form: - - http://public:private@host:port/1 - -If not provided, the port will default to `80`. - -### HTTPS -It is possible to use an encrypted connection to Sentry via HTTPS: - - https://public:private@host:port/1 - -If not provided, the port will default to `443`. - -### HTTPS (naive) -If the certificate used over HTTPS is a wildcard certificate (which is not -handled by every version of Java), and the certificate isn't added to the -truststore, you can add a protocol setting to tell the client to be -naive and ignore hostname verification: - - naive+https://public:private@host:port/1 - -### Proxying HTTP(S) connections -If your application needs to send outbound requests through an HTTP proxy, -you can configure the proxy information via JVM networking properties or -as part of the Sentry DSN. - -For example, using JVM networking properties (affects the entire JVM process), - -``` -java \ - # if you are using the HTTP protocol \ - -Dhttp.proxyHost=proxy.example.com \ - -Dhttp.proxyPort=8080 \ - \ - # if you are using the HTTPS protocol \ - -Dhttps.proxyHost=proxy.example.com \ - -Dhttps.proxyPort=8080 \ - \ - # relevant to both HTTP and HTTPS - -Dhttp.nonProxyHosts=”localhost|host.example.com” \ - \ - MyApp -``` - -See [Java Networking and -Proxies](http://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html) -for more information about the proxy properties. - -Alternatively, using the Sentry DSN (only affects the Sentry HTTP client, -useful inside shared application containers), - - http://public:private@host:port/1?raven.http.proxy.host=proxy.example.com&raven.http.proxy.port=8080 - -## Options -It is possible to enable some options by adding data to the query string of the -DSN: - - http://public:private@host:port/1?option1=value1&option2&option3=value3 - -Some options do not require a value, just being declared signifies that the -option is enabled. - -### Async connection -In order to avoid performance issues due to a large amount of logs being -generated or a slow connection to the Sentry server, an asynchronous connection -is set up, using a low priority thread pool to submit events to Sentry. - -To disable the async mode, add `raven.async=false` to the DSN: - - http://public:private@host:port/1?raven.async=false - -#### Graceful Shutdown (advanced) -In order to shutdown the asynchronous connection gracefully, a `ShutdownHook` -is created. By default, the asynchronous connection is given 1 second -to shutdown gracefully, but this can be adjusted via -`raven.async.shutdowntimeout` (represented in milliseconds): - - http://public:private@host:port/1?raven.async.shutdowntimeout=5000 - -The special value `-1` can be used to disable the timeout and wait -indefinitely for the executor to terminate. - -The `ShutdownHook` could lead to memory leaks in an environment where -the life cycle of Raven doesn't match the life cycle of the JVM. - -An example would be in a JEE environment where the application using Raven -could be deployed and undeployed regularly. - -To avoid this behaviour, it is possible to disable the graceful shutdown. -This might lead to some log entries being lost if the log application -doesn't shut down the Raven instance nicely. - -The option to do so is `raven.async.gracefulshutdown`: - - http://public:private@host:port/1?raven.async.gracefulshutdown=false - -#### Queue size (advanced) -The default queue used to store unprocessed events is limited to 50 -items. Additional items added once the queue is full are dropped and -never sent to the Sentry server. -Depending on the environment (if the memory is sparse) it is important to be -able to control the size of that queue to avoid memory issues. - -It is possible to set a maximum with the option `raven.async.queuesize`: - - http://public:private@host:port/1?raven.async.queuesize=100 - -This means that if the connection to the Sentry server is down, only the 100 -most recent events will be stored and processed as soon as the server is back up. - -The special value `-1` can be used to enable an unlimited queue. Beware -that network connectivity or Sentry server issues could mean your process -will run out of memory. - -#### Threads count (advanced) -By default the thread pool used by the async connection contains one thread per -processor available to the JVM. - -It's possible to manually set the number of threads (for example if you want -only one thread) with the option `raven.async.threads`: - - http://public:private@host:port/1?raven.async.threads=1 - -#### Threads priority (advanced) -In most cases sending logs to Sentry isn't as important as an application -running smoothly, so the threads have a -[minimal priority](http://docs.oracle.com/javase/6/docs/api/java/lang/Thread.html#MIN_PRIORITY). - -It is possible to customise this value to increase the priority of those threads -with the option `raven.async.priority`: - - http://public:private@host:port/1?raven.async.priority=10 - -#### Graceful Shutdown (advanced) -In order to shutdown the buffer flushing thread gracefully, a `ShutdownHook` -is created. By default, the buffer flushing thread is given 1 second -to shutdown gracefully, but this can be adjusted via -`raven.buffer.shutdowntimeout` (represented in milliseconds): - - http://public:private@host:port/1?raven.buffer.shutdowntimeout=5000 - -The special value `-1` can be used to disable the timeout and wait -indefinitely for the executor to terminate. - -The `ShutdownHook` could lead to memory leaks in an environment where -the life cycle of Raven doesn't match the life cycle of the JVM. - -An example would be in a JEE environment where the application using Raven -could be deployed and undeployed regularly. - -To avoid this behaviour, it is possible to disable the graceful shutdown -by setting the `raven.buffer.gracefulshutdown` option: - - http://public:private@host:port/1?raven.buffer.gracefulshutdown=false - -### Buffering to disk upon network error -Raven can be configured to write events to a specified directory on disk -anytime communication with the Sentry server fails with the `raven.buffer.dir` -option. If the directory doesn't exist, Raven will attempt to create it -on startup and may therefore need write permission on the parent directory. -Raven always requires write permission on the buffer directory itself. - - http://public:private@host:port/1?raven.buffer.dir=raven-events - -The maximum number of events that will be stored on disk defaults to 50, -but can also be configured with the option `raven.buffer.size`: - - http://public:private@host:port/1?raven.buffer.size=100 - -If a buffer directory is provided, a background thread will periodically -attempt to re-send the events that are found on disk. By default it will -attempt to send events every 60 seconds. You can change this with the -`raven.buffer.flushtime` option (in milliseconds): - - http://public:private@host:port/1?raven.buffer.flushtime=10000 - -### Event sampling -Raven can be configured to sample events with the `raven.sample.rate` option: - - http://public:private@host:port/1?raven.sample.rate=0.75 - -This option takes a number from 0.0 to 1.0, representing the percent of -events to allow through to server (from 0% to 100%). By default all -events will be sent to the Sentry server. - -### Inapp classes -Sentry differentiate `in_app` stack frames (which are directly related to your application) -and the "not `in_app`" ones. -This difference is visible in the Sentry web interface where only the `in_app` -frames are displayed by default. - -#### Same frame as enclosing exception -Raven can use the `in_app` system to hide frames in the context of chained exceptions. - -Usually when a StackTrace is printed, the result looks like this: - - HighLevelException: MidLevelException: LowLevelException - at Main.a(Main.java:13) - at Main.main(Main.java:4) - Caused by: MidLevelException: LowLevelException - at Main.c(Main.java:23) - at Main.b(Main.java:17) - at Main.a(Main.java:11) - ... 1 more - Caused by: LowLevelException - at Main.e(Main.java:30) - at Main.d(Main.java:27) - at Main.c(Main.java:21) - ... 3 more - -Some frames are replaced by the `... N more` line as they are the same frames -as in the enclosing exception. - -To enable a similar behaviour from Raven use the `raven.stacktrace.hidecommon` option. - - http://public:private@host:port/1?raven.stacktrace.hidecommon - -#### Hide frames based on the class name -Raven can also mark some frames as `in_app` based on the name of the class. - -This can be used to hide parts of the stacktrace that are irrelevant to the problem -for example the stack frames in the `java.util` package will not help determining -what the problem was and will just create a longer stacktrace. - -Currently this is not configurable (see #49) and some packages are ignored by default: - -- `com.sun.*` -- `java.*` -- `javax.*` -- `org.omg.*` -- `sun.*` -- `junit.*` -- `com.intellij.rt.*` - -### Compression -By default the content sent to Sentry is compressed and encoded in base64 before -being sent. -However, compressing and encoding the data adds a small CPU and memory hit which -might not be useful if the connection to Sentry is fast and reliable. - -Depending on the limitations of the project (e.g. a mobile application with a -limited connection, Sentry hosted on an external network), it can be useful -to compress the data beforehand or not. - -It's possible to manually enable/disable the compression with the option -`raven.compression` - - http://public:private@host:port/1?raven.compression=false - -### Max message size -By default only the first 1000 characters of a message will be sent to -the server. This can be changed with the `raven.maxmessagelength` option. - - http://public:private@host:port/1?raven.maxmessagelength=1500 - -### Timeout (advanced) -A timeout is set to avoid blocking Raven threads because establishing a -connection is taking too long. - -It's possible to manually set the timeout length with `raven.timeout` -(in milliseconds): - - http://public:private@host:port/1?raven.timeout=10000 - -## Custom RavenFactory -At times, you may require custom functionality that is not included in `raven-java` -already. The most common way to do this is to create your own RavenFactory instance -as seen in the example below. - -```java -public class MyRavenFactory extends DefaultRavenFactory { - - @Override - public Raven createRavenInstance(Dsn dsn) { - Raven raven = new Raven(); - raven.setConnection(createConnection(dsn)); - - /* - Create and use the ForwardedAddressResolver, which will use the - X-FORWARDED-FOR header for the remote address if it exists. - */ - ForwardedAddressResolver forwardedAddressResolver = new ForwardedAddressResolver(); - raven.addBuilderHelper(new HttpEventBuilderHelper(forwardedAddressResolver)); - - return raven; - } - -} -``` - -### Registration -Next, you'll need to make your class known to Raven in one of two ways. - -#### Java ServiceLoader provider (recommended) -You'll need to add a `ServiceLoader` provider file to your project at -`src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory` that contains -the name of your class so that it will be considered as a candidate `RavenFactory`. For an example, see -[how we configure the DefaultRavenFactory itself](https://github.com/getsentry/raven-java/blob/master/raven/src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory). - -#### Manual registration -You can also manually register your `RavenFactory` instance. Note that this should be done -early in your application lifecycle so that your factory is available the first time -you attempt to send an event to the Sentry server. -```java -class MyApp { - public static void main(String[] args) { - RavenFactory.registerFactory(new MyRavenFactory()); - // ... your app code ... - } -} -``` - -### Configuration -Finally, see the `README` for the logger integration you use to find out how to -configure it to use your custom `RavenFactory`. diff --git a/docs/config.rst b/docs/config.rst index 59738eb8a3f..607cd5433f3 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -1,205 +1,432 @@ Configuration ============= -Raven's configuration happens via the DSN value. This guides you through -the configuration that applies generally and configuration that is -specific to the Java client. +**Note:** Raven's library and framework integration documentation explains how to to do +basic Raven configuration for each of the supported integrations. The configuration +below is typically for more advanced use cases and can be used in combination any of the other +integrations *once you set Raven up with the integration*. Please check the integration +documentation before you attempt to do any advanced configuration. -Connection and Protocols ------------------------- +Most of Raven's advanced configuration happens by setting options in your DSN, as seen below. -It is possible to send events to Sentry over different protocols, -depending on the security and performance requirements. +Connection and Protocol +----------------------- -HTTP -```` - -The most common way send events to Sentry is through HTTP, this can be -done by using a DSN of this form:: +It is possible to send events to Sentry over different protocols, depending +on the security and performance requirements. - http://:@sentryserver/ +HTTPS +~~~~~ -This is unavailable for Hosted Sentry which requires HTTPS. +The most common way to send events to Sentry is via HTTPS, this can be done by +using a DSN of this form: -HTTPS -````` +:: -It is possible to use an encrypted connection to Sentry using HTTPS:: + https://public:private@host:port/1 - ___DSN___ +If not provided, the port will default to ``443``. HTTPS (naive) -````````````` +~~~~~~~~~~~~~ If the certificate used over HTTPS is a wildcard certificate (which is not handled by every version of Java), and the certificate isn't added to the -truststore, it is possible to add a protocol setting to tell the client to -be naive and ignore the hostname verification:: +truststore, you can add a protocol setting to tell the client to be +naive and ignore hostname verification: + +:: + + naive+https://public:private@host:port/1 + +HTTP +~~~~ + +It is possible to use an unencrypted connection to Sentry via HTTP: + +:: + + http://public:private@host:port/1 + +If not provided, the port will default to ``80``. - naive+___DSN___ +Using a Proxy +~~~~~~~~~~~~~ + +If your application needs to send outbound requests through an HTTP proxy, +you can configure the proxy information via JVM networking properties or +as part of the Sentry DSN. + +For example, using JVM networking properties (affects the entire JVM process), + +:: + + java \ + # if you are using the HTTP protocol \ + -Dhttp.proxyHost=proxy.example.com \ + -Dhttp.proxyPort=8080 \ + \ + # if you are using the HTTPS protocol \ + -Dhttps.proxyHost=proxy.example.com \ + -Dhttps.proxyPort=8080 \ + \ + # relevant to both HTTP and HTTPS + -Dhttp.nonProxyHosts=”localhost|host.example.com” \ + \ + MyApp + +See `Java Networking and +Proxies `_ +for more information about the proxy properties. + +Alternatively, using the Sentry DSN (only affects the Sentry HTTP client, +useful inside shared application containers), + +:: + + http://public:private@host:port/1?raven.http.proxy.host=proxy.example.com&raven.http.proxy.port=8080 Options ------- -It is possible to enable some options by adding data to the query string -of the DSN:: +It is possible to enable some options by adding data to the query string of the +DSN: - ___DSN___?option1=value1&option2&option3=value3 +:: -Some options do not require a value, just being declared signifies that -the option is enabled. + http://public:private@host:port/1?option1=value1&option2&option3=value3 -Async Settings -`````````````` +Some options do not require a value, just being declared signifies that the +option is enabled. -Async connection: - In order to avoid performance issues due to a large amount of logs - being generated or a slow connection to the Sentry server, an - asynchronous connection is set up, using a low priority thread pool to - submit events to Sentry. +Async Connection +~~~~~~~~~~~~~~~~ - To disable the async mode, add ``raven.async=false`` to the DSN:: +In order to avoid performance issues due to a large amount of logs being +generated or a slow connection to the Sentry server, an asynchronous connection +is set up, using a low priority thread pool to submit events to Sentry. - ___DSN___?raven.async=false +To disable the async mode, add ``raven.async=false`` to the DSN: -Graceful Shutdown (advanced): - In order to shutdown the asynchronous connection gracefully, a - ``ShutdownHook`` is created. This could lead to memory leaks in an - environment where the life cycle of Raven doesn't match the life cycle - of the JVM. +:: - An example would be in a JEE environment where the application using - Raven could be deployed and undeployed regularly. + http://public:private@host:port/1?raven.async=false - To avoid this behaviour, it is possible to disable the graceful - shutdown. This might lead to some log entries being lost if the log - application doesn't shut down the Raven instance nicely. +Graceful Shutdown (Advanced) +```````````````````````````` - The option to do so is ``raven.async.gracefulshutdown``:: +In order to shutdown the asynchronous connection gracefully, a ``ShutdownHook`` +is created. By default, the asynchronous connection is given 1 second +to shutdown gracefully, but this can be adjusted via +``raven.async.shutdowntimeout`` (represented in milliseconds): - ___DSN___?raven.async.gracefulshutdown=false +:: -Queue and Thread Settings -````````````````````````` + http://public:private@host:port/1?raven.async.shutdowntimeout=5000 -Queue size (advanced): - The default queue used to store unprocessed events is limited to 50 - items. Additional items added once the queue is full are dropped and - never sent to the Sentry server. Depending on the environment (if the - memory is sparse) it is important to be able to control the size of - that queue to avoid memory issues. +The special value ``-1`` can be used to disable the timeout and wait +indefinitely for the executor to terminate. - It is possible to set a maximum with the option ``raven.async.queuesize``:: +The ``ShutdownHook`` could lead to memory leaks in an environment where +the life cycle of Raven doesn't match the life cycle of the JVM. - ___DSN___?raven.async.queuesize=100 +An example would be in a JEE environment where the application using Raven +could be deployed and undeployed regularly. - This means that if the connection to the Sentry server is down, only - the 100 most recent events will be stored and processed as soon as the - server is back up. +To avoid this behaviour, it is possible to disable the graceful shutdown. +This might lead to some log entries being lost if the log application +doesn't shut down the Raven instance nicely. - The special value ``-1`` can be used to enable an unlimited queue. Beware - that network connectivity or Sentry server issues could mean your process - will run out of memory. +The option to do so is ``raven.async.gracefulshutdown``: -Threads count (advanced): - By default the thread pool used by the async connection contains one - thread per processor available to the JVM (more threads wouldn't be - useful). +:: - It's possible to manually set the number of threads (for example if - you want only one thread) with the option ``raven.async.threads``:: + http://public:private@host:port/1?raven.async.gracefulshutdown=false - ___DSN___?raven.async.threads=1 +Queue Size (Advanced) +````````````````````` -Threads priority (advanced): - As in most cases sending logs to Sentry isn't as important as an - application running smoothly, the threads have a `minimal priority - `_. +The default queue used to store unprocessed events is limited to 50 +items. Additional items added once the queue is full are dropped and +never sent to the Sentry server. +Depending on the environment (if the memory is sparse) it is important to be +able to control the size of that queue to avoid memory issues. - It is possible to customise this value to increase the priority of - those threads with the option ``raven.async.priority``:: +It is possible to set a maximum with the option ``raven.async.queuesize``: - ___DSN___?raven.async.priority=10 +:: -Inapp Classes Settings -`````````````````````` + http://public:private@host:port/1?raven.async.queuesize=100 -Sentry differentiate ``in_app`` stack frames (which are directly related -to your application) and the "not ``in_app``" ones. This difference is -visible in the Sentry web interface where only the ``in_app`` frames are -displayed by default. +This means that if the connection to the Sentry server is down, only the 100 +most recent events will be stored and processed as soon as the server is back up. -Same frame as enclosing exception: - Raven can use the ``in_app`` system to hide frames in the context of - chained exceptions. +The special value ``-1`` can be used to enable an unlimited queue. Beware +that network connectivity or Sentry server issues could mean your process +will run out of memory. - Usually when a ``StackTrace`` is printed, the result looks like this:: +Threads Count (Advanced) +```````````````````````` - HighLevelException: MidLevelException: LowLevelException - at Main.a(Main.java:13) - at Main.main(Main.java:4) - Caused by: MidLevelException: LowLevelException - at Main.c(Main.java:23) - at Main.b(Main.java:17) - at Main.a(Main.java:11) - ... 1 more - Caused by: LowLevelException - at Main.e(Main.java:30) - at Main.d(Main.java:27) - at Main.c(Main.java:21) - ... 3 more +By default the thread pool used by the async connection contains one thread per +processor available to the JVM. - Some frames are replaced by the ... N more line as they are the same - frames as in the enclosing exception. +It's possible to manually set the number of threads (for example if you want +only one thread) with the option ``raven.async.threads``: - To enable a similar behaviour from raven use the - ``raven.stacktrace.hidecommon`` option:: +:: - ___DSN___?raven.stacktrace.hidecommon + http://public:private@host:port/1?raven.async.threads=1 -Hide frames based on the class name: - Raven can also mark some frames as ``in_app`` based on the name of the - class. +Threads Priority (Advanced) +``````````````````````````` - This can be used to hide parts of the stacktrace that are irrelevant - to the problem for example the stack frames in the ``java.util`` - package will not help determining what the problem was and will just - create a longer stacktrace. +In most cases sending logs to Sentry isn't as important as an application +running smoothly, so the threads have a +`minimal priority `_. - Currently this is not configurable and some packages are ignored by default: +It is possible to customise this value to increase the priority of those threads +with the option ``raven.async.priority``: - * com.sun.* - * java.* - * javax.* - * org.omg.* - * sun.* - * junit.* - * com.intellij.rt.* +:: -Transmission Settings -````````````````````` + http://public:private@host:port/1?raven.async.priority=10 + +Graceful Shutdown (Advanced) +```````````````````````````` + +In order to shutdown the buffer flushing thread gracefully, a ``ShutdownHook`` +is created. By default, the buffer flushing thread is given 1 second +to shutdown gracefully, but this can be adjusted via +``raven.buffer.shutdowntimeout`` (represented in milliseconds): + +:: + + http://public:private@host:port/1?raven.buffer.shutdowntimeout=5000 + +The special value ``-1`` can be used to disable the timeout and wait +indefinitely for the executor to terminate. + +The ``ShutdownHook`` could lead to memory leaks in an environment where +the life cycle of Raven doesn't match the life cycle of the JVM. + +An example would be in a JEE environment where the application using Raven +could be deployed and undeployed regularly. + +To avoid this behaviour, it is possible to disable the graceful shutdown +by setting the ``raven.buffer.gracefulshutdown`` option: + +:: + + http://public:private@host:port/1?raven.buffer.gracefulshutdown=false + +Buffering Events to Disk +~~~~~~~~~~~~~~~~~~~~~~~~ + +Raven can be configured to write events to a specified directory on disk +anytime communication with the Sentry server fails with the ``raven.buffer.dir`` +option. If the directory doesn't exist, Raven will attempt to create it +on startup and may therefore need write permission on the parent directory. +Raven always requires write permission on the buffer directory itself. + +:: + + http://public:private@host:port/1?raven.buffer.dir=raven-events + +The maximum number of events that will be stored on disk defaults to 50, +but can also be configured with the option ``raven.buffer.size``: + +:: + + http://public:private@host:port/1?raven.buffer.size=100 + +If a buffer directory is provided, a background thread will periodically +attempt to re-send the events that are found on disk. By default it will +attempt to send events every 60 seconds. You can change this with the +``raven.buffer.flushtime`` option (in milliseconds): + +:: + + http://public:private@host:port/1?raven.buffer.flushtime=10000 + +Event Sampling +~~~~~~~~~~~~~~ + +Raven can be configured to sample events with the ``raven.sample.rate`` option: + +:: + + http://public:private@host:port/1?raven.sample.rate=0.75 -Compression: - By default the content sent to Sentry is compressed and encoded in - base64 before being sent. This operation allows to send a smaller - amount of data for each event. However compressing and encoding the - data adds a CPU and memory overhead which might not be useful if the - connection to Sentry is fast and reliable. +This option takes a number from 0.0 to 1.0, representing the percent of +events to allow through to server (from 0% to 100%). By default all +events will be sent to the Sentry server. - Depending on the limitations of the project (ie: a mobile application - with a limited connection, Sentry hosted on an external network), it - can be interesting to compress the data beforehand or not. +Inapp Classes +~~~~~~~~~~~~~ - It's possible to manually enable/disable the compression with the - option ``raven.compression``:: +Sentry differentiate ``in_app`` stack frames (which are directly related to your application) +and the "not ``in_app``" ones. +This difference is visible in the Sentry web interface where only the ``in_app`` +frames are displayed by default. - ___DSN___?raven.compression=false +Same Frame as Enclosing Exception +````````````````````````````````` -Timeout (advanced): - To avoid blocking the thread because of a connection taking too much - time, a timeout can be set by the connection. +Raven can use the ``in_app`` system to hide frames in the context of chained exceptions. - By default the connection will set up its own timeout, but it's - possible to manually set one with ``raven.timeout`` (in milliseconds):: +Usually when a StackTrace is printed, the result looks like this: + +:: + + HighLevelException: MidLevelException: LowLevelException + at Main.a(Main.java:13) + at Main.main(Main.java:4) + Caused by: MidLevelException: LowLevelException + at Main.c(Main.java:23) + at Main.b(Main.java:17) + at Main.a(Main.java:11) + ... 1 more + Caused by: LowLevelException + at Main.e(Main.java:30) + at Main.d(Main.java:27) + at Main.c(Main.java:21) + ... 3 more + +Some frames are replaced by the ``... N more`` line as they are the same frames +as in the enclosing exception. + +To enable a similar behaviour from Raven use the ``raven.stacktrace.hidecommon`` option. + +:: + + http://public:private@host:port/1?raven.stacktrace.hidecommon + +Hide Frames Based on the Class Name +``````````````````````````````````` + +Raven can also mark some frames as ``in_app`` based on the name of the class. + +This can be used to hide parts of the stacktrace that are irrelevant to the problem +for example the stack frames in the ``java.util`` package will not help determining +what the problem was and will just create a longer stacktrace. + +Currently this is not configurable and some packages are ignored by default: + +- ``com.sun.*`` +- ``java.*`` +- ``javax.*`` +- ``org.omg.*`` +- ``sun.*`` +- ``junit.*`` +- ``com.intellij.rt.*`` + +Compression +~~~~~~~~~~~ + +By default the content sent to Sentry is compressed and encoded in base64 before +being sent. +However, compressing and encoding the data adds a small CPU and memory hit which +might not be useful if the connection to Sentry is fast and reliable. + +Depending on the limitations of the project (e.g. a mobile application with a +limited connection, Sentry hosted on an external network), it can be useful +to compress the data beforehand or not. + +It's possible to manually enable/disable the compression with the option +``raven.compression`` + +:: + + http://public:private@host:port/1?raven.compression=false + +Max Message Size +~~~~~~~~~~~~~~~~ + +By default only the first 1000 characters of a message will be sent to +the server. This can be changed with the ``raven.maxmessagelength`` option. + +:: + + http://public:private@host:port/1?raven.maxmessagelength=1500 + +Timeout (Advanced) +~~~~~~~~~~~~~~~~~~ + +A timeout is set to avoid blocking Raven threads because establishing a +connection is taking too long. + +It's possible to manually set the timeout length with ``raven.timeout`` +(in milliseconds): + +:: + + http://public:private@host:port/1?raven.timeout=10000 + +Custom RavenFactory +------------------- + +At times, you may require custom functionality that is not included in ``raven-java`` +already. The most common way to do this is to create your own ``RavenFactory`` instance +as seen in the example below. Note that you'll also need to register it with Raven and +possibly configure your integration to use it, as shown below. + +Implementation +~~~~~~~~~~~~~~ + +.. sourcecode:: java + + public class MyRavenFactory extends DefaultRavenFactory { + + @Override + public Raven createRavenInstance(Dsn dsn) { + Raven raven = new Raven(); + raven.setConnection(createConnection(dsn)); + + /* + Create and use the ForwardedAddressResolver, which will use the + X-FORWARDED-FOR header for the remote address if it exists. + */ + ForwardedAddressResolver forwardedAddressResolver = new ForwardedAddressResolver(); + raven.addBuilderHelper(new HttpEventBuilderHelper(forwardedAddressResolver)); + + return raven; + } + + } + +Next, you'll need to register your class with Raven in one of two ways. + +Registration +~~~~~~~~~~~~ + +Java ServiceLoader Provider (Recommended) +````````````````````````````````````````` + +You'll need to add a ``ServiceLoader`` provider file to your project at +``src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory`` that contains +the name of your class so that it will be considered as a candidate ``RavenFactory``. For an example, see +`how we configure the DefaultRavenFactory itself `_. + +Manual Registration +``````````````````` + +You can also manually register your ``RavenFactory`` instance. If you are using +an integration that builds its own Raven client, such as a logging integration, this should +be done early in your application lifecycle so that your factory is available the first time +you attempt to send an event to the Sentry server. + +.. sourcecode:: java + + class MyApp { + public static void main(String[] args) { + RavenFactory.registerFactory(new MyRavenFactory()); + // ... your app code ... + } + } + +Configuration +~~~~~~~~~~~~~ - ___DSN___?raven.timeout=10000 +Finally, see the documentation for the integration you use to find out how to +configure it to use your custom ``RavenFactory``. diff --git a/docs/index.rst b/docs/index.rst index 46437f2699c..dc990c3ab19 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,6 +1,6 @@ .. sentry:edition:: self - Raven-Java + Raven Java ========== .. sentry:edition:: on-premise, hosted @@ -10,23 +10,23 @@ Java ==== -Raven is the Java client for Sentry. Raven relies on the most popular -logging libraries to capture and convert logs before sending details to a -Sentry instance. - -While it's strongly recommended to use one of the supported logging -frameworks to capture and send messages to Sentry, a it is possible to do -so manually with the main project raven. +Raven for Java (``raven-java``) is the official Java client for Sentry. At its core it provides +a raw client for sending events to Sentry, but it is highly recommended that you +use one of the included library or framework integrations listed below if at all possible. .. toctree:: - :maxdepth: 2 - :titlesonly: + :maxdepth: 2 + :titlesonly: - installation - config - modules/index + config + usage + modules/index Resources: +* `Documentation `_ * `Bug Tracker `_ -* `Github Project `_ +* `Code `_ +* `Mailing List `_ +* `IRC `_ (irc.freenode.net, #sentry) +* `Travis CI `_ \ No newline at end of file diff --git a/docs/installation.rst b/docs/installation.rst deleted file mode 100644 index 99fc9fa9630..00000000000 --- a/docs/installation.rst +++ /dev/null @@ -1,71 +0,0 @@ -Installation -============ - -When using Sentry with Java the strongly recommended way is to use one of -the supported logging framework integrations rather than the raw -"raven-java" clients. - -- `java.util.logging `_ - support is provided by the main project "raven-java" -- `log4j `_ support is provided in raven-log4j -- `log4j2 `_ can be used with raven-log4j2 -- `logback `_ support is provided in raven-logback - -Support for Google App Engine is provided in raven-appengine. - -Github ------- - -ALl modules are available on the `raven-java github project -`_. This includes raven-java -itself as well as all the logging integrations. - -Snapshot Versions ------------------ - -While the stable versions of raven are available on the central Maven -Repository, newer (but less stable) versions (AKA snapshots) are available -in Sonatype's snapshot repository. - -To use it with maven, add the following repository: - -.. sourcecode:: xml - - - sonatype-nexus-snapshots - Sonatype Nexus Snapshots - https://oss.sonatype.org/content/repositories/snapshots - - false - - - true - - - -Android -------- - -Raven works on Android, and relies on the `ServiceLoader -`_ -system which uses the content of ``META-INF/services``. This is used to -declare the ``RavenFactory`` implementations (to allow more control over -the automatically generated instances of ``Raven``) in -``META-INF/services/com.getsentry.raven.RavenFactory``. - -Unfortunately, when the APK is build, the content of ``META-INF/services`` of -the dependencies is lost, this prevent Raven to work properly. Solutions -exist for that problem: - -* Use `maven-android-plugin - `_ which has already - solved `this problem `_ -* Create manually a - ``META-INF/services/com.getsentry.raven.RavenFactory`` for the - project which will contain the canonical name of of implementation of - ``RavenFactory`` (ie. ``com.getsentry.raven.DefaultRavenFactory``). -* Register manually the ``RavenFactory`` when the application starts: - - .. sourcecode:: java - - RavenFactory.registerFactory(new DefaultRavenFactory()); diff --git a/docs/modules/android.rst b/docs/modules/android.rst new file mode 100644 index 00000000000..be7d3781ac3 --- /dev/null +++ b/docs/modules/android.rst @@ -0,0 +1,63 @@ +Android +======= + +Installation +------------ + +Using Gradle (Android Studio) in your ``app/build.gradle`` add: + +.. sourcecode:: groovy + + compile 'com.getsentry.raven:raven-android:7.8.2' + +For other dependency managers see the `central Maven repository `_. + +Usage +----- + +Configure your Sentry DSN (client key) in your ``AndroidManifest.xml``: + +.. sourcecode:: xml + + + + + +Your application must also have permission to access the internet in order to send +event to the Sentry server. In your ``AndroidManifest.xml``: + +.. sourcecode:: xml + + + + +Then, in your application's ``onCreate``, initialize the Raven client: + +.. sourcecode:: java + + import com.getsentry.raven.android.Raven; + + // "this" should be a reference to your main Activity + Raven.init(this.getApplicationContext()); + +Now you can use ``Raven`` to capture events anywhere in your application: + +.. sourcecode:: java + + // Send a simple event to the Sentry server + Raven.capture("Error message"); + + try { + something() + } catch (Exception e) { + // Send an exception event to the Sentry server + Raven.capture(e); + } + + // Or build an event manually + EventBuilder eventBuilder = new EventBuilder() + .withMessage("Exception caught") + .withLevel(Event.Level.ERROR); + Raven.capture(eventBuilder.build()); diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 0108b53732a..de7a7bf6c5c 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -1,24 +1,17 @@ Google App Engine ================= -The `raven-appengine` module enables the support of the async connections -in Google App Engine. +The ``raven-appengine`` library provides `Google App Engine `_ +support for Raven via the `Task Queue API +`_. -The project can be found on github: `raven-java/appengine -`_ - -Google App Engine doesn't support threads but provides instead a -TaskQueueing system allowing tasks to be run in the background. - -This module replaces the async system provided by default with one relying -on the tasks. - -This module is not useful outside of Google App Engine. +The source can be found `on Github +`_. Installation ------------ -If you want to use Maven you can install Raven-AppEngine as dependency: +Using Maven: .. sourcecode:: xml @@ -28,17 +21,29 @@ If you want to use Maven you can install Raven-AppEngine as dependency: 7.8.2 +Using Gradle: + +.. sourcecode:: groovy + + compile 'com.getsentry.raven:raven-appengine:7.8.2' + +Using SBT: + +.. sourcecode:: scala + + libraryDependencies += "com.getsentry.raven" % "raven-appengine" % "7.8.2" + +For other dependency managers see the `central Maven repository `_. + Usage ----- -This module provides a new ``RavenFactory`` which replaces the default async -system with a GAE compatible one. +This module provides a new ``RavenFactory`` implementation which replaces the default async +system with a Google App Engine compatible one. You'll need to configure Raven to use the +``com.getsentry.raven.appengine.AppEngineRavenFactory`` as its factory. The queue size and thread options will not be used as they are specific to -the default multithreaded system. - -It is necessary to force the raven factory name to -``com.getsentry.raven.appengine.AppEngineRavenFactory``. +the default Java threading system. Queue Name ---------- @@ -46,7 +51,7 @@ Queue Name By default, the default task queue will be used, but it's possible to specify which one will be used with the ``raven.async.gae.queuename`` option:: - ___DSN___?raven.async.gae.queuename=MyQueueName + http://public:private@host:port/1?raven.async.gae.queuename=MyQueueName Connection Name --------------- @@ -55,7 +60,7 @@ As the queued tasks are sent across different instances of the application, it's important to be able to identify which connection should be used when processing the event. To do so, the GAE module will identify each connection based on an identifier either automatically generated or -user defined. TO manually set the connection identifier (only used -internally) use the option ``raven.async.connectionid``:: +user defined. To manually set the connection identifier (only used +internally) use the option ``raven.async.gae.connectionid``:: - ___DSN___?raven.async.gae.connectionid=MyConnection + http://public:private@host:port/1?raven.async.gae.connectionid=MyConnection diff --git a/docs/modules/index.rst b/docs/modules/index.rst index eb48f1f1cef..9cf4429544b 100644 --- a/docs/modules/index.rst +++ b/docs/modules/index.rst @@ -1,8 +1,8 @@ -Modules -======= +Integrations +============ -Raven-Java comes in different modules from low-level access to event -submission as well as integration into different packages. +The Raven Java SDK comes with support for some frameworks and libraries so that +you don't have to capture and send errors manually. .. toctree:: :maxdepth: 1 @@ -12,3 +12,4 @@ submission as well as integration into different packages. log4j2 logback appengine + android diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index f1f0a0d12f1..a29a306ca37 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -1,18 +1,18 @@ -Log4j -===== +Log4j 1.x +========= -Raven-Java provides `log4j `_ -support for Raven. It provides an `Appender +The ``raven-log4j`` library provides `Log4j 1.x `_ +support for Raven via an `Appender `_ -for log4j to send the logged events to Sentry. +that sends logged exceptions to Sentry. -The project can be found on github: `raven-java/raven-log4j -`_ +The source can be found `on Github +`_. Installation ------------ -If you want to use Maven you can install Raven-Log4j as dependency: +Using Maven: .. sourcecode:: xml @@ -22,50 +22,246 @@ If you want to use Maven you can install Raven-Log4j as dependency: 7.8.2 -If you manually want to manage your dependencies: +Using Gradle: -- :doc:`raven dependencies ` -- `log4j-1.2.17.jar `_ -- `slf4j-log4j12-1.7.7.jar - `_ - is recommended as the implementation of slf4j (instead of slf4j-jdk14). +.. sourcecode:: groovy + + compile 'com.getsentry.raven:raven-log4j:7.8.2' + +Using SBT: + +.. sourcecode:: scala + + libraryDependencies += "com.getsentry.raven" % "raven-log4j" % "7.8.2" + +For other dependency managers see the `central Maven repository `_. Usage ----- -The following configuration (``logging.properties``) gets you started for -logging with log4j and Sentry: +The following examples configure a ``ConsoleAppender`` that logs to standard out +at the ``INFO`` level and a ``SentryAppender`` that logs to the Sentry server at +the ``WARN`` level. The ``ConsoleAppender`` is only provided as an example of +a non-Sentry appender that is set to a different logging threshold, like one you +may already have in your project. + +Example configuration using the ``log4j.properties`` format: .. sourcecode:: ini - log4j.rootLogger=WARN, SentryAppender - log4j.appender.SentryAppender=com.getsentry.raven.log4j.SentryAppender - log4j.appender.SentryAppender.dsn=___DSN___ - log4j.appender.SentryAppender.tags=tag1:value1,tag2:value2 - # Optional, allows to select the ravenFactory - #log4j.appender.SentryAppender.ravenFactory=com.getsentry.raven.DefaultRavenFactory + # Enable the Console and Sentry appenders + log4j.rootLogger=INFO, Console, Sentry + + # Configure the Console appender + log4j.appender.Console=org.apache.log4j.ConsoleAppender + log4j.appender.Console.layout=org.apache.log4j.PatternLayout + log4j.appender.Console.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5p: %m%n + + # Configure the Sentry appender, overriding the logging threshold to the WARN level + log4j.appender.Sentry=com.getsentry.raven.log4j.SentryAppender + log4j.appender.Sentry.threshold=WARN -Alternatively in the ``log4j.xml`` file set: +Alternatively, using the ``log4j.xml`` format: .. sourcecode:: xml - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + +Next, **you'll need to configure your DSN** (client key) and optionally other values such as +``environment`` and ``release``. See below for the two ways you can do this. + +Configuration via Runtime Environment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is the most flexible method for configuring the ``SentryAppender``, +because it can be easily changed based on the environment you run your +application in. + +The following can be set as System Environment variables: + +.. sourcecode:: shell + + SENTRY_EXAMPLE=xxx java -jar app.jar + +Or as Java System Properties: + +.. sourcecode:: shell + + java -Dsentry.example=xxx -jar app.jar -It's possible to add extra details to events captured by the Log4j module -thanks to both `the MDC +Configuration parameters follow: + +======================= ======================= =============================== =========== +Environment variable Java System Property Example value Description +======================= ======================= =============================== =========== +``SENTRY_DSN`` ``sentry.dsn`` ``https://host:port/1?options`` Your Sentry DSN (client key), if left blank Raven will no-op +``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application +``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in +``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) +``SENTRY_RAVENFACTORY`` ``sentry.ravenfactory`` ``com.foo.RavenFactory`` Optional, select the ravenFactory class +``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags +``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC +======================= ======================= =============================== =========== + +Configuration via Static File +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also configure everything statically within the ``log4j.properties`` or ``log4j.xml`` +file itself. This is less flexible and not recommended because it's more difficult to change +the values when you run your application in different environments. + +Example configuration in the ``log4j.properties`` file: + +.. sourcecode:: ini + + # Enable the Console and Sentry appenders + log4j.rootLogger=INFO, Console, Sentry + + # Configure the Console appender + log4j.appender.Console=org.apache.log4j.ConsoleAppender + log4j.appender.Console.layout=org.apache.log4j.PatternLayout + log4j.appender.Console.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5p: %m%n + + # Configure the Sentry appender, overriding the logging threshold to the WARN level + log4j.appender.Sentry=com.getsentry.raven.log4j.SentryAppender + log4j.appender.Sentry.threshold=WARN + + # Set the Sentry DSN + log4j.appender.Sentry.dsn=https://host:port/1?options + + # Optional, provide release version of your application + log4j.appender.Sentry.release=1.0.0 + + # Optional, provide environment your application is running in + log4j.appender.Sentry.environment=production + + # Optional, override the server name (rather than looking it up dynamically) + log4j.appender.Sentry.serverName=server1 + + # Optional, select the ravenFactory class + log4j.appender.Sentry.ravenFactory=com.foo.RavenFactory + + # Optional, provide tags + log4j.appender.Sentry.tags=tag1:value1,tag2:value2 + + # Optional, provide tag names to be extracted from MDC + log4j.appender.Sentry.extraTags=foo,bar,baz + +Alternatively, using the ``log4j.xml`` format: + +.. sourcecode:: xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Additional Data +--------------- + +It's possible to add extra data to events thanks to `the MDC `_ and `the NDC `_ -systems provided by Log4j are usable, allowing to attach extras -information to the event. +systems provided by Log4j 1.x. + +Mapped Tags +~~~~~~~~~~~ + +By default all MDC parameters are stored under the "Additional Data" tab in Sentry. By +specifying the ``extraTags`` parameter in your configuration file you can +choose which MDC keys to send as tags instead, which allows them to be used as +filters within the Sentry UI. + +.. sourcecode:: ini + + log4j.appender.SentryAppender.extraTags=Environment,OS + +.. sourcecode:: java + + void logWithExtras() { + // MDC extras + MDC.put("Environment", "Development"); + MDC.put("OS", "Linux"); + + // This sends an event where the Environment and OS MDC values are set as tags + logger.error("This is a test"); + } -Practical Example ------------------ +In Practice +----------- .. sourcecode:: java @@ -77,8 +273,8 @@ Practical Example private static final Logger logger = Logger.getLogger(MyClass.class); void logSimpleMessage() { - // This adds a simple message to the logs - logger.info("This is a test"); + // This sends a simple event to Sentry + logger.error("This is a test"); } void logWithExtras() { @@ -86,50 +282,27 @@ Practical Example MDC.put("extra_key", "extra_value"); // NDC extras are sent under 'log4J-NDC' NDC.push("Extra_details"); - // This adds a message with extras to the logs - logger.info("This is a test"); + // This sends an event with extra data to Sentry + logger.error("This is a test"); } void logException() { try { unsafeMethod(); } catch (Exception e) { - // This adds an exception to the logs + // This sends an exception event to Sentry logger.error("Exception caught", e); } } void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call that"); + throw new UnsupportedOperationException("You shouldn't call this!"); } } -Mapped Tags ------------ - -By default all MDC parameters are sent under the Additional Data Tab. By -specify the ``extraTags`` parameter in your configuration file. You can -specify MDC keys to send as tags instead of including them in Additional -Data. This allows them to be filtered within Sentry. - -.. sourcecode:: java - - log4j.appender.SentryAppender.extraTags=Environment,OS - void logWithExtras() { - // MDC extras - MDC.put("Environment", "Development"); - MDC.put("OS", "Linux"); - - // This adds a message with extras and MDC keys declared in extraTags as tags to Sentry - logger.info("This is a test"); - } - Asynchronous Logging -------------------- -It is not recommended to attempt to set up ``SentryAppender`` within an -`AsyncAppender +Raven uses asynchronous communication by default, and so it is unnecessary +to use an `AsyncAppender `_. -While this is a common solution to avoid blocking the current thread until -the event is sent to Sentry, it is recommended to rely instead on the -asynchronous connection provided by Raven. diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index ea28817ba7d..74025050200 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -1,18 +1,18 @@ -Log4j 2 -======= +Log4j 2.x +========= -Raven-Java provides `Log4j 2 `_ -support for Raven. It provides an `Appender +The ``raven-log4j2`` library provides `Log4j 2.x `_ +support for Raven via an `Appender `_ -for Log4j 2 to send the logged events to Sentry. +that sends logged exceptions to Sentry. -The project can be found on github: `raven-java/raven-log4j2 -`_ +The source can be found `on Github +`_. Installation ------------ -If you want to use Maven you can install Raven-Log4j2 as dependency: +Using Maven: .. sourcecode:: xml @@ -22,72 +22,157 @@ If you want to use Maven you can install Raven-Log4j2 as dependency: 7.8.2 -If you manually want to manage your dependencies: +Using Gradle: -- :doc:`raven dependencies ` -- `log4j-api-2.1.jar - `_ -- `log4j-core-2.0.jar - `_ -- `log4j-slf4j-impl-2.1.jar - `_ - is recommended as the implementation of slf4j (instead of slf4j-jdk14). +.. sourcecode:: groovy + + compile 'com.getsentry.raven:raven-log4j2:7.8.2' + +Using SBT: + +.. sourcecode:: scala + + libraryDependencies += "com.getsentry.raven" % "raven-log4j2" % "7.8.2" + +For other dependency managers see the `central Maven repository `_. Usage ----- -The following configuration (``log4j2.xml``) gets you started for -logging with log4j2 and Sentry: +The following example configures a ``ConsoleAppender`` that logs to standard out +at the ``INFO`` level and a ``SentryAppender`` that logs to the Sentry server at +the ``WARN`` level. The ``ConsoleAppender`` is only provided as an example of +a non-Sentry appender that is set to a different logging threshold, like one you +may already have in your project. -.. sourcecode:: java +Example configuration using the ``log4j2.xml`` format: + +.. sourcecode:: xml - - - - ___DSN___?options - - - tag1:value1,tag2:value2 - - - - - - - - - - - + + + + + + + + + + + + + + + -It's possible to add extra details to events captured by the Log4j 2 -module thanks to the `marker system -`_ which will -add a tag log4j2-Marker. Both the MDC and the NDC systems provided by -Log4j 2 are usable, allowing to attach extras information to the event. +Next, **you'll need to configure your DSN** (client key) and optionally other values such as +``environment`` and ``release``. See below for the two ways you can do this. + +Configuration via Runtime Environment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is the most flexible method for configuring the ``SentryAppender``, +because it can be easily changed based on the environment you run your +application in. + +The following can be set as System Environment variables: + +.. sourcecode:: shell + + SENTRY_EXAMPLE=xxx java -jar app.jar + +Or as Java System Properties: + +.. sourcecode:: shell + + java -Dsentry.example=xxx -jar app.jar + +Configuration parameters follow: + +======================= ======================= =============================== =========== +Environment variable Java System Property Example value Description +======================= ======================= =============================== =========== +``SENTRY_DSN`` ``sentry.dsn`` ``https://host:port/1?options`` Your Sentry DSN (client key), if left blank Raven will no-op +``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application +``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in +``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) +``SENTRY_RAVENFACTORY`` ``sentry.ravenfactory`` ``com.foo.RavenFactory`` Optional, select the ravenFactory class +``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags +``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC +======================= ======================= =============================== =========== + +Configuration via Static File +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also configure everything statically within the ``log4j2.xml`` +file itself. This is less flexible and not recommended because it's more difficult to change +the values when you run your application in different environments. + +Example configuration in the ``log4j.properties`` file: + +.. sourcecode:: xml + + + + + + + + + + + https://host:port/1?options + + + 1.0.0 + + + production + + + server1 + + + com.foo.RavenFactory + + + tag1:value1,tag2:value2 + + + foo,bar,baz + + + + + + + + + + + + +Additional Data +--------------- + +It's possible to add extra data to events thanks to `the marker system +`_ +provided by Log4j 2.x. Mapped Tags ------------ +~~~~~~~~~~~ -By default all MDC parameters are sent under the Additional Data Tab. By -specify the ``extraTags`` parameter in your configuration file. You can -specify MDC keys to send as tags instead of including them in Additional -Data. This allows them to be filtered within Sentry. +By default all MDC parameters are stored under the "Additional Data" tab in Sentry. By +specifying the ``extraTags`` parameter in your configuration file you can +choose which MDC keys to send as tags instead, which allows them to be used as +filters within the Sentry UI. .. sourcecode:: xml - - Environment,OS - + Environment,OS .. sourcecode:: java @@ -96,12 +181,12 @@ Data. This allows them to be filtered within Sentry. MDC.put("Environment", "Development"); MDC.put("OS", "Linux"); - // This adds a message with extras and MDC keys declared in extraTags as tags to Sentry - logger.info("This is a test"); + // This sends an event where the Environment and OS MDC values are set as tags + logger.error("This is a test"); } -Practical Example ------------------ +In Practice +----------- .. sourcecode:: java @@ -115,13 +200,13 @@ Practical Example private static final Marker MARKER = MarkerManager.getMarker("myMarker"); void logSimpleMessage() { - // This adds a simple message to the logs - logger.info("This is a test"); + // This sends a simple event to Sentry + logger.error("This is a test"); } void logWithTag() { - // This adds a message with a tag to the logs named 'log4j2-Marker' - logger.info(MARKER, "This is a test"); + // This sends an event with a tag named 'log4j2-Marker' to Sentry + logger.error(MARKER, "This is a test"); } void logWithExtras() { @@ -129,20 +214,20 @@ Practical Example ThreadContext.put("extra_key", "extra_value"); // NDC extras are sent under 'log4j2-NDC' ThreadContext.push("Extra_details"); - // This adds a message with extras to the logs - logger.info("This is a test"); + // This sends an event with extra data to Sentry + logger.error("This is a test"); } void logException() { try { unsafeMethod(); } catch (Exception e) { - // This adds an exception to the logs + // This sends an exception event to Sentry logger.error("Exception caught", e); } } void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call that"); + throw new UnsupportedOperationException("You shouldn't call this!"); } } diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index fd523d5e92a..08cfd5ce0bb 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -1,64 +1,181 @@ Logback ======= -Raven-Java provides `logback `_ -support for Raven. It provides an `Appender +The ``raven-logback`` library provides `Logback `_ +support for Raven via an `Appender `_ -for logback to send the logged events to Sentry. +that sends logged exceptions to Sentry. + +The source can be found `on Github +`_. Installation ------------ -If you want to use Maven you can install Raven-Logback as dependency: +Using Maven: .. sourcecode:: xml - com.getsentry.raven - raven-logback - 7.8.2 + com.getsentry.raven + raven-logback + 7.8.2 -- :doc:`raven dependencies ` -- `logback-core-1.1.2.jar - `_ -- `logback-classic-1.1.2.jar - `_ - will act as the implementation of slf4j (instead of slf4j-jdk14). +Using Gradle: + +.. sourcecode:: groovy + + compile 'com.getsentry.raven:raven-logback:7.8.2' + +Using SBT: + +.. sourcecode:: scala + + libraryDependencies += "com.getsentry.raven" % "raven-logback" % "7.8.2" + +For other dependency managers see the `central Maven repository `_. Usage ----- -In the ``logback.xml`` file set: +The following example configures a ``ConsoleAppender`` that logs to standard out +at the ``INFO`` level and a ``SentryAppender`` that logs to the Sentry server at +the ``WARN`` level. The ``ConsoleAppender`` is only provided as an example of +a non-Sentry appender that is set to a different logging threshold, like one you +may already have in your project. + +Example configuration using the ``logback.xml`` format: .. sourcecode:: xml - - ___DSN___?options - tag1:value1,tag2:value2 - - - - - - + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + WARN + + + + + + + + -It's possible to add extra details to events captured by the logback -module thanks to the `marker system -`_ which will add a tag -logback-Marker. The `MDC system provided by logback -`_ allows to add extra information -to the event. +Next, **you'll need to configure your DSN** (client key) and optionally other values such as +``environment`` and ``release``. See below for the two ways you can do this. + +Configuration via Runtime Environment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is the most flexible method for configuring the ``SentryAppender``, +because it can be easily changed based on the environment you run your +application in. + +The following can be set as System Environment variables: + +.. sourcecode:: shell + + SENTRY_EXAMPLE=xxx java -jar app.jar + +Or as Java System Properties: + +.. sourcecode:: shell + + java -Dsentry.example=xxx -jar app.jar + +Configuration parameters follow: + +======================= ======================= =============================== =========== +Environment variable Java System Property Example value Description +======================= ======================= =============================== =========== +``SENTRY_DSN`` ``sentry.dsn`` ``https://host:port/1?options`` Your Sentry DSN (client key), if left blank Raven will no-op +``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application +``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in +``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) +``SENTRY_RAVENFACTORY`` ``sentry.ravenfactory`` ``com.foo.RavenFactory`` Optional, select the ravenFactory class +``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags +``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC +======================= ======================= =============================== =========== + +Configuration via Static File +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also configure everything statically within the ``logback.xml`` +file itself. This is less flexible and not recommended because it's more difficult to change +the values when you run your application in different environments. + +Example configuration in the ``logback.xml`` file: + +.. sourcecode:: xml + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + WARN + + + + https://host:port/1?options + + + 1.0.0 + + + production + + + server1 + + + com.foo.RavenFactory + + + tag1:value1,tag2:value2 + + + foo,bar,baz + + + + + + + + + +Additional Data +--------------- + +It's possible to add extra data to events thanks to `the MDC system provided by Logback +`_. Mapped Tags ------------ +~~~~~~~~~~~ -By default all MDC parameters are sent under the Additional Data Tab. By -specify the extraTags parameter in your configuration file. You can -specify MDC keys to send as tags instead of including them in Additional -Data. This allows them to be filtered within Sentry. +By default all MDC parameters are stored under the "Additional Data" tab in Sentry. By +specifying the ``extraTags`` parameter in your configuration file you can +choose which MDC keys to send as tags instead, which allows them to be used as +filters within the Sentry UI. .. sourcecode:: xml @@ -71,12 +188,12 @@ Data. This allows them to be filtered within Sentry. MDC.put("Environment", "Development"); MDC.put("OS", "Linux"); - // This adds a message with extras and MDC keys declared in extraTags as tags to Sentry - logger.info("This is a test"); + // This sends an event where the Environment and OS MDC values are set as tags + logger.error("This is a test"); } -Practical Example ------------------ +In Practice +----------- .. sourcecode:: java @@ -90,19 +207,19 @@ Practical Example private static final Marker MARKER = MarkerFactory.getMarker("myMarker"); void logSimpleMessage() { - // This adds a simple message to the logs - logger.info("This is a test"); + // This sends a simple event to Sentry + logger.error("This is a test"); } void logWithTag() { - // This adds a message with a tag to the logs named 'logback-Marker' + // This sends an event with a tag named 'logback-Marker' to Sentry logger.info(MARKER, "This is a test"); } void logWithExtras() { // MDC extras MDC.put("extra_key", "extra_value"); - // This adds a message with extras to the logs + // This sends an event with extra data to Sentry logger.info("This is a test"); } @@ -110,12 +227,12 @@ Practical Example try { unsafeMethod(); } catch (Exception e) { - // This adds an exception to the logs + // This sends an exception event to Sentry logger.error("Exception caught", e); } } void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call that"); + throw new UnsupportedOperationException("You shouldn't call this!"); } } diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index 22ebc9ab83d..71db9fd41f7 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -1,18 +1,17 @@ -Raven -===== +java.util.logging +================= -Main module of the Raven project in java. It provides a client to send -messages to a Sentry server as well as an implementation of an `Handler +The ``raven`` library provides a `java.util.logging Handler `_ -for ``java.util.logging``. +that sends logged exceptions to Sentry. -The project can be found on github: `raven-java/raven -`_ +The source for ``raven-java`` can be found `on Github +`_. Installation ------------ -If you want to use Maven you can install Raven as dependency: +Using Maven: .. sourcecode:: xml @@ -22,29 +21,41 @@ If you want to use Maven you can install Raven as dependency: 7.8.2 -If you manually want to manage your dependencies: +Using Gradle: -- `jackson-core-2.5.0.jar `_ -- `slf4j-api-1.7.9.jar `_ -- `slf4j-jdk14-1.7.9.jar `_ - is recommended as the implementation of slf4j to capture the log events - generated by Raven (connection errors, ...) if `java.util.logging` is - used. +.. sourcecode:: groovy + + compile 'com.getsentry.raven:raven:7.8.2' + +Using SBT: + +.. sourcecode:: scala + + libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.2" + +For other dependency managers see the `central Maven repository `_. Usage ----- -To use Raven you need to use it through ``java.util.logging``. The -following configuration (``logging.properties``) gets you started: +The following example configures a ``ConsoleHandler`` that logs to standard out +at the ``INFO`` level and a ``SentryHandler`` that logs to the Sentry server at +the ``WARN`` level. The ``ConsoleHandler`` is only provided as an example of +a non-Sentry appender that is set to a different logging threshold, like one you +may already have in your project. + +Example configuration using the ``logging.properties`` format: .. sourcecode:: ini - .level=WARNING - handlers=com.getsentry.raven.jul.SentryHandler - com.getsentry.raven.jul.SentryHandler.dsn=___DSN___ - com.getsentry.raven.jul.SentryHandler.tags=tag1:value1,tag2:value2 - # Optional, allows to select the ravenFactory - #com.getsentry.raven.jul.SentryHandler.ravenFactory=com.getsentry.raven.DefaultRavenFactory + # Enable the Console and Sentry handlers + handlers=java.util.logging.ConsoleHandler,com.getsentry.raven.jul.SentryHandler + + # Set the default log level to INFO + .level=INFO + + # Override the Sentry handler log level to WARNING + com.getsentry.raven.jul.SentryHandler.level=WARNING When starting your application, add the ``java.util.logging.config.file`` to the system properties, with the full path to the ``logging.properties`` as @@ -52,138 +63,109 @@ its value:: $ java -Djava.util.logging.config.file=/path/to/app.properties MyClass -Practical Example ------------------ +Next, **you'll need to configure your DSN** (client key) and optionally other values such as +``environment`` and ``release``. See below for the two ways you can do this. -.. sourcecode:: java +Configuration via Runtime Environment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - import java.util.logging.Level; - import java.util.logging.Logger; +This is the most flexible method for configuring the ``SentryHandler``, +because it can be easily changed based on the environment you run your +application in. - public class MyClass { - private static final Logger logger = Logger.getLogger(MyClass.class.getName()); +The following can be set as System Environment variables: - void logSimpleMessage() { - // This adds a simple message to the logs - logger.log(Level.INFO, "This is a test"); - } +.. sourcecode:: shell - void logException() { - try { - unsafeMethod(); - } catch (Exception e) { - // This adds an exception to the logs - logger.log(Level.SEVERE, "Exception caught", e); - } - } + SENTRY_EXAMPLE=xxx java -jar app.jar - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call that"); - } - } +Or as Java System Properties: -``java.util.logging`` does not support either MDC nor NDC, meaning that it -is not possible to attach additional/custom context values to the logs. In -other terms, it is not possible to use the "extra" field supported by -Sentry. +.. sourcecode:: shell -Manual Usage ------------- + java -Dsentry.example=xxx -jar app.jar -It is possible to use the client manually rather than using a logging -framework in order to send messages to Sentry. It is not recommended to -use this solution as the API is more verbose and requires the developer to -specify the value of each field sent to Sentry: +Configuration parameters follow: -.. sourcecode:: java +======================= ======================= =============================== =========== +Environment variable Java System Property Example value Description +======================= ======================= =============================== =========== +``SENTRY_DSN`` ``sentry.dsn`` ``https://host:port/1?options`` Your Sentry DSN (client key), if left blank Raven will no-op +``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application +``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in +``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) +``SENTRY_RAVENFACTORY`` ``sentry.ravenfactory`` ``com.foo.RavenFactory`` Optional, select the ravenFactory class +``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags +``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC +======================= ======================= =============================== =========== - import com.getsentry.raven.Raven; - import com.getsentry.raven.RavenFactory; +Configuration via Static File +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - public class MyClass { - private static Raven raven; +You can also configure everything statically within the ``logging.properties`` +file itself. This is less flexible and not recommended because it's more difficult to change +the values when you run your application in different environments. - public static void main(String... args) { - // Creation of the client with a specific DSN - String dsn = args[0]; - raven = RavenFactory.ravenInstance(dsn); +Example configuration in the ``logging.properties`` file: - // It is also possible to use the DSN detection system like this - raven = RavenFactory.ravenInstance(); - } +.. sourcecode:: ini - void logSimpleMessage() { - // This adds a simple message to the logs - raven.sendMessage("This is a test"); - } + # Enable the Console and Sentry handlers + handlers=java.util.logging.ConsoleHandler, com.getsentry.raven.jul.SentryHandler - void logException() { - try { - unsafeMethod(); - } catch (Exception e) { - // This adds an exception to the logs - raven.sendException(e); - } - } + # Set the default log level to INFO + .level=INFO - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call that"); - } - } + # Override the Sentry handler log level to WARNING + com.getsentry.raven.jul.SentryHandler.level=WARNING -For more complex messages, it will be necessary to build an ``Event`` with the -``EventBuilder`` class: + # Set Sentry DSN + com.getsentry.raven.jul.SentryHandler.dsn=https://host:port/1?options -.. sourcecode:: java + # Optional, provide tags + com.getsentry.raven.jul.SentryHandler.tags=tag1:value1,tag2:value2 - import com.getsentry.raven.Raven; - import com.getsentry.raven.RavenFactory; - import com.getsentry.raven.event.Event; - import com.getsentry.raven.event.EventBuilder; - import com.getsentry.raven.event.interfaces.ExceptionInterface; - import com.getsentry.raven.event.interfaces.MessageInterface; + # Optional, provide release version of your application + com.getsentry.raven.jul.SentryHandler.release=1.0.0 - public class MyClass { - private static Raven raven; + # Optional, provide environment your application is running in + com.getsentry.raven.jul.SentryHandler.environment=production - public static void main(String... args) { - // Creation of the client with a specific DSN - String dsn = args[0]; - raven = RavenFactory.ravenInstance(dsn); + # Optional, override the server name (rather than looking it up dynamically) + com.getsentry.raven.jul.SentryHandler.serverName=server1 - // It is also possible to use the DSN detection system like this - raven = RavenFactory.ravenInstance(); + # Optional, select the ravenFactory class + com.getsentry.raven.jul.SentryHandler.ravenFactory=com.foo.RavenFactory - // Advanced: To specify the ravenFactory used - raven = RavenFactory.ravenInstance(new Dsn(dsn), "com.getsentry.raven.DefaultRavenFactory"); - } + # Optional, provide tag names to be extracted from MDC + com.getsentry.raven.jul.SentryHandler.extraTags=foo,bar,baz + +In Practice +----------- + +.. sourcecode:: java + + import java.util.logging.Level; + import java.util.logging.Logger; + + public class MyClass { + private static final Logger logger = Logger.getLogger(MyClass.class.getName()); void logSimpleMessage() { - // This adds a simple message to the logs - EventBuilder eventBuilder = new EventBuilder() - .withMessage("This is a test") - .withLevel(Event.Level.INFO) - .withLogger(MyClass.class.getName()); - raven.runBuilderHelpers(eventBuilder); // Optional - raven.sendEvent(eventBuilder.build()); + // This sends a simple event to Sentry + logger.error(Level.INFO, "This is a test"); } void logException() { try { unsafeMethod(); } catch (Exception e) { - // This adds an exception to the logs - EventBuilder eventBuilder = new EventBuilder() - .withMessage("Exception caught") - .withLevel(Event.Level.ERROR) - .withLogger(MyClass.class.getName()) - .withSentryInterface(new ExceptionInterface(e)); - raven.runBuilderHelpers(eventBuilder); // Optional - raven.sendEvent(eventBuilder.build()); + // This sends an exception event to Sentry + logger.error(Level.SEVERE, "Exception caught", e); } } void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call that"); + throw new UnsupportedOperationException("You shouldn't call this!"); } } diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json index f34027f8ae8..af1dd53d421 100644 --- a/docs/sentry-doc-config.json +++ b/docs/sentry-doc-config.json @@ -4,6 +4,15 @@ "java": { "name": "Java", "type": "language", + "doc_link": "", + "wizard": [ + "usage#installation", + "usage#capture-an-error" + ] + }, + "java.logging": { + "name": "java.util.logging", + "type": "framework", "doc_link": "modules/raven/", "wizard": [ "modules/raven#installation", @@ -11,7 +20,7 @@ ] }, "java.log4j": { - "name": "Log4j", + "name": "Log4j 1.x", "type": "framework", "doc_link": "modules/log4j/", "wizard": [ @@ -20,7 +29,7 @@ ] }, "java.log4j2": { - "name": "Log4j 2", + "name": "Log4j 2.x", "type": "framework", "doc_link": "modules/log4j2/", "wizard": [ @@ -38,13 +47,22 @@ ] }, "java.appengine": { - "name": "App Engine", + "name": "Google App Engine", "type": "framework", "doc_link": "modules/appengine/", "wizard": [ "modules/appengine#installation", "modules/appengine#usage" ] + }, + "java.android": { + "name": "Android", + "type": "framework", + "doc_link": "modules/android/", + "wizard": [ + "modules/android#installation", + "modules/android#usage" + ] } } } diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 00000000000..f8a75125eaf --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,177 @@ +Manual Usage +============ + +**Note:** The following page provides examples on how to configure and use +Raven directly. It is **highly recommended** that you use one of the provided +integrations instead if possible. + +Installation +------------ + +Using Maven: + +.. sourcecode:: xml + + + com.getsentry.raven + raven + 7.8.2 + + +Using Gradle: + +.. sourcecode:: groovy + + compile 'com.getsentry.raven:raven:7.8.2' + +Using SBT: + +.. sourcecode:: scala + + libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.2" + +For other dependency managers see the `central Maven repository `_. + +Capture an Error +---------------- + +To report an event manually you need to construct a ``Raven`` instance and use one +of the send methods it provides. + +.. sourcecode:: java + + import com.getsentry.raven.Raven; + import com.getsentry.raven.RavenFactory; + + public class MyClass { + private static Raven raven; + + public static void main(String... args) { + // Creation of the client with a specific DSN + String dsn = args[0]; + raven = RavenFactory.ravenInstance(dsn); + + // Or, if you don't provide a DSN, + raven = RavenFactory.ravenInstance(); + + // It is also possible to use the DSN detection system, which + // will check the environment variable "SENTRY_DSN" and the Java + // System Property "sentry.dsn". + raven = RavenFactory.ravenInstance(); + } + + void logSimpleMessage() { + // This sends a simple event to Sentry + raven.sendMessage("This is a test"); + } + + void logException() { + try { + unsafeMethod(); + } catch (Exception e) { + // This sends an exception event to Sentry + raven.sendException(e); + } + } + + void unsafeMethod() { + throw new UnsupportedOperationException("You shouldn't call this!"); + } + } + +Building More Complex Events +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For more complex messages, you'll need to build an ``Event`` with the +``EventBuilder`` class: + +.. sourcecode:: java + + import com.getsentry.raven.Raven; + import com.getsentry.raven.RavenFactory; + import com.getsentry.raven.event.Event; + import com.getsentry.raven.event.EventBuilder; + import com.getsentry.raven.event.interfaces.ExceptionInterface; + import com.getsentry.raven.event.interfaces.MessageInterface; + + public class MyClass { + private static Raven raven; + + public static void main(String... args) { + // Creation of the client with a specific DSN + String dsn = args[0]; + raven = RavenFactory.ravenInstance(dsn); + + // It is also possible to use the DSN detection system, which + // will check the environment variable "SENTRY_DSN" and the Java + // System Property "sentry.dsn". + raven = RavenFactory.ravenInstance(); + + // Advanced: specify the ravenFactory used + raven = RavenFactory.ravenInstance(new Dsn(dsn), "com.getsentry.raven.DefaultRavenFactory"); + } + + void logSimpleMessage() { + // This sends an event to Sentry + EventBuilder eventBuilder = new EventBuilder() + .withMessage("This is a test") + .withLevel(Event.Level.INFO) + .withLogger(MyClass.class.getName()); + raven.sendEvent(eventBuilder); + } + + void logException() { + try { + unsafeMethod(); + } catch (Exception e) { + // This sends an exception event to Sentry + EventBuilder eventBuilder = new EventBuilder() + .withMessage("Exception caught") + .withLevel(Event.Level.ERROR) + .withLogger(MyClass.class.getName()) + .withSentryInterface(new ExceptionInterface(e)); + raven.sendEvent(eventBuilder); + } + } + + void unsafeMethod() { + throw new UnsupportedOperationException("You shouldn't call this!"); + } + } + +Static Access to Raven +---------------------- + +The most recently constructed ``Raven`` instance is stored statically so it may +be used easily from anywhere in your application. + +.. sourcecode:: java + + import com.getsentry.raven.Raven; + import com.getsentry.raven.RavenFactory; + + public class MyClass { + public static void main(String... args) { + // Create a Raven instance + RavenFactory.ravenInstance(); + } + + public somewhereElse() { + // Use the Raven instance statically. Note that we are + // using the Class (and a static method) here + Raven.capture("Error message"); + + // Or pass it a throwable + Raven.capture(new Exception("Error message")); + + // Or build an event yourself + EventBuilder eventBuilder = new EventBuilder() + .withMessage("Exception caught") + .withLevel(Event.Level.ERROR); + Raven.capture(eventBuilder.build()); + } + + } + +Note that a Raven instance *must* be created before you can use the ``Raven.capture`` +method, otherwise a ``NullPointerException`` (with an explanation) will be thrown. diff --git a/raven-android/README.md b/raven-android/README.md index 1397bf1b6ca..a677e4fe59e 100644 --- a/raven-android/README.md +++ b/raven-android/README.md @@ -1,61 +1,3 @@ -# Raven-Android +# raven-android -## Installation - -### Gradle (Android Studio) - -In your `app/build.gradle` add: `compile 'com.getsentry.raven:raven-android:7.8.2'` - -### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-android%7C7.8.2%7Cjar). - -## Usage - -### Configuration - -Configure your Sentry DSN (client key) in `AndroidManifest.xml`: - -```xml - - - -``` - -Your application must also have permission to access the internet in order to send -event to the Sentry server. In `AndroidManifest.xml`: - -```xml - - -``` - -Then, in your application's `onCreate`, initialize the Raven client: - -```java -import com.getsentry.raven.android.Raven; - -// `this` is your main Activity -Raven.init(this.getApplicationContext()); -``` - -Now you can use `Raven` to capture events in your application: - -```java -// Pass a String event -Raven.capture("Error message"); - -// Or pass it a throwable -try { - something() -} catch (Exception e) { - Raven.capture(e); -} - -// Or build an event yourself -EventBuilder eventBuilder = new EventBuilder() - .withMessage("Exception caught") - .withLevel(Event.Level.ERROR); -Raven.capture(eventBuilder.build()); -``` +See the [Sentry documentation](https://docs.sentry.io/clients/java/modules/android/) for more information. diff --git a/raven-appengine/README.md b/raven-appengine/README.md index 40be559f24a..1be84b4b932 100644 --- a/raven-appengine/README.md +++ b/raven-appengine/README.md @@ -1,52 +1,3 @@ -# AppEngine (module) -Module enabling the support of the async connections in Google App Engine. +# raven-appengine -Google App Engine doesn't support threads but provides instead a TaskQueueing system allowing tasks to be run in the -background. - -This module replaces the async system provided by default with one relying on the tasks. - -__This module is not useful outside of Google App Engine.__ - -## Installation - -### Maven -```xml - - com.getsentry.raven - raven-appengine - 7.8.2 - -``` - -### Gradle -``` -compile 'com.getsentry.raven:raven-appengine:7.8.2' -``` - -### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-appengine%7C7.8.2%7Cjar). - -## Usage - -This module provides a new `RavenFactory` which replaces the default async system with a GAE compatible one. - -The queue size and thread options will not be used as they are specific to the default multithreaded system. - -It is necessary to force the raven factory name to `com.getsentry.raven.appengine.AppEngineRavenFactory`. - -### Queue name - -By default, the default task queue will be used, but it's possible to specify which one will be used with the -`raven.async.gae.queuename` option: - - http://public:private@getsentry.com/1?raven.async.gae.queuename=MyQueueName - -### Connection name - -As the queued tasks are sent across different instances of the application, it's important to be able to identify which -connection should be used when processing the event. -To do so, the GAE module will identify each connection based on an identifier either automatically generated or user defined. -TO manually set the connection identifier (only used internally) use the option `raven.async.connectionid`: - - http://public:private@getsentry.com/1?raven.async.gae.connectionid=MyConnection +See the [Sentry documentation](https://docs.sentry.io/clients/java/modules/appengine/) for more information. diff --git a/raven-appengine/src/main/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnection.java b/raven-appengine/src/main/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnection.java index bd29e133d3d..f5a6205d8bd 100644 --- a/raven-appengine/src/main/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnection.java +++ b/raven-appengine/src/main/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnection.java @@ -18,10 +18,10 @@ import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withPayload; /** - * Asynchronous usage of a connection withing Google App Engine. + * Asynchronous connections that can be used within Google App Engine. *

    - * Instead of synchronously sending each event to a connection, use a the task queue system to establish the connection - * and submit the event. + * Instead of synchronously sending each event, use a the App Engine queue system to establish the connection + * and send the event. *

    * Google App Engine serialises the tasks before queuing them, to keep a link between the task and the * {@link AppEngineAsyncConnection} associated, a register of the instances of {@code AppEngineAsyncConnection} is diff --git a/raven-log4j/README.md b/raven-log4j/README.md index 47655083cf2..498cff6884d 100644 --- a/raven-log4j/README.md +++ b/raven-log4j/README.md @@ -1,197 +1,3 @@ -# Raven-log4j -[log4j](https://logging.apache.org/log4j/1.2/) support for Raven. -It provides an [`Appender`](https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Appender.html) -for log4j to send the logged events to Sentry. +# raven-log4j -## Installation - -### Maven -```xml - - com.getsentry.raven - raven-log4j - 7.8.2 - -``` - -### Gradle -``` -compile 'com.getsentry.raven:raven-log4j:7.8.2' -``` - -### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j%7C7.8.2%7Cjar). - -## Usage -### Configuration -Add the `SentryAppender` to the `log4j.properties` file: - -```properties -# Enable the Console and Sentry appenders -log4j.rootLogger=INFO, Console, Sentry - -# Configure the Console appender -log4j.appender.Console=org.apache.log4j.ConsoleAppender -log4j.appender.Console.layout=org.apache.log4j.PatternLayout -log4j.appender.Console.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5p: %m%n - -# Configure the Sentry appender, overriding the logging threshold to the WARN level -log4j.appender.Sentry=com.getsentry.raven.log4j.SentryAppender -log4j.appender.Sentry.threshold=WARN -``` - -Alternatively in the `log4j.xml` file add: - -``` - - - - - - -``` - -You'll also need to associate the `Sentry` appender with your root logger, like so: - -``` - --> - - -``` - -Next, you'll need to configure your DSN (client key) and optionally other -values such as `environment` and `release`. See below for the two -ways you can do this. - -#### Configuration via runtime environment - -This is the most flexible method to configure the `SentryAppender`, -because it can be easily changed based on the environment you run your -application in. - -The following can be set as System Environment variables: - -```bash -SENTRY_EXAMPLE=xxx java -jar app.jar -``` - -or as Java System Properties: - -```bash -java -Dsentry.example=xxx -jar app.jar -``` - -Configuration parameters follow: - -| Environment variable | Java System Property | Example value | Description | -|---|---|---|---| -| `SENTRY_DSN` | `sentry.dsn` | `https://host:port/1?options` | Your Sentry DSN (client key), if left blank Raven will no-op | -| `SENTRY_RELEASE` | `sentry.release` | `1.0.0` | Optional, provide release version of your application | -| `SENTRY_ENVIRONMENT` | `sentry.environment` | `production` | Optional, provide environment your application is running in | -| `SENTRY_SERVERNAME` | `sentry.servername` | `server1` | Optional, override the server name (rather than looking it up dynamically) | -| `SENTRY_RAVENFACTORY` | `sentry.ravenfactory` | `com.foo.RavenFactory` | Optional, select the ravenFactory class | -| `SENTRY_TAGS` | `sentry.tags` | `tag1:value1,tag2:value2` | Optional, provide tags | -| `SENTRY_EXTRA_TAGS` | `sentry.extratags` | `foo,bar,baz` | Optional, provide tag names to be extracted from MDC when using SLF4J | - -#### Configuration via `log4j.properties` (or `log4j.xml`) - -You can also configure everything statically within the `log4j.properties` (or `log4j.xml`) -file itself. This is less flexible because it's harder to change when you run -your application in different environments. - -```properties -# Enable the Console and Sentry appenders -log4j.rootLogger=INFO, Console, Sentry - -# Configure the Console appender -log4j.appender.Console=org.apache.log4j.ConsoleAppender - -# Configure the Sentry appender, overriding the logging threshold to the WARN level -log4j.appender.Sentry=com.getsentry.raven.log4j.SentryAppender -log4j.appender.Sentry.threshold=WARN - -# Set Sentry DSN -log4j.appender.SentryAppender.dsn=https://host:port/1?options -# Optional, provide release version of your application -log4j.appender.SentryAppender.release=1.0.0 -# Optional, provide environment your application is running in -log4j.appender.SentryAppender.environment=production -# Optional, override the server name (rather than looking it up dynamically) -log4j.appender.SentryAppender.serverName=server1 -# Optional, select the ravenFactory class -log4j.appender.SentryAppender.ravenFactory=com.foo.RavenFactory -# Optional, provide tags -log4j.appender.SentryAppender.tags=tag1:value1,tag2:value2 -# Optional, provide tag names to be extracted from MDC when using SLF4J -log4j.appender.SentryAppender.extraTags=foo,bar,baz -``` - -### Additional data and information -It's possible to add extra data to events, -thanks to both [the MDC](https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/MDC.html) -and [the NDC](https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/NDC.html) systems provided by Log4j. - -### In practice -```java -import org.apache.log4j.Logger; -import org.apache.log4j.MDC; -import org.apache.log4j.NDC; - -public class MyClass { - private static final Logger logger = Logger.getLogger(MyClass.class); - - void logSimpleMessage() { - // This adds a simple message to the logs - logger.info("This is a test"); - } - - void logWithExtras() { - // MDC extras - MDC.put("extra_key", "extra_value"); - // NDC extras are sent under 'log4J-NDC' - NDC.push("Extra_details"); - // This adds a message with extras to the logs - logger.info("This is a test"); - } - - void logException() { - try { - unsafeMethod(); - } catch (Exception e) { - // This adds an exception to the logs - logger.error("Exception caught", e); - } - } - - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call that"); - } -} -``` - -### Mapped Tags -By default all MDC parameters are sent under the Additional Data Tab. By specify the extraTags parameter in your -configuration file. You can specify MDC keys to send as tags instead of including them in Additional Data. -This allows them to be filtered within Sentry. - -```properties -log4j.appender.SentryAppender.extraTags=User,OS -``` -```java - void logWithExtras() { - // MDC extras - MDC.put("User", "test user"); - MDC.put("OS", "Linux"); - - // This adds a message with extras and MDC keys declared in extraTags as tags to Sentry - logger.info("This is a test"); - } -``` - -## Asynchronous logging -It is not recommended to attempt to set up `SentryAppender` within an -[AsyncAppender](https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/AsyncAppender.html). -While this is a common solution to avoid blocking the current thread until the -event is sent to Sentry, it is recommended to rely instead on the asynchronous -connection provided by Raven. +See the [Sentry documentation](https://docs.sentry.io/clients/java/modules/log4j/) for more information. diff --git a/raven-log4j2/README.md b/raven-log4j2/README.md index 54445bc1a2b..879e7e67a18 100644 --- a/raven-log4j2/README.md +++ b/raven-log4j2/README.md @@ -1,194 +1,3 @@ -# Raven-Log4j 2 -[Log4j 2](https://logging.apache.org/log4j/2.x/) support for Raven. -It provides an [`Appender`](https://logging.apache.org/log4j/2.x/log4j-core/apidocs/org/apache/logging/log4j/core/Appender.html) -for Log4j 2 to send the logged events to Sentry. +# raven-log4j 2.x -## Installation - -### Maven -```xml - - com.getsentry.raven - raven-log4j2 - 7.8.2 - -``` - -### Gradle -``` -compile 'com.getsentry.raven:raven-log4j2:7.8.2' -``` - -### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-log4j2%7C7.8.2%7Cjar). - -## Usage -### Configuration -Add the `SentryAppender` to your `log4j2.xml` file: - -```xml - - - - - - - - - - - - - - - - - -``` - -Next, you'll need to configure your DSN (client key) and optionally other -values such as `environment` and `release`. See below for the two -ways you can do this. - -#### Configuration via runtime environment - -This is the most flexible method to configure the `SentryAppender`, -because it can be easily changed based on the environment you run your -application in. - -The following can be set as System Environment variables: - -```bash -SENTRY_EXAMPLE=xxx java -jar app.jar -``` - -or as Java System Properties: - -```bash -java -Dsentry.example=xxx -jar app.jar -``` - -Configuration parameters follow: - -| Environment variable | Java System Property | Example value | Description | -|---|---|---|---| -| `SENTRY_DSN` | `sentry.dsn` | `https://host:port/1?options` | Your Sentry DSN (client key), if left blank Raven will no-op | -| `SENTRY_RELEASE` | `sentry.release` | `1.0.0` | Optional, provide release version of your application | -| `SENTRY_ENVIRONMENT` | `sentry.environment` | `production` | Optional, provide environment your application is running in | -| `SENTRY_SERVERNAME` | `sentry.servername` | `server1` | Optional, override the server name (rather than looking it up dynamically) | -| `SENTRY_RAVENFACTORY` | `sentry.ravenfactory` | `com.foo.RavenFactory` | Optional, select the ravenFactory class | -| `SENTRY_TAGS` | `sentry.tags` | `tag1:value1,tag2:value2` | Optional, provide tags | -| `SENTRY_EXTRA_TAGS` | `sentry.extratags` | `foo,bar,baz` | Optional, provide tag names to be extracted from MDC when using SLF4J | - -#### Configuration via `log4j2.xml` - -You can also configure everything statically within the `log4j2.xml` file -itself. This is less flexible because it's harder to change when you run -your application in different environments. - -```xml - - - - - - - - - https://host:port/1?options - - 1.0.0 - - production - - server1 - - com.foo.RavenFactory - - tag1:value1,tag2:value2 - - foo,bar,baz - - - - - - - - - - -``` - -### Additional data and information -It's possible to add extra details to events captured by the Log4j 2 module -thanks to the [marker system](https://logging.apache.org/log4j/2.x/manual/markers.html) -which will add a tag `log4j2-Marker`. -Both [the MDC and the NDC systems provided by Log4j 2](https://logging.apache.org/log4j/2.x/manual/thread-context.html) -are usable, allowing to attach extras information to the event. - -### Mapped Tags -By default all Thread Context parameters are sent under the Additional Data Tab. By specifying the extraTags parameter in your -configuration file. You can specify Thread Context keys to send as tags instead of including them in Additional Data. -This allows them to be filtered within Sentry. In older Log4j versions, the Thread Context Map was known as the MDC. - -```xml - - User,OS - -``` -```java - void logWithExtras() { - // ThreadContext extras - ThreadContext.put("User", "test user"); - ThreadContext.put("OS", "Linux"); - - // This adds a message with extras and ThreadContext keys declared in extraTags as tags to Sentry - logger.info("This is a test"); - } -``` - -### In practice -```java -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.MarkerManager; - -public class MyClass { - private static final Logger logger = LogManager.getLogger(MyClass.class); - private static final Marker MARKER = MarkerManager.getMarker("myMarker"); - - void logSimpleMessage() { - // This adds a simple message to the logs - logger.info("This is a test"); - } - - void logWithTag() { - // This adds a message with a tag to the logs named 'log4j2-Marker' - logger.info(MARKER, "This is a test"); - } - - void logWithExtras() { - // MDC extras - ThreadContext.put("extra_key", "extra_value"); - // NDC extras are sent under 'log4j2-NDC' - ThreadContext.push("Extra_details"); - // This adds a message with extras to the logs - logger.info("This is a test"); - } - - void logException() { - try { - unsafeMethod(); - } catch (Exception e) { - // This adds an exception to the logs - logger.error("Exception caught", e); - } - } - - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call that"); - } -} -``` +See the [Sentry documentation](https://docs.sentry.io/clients/java/modules/log4j2/) for more information. diff --git a/raven-logback/README.md b/raven-logback/README.md index 035e3c579c4..9a203a9d6e1 100644 --- a/raven-logback/README.md +++ b/raven-logback/README.md @@ -1,193 +1,3 @@ -# Raven-logback -[logback](http://logback.qos.ch/) support for Raven. -It provides an [`Appender`](http://logback.qos.ch/apidocs/ch/qos/logback/core/Appender.html) -for logback to send the logged events to Sentry. +# raven-logback -## Installation - -### Maven -```xml - - com.getsentry.raven - raven-logback - 7.8.2 - -``` - -### Gradle -``` -compile 'com.getsentry.raven:raven-logback:7.8.2' -``` - -### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven-logback%7C7.8.2%7Cjar). - -## Usage -### Configuration -Add the `SentryAppender` to your `logback.xml` file: - -```xml - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - WARN - - - - - - - - -``` - -Next, you'll need to configure your DSN (client key) and optionally other -values such as `environment` and `release`. See below for the two -ways you can do this. - -#### Configuration via runtime environment - -This is the most flexible method to configure the `SentryAppender`, -because it can be easily changed based on the environment you run your -application in. - -The following can be set as System Environment variables: - -```bash -SENTRY_EXAMPLE=xxx java -jar app.jar -``` - -or as Java System Properties: - -```bash -java -Dsentry.example=xxx -jar app.jar -``` - -Configuration parameters follow: - -| Environment variable | Java System Property | Example value | Description | -|---|---|---|---| -| `SENTRY_DSN` | `sentry.dsn` | `https://host:port/1?options` | Your Sentry DSN (client key), if left blank Raven will no-op | -| `SENTRY_RELEASE` | `sentry.release` | `1.0.0` | Optional, provide release version of your application | -| `SENTRY_ENVIRONMENT` | `sentry.environment` | `production` | Optional, provide environment your application is running in | -| `SENTRY_SERVERNAME` | `sentry.servername` | `server1` | Optional, override the server name (rather than looking it up dynamically) | -| `SENTRY_RAVENFACTORY` | `sentry.ravenfactory` | `com.foo.RavenFactory` | Optional, select the ravenFactory class | -| `SENTRY_TAGS` | `sentry.tags` | `tag1:value1,tag2:value2` | Optional, provide tags | -| `SENTRY_EXTRA_TAGS` | `sentry.extratags` | `foo,bar,baz` | Optional, provide tag names to be extracted from MDC when using SLF4J | - -#### Configuration via `logback.xml` - -You can also configure everything statically within the `logback.xml` file -itself. This is less flexible because it's harder to change when you run -your application in different environments. - -```xml - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - WARN - - - - https://host:port/1?options - - 1.0.0 - - production - - server1 - - com.foo.RavenFactory - - tag1:value1,tag2:value2 - - foo,bar,baz - - - - - - - -``` - -### Additional data and information -It's possible to add extra details to events captured by the logback module -thanks to the [marker system](http://www.slf4j.org/faq.html#fatal) which will -add a tag `logback-Marker`. -[The MDC system provided by logback](http://logback.qos.ch/manual/mdc.html) -allows to add extra information to the event. - -### Mapped Tags -By default all MDC parameters are sent under the Additional Data Tab. By specifying the extraTags parameter in your -configuration file. You can specify MDC keys to send as tags instead of including them in Additional Data. -This allows them to be filtered within Sentry. - -```xml -User,OS -``` -```java - void logWithExtras() { - // MDC extras - MDC.put("User", "test user"); - MDC.put("OS", "Linux"); - - // This adds a message with extras and MDC keys declared in extraTags as tags to Sentry - logger.info("This is a test"); - } -``` - -### In practice -```java -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.MDC; -import org.slf4j.MarkerFactory; - -public class MyClass { - private static final Logger logger = LoggerFactory.getLogger(MyClass.class); - private static final Marker MARKER = MarkerFactory.getMarker("myMarker"); - - void logSimpleMessage() { - // This adds a simple message to the logs - logger.info("This is a test"); - } - - void logWithTag() { - // This adds a message with a tag to the logs named 'logback-Marker' - logger.info(MARKER, "This is a test"); - } - - void logWithExtras() { - // MDC extras - MDC.put("extra_key", "extra_value"); - // This adds a message with extras to the logs - logger.info("This is a test"); - } - - void logException() { - try { - unsafeMethod(); - } catch (Exception e) { - // This adds an exception to the logs - logger.error("Exception caught", e); - } - } - - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call that"); - } -} -``` +See the [Sentry documentation](https://docs.sentry.io/clients/java/modules/logback/) for more information. diff --git a/raven/README.md b/raven/README.md index 838ee94ad68..7b34e2f3f20 100644 --- a/raven/README.md +++ b/raven/README.md @@ -1,284 +1,3 @@ -# Raven (module) -Main module of the Raven project in java. It provides a client to send messages -to a Sentry server as well as an implementation of an [`Handler`](http://docs.oracle.com/javase/7/docs/api/java/util/logging/Handler.html) -for `java.util.logging`. +# raven -## Installation - -### Maven -```xml - - com.getsentry.raven - raven - 7.8.2 - -``` - -### Gradle -``` -compile 'com.getsentry.raven:raven:7.8.2' -``` - -### Other dependency managers -Details in the [central Maven repository](https://search.maven.org/#artifactdetails%7Ccom.getsentry.raven%7Craven%7C7.8.2%7Cjar). - -## Usage (`java.util.logging`) -### Configuration -Add the `SentryHandler` to the `logging.properties` file: - -```properties -# Enable the Console and Sentry handlers -handlers=java.util.logging.ConsoleHandler,com.getsentry.raven.jul.SentryHandler - -# Set the default log level to INFO -.level=INFO - -# Override the Sentry handler log level to WARNING -com.getsentry.raven.jul.SentryHandler.level=WARNING -``` - -When starting your application, add the `java.util.logging.config.file` to the -system properties, with the full path to the `logging.properties` as its value. - - $ java -Djava.util.logging.config.file=/path/to/app.properties MyClass - -Next, you'll need to configure your DSN (client key) and optionally other -values such as `environment` and `release`. See below for the two -ways you can do this. - -#### Configuration via runtime environment - -This is the most flexible method to configure the `SentryAppender`, -because it can be easily changed based on the environment you run your -application in. - -The following can be set as System Environment variables: - -```bash -SENTRY_EXAMPLE=xxx java -jar app.jar -``` - -or as Java System Properties: - -```bash -java -Dsentry.example=xxx -jar app.jar -``` - -Configuration parameters follow: - -| Environment variable | Java System Property | Example value | Description | -|---|---|---|---| -| `SENTRY_DSN` | `sentry.dsn` | `https://host:port/1?options` | Your Sentry DSN (client key), if left blank Raven will no-op | -| `SENTRY_RELEASE` | `sentry.release` | `1.0.0` | Optional, provide release version of your application | -| `SENTRY_ENVIRONMENT` | `sentry.environment` | `production` | Optional, provide environment your application is running in | -| `SENTRY_SERVERNAME` | `sentry.servername` | `server1` | Optional, override the server name (rather than looking it up dynamically) | -| `SENTRY_RAVENFACTORY` | `sentry.ravenfactory` | `com.foo.RavenFactory` | Optional, select the ravenFactory class | -| `SENTRY_TAGS` | `sentry.tags` | `tag1:value1,tag2:value2` | Optional, provide tags | -| `SENTRY_EXTRA_TAGS` | `sentry.extratags` | `foo,bar,baz` | Optional, provide tag names to be extracted from MDC when using SLF4J | - -#### Configuration via `logging.properties` - -You can also configure everything statically within the `logging.properties` file -itself. This is less flexible because it's harder to change when you run -your application in different environments. - -```properties -# Enable the Console and Sentry handlers -handlers=java.util.logging.ConsoleHandler,com.getsentry.raven.jul.SentryHandler - -# Set the default log level to INFO -.level=INFO - -# Override the Sentry handler log level to WARNING -com.getsentry.raven.jul.SentryHandler.level=WARNING - -# Set Sentry DSN -com.getsentry.raven.jul.SentryHandler.dsn=https://host:port/1?options -# Optional, provide tags -com.getsentry.raven.jul.SentryHandler.tags=tag1:value1,tag2:value2 -# Optional, provide release version of your application -com.getsentry.raven.jul.SentryHandler.release=1.0.0 -# Optional, provide environment your application is running in -com.getsentry.raven.jul.SentryHandler.environment=production -# Optional, override the server name (rather than looking it up dynamically) -com.getsentry.raven.jul.SentryHandler.serverName=server1 -# Optional, select the ravenFactory class -com.getsentry.raven.jul.SentryHandler.ravenFactory=com.foo.RavenFactory -# Optional, provide tag names to be extracted from MDC when using SLF4J -com.getsentry.raven.jul.SentryHandler.extraTags=foo,bar,baz -``` - -### In practice -```java -import java.util.logging.Level; -import java.util.logging.Logger; - -public class MyClass { - private static final Logger logger = Logger.getLogger(MyClass.class.getName()); - - void logSimpleMessage() { - // This adds a simple message to the logs - logger.log(Level.INFO, "This is a test"); - } - - void logException() { - try { - unsafeMethod(); - } catch (Exception e) { - // This adds an exception to the logs - logger.log(Level.SEVERE, "Exception caught", e); - } - } - - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call that"); - } -} -``` - -### Unsupported features - -As `java.util.logging` has no notion of MDC, the `extraTags` parameter is only -available when logging via SLF4J. - -## Manual usage (NOT RECOMMENDED) -It is possible to use the client manually rather than using a logging framework -in order to send messages to Sentry. It is not recommended to use this solution -as the API is more verbose and requires the developer to specify the value of -each field sent to Sentry. - -### In practice -```java -import com.getsentry.raven.Raven; -import com.getsentry.raven.RavenFactory; - - -public class MyClass { - private static Raven raven; - - public static void main(String... args) { - // Creation of the client with a specific DSN - String dsn = args[0]; - raven = RavenFactory.ravenInstance(dsn); - - // It is also possible to use the DSN detection system like this - raven = RavenFactory.ravenInstance(); - } - - void logSimpleMessage() { - // This adds a simple message to the logs - raven.sendMessage("This is a test"); - } - - void logException() { - try { - unsafeMethod(); - } catch (Exception e) { - // This adds an exception to the logs - raven.sendException(e); - } - } - - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call that"); - } -} -``` - -### In practice (advanced) - -For more complex messages, it will be necessary to build an `Event` with the -`EventBuilder` class. - -```java -import com.getsentry.raven.Raven; -import com.getsentry.raven.RavenFactory; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.EventBuilder; -import com.getsentry.raven.event.interfaces.ExceptionInterface; -import com.getsentry.raven.event.interfaces.MessageInterface; - -public class MyClass { - private static Raven raven; - - public static void main(String... args) { - // Creation of the client with a specific DSN - String dsn = args[0]; - raven = RavenFactory.ravenInstance(dsn); - - // It is also possible to use the DSN detection system like this - raven = RavenFactory.ravenInstance(); - - // Advanced: To specify the ravenFactory used - raven = RavenFactory.ravenInstance(new Dsn(dsn), "com.getsentry.raven.DefaultRavenFactory"); - } - - void logSimpleMessage() { - // This adds a simple message to the logs - EventBuilder eventBuilder = new EventBuilder() - .withMessage("This is a test") - .withLevel(Event.Level.INFO) - .withLogger(MyClass.class.getName()); - raven.runBuilderHelpers(eventBuilder); // Optional - raven.sendEvent(eventBuilder.build()); - } - - void logException() { - try { - unsafeMethod(); - } catch (Exception e) { - // This adds an exception to the logs - EventBuilder eventBuilder = new EventBuilder() - .withMessage("Exception caught") - .withLevel(Event.Level.ERROR) - .withLogger(MyClass.class.getName()) - .withSentryInterface(new ExceptionInterface(e)); - raven.runBuilderHelpers(eventBuilder); // Optional - raven.sendEvent(eventBuilder.build()); - } - } - - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call that"); - } -} -``` - -This gives more control over the content of the `Event` and gives access to the -complete API supported by Sentry. - -### Static access - -The most recently constructed `Raven` instance is stored statically so it may -be used easily from anywhere in your application. - -```java -import com.getsentry.raven.Raven; -import com.getsentry.raven.RavenFactory; - -public class MyClass { - public static void main(String... args) { - // Create a Raven instance - RavenFactory.ravenInstance(); - } - - public somewhereElse() { - // Use the Raven instance statically. Note that we are - // using the Class (and a static method) here - Raven.capture("Error message"); - - // Or pass it a throwable - Raven.capture(new Exception("Error message")); - - // Or build an event yourself - EventBuilder eventBuilder = new EventBuilder() - .withMessage("Exception caught") - .withLevel(Event.Level.ERROR); - Raven.capture(eventBuilder.build()); - } - -} -``` - -Note that a Raven instance *must* be created before you can use the `Raven.capture` -method, otherwise a `NullPointerException` (with an explaination) will be thrown. +See the [Sentry documentation](https://docs.sentry.io/clients/java/modules/raven/) for more information. From 69f4ccaa70024dd3de3f3ff073788e56f46ba2d2 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 17 Feb 2017 16:41:15 -0600 Subject: [PATCH 1509/2152] Match integrations doc order with sidebar. --- docs/modules/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/index.rst b/docs/modules/index.rst index 9cf4429544b..225a4ed0bdf 100644 --- a/docs/modules/index.rst +++ b/docs/modules/index.rst @@ -7,9 +7,9 @@ you don't have to capture and send errors manually. .. toctree:: :maxdepth: 1 + android + appengine raven log4j log4j2 logback - appengine - android From e1877a4910f93260afc796a2fa2c3cf5f7d874bb Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 27 Feb 2017 16:53:10 -0600 Subject: [PATCH 1510/2152] Add Clock interface for testing. (#315) --- .../java/com/getsentry/raven/time/Clock.java | 24 ++++++++++ .../com/getsentry/raven/time/FixedClock.java | 45 +++++++++++++++++++ .../com/getsentry/raven/time/SystemClock.java | 19 ++++++++ .../com/getsentry/raven/time/ClockTest.java | 27 +++++++++++ 4 files changed, 115 insertions(+) create mode 100644 raven/src/main/java/com/getsentry/raven/time/Clock.java create mode 100644 raven/src/main/java/com/getsentry/raven/time/FixedClock.java create mode 100644 raven/src/main/java/com/getsentry/raven/time/SystemClock.java create mode 100644 raven/src/test/java/com/getsentry/raven/time/ClockTest.java diff --git a/raven/src/main/java/com/getsentry/raven/time/Clock.java b/raven/src/main/java/com/getsentry/raven/time/Clock.java new file mode 100644 index 00000000000..e11486b491f --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/time/Clock.java @@ -0,0 +1,24 @@ +package com.getsentry.raven.time; + +import java.util.Date; + +/** + * Clock interface, used to inject a Clock dependency so time can be adjusted in tests. + */ +interface Clock { + + /** + * Returns the Clock's time in milliseconds. + * + * @return the Clock's time in milliseconds. + */ + long millis(); + + /** + * Returns the Clock's time as a Date object. + * + * @return the Clock's time as a Date object. + */ + Date date(); + +} diff --git a/raven/src/main/java/com/getsentry/raven/time/FixedClock.java b/raven/src/main/java/com/getsentry/raven/time/FixedClock.java new file mode 100644 index 00000000000..54d846db343 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/time/FixedClock.java @@ -0,0 +1,45 @@ +package com.getsentry.raven.time; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +/** + * Clock implementation where time doesn't move unless it is manually adjusted. + * Useful for mocking time in tests. + */ +public class FixedClock implements Clock { + private Date date; + + /** + * Construct a FixedClock with the given date. + * + * @param date Date to set the FixedClock to. + */ + public FixedClock(Date date) { + this.date = date; + } + + @Override + public long millis() { + return date.getTime(); + } + + @Override + public Date date() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + /** + * Adjust the FixedClock by the given amount. + * + * @param duration Duration of time to adjust the clock by. + * @param unit Unit of time to adjust the clock by. + */ + public void tick(long duration, TimeUnit unit) { + this.date = new Date(date.getTime() + unit.toMillis(duration)); + } +} diff --git a/raven/src/main/java/com/getsentry/raven/time/SystemClock.java b/raven/src/main/java/com/getsentry/raven/time/SystemClock.java new file mode 100644 index 00000000000..9ac2e61e611 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/time/SystemClock.java @@ -0,0 +1,19 @@ +package com.getsentry.raven.time; + +import java.util.Date; + +/** + * Clock implementation that defers to the system clock. Should be used + * outside of tests. + */ +public class SystemClock implements Clock { + @Override + public long millis() { + return System.currentTimeMillis(); + } + + @Override + public Date date() { + return new Date(); + } +} diff --git a/raven/src/test/java/com/getsentry/raven/time/ClockTest.java b/raven/src/test/java/com/getsentry/raven/time/ClockTest.java new file mode 100644 index 00000000000..bf3b6775e03 --- /dev/null +++ b/raven/src/test/java/com/getsentry/raven/time/ClockTest.java @@ -0,0 +1,27 @@ +package com.getsentry.raven.time; + +import org.testng.annotations.Test; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +public class ClockTest { + + @Test + public void testFixedClock() { + FixedClock fixedClock = new FixedClock(new Date(0)); + assertThat(new Date(0), equalTo(fixedClock.date())); + + fixedClock.setDate(new Date(1)); + assertThat(new Date(1), equalTo(fixedClock.date())); + + fixedClock.tick(1, TimeUnit.MILLISECONDS); + assertThat(new Date(2), equalTo(fixedClock.date())); + + assertThat(new Date(2).getTime(), equalTo(fixedClock.millis())); + } + +} From e0064cd12b486d3f084269b9f284b52bdf51f599 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 27 Feb 2017 16:54:32 -0600 Subject: [PATCH 1511/2152] Remove sentry-stub, use WireMock for integration tests. (#316) --- docs/modules/log4j.rst | 4 +- docs/modules/logback.rst | 4 +- pom.xml | 79 +----- raven-android/pom.xml | 25 ++ .../getsentry/raven/android/AndroidTest.java | 13 +- .../com/getsentry/raven/android/RavenIT.java | 8 +- raven-log4j/pom.xml | 15 ++ .../raven/log4j/SentryAppenderIT.java | 54 ++-- .../resources/log4j-integration.properties | 14 +- raven-log4j2/pom.xml | 15 ++ .../raven/log4j2/SentryAppenderIT.java | 54 ++-- .../src/test/resources/log4j2-integration.xml | 19 +- raven-logback/pom.xml | 15 ++ .../raven/logback/SentryAppenderIT.java | 55 ++-- .../test/resources/logback-integration.xml | 26 +- raven/pom.xml | 19 +- .../test/java/com/getsentry/raven/BaseIT.java | 75 ++++++ .../getsentry/raven/jul/SentryHandlerIT.java | 55 ++-- .../com/getsentry/raven/stub/SentryStub.java | 90 ------- .../getsentry/raven/unmarshaller}/Base64.java | 240 +++++++++--------- .../unmarshaller}/Base64InputStream.java | 2 +- .../raven}/unmarshaller/JsonDecoder.java | 8 +- .../raven}/unmarshaller/JsonUnmarshaller.java | 14 +- .../raven/unmarshaller/Unmarshaller.java | 9 + .../unmarshaller/event/UnmarshalledEvent.java | 12 +- .../event/interfaces/ExceptionInterface.java | 4 +- .../event/interfaces/MessageInterface.java | 4 +- .../event/interfaces/StackTraceInterface.java | 4 +- .../resources/logging-integration.properties | 15 +- sentry-stub/pom.xml | 41 --- .../SentryAuthenticationFilter.java | 73 ------ .../raven/sentrystub/SentryHttpServlet.java | 21 -- .../raven/sentrystub/SentryStub.java | 55 ---- .../raven/sentrystub/SentryStubServlet.java | 52 ---- .../raven/sentrystub/auth/AuthValidator.java | 174 ------------- .../sentrystub/auth/InvalidAuthException.java | 36 --- .../sentrystub/unmarshaller/Unmarshaller.java | 9 - .../raven/sentrystub/sentry.properties | 5 - sentry-stub/src/main/webapp/WEB-INF/web.xml | 5 - src/checkstyle/suppressions.xml | 1 - 40 files changed, 501 insertions(+), 922 deletions(-) create mode 100644 raven/src/test/java/com/getsentry/raven/BaseIT.java delete mode 100644 raven/src/test/java/com/getsentry/raven/stub/SentryStub.java rename {sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util => raven/src/test/java/com/getsentry/raven/unmarshaller}/Base64.java (83%) rename {sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util => raven/src/test/java/com/getsentry/raven/unmarshaller}/Base64InputStream.java (98%) rename {sentry-stub/src/main/java/com/getsentry/raven/sentrystub => raven/src/test/java/com/getsentry/raven}/unmarshaller/JsonDecoder.java (94%) rename {sentry-stub/src/main/java/com/getsentry/raven/sentrystub => raven/src/test/java/com/getsentry/raven}/unmarshaller/JsonUnmarshaller.java (71%) create mode 100644 raven/src/test/java/com/getsentry/raven/unmarshaller/Unmarshaller.java rename sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/Event.java => raven/src/test/java/com/getsentry/raven/unmarshaller/event/UnmarshalledEvent.java (87%) rename {sentry-stub/src/main/java/com/getsentry/raven/sentrystub => raven/src/test/java/com/getsentry/raven/unmarshaller}/event/interfaces/ExceptionInterface.java (85%) rename {sentry-stub/src/main/java/com/getsentry/raven/sentrystub => raven/src/test/java/com/getsentry/raven/unmarshaller}/event/interfaces/MessageInterface.java (84%) rename {sentry-stub/src/main/java/com/getsentry/raven/sentrystub => raven/src/test/java/com/getsentry/raven/unmarshaller}/event/interfaces/StackTraceInterface.java (94%) delete mode 100644 sentry-stub/pom.xml delete mode 100644 sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryAuthenticationFilter.java delete mode 100644 sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryHttpServlet.java delete mode 100644 sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryStub.java delete mode 100644 sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryStubServlet.java delete mode 100644 sentry-stub/src/main/java/com/getsentry/raven/sentrystub/auth/AuthValidator.java delete mode 100644 sentry-stub/src/main/java/com/getsentry/raven/sentrystub/auth/InvalidAuthException.java delete mode 100644 sentry-stub/src/main/java/com/getsentry/raven/sentrystub/unmarshaller/Unmarshaller.java delete mode 100644 sentry-stub/src/main/resources/com/getsentry/raven/sentrystub/sentry.properties delete mode 100644 sentry-stub/src/main/webapp/WEB-INF/web.xml diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index a29a306ca37..a74c6e7900f 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -82,7 +82,7 @@ Alternatively, using the ``log4j.xml`` format: - + @@ -195,7 +195,7 @@ Alternatively, using the ``log4j.xml`` format: - + diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 08cfd5ce0bb..916e25830a1 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -68,7 +68,7 @@ Example configuration using the ``logback.xml`` format: of a non-Raven logger that is set to a different logging threshold --> - + @@ -159,7 +159,7 @@ Example configuration in the ``logback.xml`` file: of a non-Raven logger that is set to a different logging threshold --> - + diff --git a/pom.xml b/pom.xml index db4fa1a24b8..5fde186869d 100644 --- a/pom.xml +++ b/pom.xml @@ -79,7 +79,6 @@ raven-log4j raven-logback raven-log4j2 - sentry-stub @@ -123,6 +122,8 @@ 1.14 6.9.10 1.3 + 4.12 + 2.5.1 @@ -184,6 +185,16 @@ hamcrest-library ${hamcrest.version} + + junit + junit + ${junit.version} + + + com.github.tomakehurst + wiremock-standalone + ${wiremock.version} + @@ -257,39 +268,6 @@ - - org.eclipse.jetty - jetty-maven-plugin - 9.2.21.v20170120 - - 10 - foo - 10 - 9999 - ${project.build.directory}/webapps/sentry-stub.war - - - - start-sentry-stub - pre-integration-test - - deploy-war - - - 0 - true - ${skipTests} - - - - stop-sentry-stub - post-integration-test - - stop - - - - org.apache.maven.plugins maven-surefire-plugin @@ -326,35 +304,7 @@ true - - copy-war-for-integration-tests - package - - copy - - - - - ${project.groupId} - sentry-stub - ${project.version} - war - - - true - true - ${project.build.directory}/webapps - - - - - ${project.groupId} - sentry-stub - ${project.version} - war - - org.apache.maven.plugins @@ -432,7 +382,7 @@ maven-javadoc-plugin 2.10.4 - com.getsentry.raven.util.base64*:com.getsentry.raven.sentrystub.util.base64 + com.getsentry.raven.util.base64* @@ -520,8 +470,7 @@ pitest - 1.8 - 4.11 + ${junit.version} diff --git a/raven-android/pom.xml b/raven-android/pom.xml index 07b64f707c6..8d5238bfab4 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -52,6 +52,26 @@ ${maven-ant.version} test + + org.hamcrest + hamcrest-core + test + + + org.hamcrest + hamcrest-library + test + + + junit + junit + test + + + com.github.tomakehurst + wiremock-standalone + test + @@ -66,6 +86,11 @@ jackson-databind test + + com.fasterxml.jackson.core + jackson-annotations + test + diff --git a/raven-android/src/test/java/com/getsentry/raven/android/AndroidTest.java b/raven-android/src/test/java/com/getsentry/raven/android/AndroidTest.java index 4393cedc183..234a21a280a 100644 --- a/raven-android/src/test/java/com/getsentry/raven/android/AndroidTest.java +++ b/raven-android/src/test/java/com/getsentry/raven/android/AndroidTest.java @@ -1,24 +1,21 @@ package com.getsentry.raven.android; -import com.getsentry.raven.BaseTest; -import com.getsentry.raven.stub.SentryStub; +import com.getsentry.raven.BaseIT; import org.junit.After; import org.junit.Before; -public class AndroidTest extends BaseTest { +import static com.github.tomakehurst.wiremock.client.WireMock.*; - protected SentryStub sentryStub; +public class AndroidTest extends BaseIT { @Before - public void setUp() throws Exception { - sentryStub = new SentryStub(); - sentryStub.removeEvents(); + public void setup() { + stub200ForProject1Store(); } @After public void tearDown() throws Exception { Raven.clearStoredRaven(); - sentryStub.removeEvents(); } } \ No newline at end of file diff --git a/raven-android/src/test/java/com/getsentry/raven/android/RavenIT.java b/raven-android/src/test/java/com/getsentry/raven/android/RavenIT.java index 332c838a830..e991b3e41c0 100644 --- a/raven-android/src/test/java/com/getsentry/raven/android/RavenIT.java +++ b/raven-android/src/test/java/com/getsentry/raven/android/RavenIT.java @@ -13,7 +13,8 @@ public class RavenIT extends AndroidTest { @Test public void test() throws Exception { - Assert.assertEquals(sentryStub.getEventCount(), 0); + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); RavenITActivity activity = Robolectric.setupActivity(RavenITActivity.class); Assert.assertEquals(activity.getCustomFactoryUsed(), true); @@ -22,11 +23,12 @@ public void test() throws Exception { waitUntilTrue(1000, new Callable() { @Override public Boolean call() throws Exception { - return sentryStub.getEventCount() == 1; + return getStoredEvents().size() == 1; } }); - Assert.assertEquals(sentryStub.getEventCount(), 1); + verifyProject1PostRequestCount(1); + verifyStoredEventCount(1); } } diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 2436d8ab02c..f0d96f28d09 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -50,6 +50,16 @@ hamcrest-library test + + junit + junit + test + + + com.github.tomakehurst + wiremock-standalone + test + @@ -64,6 +74,11 @@ jackson-databind test + + com.fasterxml.jackson.core + jackson-annotations + test + org.slf4j slf4j-log4j12 diff --git a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderIT.java b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderIT.java index aec04c04ca0..2b07b5cfe5a 100644 --- a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderIT.java +++ b/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderIT.java @@ -1,52 +1,56 @@ package com.getsentry.raven.log4j; -import com.getsentry.raven.stub.SentryStub; +import com.getsentry.raven.BaseIT; import org.apache.log4j.Logger; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; +import static com.github.tomakehurst.wiremock.client.WireMock.*; -public class SentryAppenderIT { +public class SentryAppenderIT extends BaseIT { /* We filter out loggers that start with `com.getsentry.raven`, so we deliberately use a custom logger name here. */ - private static final Logger logger = Logger.getLogger("SentryAppenderIT: log4j"); + private static final Logger logger = Logger.getLogger("log4j.SentryAppenderIT"); private static final Logger ravenLogger = Logger.getLogger(SentryAppenderIT.class); - private SentryStub sentryStub; - @BeforeMethod - public void setUp() throws Exception { - sentryStub = new SentryStub(); - } - - @AfterMethod - public void tearDown() throws Exception { - sentryStub.removeEvents(); + @Before + public void setup() { + stub200ForProject1Store(); } @Test - public void testInfoLog() throws Exception { - assertThat(sentryStub.getEventCount(), is(0)); - logger.info("This is a test"); - assertThat(sentryStub.getEventCount(), is(1)); + public void testErrorLog() throws Exception { + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); + + logger.error("This is a test"); + + verifyProject1PostRequestCount(1); + verifyStoredEventCount(1); } @Test public void testChainedExceptions() throws Exception { - assertThat(sentryStub.getEventCount(), is(0)); + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); + logger.error("This is an exception", new UnsupportedOperationException("Test", new UnsupportedOperationException())); - assertThat(sentryStub.getEventCount(), is(1)); + + verifyProject1PostRequestCount(1); + verifyStoredEventCount(1); } @Test public void testNoRavenLogging() throws Exception { - assertThat(sentryStub.getEventCount(), is(0)); + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); + ravenLogger.error("This is a test"); - assertThat(sentryStub.getEventCount(), is(0)); + + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); } } diff --git a/raven-log4j/src/test/resources/log4j-integration.properties b/raven-log4j/src/test/resources/log4j-integration.properties index 4e5dab23335..d7e87d685c4 100644 --- a/raven-log4j/src/test/resources/log4j-integration.properties +++ b/raven-log4j/src/test/resources/log4j-integration.properties @@ -1,9 +1,11 @@ -log4j.rootLogger=ALL, ConsoleAppender, SentryAppender -log4j.com.getsentry.raven.stub=ALL, ConsoleAppender - -log4j.appender.SentryAppender=com.getsentry.raven.log4j.SentryAppender log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender - -log4j.appender.SentryAppender.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false log4j.appender.ConsoleAppender.layout=org.apache.log4j.EnhancedPatternLayout log4j.appender.ConsoleAppender.layout.ConversionPattern=[RAVEN] [%-5p] %c - %m%n%throwable{none} + +log4j.appender.SentryAppender=com.getsentry.raven.log4j.SentryAppender +log4j.appender.SentryAppender.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false +# Set Raven to WARNING level, as we recommend this as the lowest users go in their own configuration +log4j.appender.SentryAppender.threshold=WARN + +log4j.rootLogger=INFO, ConsoleAppender +log4j.logger.log4j.SentryAppenderIT=INFO, SentryAppender diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 1f4344d6a01..85193eca6fe 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -55,6 +55,16 @@ hamcrest-library test + + junit + junit + test + + + com.github.tomakehurst + wiremock-standalone + test + @@ -69,6 +79,11 @@ jackson-databind test + + com.fasterxml.jackson.core + jackson-annotations + test + org.apache.logging.log4j log4j-slf4j-impl diff --git a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderIT.java b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderIT.java index bb77ab7db29..290dc80e6ca 100644 --- a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderIT.java +++ b/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderIT.java @@ -1,53 +1,57 @@ package com.getsentry.raven.log4j2; -import com.getsentry.raven.stub.SentryStub; +import com.getsentry.raven.BaseIT; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; +import static com.github.tomakehurst.wiremock.client.WireMock.*; -public class SentryAppenderIT { +public class SentryAppenderIT extends BaseIT { /* We filter out loggers that start with `com.getsentry.raven`, so we deliberately use a custom logger name here. */ - private static final Logger logger = LogManager.getLogger("SentryAppenderIT: log4j2"); + private static final Logger logger = LogManager.getLogger("log4j2.SentryAppenderIT"); private static final Logger ravenLogger = LogManager.getLogger(SentryAppenderIT.class); - private SentryStub sentryStub; - @BeforeMethod - public void setUp() throws Exception { - sentryStub = new SentryStub(); - } - - @AfterMethod - public void tearDown() throws Exception { - sentryStub.removeEvents(); + @Before + public void setup() { + stub200ForProject1Store(); } @Test - public void testInfoLog() throws Exception { - assertThat(sentryStub.getEventCount(), is(0)); - logger.info("This is a test"); - assertThat(sentryStub.getEventCount(), is(1)); + public void testErrorLog() throws Exception { + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); + + logger.error("This is a test"); + + verifyProject1PostRequestCount(1); + verifyStoredEventCount(1); } @Test public void testChainedExceptions() throws Exception { - assertThat(sentryStub.getEventCount(), is(0)); + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); + logger.error("This is an exception", new UnsupportedOperationException("Test", new UnsupportedOperationException())); - assertThat(sentryStub.getEventCount(), is(1)); + + verifyProject1PostRequestCount(1); + verifyStoredEventCount(1); } @Test public void testNoRavenLogging() throws Exception { - assertThat(sentryStub.getEventCount(), is(0)); + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); + ravenLogger.error("This is a test"); - assertThat(sentryStub.getEventCount(), is(0)); + + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); } } diff --git a/raven-log4j2/src/test/resources/log4j2-integration.xml b/raven-log4j2/src/test/resources/log4j2-integration.xml index 31c3b28990f..79e8dda443f 100644 --- a/raven-log4j2/src/test/resources/log4j2-integration.xml +++ b/raven-log4j2/src/test/resources/log4j2-integration.xml @@ -1,24 +1,21 @@ - + - - http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false - - - User,OS - - com.getsentry.raven.DefaultRavenFactory + http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false - - - + + + + + + diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 52a8f3b8d67..ea1bf0c863c 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -59,6 +59,16 @@ hamcrest-library test + + junit + junit + test + + + com.github.tomakehurst + wiremock-standalone + test + @@ -73,6 +83,11 @@ jackson-databind test + + com.fasterxml.jackson.core + jackson-annotations + test + diff --git a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderIT.java b/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderIT.java index 49819f91ad5..4500b25326e 100644 --- a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderIT.java +++ b/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderIT.java @@ -1,54 +1,57 @@ package com.getsentry.raven.logback; -import com.getsentry.raven.stub.SentryStub; +import com.getsentry.raven.BaseIT; +import org.junit.Before; +import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; +import static com.github.tomakehurst.wiremock.client.WireMock.*; -public class SentryAppenderIT { +public class SentryAppenderIT extends BaseIT { /* We filter out loggers that start with `com.getsentry.raven`, so we deliberately use a custom logger name here. */ - private static final Logger logger = LoggerFactory.getLogger("SentryAppenderIT: logback"); + private static final Logger logger = LoggerFactory.getLogger("logback.SentryAppenderIT"); private static final Logger ravenLogger = LoggerFactory.getLogger(SentryAppenderIT.class); - private SentryStub sentryStub; - @BeforeMethod - public void setUp() throws Exception { - sentryStub = new SentryStub(); - sentryStub.removeEvents(); - } - - @AfterMethod - public void tearDown() throws Exception { - sentryStub.removeEvents(); + @Before + public void setup() { + stub200ForProject1Store(); } @Test - public void testWarnLog() throws Exception { - assertThat(sentryStub.getEventCount(), is(0)); - logger.warn("This is a test"); - assertThat(sentryStub.getEventCount(), is(1)); + public void testErrorLog() throws Exception { + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); + + logger.error("This is a test"); + + verifyProject1PostRequestCount(1); + verifyStoredEventCount(1); } @Test public void testChainedExceptions() throws Exception { - assertThat(sentryStub.getEventCount(), is(0)); + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); + logger.error("This is an exception", new UnsupportedOperationException("Test", new UnsupportedOperationException())); - assertThat(sentryStub.getEventCount(), is(1)); + + verifyProject1PostRequestCount(1); + verifyStoredEventCount(1); } @Test public void testNoRavenLogging() throws Exception { - assertThat(sentryStub.getEventCount(), is(0)); + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); + ravenLogger.error("This is a test"); - assertThat(sentryStub.getEventCount(), is(0)); + + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); } } diff --git a/raven-logback/src/test/resources/logback-integration.xml b/raven-logback/src/test/resources/logback-integration.xml index 57e9467875e..cc44189820f 100644 --- a/raven-logback/src/test/resources/logback-integration.xml +++ b/raven-logback/src/test/resources/logback-integration.xml @@ -1,17 +1,29 @@ - + [RAVEN] [%-5level] %logger{36} - %msg%n%nopex + - - http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false - + http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false + + + + WARN + - - - + + + + + + + + + + + diff --git a/raven/pom.xml b/raven/pom.xml index 87ce92582cb..7eb46ec57c5 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -52,6 +52,11 @@ jackson-databind test + + com.fasterxml.jackson.core + jackson-annotations + test + org.jmockit jmockit @@ -72,6 +77,16 @@ testng test + + junit + junit + test + + + com.github.tomakehurst + wiremock-standalone + test + @@ -147,10 +162,6 @@ org.apache.maven.plugins maven-dependency-plugin - - org.eclipse.jetty - jetty-maven-plugin - org.apache.maven.plugins maven-checkstyle-plugin diff --git a/raven/src/test/java/com/getsentry/raven/BaseIT.java b/raven/src/test/java/com/getsentry/raven/BaseIT.java new file mode 100644 index 00000000000..95992bc4099 --- /dev/null +++ b/raven/src/test/java/com/getsentry/raven/BaseIT.java @@ -0,0 +1,75 @@ +package com.getsentry.raven; + +import com.getsentry.raven.connection.HttpConnection; +import com.getsentry.raven.unmarshaller.JsonUnmarshaller; +import com.getsentry.raven.unmarshaller.event.UnmarshalledEvent; +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.matching.RegexPattern; +import com.github.tomakehurst.wiremock.matching.StringValuePattern; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import org.junit.Rule; + +import java.io.ByteArrayInputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class BaseIT extends BaseTest { + + public static final String PROJECT1_ID = "1"; + public static final String PROJECT1_STORE_URL = "/api/" + PROJECT1_ID + "/store/"; + public static final String AUTH_HEADER = "X-Sentry-Auth"; + public static final StringValuePattern AUTH_HEADER_PATTERN = new RegexPattern("Sentry sentry_version=6,sentry_client=raven-java/[\\w\\-\\.]+,sentry_key=8292bf61d620417282e68a72ae03154a,sentry_secret=e3908e05ad874b24b7a168992bfa3577"); + + @Rule + public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().port(8080)); + + public void stub200ForProject1Store() { + wireMockRule.stubFor( + post(urlEqualTo(PROJECT1_STORE_URL)) + .withHeader(AUTH_HEADER, AUTH_HEADER_PATTERN) + .willReturn(aResponse().withStatus(200))); + } + + public String getDsn(String projectId) { + return "http://public:private@localhost:" + wireMockRule.port() + "/" + projectId; + } + + public URI getSentryServerUri() throws URISyntaxException { + return new URI("http://localhost:" + wireMockRule.port() + "/"); + } + + public URL getProject1SentryStoreUrl() throws URISyntaxException { + return HttpConnection.getSentryApiUrl(getSentryServerUri(), PROJECT1_ID); + } + + public void verifyProject1PostRequestCount(int count) { + verify(exactly(count), postRequestedFor(urlEqualTo(PROJECT1_STORE_URL))); + } + + public void verifyStoredEventCount(int count) { + assertThat(getStoredEvents().size(), is(count)); + } + + public List getStoredEvents() { + JsonUnmarshaller unmarshaller = new JsonUnmarshaller(); + List events = new ArrayList<>(); + + for (ServeEvent serveEvent : wireMockRule.getAllServeEvents()) { + UnmarshalledEvent event = unmarshaller.unmarshal(new ByteArrayInputStream(serveEvent.getRequest().getBody())); + if (event != null) { + events.add(event); + } + } + + return events; + } + +} diff --git a/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerIT.java b/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerIT.java index c5118522fb5..96e9ef5a11f 100644 --- a/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerIT.java +++ b/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerIT.java @@ -1,54 +1,59 @@ package com.getsentry.raven.jul; -import com.getsentry.raven.stub.SentryStub; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import com.getsentry.raven.BaseIT; +import org.junit.Before; +import org.junit.Test; import java.util.logging.Level; import java.util.logging.Logger; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; +import static com.github.tomakehurst.wiremock.client.WireMock.*; -public class SentryHandlerIT { +public class SentryHandlerIT extends BaseIT { /* We filter out loggers that start with `com.getsentry.raven`, so we deliberately use a custom logger name here. */ - private static final Logger logger = Logger.getLogger("SentryHandlerIT: jul"); + private static final Logger logger = Logger.getLogger("jul.SentryHandlerIT"); private static final Logger ravenLogger = Logger.getLogger(SentryHandlerIT.class.getName()); - private SentryStub sentryStub; - @BeforeMethod - public void setUp() throws Exception { - sentryStub = new SentryStub(); - } - - @AfterMethod - public void tearDown() throws Exception { - sentryStub.removeEvents(); + @Before + public void setup() { + stub200ForProject1Store(); } @Test - public void testInfoLog() throws Exception { - assertThat(sentryStub.getEventCount(), is(0)); - logger.info("This is a test"); - assertThat(sentryStub.getEventCount(), is(1)); + public void testErrorLog() throws Exception { + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); + + logger.log(Level.SEVERE, "This is a test"); + + verifyProject1PostRequestCount(1); + verifyStoredEventCount(1); } @Test public void testChainedExceptions() throws Exception { - assertThat(sentryStub.getEventCount(), is(0)); + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); + logger.log(Level.SEVERE, "This is an exception", new UnsupportedOperationException("Test", new UnsupportedOperationException())); - assertThat(sentryStub.getEventCount(), is(1)); + + verifyProject1PostRequestCount(1); + verifyStoredEventCount(1); } @Test public void testNoRavenLogging() throws Exception { - assertThat(sentryStub.getEventCount(), is(0)); + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); + ravenLogger.log(Level.SEVERE, "This is a test"); - assertThat(sentryStub.getEventCount(), is(0)); + + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); } + } diff --git a/raven/src/test/java/com/getsentry/raven/stub/SentryStub.java b/raven/src/test/java/com/getsentry/raven/stub/SentryStub.java deleted file mode 100644 index 4c42de504c1..00000000000 --- a/raven/src/test/java/com/getsentry/raven/stub/SentryStub.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.getsentry.raven.stub; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.getsentry.raven.environment.RavenEnvironment; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Map; - -public class SentryStub { - private static final Logger logger = LoggerFactory.getLogger(SentryStub.class); - private static final URL DEFAULT_URL; - private final URL url; - private final ObjectMapper mapper = new ObjectMapper(); - - static { - URL url = null; - try { - url = new URL("http://localhost:8080/stub/"); - } catch (MalformedURLException e) { - logger.debug("Couldn't create the URL http://localhost:8080/stub", e); - } - - DEFAULT_URL = url; - } - - public SentryStub() { - this(DEFAULT_URL); - } - - public SentryStub(URL url) { - this.url = url; - } - - public int getEventCount() { - RavenEnvironment.startManagingThread(); - try { - HttpURLConnection connection = connectTo("count"); - connection.setRequestMethod("GET"); - return (Integer) getContent(connection).get("count"); - } catch (Exception e) { - logger.error("Couldn't get the number of events created.", e); - return -1; - } finally { - RavenEnvironment.stopManagingThread(); - } - } - - public void removeEvents() { - RavenEnvironment.startManagingThread(); - try { - HttpURLConnection connection = connectTo("cleanup"); - connection.setRequestMethod("DELETE"); - connection.setDoOutput(false); - connection.connect(); - connection.getInputStream().close(); - } catch (Exception e) { - logger.error("Couldn't remove stub events.", e); - } finally { - RavenEnvironment.stopManagingThread(); - } - } - - @SuppressWarnings("unchecked") - private Map getContent(HttpURLConnection connection) { - try { - connection.setDoInput(true); - connection.connect(); - return (Map) mapper.readValue(connection.getInputStream(), Map.class); - } catch (Exception e) { - throw new IllegalStateException( - "Couldn't get the JSON content for the connection '" + connection + "'", e); - } finally { - connection.disconnect(); - } - } - - private HttpURLConnection connectTo(String path) { - try { - URL test = new URL(url, path); - return (HttpURLConnection) test.openConnection(); - } catch (Exception e) { - throw new IllegalStateException( - "Couldn't open a connection to the path '" + path + "' in '" + url + "'", e); - } - } -} diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64.java b/raven/src/test/java/com/getsentry/raven/unmarshaller/Base64.java similarity index 83% rename from sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64.java rename to raven/src/test/java/com/getsentry/raven/unmarshaller/Base64.java index 03b5c612301..190d1c67f0c 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64.java +++ b/raven/src/test/java/com/getsentry/raven/unmarshaller/Base64.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.getsentry.raven.sentrystub.util; +package com.getsentry.raven.unmarshaller; import java.io.UnsupportedEncodingException; @@ -287,73 +287,73 @@ public boolean process(byte[] input, int offset, int len, boolean finish) { // machine implementation. int d = alphabet[input[p++] & 0xff]; switch (state) { - case 0: - if (d >= 0) { - value = d; - ++state; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - case 1: - if (d >= 0) { - value = (value << 6) | d; - ++state; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - case 2: - if (d >= 0) { - value = (value << 6) | d; - ++state; - } else if (d == EQUALS) { - // Emit the last (partial) output tuple; - // expect exactly one more padding character. - output[op++] = (byte) (value >> 4); - state = 4; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - case 3: - if (d >= 0) { - // Emit the output triple and return to state 0. - value = (value << 6) | d; - output[op+2] = (byte) value; - output[op+1] = (byte) (value >> 8); - output[op] = (byte) (value >> 16); - op += 3; - state = 0; - } else if (d == EQUALS) { - // Emit the last (partial) output tuple; - // expect no further data or padding characters. - output[op+1] = (byte) (value >> 2); - output[op] = (byte) (value >> 10); - op += 2; - state = 5; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - case 4: - if (d == EQUALS) { - ++state; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - case 5: - if (d != SKIP) { - this.state = 6; - return false; - } - break; + case 0: + if (d >= 0) { + value = d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 1: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 2: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect exactly one more padding character. + output[op++] = (byte) (value >> 4); + state = 4; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 3: + if (d >= 0) { + // Emit the output triple and return to state 0. + value = (value << 6) | d; + output[op+2] = (byte) value; + output[op+1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + state = 0; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect no further data or padding characters. + output[op+1] = (byte) (value >> 2); + output[op] = (byte) (value >> 10); + op += 2; + state = 5; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 4: + if (d == EQUALS) { + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 5: + if (d != SKIP) { + this.state = 6; + return false; + } + break; } } if (!finish) { @@ -367,33 +367,33 @@ public boolean process(byte[] input, int offset, int len, boolean finish) { // Done reading input. Now figure out where we are left in // the state machine and finish up. switch (state) { - case 0: - // Output length is a multiple of three. Fine. - break; - case 1: - // Read one extra input byte, which isn't enough to - // make another output byte. Illegal. - this.state = 6; - return false; - case 2: - // Read two extra input bytes, enough to emit 1 more - // output byte. Fine. - output[op++] = (byte) (value >> 4); - break; - case 3: - // Read three extra input bytes, enough to emit 2 more - // output bytes. Fine. - output[op++] = (byte) (value >> 10); - output[op++] = (byte) (value >> 2); - break; - case 4: - // Read one padding '=' when we expected 2. Illegal. - this.state = 6; - return false; - case 5: - // Read all the padding '='s we expected and no more. - // Fine. - break; + case 0: + // Output length is a multiple of three. Fine. + break; + case 1: + // Read one extra input byte, which isn't enough to + // make another output byte. Illegal. + this.state = 6; + return false; + case 2: + // Read two extra input bytes, enough to emit 1 more + // output byte. Fine. + output[op++] = (byte) (value >> 4); + break; + case 3: + // Read three extra input bytes, enough to emit 2 more + // output bytes. Fine. + output[op++] = (byte) (value >> 10); + output[op++] = (byte) (value >> 2); + break; + case 4: + // Read one padding '=' when we expected 2. Illegal. + this.state = 6; + return false; + case 5: + // Read all the padding '='s we expected and no more. + // Fine. + break; } this.state = state; this.op = op; @@ -483,9 +483,9 @@ public static byte[] encode(byte[] input, int offset, int len, int flags) { } } else { switch (len % 3) { - case 0: break; - case 1: output_len += 2; break; - case 2: output_len += 3; break; + case 0: break; + case 1: output_len += 2; break; + case 2: output_len += 3; break; } } // Account for the newlines, if any. @@ -562,28 +562,28 @@ public boolean process(byte[] input, int offset, int len, boolean finish) { // with any input bytes available now and see if we can empty // the tail. switch (tailLen) { - case 0: - // There was no tail. - break; - case 1: - if (p+2 <= len) { - // A 1-byte tail with at least 2 bytes of - // input available now. - v = ((tail[0] & 0xff) << 16) | - ((input[p++] & 0xff) << 8) | - (input[p++] & 0xff); - tailLen = 0; - }; - break; - case 2: - if (p+1 <= len) { - // A 2-byte tail with at least 1 byte of input. - v = ((tail[0] & 0xff) << 16) | - ((tail[1] & 0xff) << 8) | - (input[p++] & 0xff); - tailLen = 0; - } - break; + case 0: + // There was no tail. + break; + case 1: + if (p+2 <= len) { + // A 1-byte tail with at least 2 bytes of + // input available now. + v = ((tail[0] & 0xff) << 16) | + ((input[p++] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + }; + break; + case 2: + if (p+1 <= len) { + // A 2-byte tail with at least 1 byte of input. + v = ((tail[0] & 0xff) << 16) | + ((tail[1] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + } + break; } if (v != -1) { output[op++] = alphabet[(v >> 18) & 0x3f]; diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64InputStream.java b/raven/src/test/java/com/getsentry/raven/unmarshaller/Base64InputStream.java similarity index 98% rename from sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64InputStream.java rename to raven/src/test/java/com/getsentry/raven/unmarshaller/Base64InputStream.java index 65f8b1fe730..d56f8af9b82 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/util/Base64InputStream.java +++ b/raven/src/test/java/com/getsentry/raven/unmarshaller/Base64InputStream.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.getsentry.raven.sentrystub.util; +package com.getsentry.raven.unmarshaller; import java.io.FilterInputStream; import java.io.IOException; diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/unmarshaller/JsonDecoder.java b/raven/src/test/java/com/getsentry/raven/unmarshaller/JsonDecoder.java similarity index 94% rename from sentry-stub/src/main/java/com/getsentry/raven/sentrystub/unmarshaller/JsonDecoder.java rename to raven/src/test/java/com/getsentry/raven/unmarshaller/JsonDecoder.java index 6651d051632..fb56d744034 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/unmarshaller/JsonDecoder.java +++ b/raven/src/test/java/com/getsentry/raven/unmarshaller/JsonDecoder.java @@ -1,9 +1,7 @@ -package com.getsentry.raven.sentrystub.unmarshaller; +package com.getsentry.raven.unmarshaller; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; -import com.getsentry.raven.sentrystub.util.Base64; -import com.getsentry.raven.sentrystub.util.Base64InputStream; import java.io.BufferedInputStream; import java.io.IOException; @@ -49,7 +47,7 @@ public InputStream decapsulateContent(InputStream originalStream) throws IOExcep originalStream.reset(); if (!isJson(new InflaterInputStream(new Base64InputStream(originalStream, Base64.NO_WRAP)))) { throw new IllegalArgumentException("The given Stream is neither JSON, Base64'd JSON " - + "nor Base64'd deflated JSON."); + + "nor Base64'd deflated JSON."); } } } @@ -133,4 +131,4 @@ public boolean markSupported() { return original.markSupported(); } } -} +} \ No newline at end of file diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/unmarshaller/JsonUnmarshaller.java b/raven/src/test/java/com/getsentry/raven/unmarshaller/JsonUnmarshaller.java similarity index 71% rename from sentry-stub/src/main/java/com/getsentry/raven/sentrystub/unmarshaller/JsonUnmarshaller.java rename to raven/src/test/java/com/getsentry/raven/unmarshaller/JsonUnmarshaller.java index e14e676ba14..9f5034eaede 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/unmarshaller/JsonUnmarshaller.java +++ b/raven/src/test/java/com/getsentry/raven/unmarshaller/JsonUnmarshaller.java @@ -1,7 +1,7 @@ -package com.getsentry.raven.sentrystub.unmarshaller; +package com.getsentry.raven.unmarshaller; import com.fasterxml.jackson.databind.ObjectMapper; -import com.getsentry.raven.sentrystub.event.Event; +import com.getsentry.raven.unmarshaller.event.UnmarshalledEvent; import java.io.IOException; import java.io.InputStream; @@ -10,18 +10,20 @@ public class JsonUnmarshaller implements Unmarshaller { private static final Logger logger = Logger.getLogger(JsonUnmarshaller.class.getCanonicalName()); + private JsonDecoder jsonDecoder = new JsonDecoder(); private ObjectMapper om = new ObjectMapper(); @Override - public Event unmarshall(InputStream source) { - Event event = null; + public UnmarshalledEvent unmarshal(InputStream source) { + UnmarshalledEvent event = null; try { InputStream jsonStream = jsonDecoder.decapsulateContent(source); - event = om.readValue(jsonStream, Event.class); + event = om.readValue(jsonStream, UnmarshalledEvent.class); } catch (IOException e) { logger.log(Level.WARNING, "Couldn't parse some JSON content.", e); } return event; } -} + +} \ No newline at end of file diff --git a/raven/src/test/java/com/getsentry/raven/unmarshaller/Unmarshaller.java b/raven/src/test/java/com/getsentry/raven/unmarshaller/Unmarshaller.java new file mode 100644 index 00000000000..304f638317b --- /dev/null +++ b/raven/src/test/java/com/getsentry/raven/unmarshaller/Unmarshaller.java @@ -0,0 +1,9 @@ +package com.getsentry.raven.unmarshaller; + +import com.getsentry.raven.unmarshaller.event.UnmarshalledEvent; + +import java.io.InputStream; + +public interface Unmarshaller { + UnmarshalledEvent unmarshal(InputStream source); +} \ No newline at end of file diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/Event.java b/raven/src/test/java/com/getsentry/raven/unmarshaller/event/UnmarshalledEvent.java similarity index 87% rename from sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/Event.java rename to raven/src/test/java/com/getsentry/raven/unmarshaller/event/UnmarshalledEvent.java index 44556df47af..71926d2b68b 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/Event.java +++ b/raven/src/test/java/com/getsentry/raven/unmarshaller/event/UnmarshalledEvent.java @@ -1,15 +1,15 @@ -package com.getsentry.raven.sentrystub.event; +package com.getsentry.raven.unmarshaller.event; import com.fasterxml.jackson.annotation.JsonProperty; -import com.getsentry.raven.sentrystub.event.interfaces.ExceptionInterface; -import com.getsentry.raven.sentrystub.event.interfaces.MessageInterface; -import com.getsentry.raven.sentrystub.event.interfaces.StackTraceInterface; +import com.getsentry.raven.unmarshaller.event.interfaces.ExceptionInterface; +import com.getsentry.raven.unmarshaller.event.interfaces.MessageInterface; +import com.getsentry.raven.unmarshaller.event.interfaces.StackTraceInterface; import java.util.Date; import java.util.List; import java.util.Map; -public class Event { +public class UnmarshalledEvent { @JsonProperty(value = "event_id", required = true) private String eventId; @JsonProperty(value = "checksum") @@ -64,4 +64,4 @@ public void setExceptionInterfacesLong(List exceptionInterfa public void setStackTraceInterfaceLong(StackTraceInterface stackTraceInterface) { this.stackTraceInterface = stackTraceInterface; } -} +} \ No newline at end of file diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/interfaces/ExceptionInterface.java b/raven/src/test/java/com/getsentry/raven/unmarshaller/event/interfaces/ExceptionInterface.java similarity index 85% rename from sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/interfaces/ExceptionInterface.java rename to raven/src/test/java/com/getsentry/raven/unmarshaller/event/interfaces/ExceptionInterface.java index ce89c9f7d01..9bb9a6568c7 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/interfaces/ExceptionInterface.java +++ b/raven/src/test/java/com/getsentry/raven/unmarshaller/event/interfaces/ExceptionInterface.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.sentrystub.event.interfaces; +package com.getsentry.raven.unmarshaller.event.interfaces; import com.fasterxml.jackson.annotation.JsonProperty; @@ -11,4 +11,4 @@ public class ExceptionInterface { private String module; @JsonProperty(value = "stacktrace") private StackTraceInterface stackTraceInterface; -} +} \ No newline at end of file diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/interfaces/MessageInterface.java b/raven/src/test/java/com/getsentry/raven/unmarshaller/event/interfaces/MessageInterface.java similarity index 84% rename from sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/interfaces/MessageInterface.java rename to raven/src/test/java/com/getsentry/raven/unmarshaller/event/interfaces/MessageInterface.java index b2b07a1c01b..9f4a28c5d0f 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/interfaces/MessageInterface.java +++ b/raven/src/test/java/com/getsentry/raven/unmarshaller/event/interfaces/MessageInterface.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.sentrystub.event.interfaces; +package com.getsentry.raven.unmarshaller.event.interfaces; import com.fasterxml.jackson.annotation.JsonProperty; @@ -11,4 +11,4 @@ public class MessageInterface { private List params; @JsonProperty(value = "formatted") private String formatted; -} +} \ No newline at end of file diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/interfaces/StackTraceInterface.java b/raven/src/test/java/com/getsentry/raven/unmarshaller/event/interfaces/StackTraceInterface.java similarity index 94% rename from sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/interfaces/StackTraceInterface.java rename to raven/src/test/java/com/getsentry/raven/unmarshaller/event/interfaces/StackTraceInterface.java index b635df5840c..a710ba8692d 100644 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/event/interfaces/StackTraceInterface.java +++ b/raven/src/test/java/com/getsentry/raven/unmarshaller/event/interfaces/StackTraceInterface.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.sentrystub.event.interfaces; +package com.getsentry.raven.unmarshaller.event.interfaces; import com.fasterxml.jackson.annotation.JsonProperty; @@ -32,4 +32,4 @@ public static class StackFrame { @JsonProperty(value = "vars") private Object vars; } -} +} \ No newline at end of file diff --git a/raven/src/test/resources/logging-integration.properties b/raven/src/test/resources/logging-integration.properties index 68f064f488c..59f2f278827 100644 --- a/raven/src/test/resources/logging-integration.properties +++ b/raven/src/test/resources/logging-integration.properties @@ -1,11 +1,12 @@ -handlers=java.util.logging.ConsoleHandler, com.getsentry.raven.jul.SentryHandler -.level=ALL -# Ignore raven stub logs -com.getsentry.raven.stub.handlers=java.util.logging.ConsoleHandler -com.getsentry.raven.stub.level=ALL - java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter -java.util.logging.ConsoleHandler.level=ALL +java.util.logging.ConsoleHandler.level=INFO java.util.logging.SimpleFormatter.format=[RAVEN] [%4$.4s] %3$s - %5$s%n com.getsentry.raven.jul.SentryHandler.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false +# Set Raven to WARNING level, as we recommend this as the lowest users go in their own configuration +com.getsentry.raven.jul.SentryHandler.level=WARNING + +handlers=java.util.logging.ConsoleHandler +.level=INFO + +jul.SentryHandlerIT.handlers=java.util.logging.ConsoleHandler, com.getsentry.raven.jul.SentryHandler diff --git a/sentry-stub/pom.xml b/sentry-stub/pom.xml deleted file mode 100644 index 4436f0ddfa5..00000000000 --- a/sentry-stub/pom.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - 4.0.0 - - - com.getsentry.raven - raven-all - 7.8.3-SNAPSHOT - - - sentry-stub - war - - Sentry stub - Stub for tests against a Sentry server. - - - 3.0.1 - - - - - com.fasterxml.jackson.core - jackson-core - - - com.fasterxml.jackson.core - jackson-annotations - - - com.fasterxml.jackson.core - jackson-databind - - - javax.servlet - javax.servlet-api - ${javax.servlet-api.version} - provided - - - diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryAuthenticationFilter.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryAuthenticationFilter.java deleted file mode 100644 index b648da67a29..00000000000 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryAuthenticationFilter.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.getsentry.raven.sentrystub; - -import com.getsentry.raven.sentrystub.auth.InvalidAuthException; - -import javax.servlet.*; -import javax.servlet.annotation.WebFilter; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -@WebFilter(servletNames = "SentryHttpServlet") -public class SentryAuthenticationFilter implements Filter { - private static final String SENTRY_AUTH = "X-Sentry-Auth"; - private final SentryStub sentryStub = SentryStub.getInstance(); - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - HttpServletRequest req = (HttpServletRequest) request; - HttpServletResponse resp = (HttpServletResponse) response; - - if (!validateAuth(req, resp)) { - return; - } - - chain.doFilter(request, response); - } - - private boolean validateAuth(HttpServletRequest req, HttpServletResponse resp) throws IOException { - Map sentryAuthDetails = extractSentryAuthDetails(req); - //Can throw an exception, but a request which doesn't provide a project ID should fail anyway - String projectId = req.getPathInfo().substring(1, req.getPathInfo().indexOf('/', 1)); - - try { - sentryStub.validateAuth(sentryAuthDetails, projectId); - } catch (InvalidAuthException iae) { - resp.setStatus(HttpServletResponse.SC_FORBIDDEN); - PrintWriter writer = resp.getWriter(); - for (String message : iae.getDetailedMessages()) { - writer.println(message); - } - return false; - } - return true; - } - - private Map extractSentryAuthDetails(HttpServletRequest request) { - String sentryAuth = request.getHeader(SENTRY_AUTH); - if (sentryAuth == null) { - return Collections.emptyMap(); - } - - String[] authParameters = sentryAuth.split(","); - Map authDetails = new HashMap<>(authParameters.length); - for (String authParameter : authParameters) { - String[] splitParameter = authParameter.split("="); - authDetails.put(splitParameter[0], splitParameter[1]); - } - - return authDetails; - } - - @Override - public void destroy() { - } -} diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryHttpServlet.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryHttpServlet.java deleted file mode 100644 index 1cf09682a2b..00000000000 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryHttpServlet.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.getsentry.raven.sentrystub; - -import com.getsentry.raven.sentrystub.event.Event; - -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -@WebServlet(name = "SentryHttpServlet", displayName = "SentryHttpServlet", urlPatterns = "/api/*") -public class SentryHttpServlet extends HttpServlet { - private SentryStub sentryStub = SentryStub.getInstance(); - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - Event event = sentryStub.parseEvent(req.getInputStream()); - sentryStub.addEvent(event); - } -} diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryStub.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryStub.java deleted file mode 100644 index eef1a5a2824..00000000000 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryStub.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.getsentry.raven.sentrystub; - -import com.getsentry.raven.sentrystub.auth.AuthValidator; -import com.getsentry.raven.sentrystub.event.Event; -import com.getsentry.raven.sentrystub.unmarshaller.JsonUnmarshaller; -import com.getsentry.raven.sentrystub.unmarshaller.Unmarshaller; - -import java.io.InputStream; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; -import java.util.Map; - -public final class SentryStub { - private static SentryStub instance = new SentryStub(); - private final Collection events = new LinkedList<>(); - private final AuthValidator authValidator = new AuthValidator(); - private final Unmarshaller unmarshaller = new JsonUnmarshaller(); - - private SentryStub() { - authValidator.loadSentryUsers("/com/getsentry/raven/sentrystub/sentry.properties"); - } - - public static SentryStub getInstance() { - return instance; - } - - public void addEvent(Event event) { - validateEvent(event); - events.add(event); - } - - public void validateEvent(Event event) { - } - - public Event parseEvent(InputStream source) { - return unmarshaller.unmarshall(source); - } - - public Collection getEvents() { - return Collections.unmodifiableCollection(events); - } - - public void validateAuth(Map authHeader, String projectId) { - authValidator.validateSentryAuth(authHeader, projectId); - } - - public void validateAuth(Map authHeader) { - authValidator.validateSentryAuth(authHeader); - } - - public void removeEvents() { - events.clear(); - } -} diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryStubServlet.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryStubServlet.java deleted file mode 100644 index 32d1935ba47..00000000000 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/SentryStubServlet.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.getsentry.raven.sentrystub; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; - -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -/** - * Simple API to access the sentry stub details. - */ -@WebServlet(name = "SentryStubServlet", displayName = "SentryStubServlet", urlPatterns = "/stub/*") -public class SentryStubServlet extends HttpServlet { - private static final String COUNT_OPERATION = "count"; - private static final String CLEANUP_OPERATION = "cleanup"; - private SentryStub sentryStub = SentryStub.getInstance(); - private JsonFactory jsonFactory = new JsonFactory(); - - public void getEventsCounter(JsonGenerator generator) throws IOException { - generator.writeStartObject(); - generator.writeNumberField("count", sentryStub.getEvents().size()); - generator.writeEndObject(); - } - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - String operation = req.getPathInfo().substring(1); - - JsonGenerator jsonGenerator = jsonFactory.createGenerator(resp.getOutputStream()); - - if (COUNT_OPERATION.equals(operation)) { - getEventsCounter(jsonGenerator); - } else { - resp.setStatus(HttpServletResponse.SC_NOT_FOUND); - } - - jsonGenerator.close(); - } - - @Override - protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - String operation = req.getPathInfo().substring(1); - - if (CLEANUP_OPERATION.equals(operation)) { - sentryStub.removeEvents(); - } - } -} diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/auth/AuthValidator.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/auth/AuthValidator.java deleted file mode 100644 index c1b18f327f0..00000000000 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/auth/AuthValidator.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.getsentry.raven.sentrystub.auth; - -import java.io.InputStream; -import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Validate a Sentry auth Header (in HTTP {@code X-Sentry-Auth}). - *

    - * The validation of a header goes from the validation of the content to the authorisation check for the given public - * and secret keys. - */ -public class AuthValidator { - private static final Logger logger = Logger.getLogger(AuthValidator.class.getCanonicalName()); - private static final Collection SENTRY_PROTOCOL_VERSIONS = Arrays.asList("6"); - private static final String SENTRY_VERSION_PARAMETER = "Sentry sentry_version"; - private static final String PUBLIC_KEY_PARAMETER = "sentry_key"; - private static final String SECRET_KEY_PARAMETER = "sentry_secret"; - private static final String SENTRY_CLIENT_PARAMETER = "sentry_client"; - private final Map publicKeySecretKey = new HashMap<>(); - private final Map publicKeyProjectId = new HashMap<>(); - - /** - * Adds a user to consider as valid of an Auth header. - * - * @param publicKey public key of the user. - * @param secretKey secret key of the user. - * @param projectId identifier of the project on which the user is allowed to push events. - */ - public void addUser(String publicKey, String secretKey, String projectId) { - if (publicKeySecretKey.containsKey(publicKey) || publicKeyProjectId.containsKey(publicKey)) { - throw new IllegalArgumentException("There is already a user " + publicKey); - } - - publicKeySecretKey.put(publicKey, secretKey); - publicKeyProjectId.put(publicKey, projectId); - } - - /** - * Validates an auth header. - * - * @param authParameters auth header as a {@code Map}. - */ - public void validateSentryAuth(Map authParameters) { - InvalidAuthException invalidAuthException = new InvalidAuthException("The auth parameters weren't valid"); - - validateVersion(authParameters.get(SENTRY_VERSION_PARAMETER), invalidAuthException); - validateKeys(authParameters.get(PUBLIC_KEY_PARAMETER), authParameters.get(SECRET_KEY_PARAMETER), - invalidAuthException); - validateClient(authParameters.get(SENTRY_CLIENT_PARAMETER), invalidAuthException); - - if (!invalidAuthException.isEmpty()) { - throw invalidAuthException; - } - } - - /** - * Validates an auth header and the access to a project. - * - * @param authParameters auth header as a {@code Map}. - * @param projectId identifier of the project being accessed (isn't a part - * @see #validateSentryAuth(java.util.Map) - */ - public void validateSentryAuth(Map authParameters, String projectId) { - InvalidAuthException invalidAuthException = new InvalidAuthException("The auth parameters weren't valid"); - try { - validateSentryAuth(authParameters); - } catch (InvalidAuthException e) { - invalidAuthException = e; - } - - validateProject(authParameters.get(PUBLIC_KEY_PARAMETER), projectId, invalidAuthException); - - if (!invalidAuthException.isEmpty()) { - throw invalidAuthException; - } - } - - /** - * Validates the version of the protocol given in the Auth header. - *

    - * The only supported versions are listed in {@link #SENTRY_PROTOCOL_VERSIONS}. - * - * @param authSentryVersion version of the Sentry protocol given in the auth header. - * @param invalidAuthException exception thrown if the auth header is invalid. - */ - private void validateVersion(String authSentryVersion, InvalidAuthException invalidAuthException) { - if (authSentryVersion == null || !SENTRY_PROTOCOL_VERSIONS.contains(authSentryVersion)) { - invalidAuthException.addDetailedMessage("The version '" + authSentryVersion + "' isn't valid, " - + "only those " + SENTRY_PROTOCOL_VERSIONS + " are supported."); - } - } - - /** - * Validates the public and secret user keys provided in the Auth Header. - *

    - * Valid keys are listed in {@link #publicKeySecretKey}. - * - * @param publicKey public key used to identify a user. - * @param secretKey secret key used as a password. - * @param invalidAuthException exception thrown if the auth header is invalid. - * @see #addUser(String, String, String) - */ - private void validateKeys(String publicKey, String secretKey, - InvalidAuthException invalidAuthException) { - if (publicKey == null) { - invalidAuthException.addDetailedMessage("No public key provided"); - } else if (!publicKeySecretKey.containsKey(publicKey)) { - invalidAuthException.addDetailedMessage("The public key '" + publicKey + "' isn't associated " - + "with a secret key."); - } - - if (secretKey == null) { - invalidAuthException.addDetailedMessage("No secret key provided"); - } - - if (secretKey != null && publicKey != null && !secretKey.equals(publicKeySecretKey.get(publicKey))) { - invalidAuthException.addDetailedMessage("The secret key '" + secretKey + "' " - + "isn't valid for '" + publicKey + "'"); - } - } - - /** - * Validates the project and checks if the given user can indeed access the project. - * - * @param publicKey public key used to identify a user. - * @param projectId identifier of the project on which the user is allowed to push events. - * @param invalidAuthException exception thrown if the auth header is invalid. - * @see #addUser(String, String, String) - */ - private void validateProject(String publicKey, String projectId, InvalidAuthException invalidAuthException) { - if (projectId == null) { - invalidAuthException.addDetailedMessage("No project ID provided"); - } - - if (projectId != null && publicKey != null && !projectId.equals(publicKeyProjectId.get(publicKey))) { - invalidAuthException.addDetailedMessage("The project '" + projectId + "' " - + "can't be accessed by ' " + publicKey + " '"); - } - } - - /** - * Validates the client part of the header. - *

    - * The client should always be provided. - * - * @param client string identifying a client type (such as Java/3.0) - * @param invalidAuthException exception thrown if the auth header is invalid. - */ - private void validateClient(String client, InvalidAuthException invalidAuthException) { - if (client == null) { - invalidAuthException.addDetailedMessage("The client name is mandatory."); - } - } - - public void loadSentryUsers(String resourceName) { - Properties sentryProperties = new Properties(); - - try(InputStream resourceAsStream = AuthValidator.class.getResourceAsStream(resourceName)) { - sentryProperties.load(resourceAsStream); - int userCount = Integer.parseInt(sentryProperties.getProperty("sentry.user.count", "0")); - for (int i = 1; i <= userCount; i++) { - String publicKey = sentryProperties.getProperty("sentry.user." + i + ".publicKey"); - String secretKey = sentryProperties.getProperty("sentry.user." + i + ".secretKey"); - String projectId = sentryProperties.getProperty("sentry.user." + i + ".projectId"); - addUser(publicKey, secretKey, projectId); - } - } catch (Exception e) { - logger.log(Level.SEVERE, "Couldn't load the sentry.properties file", e); - } - } - -} diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/auth/InvalidAuthException.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/auth/InvalidAuthException.java deleted file mode 100644 index a1d7ba50842..00000000000 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/auth/InvalidAuthException.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.getsentry.raven.sentrystub.auth; - -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; - -public class InvalidAuthException extends RuntimeException { - private final Collection detailedMessages = new LinkedList<>(); - - public InvalidAuthException() { - } - - public InvalidAuthException(String message) { - super(message); - } - - public InvalidAuthException(String message, Throwable cause) { - super(message, cause); - } - - public InvalidAuthException(Throwable cause) { - super(cause); - } - - public Collection getDetailedMessages() { - return Collections.unmodifiableCollection(detailedMessages); - } - - public void addDetailedMessage(String message) { - detailedMessages.add(message); - } - - public boolean isEmpty() { - return detailedMessages.isEmpty(); - } -} diff --git a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/unmarshaller/Unmarshaller.java b/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/unmarshaller/Unmarshaller.java deleted file mode 100644 index 629dad5b757..00000000000 --- a/sentry-stub/src/main/java/com/getsentry/raven/sentrystub/unmarshaller/Unmarshaller.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.getsentry.raven.sentrystub.unmarshaller; - -import com.getsentry.raven.sentrystub.event.Event; - -import java.io.InputStream; - -public interface Unmarshaller { - Event unmarshall(InputStream source); -} diff --git a/sentry-stub/src/main/resources/com/getsentry/raven/sentrystub/sentry.properties b/sentry-stub/src/main/resources/com/getsentry/raven/sentrystub/sentry.properties deleted file mode 100644 index 419280300de..00000000000 --- a/sentry-stub/src/main/resources/com/getsentry/raven/sentrystub/sentry.properties +++ /dev/null @@ -1,5 +0,0 @@ -sentry.user.count=1 - -sentry.user.1.publicKey=8292bf61d620417282e68a72ae03154a -sentry.user.1.secretKey=e3908e05ad874b24b7a168992bfa3577 -sentry.user.1.projectId=1 diff --git a/sentry-stub/src/main/webapp/WEB-INF/web.xml b/sentry-stub/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 52a30ef50f5..00000000000 --- a/sentry-stub/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,5 +0,0 @@ - - diff --git a/src/checkstyle/suppressions.xml b/src/checkstyle/suppressions.xml index 72e80a7ee12..93097875bb7 100644 --- a/src/checkstyle/suppressions.xml +++ b/src/checkstyle/suppressions.xml @@ -4,7 +4,6 @@ "http://www.puppycrawl.com/dtds/suppressions_1_1.dtd"> - From 78c2c3f81d7b6d052b58f64a201369a5d6efca37 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 27 Feb 2017 17:20:31 -0600 Subject: [PATCH 1512/2152] Don't squash existing breadcrumbs on an EventBuilder object unless Context is actually set (#318) --- .../event/helper/ContextBuilderHelper.java | 5 +- .../getsentry/raven/event/BreadcrumbTest.java | 96 +++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 raven/src/test/java/com/getsentry/raven/event/BreadcrumbTest.java diff --git a/raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java b/raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java index a3b4ee44aee..7a621f85027 100644 --- a/raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java +++ b/raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java @@ -40,7 +40,10 @@ public void helpBuildingEvent(EventBuilder eventBuilder) { while (breadcrumbIterator.hasNext()) { breadcrumbs.add(breadcrumbIterator.next()); } - eventBuilder.withBreadcrumbs(breadcrumbs); + + if (!breadcrumbs.isEmpty()) { + eventBuilder.withBreadcrumbs(breadcrumbs); + } if (context.getUser() != null) { eventBuilder.withSentryInterface(fromUser(context.getUser())); diff --git a/raven/src/test/java/com/getsentry/raven/event/BreadcrumbTest.java b/raven/src/test/java/com/getsentry/raven/event/BreadcrumbTest.java new file mode 100644 index 00000000000..bcdf1109c35 --- /dev/null +++ b/raven/src/test/java/com/getsentry/raven/event/BreadcrumbTest.java @@ -0,0 +1,96 @@ +package com.getsentry.raven.event; + +import com.getsentry.raven.Raven; +import com.getsentry.raven.connection.Connection; +import com.getsentry.raven.event.helper.ContextBuilderHelper; +import mockit.Injectable; +import mockit.Tested; +import mockit.Verifications; +import org.testng.annotations.Test; + +import java.util.ArrayList; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; + +public class BreadcrumbTest { + @Tested + private Raven raven = null; + + @Injectable + private Connection mockConnection = null; + + @Test + public void testBreadcrumbsViaContextRecording() { + raven.addBuilderHelper(new ContextBuilderHelper(raven)); + + final Breadcrumb breadcrumb = new BreadcrumbBuilder() + .setLevel("info") + .setMessage("message") + .setCategory("step") + .build(); + + raven.getContext().recordBreadcrumb(breadcrumb); + + raven.sendEvent(new EventBuilder() + .withMessage("Some random message") + .withLevel(Event.Level.INFO)); + + new Verifications() {{ + Event event; + mockConnection.send(event = withCapture()); + assertThat(event.getBreadcrumbs().size(), equalTo(1)); + }}; + } + + @Test + public void testBreadcrumbsViaEventBuilder() { + raven.addBuilderHelper(new ContextBuilderHelper(raven)); + + final Breadcrumb breadcrumb = new BreadcrumbBuilder() + .setLevel("info") + .setMessage("message") + .setCategory("step") + .build(); + + ArrayList breadcrumbs = new ArrayList<>(); + breadcrumbs.add(breadcrumb); + + raven.sendEvent(new EventBuilder() + .withBreadcrumbs(breadcrumbs) + .withMessage("Some random message") + .withLevel(Event.Level.INFO)); + + new Verifications() {{ + Event event; + mockConnection.send(event = withCapture()); + assertThat(event.getBreadcrumbs().size(), equalTo(1)); + }}; + } + + @Test + public void testBreadcrumbsViaEvent() { + raven.addBuilderHelper(new ContextBuilderHelper(raven)); + + final Breadcrumb breadcrumb = new BreadcrumbBuilder() + .setLevel("info") + .setMessage("message") + .setCategory("step") + .build(); + + ArrayList breadcrumbs = new ArrayList<>(); + breadcrumbs.add(breadcrumb); + + raven.sendEvent(new EventBuilder() + .withBreadcrumbs(breadcrumbs) + .withMessage("Some random message") + .withLevel(Event.Level.INFO) + .build()); + + new Verifications() {{ + Event event; + mockConnection.send(event = withCapture()); + assertThat(event.getBreadcrumbs().size(), equalTo(1)); + }}; + } +} \ No newline at end of file From f8c35e588103dcb211887a3be2f4c5dad05d31ec Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 27 Feb 2017 20:22:54 -0600 Subject: [PATCH 1513/2152] Buffer events to disk *before* sending, discard them on success. (#305) --- .../getsentry/raven/DefaultRavenFactory.java | 9 +- .../getsentry/raven/buffer/DiskBuffer.java | 8 +- .../raven/connection/BufferedConnection.java | 95 ++++++++++++++++--- .../connection/BufferedConnectionTest.java | 13 ++- 4 files changed, 104 insertions(+), 21 deletions(-) diff --git a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java index 8fc4c0ce431..f8b4b1e2f53 100644 --- a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java @@ -219,12 +219,14 @@ protected Connection createConnection(Dsn dsn) { } Buffer eventBuffer = getBuffer(dsn); + BufferedConnection bufferedConnection = null; if (eventBuffer != null) { long flushtime = getBufferFlushtime(dsn); boolean gracefulShutdown = getBufferedConnectionGracefulShutdownEnabled(dsn); Long shutdownTimeout = getBufferedConnectionShutdownTimeout(dsn); - connection = new BufferedConnection(connection, eventBuffer, flushtime, gracefulShutdown, + bufferedConnection = new BufferedConnection(connection, eventBuffer, flushtime, gracefulShutdown, shutdownTimeout); + connection = bufferedConnection; } // Enable async unless its value is 'false'. @@ -232,6 +234,11 @@ protected Connection createConnection(Dsn dsn) { connection = createAsyncConnection(dsn, connection); } + // If buffering is enabled, wrap connection with synchronous disk buffering "connection" + if (bufferedConnection != null) { + connection = bufferedConnection.wrapConnectionWithBufferWriter(connection); + } + return connection; } diff --git a/raven/src/main/java/com/getsentry/raven/buffer/DiskBuffer.java b/raven/src/main/java/com/getsentry/raven/buffer/DiskBuffer.java index 9e578181f28..50f9c2f3d1b 100644 --- a/raven/src/main/java/com/getsentry/raven/buffer/DiskBuffer.java +++ b/raven/src/main/java/com/getsentry/raven/buffer/DiskBuffer.java @@ -70,7 +70,13 @@ public void add(Event event) { } File eventFile = new File(bufferDir.getAbsolutePath(), event.getId().toString() + FILE_SUFFIX); - logger.debug("Adding Event to offline storage: " + eventFile.getAbsolutePath()); + if (eventFile.exists()) { + logger.trace("Not adding Event to offline storage because it already exists: " + + eventFile.getAbsolutePath()); + return; + } else { + logger.debug("Adding Event to offline storage: " + eventFile.getAbsolutePath()); + } try (FileOutputStream fileOutputStream = new FileOutputStream(eventFile); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) { diff --git a/raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java b/raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java index 2c08fe99fb8..abfc2381d34 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java @@ -15,7 +15,19 @@ import java.util.concurrent.TimeUnit; /** - * Connection wrapper that sends Events to an Buffer when send fails. + * Connection wrapper that handles storing and deleting {@link Event}s from a {@link Buffer}. + * + * This exists as a {@link Connection} implementation because the existing API (and the Java 7 + * standard library) has no simple way of knowing whether an asynchronous request succeeded + * or failed. The {@link #wrapConnectionWithBufferWriter} method is used to wrap an existing + * Connection in a small anonymous Connection implementation that will always synchronously + * write the sent Event to a Buffer and then pass it to the underlying Connection (often an + * AsyncConnection in practice). Then, an instance of the {@link BufferedConnection} is used + * to wrap the "real" Connection ("under" the AsyncConnection) so that it remove Events from + * the Buffer if and only if the underlying {@link #send(Event)} call doesn't throw an exception. + * + * Note: In the future, if we are able to migrate to Java 8 at a minimum, we would probably make use + * of CompletableFutures, though that would require changing the existing API regardless. */ public class BufferedConnection implements Connection { @@ -81,21 +93,16 @@ public BufferedConnection(Connection actualConnection, Buffer buffer, long flush Runtime.getRuntime().addShutdownHook(shutDownHook); } - Flusher flusher = new BufferedConnection.Flusher(); + Flusher flusher = new BufferedConnection.Flusher(flushtime); executorService.scheduleWithFixedDelay(flusher, flushtime, flushtime, TimeUnit.MILLISECONDS); } @Override public void send(Event event) { - try { - actualConnection.send(event); - // success: ensure the even isn't buffered - buffer.discard(event); - } catch (Exception e) { - // failure: buffer the event - buffer.add(event); - throw e; - } + actualConnection.send(event); + + // success, remove the event from the buffer + buffer.discard(event); } @Override @@ -138,16 +145,61 @@ public void close() throws IOException { } } + /** + * Wrap a connection so that {@link Event}s are buffered before being passed on to + * the underlying connection. + * + * This is important to ensure buffering happens synchronously with {@link Event} creation, + * before they are passed on to the (optional) asynchronous connection so that Events will + * be stored even if the application is about to exit due to a crash. + * + * @param connectionToWrap {@link Connection} to wrap with buffering logic. + * @return Connection that will write {@link Event}s to a buffer before passing along to + * a wrapped {@link Connection}. + */ + public Connection wrapConnectionWithBufferWriter(final Connection connectionToWrap) { + return new Connection() { + final Connection wrappedConnection = connectionToWrap; + + @Override + public void send(Event event) throws ConnectionException { + try { + // buffer before we attempt to send + buffer.add(event); + } catch (Exception e) { + logger.error("Exception occurred while attempting to add Event to buffer: ", e); + } + + wrappedConnection.send(event); + } + + @Override + public void addEventSendFailureCallback(EventSendFailureCallback eventSendFailureCallback) { + wrappedConnection.addEventSendFailureCallback(eventSendFailureCallback); + } + + @Override + public void close() throws IOException { + wrappedConnection.close(); + } + }; + } + /** * Flusher is scheduled to periodically run by the BufferedConnection. It retrieves * an Iterator of Events from the underlying {@link Buffer} and attempts to send - * one at time, discarding from the buffer on success and re-adding to the buffer - * on failure. + * one at time, discarding from the buffer on success. * - * Upon the first failure, Flusher will return and wait to be run again in the futre, + * Upon the first failure, Flusher will return and wait to be run again in the future, * under the assumption that the network is now/still down. */ private class Flusher implements Runnable { + private long minAgeMillis; + + Flusher(long minAgeMillis) { + this.minAgeMillis = minAgeMillis; + } + @Override public void run() { logger.trace("Running Flusher"); @@ -158,6 +210,21 @@ public void run() { while (events.hasNext() && !closed) { Event event = events.next(); + /* + Skip events that have been in the buffer for less than minAgeMillis + milliseconds. We need to do this because events are added to the + buffer before they are passed on to the underlying "real" connection, + which means the Flusher might run and see them before we even attempt + to send them for the first time. + */ + long now = System.currentTimeMillis(); + long eventTime = event.getTimestamp().getTime(); + long age = now - eventTime; + if (age < minAgeMillis) { + logger.trace("Ignoring buffered event because it only " + age + "ms old."); + return; + } + try { logger.trace("Flusher attempting to send Event: " + event.getId()); send(event); diff --git a/raven/src/test/java/com/getsentry/raven/connection/BufferedConnectionTest.java b/raven/src/test/java/com/getsentry/raven/connection/BufferedConnectionTest.java index 6e6a4b5ab96..24cef72821b 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/BufferedConnectionTest.java +++ b/raven/src/test/java/com/getsentry/raven/connection/BufferedConnectionTest.java @@ -8,26 +8,28 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import org.testng.collections.Lists; +import org.testng.collections.Sets; import java.io.IOException; import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.concurrent.Callable; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; public class BufferedConnectionTest extends BaseTest { - private List bufferedEvents; + private Set bufferedEvents; private List sentEvents; private Buffer mockBuffer; private Connection mockConnection; - private BufferedConnection bufferedConnection; + private Connection bufferedConnection; private volatile boolean connectionUp; @BeforeMethod public void setup() { - bufferedEvents = Lists.newArrayList(); + bufferedEvents = Sets.newHashSet(); sentEvents = Lists.newArrayList(); connectionUp = true; @@ -71,7 +73,8 @@ public Iterator getEvents() { int flushtime = 10; int shutdownTimeout = 0; - bufferedConnection = new BufferedConnection(mockConnection, mockBuffer, flushtime, false, shutdownTimeout); + BufferedConnection innerBufferedConnection = new BufferedConnection(mockConnection, mockBuffer, flushtime, false, shutdownTimeout); + this.bufferedConnection = innerBufferedConnection.wrapConnectionWithBufferWriter(innerBufferedConnection); } @AfterMethod @@ -89,7 +92,7 @@ public void test() throws Exception { } assertThat(bufferedEvents.size(), equalTo(1)); - assertThat(bufferedEvents.get(0), equalTo(event)); + assertThat(bufferedEvents.iterator().next(), equalTo(event)); connectionUp = true; waitUntilTrue(1000, new Callable() { From 804d3251df22a17b70282f058219da5a0555fab0 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 1 Mar 2017 16:02:32 -0600 Subject: [PATCH 1514/2152] Fix out of order documentation. --- docs/config.rst | 56 ++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 607cd5433f3..22ff2cfee66 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -192,34 +192,6 @@ with the option ``raven.async.priority``: http://public:private@host:port/1?raven.async.priority=10 -Graceful Shutdown (Advanced) -```````````````````````````` - -In order to shutdown the buffer flushing thread gracefully, a ``ShutdownHook`` -is created. By default, the buffer flushing thread is given 1 second -to shutdown gracefully, but this can be adjusted via -``raven.buffer.shutdowntimeout`` (represented in milliseconds): - -:: - - http://public:private@host:port/1?raven.buffer.shutdowntimeout=5000 - -The special value ``-1`` can be used to disable the timeout and wait -indefinitely for the executor to terminate. - -The ``ShutdownHook`` could lead to memory leaks in an environment where -the life cycle of Raven doesn't match the life cycle of the JVM. - -An example would be in a JEE environment where the application using Raven -could be deployed and undeployed regularly. - -To avoid this behaviour, it is possible to disable the graceful shutdown -by setting the ``raven.buffer.gracefulshutdown`` option: - -:: - - http://public:private@host:port/1?raven.buffer.gracefulshutdown=false - Buffering Events to Disk ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -249,6 +221,34 @@ attempt to send events every 60 seconds. You can change this with the http://public:private@host:port/1?raven.buffer.flushtime=10000 +Graceful Shutdown (Advanced) +```````````````````````````` + +In order to shutdown the buffer flushing thread gracefully, a ``ShutdownHook`` +is created. By default, the buffer flushing thread is given 1 second +to shutdown gracefully, but this can be adjusted via +``raven.buffer.shutdowntimeout`` (represented in milliseconds): + +:: + + http://public:private@host:port/1?raven.buffer.shutdowntimeout=5000 + +The special value ``-1`` can be used to disable the timeout and wait +indefinitely for the executor to terminate. + +The ``ShutdownHook`` could lead to memory leaks in an environment where +the life cycle of Raven doesn't match the life cycle of the JVM. + +An example would be in a JEE environment where the application using Raven +could be deployed and undeployed regularly. + +To avoid this behaviour, it is possible to disable the graceful shutdown +by setting the ``raven.buffer.gracefulshutdown`` option: + +:: + + http://public:private@host:port/1?raven.buffer.gracefulshutdown=false + Event Sampling ~~~~~~~~~~~~~~ From 39ac727a4c2a98f1d9c4cc0c7a8a128900936786 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 2 Mar 2017 18:13:09 -0600 Subject: [PATCH 1515/2152] =?UTF-8?q?Change=20lockdown=20to=20drop=20Event?= =?UTF-8?q?s,=20allow=20subclasses=20to=20hint=20lockdown=20tim=E2=80=A6?= =?UTF-8?q?=20(#319)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Change lockdown to drop Events, allow subclasses to hint lockdown times, add support for Retry-After header. --- CHANGES | 2 + .../raven/connection/AbstractConnection.java | 132 ++++++------------ .../raven/connection/ConnectionException.java | 15 ++ .../raven/connection/HttpConnection.java | 21 ++- .../raven/connection/LockdownManager.java | 110 +++++++++++++++ .../raven/connection/LockedDownException.java | 17 +++ .../java/com/getsentry/raven/dsn/Dsn.java | 4 +- .../java/com/getsentry/raven/time/Clock.java | 2 +- .../connection/AbstractConnectionTest.java | 105 +++++++++----- .../connection/BufferedConnectionTest.java | 33 ++++- .../raven/connection/HttpConnectionTest.java | 21 +++ 11 files changed, 328 insertions(+), 134 deletions(-) create mode 100644 raven/src/main/java/com/getsentry/raven/connection/LockdownManager.java create mode 100644 raven/src/main/java/com/getsentry/raven/connection/LockedDownException.java diff --git a/CHANGES b/CHANGES index ac16fdcdc52..435af29bc8a 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,8 @@ Version 7.8.3 ------------- - Add User to RavenContext and ContextBuilderHelper. (thanks mpecan) +- Drop Events when the connection is locked down, users must enable buffering to attempt to resend later. +- Respect the Sentry server's ``Retry-After`` header. Version 7.8.2 ------------- diff --git a/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java b/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java index 328910d78fa..9c5ff40f9fa 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java @@ -7,11 +7,6 @@ import java.util.HashSet; import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; /** * Abstract connection to a Sentry server. @@ -22,38 +17,20 @@ */ public abstract class AbstractConnection implements Connection { /** - * Current sentry protocol version. + * Current Sentry protocol version. */ public static final String SENTRY_PROTOCOL_VERSION = "6"; - /** - * Default maximum duration for a lockdown. - */ - public static final long DEFAULT_MAX_WAITING_TIME = TimeUnit.MINUTES.toMillis(5); - /** - * Default base duration for a lockdown. - */ - public static final long DEFAULT_BASE_WAITING_TIME = TimeUnit.MILLISECONDS.toMillis(10); private static final Logger logger = LoggerFactory.getLogger(AbstractConnection.class); - private final AtomicBoolean lockdown = new AtomicBoolean(); - private final Lock lock = new ReentrantLock(); - private final Condition condition = lock.newCondition(); - private final String authHeader; /** - * Maximum duration for a lockdown. + * Value of the X-Sentry-Auth header. */ - private long maxWaitingTime = DEFAULT_MAX_WAITING_TIME; - /** - * Base duration for a lockdown. - *

    - * On each attempt the time is doubled until it reaches {@link #maxWaitingTime}. - */ - private long baseWaitingTime = DEFAULT_BASE_WAITING_TIME; - private long waitingTime = baseWaitingTime; + private final String authHeader; /** * Set of callbacks that will be called when an exception occurs while attempting to * send events to the Sentry server. */ private Set eventSendFailureCallbacks; + private LockdownManager lockdownManager; /** * Creates a connection based on the public and secret keys. @@ -62,15 +39,16 @@ public abstract class AbstractConnection implements Connection { * @param secretKey secret key (password) to the Sentry server. */ protected AbstractConnection(String publicKey, String secretKey) { - eventSendFailureCallbacks = new HashSet<>(); - authHeader = "Sentry sentry_version=" + SENTRY_PROTOCOL_VERSION + "," - + "sentry_client=" + RavenEnvironment.getRavenName() + "," - + "sentry_key=" + publicKey + "," - + "sentry_secret=" + secretKey; + this.lockdownManager = new LockdownManager(); + this.eventSendFailureCallbacks = new HashSet<>(); + this.authHeader = "Sentry sentry_version=" + SENTRY_PROTOCOL_VERSION + "," + + "sentry_client=" + RavenEnvironment.getRavenName() + "," + + "sentry_key=" + publicKey + "," + + "sentry_secret=" + secretKey; } /** - * Creates an authentication header for the sentry protocol. + * Creates an authentication header for the Sentry protocol. * * @return an authentication header as a String. */ @@ -81,14 +59,19 @@ protected String getAuthHeader() { @Override public final void send(Event event) throws ConnectionException { try { - waitIfLockedDown(); + if (lockdownManager.isLockedDown()) { + /* + An exception is thrown to signal that this Event was not sent, which may be + important in, for example, a BufferedConnection where the Event would be deleted + from the Buffer if an exception isn't raised in the call to send. + */ + throw new LockedDownException("Dropping an Event due to lockdown: " + event); + } doSend(event); - waitingTime = baseWaitingTime; - } catch (ConnectionException e) { - logger.warn("An exception due to the connection occurred, a lockdown will be initiated.", e); - lockDown(); + lockdownManager.resetState(); + } catch (ConnectionException e) { for (EventSendFailureCallback eventSendFailureCallback : eventSendFailureCallbacks) { try { eventSendFailureCallback.onFailure(event, e); @@ -98,74 +81,41 @@ public final void send(Event event) throws ConnectionException { } } - throw e; - } - } - - /** - * Pauses the current thread if there is a lockdown. - */ - private void waitIfLockedDown() { - while (lockdown.get()) { - lock.lock(); - try { - if (lockdown.get()) { - condition.await(); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - logger.warn("An exception occurred during the lockdown.", e); - } finally { - lock.unlock(); - } - } - } - - /** - * Initiates a lockdown for {@link #waitingTime}ms and resume the paused threads once the lockdown ends. - */ - private void lockDown() { - if (!lockdown.compareAndSet(false, true)) { - return; - } - - try { - logger.warn("Lockdown started for {}ms.", waitingTime); - Thread.sleep(waitingTime); - - // Double the wait until the maximum is reached - if (waitingTime < maxWaitingTime) { - waitingTime <<= 1; - } - } catch (Exception e) { - logger.warn("An exception occurred during the lockdown.", e); - } finally { - lockdown.set(false); + logger.warn("An exception due to the connection occurred, a lockdown will be initiated.", e); + lockdownManager.setState(e); - lock.lock(); - try { - condition.signalAll(); - } finally { - lock.unlock(); - } - logger.warn("Lockdown ended."); + throw e; } } /** - * Sends an event to the sentry server. + * Sends an event to the Sentry server. * * @param event captured event to add in Sentry. * @throws ConnectionException whenever a temporary exception due to the connection happened. */ protected abstract void doSend(Event event) throws ConnectionException; + /** + * Set the maximum waiting time for a lockdown, in milliseconds. + * + * @param maxWaitingTime maximum waiting time for a lockdown, in milliseconds. + * @deprecated slated for removal + */ + @Deprecated public void setMaxWaitingTime(long maxWaitingTime) { - this.maxWaitingTime = maxWaitingTime; + lockdownManager.setMaxLockdownTime(maxWaitingTime); } + /** + * Set the base waiting time for a lockdown, in milliseconds. + * + * @param baseWaitingTime base waiting time for a lockdown, in milliseconds. + * @deprecated slated for removal + */ + @Deprecated public void setBaseWaitingTime(long baseWaitingTime) { - this.baseWaitingTime = baseWaitingTime; + lockdownManager.setBaseLockdownTime(baseWaitingTime); } /** diff --git a/raven/src/main/java/com/getsentry/raven/connection/ConnectionException.java b/raven/src/main/java/com/getsentry/raven/connection/ConnectionException.java index 92a89d69142..63e0f6e58c3 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/ConnectionException.java +++ b/raven/src/main/java/com/getsentry/raven/connection/ConnectionException.java @@ -6,8 +6,14 @@ * This allows connections to know when to back off for a while. */ public class ConnectionException extends RuntimeException { + /** + * Recommended duration to initiate a lockdown for, in milliseconds. + */ + private Long recommendedLockdownTime = null; + //CHECKSTYLE.OFF: JavadocMethod public ConnectionException() { + } public ConnectionException(String message) { @@ -18,8 +24,17 @@ public ConnectionException(String message, Throwable cause) { super(message, cause); } + public ConnectionException(String message, Throwable cause, Long recommendedLockdownTime) { + super(message, cause); + this.recommendedLockdownTime = recommendedLockdownTime; + } + public ConnectionException(Throwable cause) { super(cause); } + + public Long getRecommendedLockdownTime() { + return recommendedLockdownTime; + } //CHECKSTYLE.ON: JavadocMethod } diff --git a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java b/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java index 166e3c7e9ef..decccea1712 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java @@ -142,7 +142,7 @@ protected HttpURLConnection getConnection() { connection.setRequestProperty(SENTRY_AUTH, getAuthHeader()); return connection; } catch (IOException e) { - throw new IllegalStateException("Couldn't set up a connection to the sentry server.", e); + throw new IllegalStateException("Couldn't set up a connection to the Sentry server.", e); } } @@ -166,9 +166,24 @@ protected void doSend(Event event) throws ConnectionException { errorMessage = getErrorMessageFromStream(errorStream); } if (null == errorMessage || errorMessage.isEmpty()) { - errorMessage = "An exception occurred while submitting the event to the sentry server."; + errorMessage = "An exception occurred while submitting the event to the Sentry server."; } - throw new ConnectionException(errorMessage, e); + + Long retryAfterMs = null; + String retryAfterHeader = connection.getHeaderField("Retry-After"); + if (retryAfterHeader != null) { + // CHECKSTYLE.OFF: EmptyCatchBlock + try { + // CHECKSTYLE.OFF: MagicNumber + retryAfterMs = Long.parseLong(retryAfterHeader) * 1000L; // seconds -> milliseconds + // CHECKSTYLE.ON: MagicNumber + } catch (NumberFormatException nfe) { + // noop, use default retry + } + // CHECKSTYLE.ON: EmptyCatchBlock + } + + throw new ConnectionException(errorMessage, e, retryAfterMs); } finally { connection.disconnect(); } diff --git a/raven/src/main/java/com/getsentry/raven/connection/LockdownManager.java b/raven/src/main/java/com/getsentry/raven/connection/LockdownManager.java new file mode 100644 index 00000000000..4cc7cd89c5d --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/connection/LockdownManager.java @@ -0,0 +1,110 @@ +package com.getsentry.raven.connection; + +import com.getsentry.raven.time.Clock; +import com.getsentry.raven.time.SystemClock; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +/** + * Abstracts the connection lockdown logic (and state) to a single place so that + * it's easier to understand. + */ +public class LockdownManager { + /** + * Default maximum duration for a lockdown. + */ + public static final long DEFAULT_MAX_LOCKDOWN_TIME = TimeUnit.MINUTES.toMillis(5); + /** + * Default base duration for a lockdown. + */ + public static final long DEFAULT_BASE_LOCKDOWN_TIME = TimeUnit.SECONDS.toMillis(1); + /** + * Maximum duration for a lockdown, in milliseconds. + */ + private long maxLockdownTime = DEFAULT_MAX_LOCKDOWN_TIME; + /** + * Base duration for a lockdown, in milliseconds. + *

    + * On each attempt the time is doubled until it reaches {@link #maxLockdownTime}. + */ + private long baseLockdownTime = DEFAULT_BASE_LOCKDOWN_TIME; + /** + * Number of milliseconds after lockdownStartTime to lockdown for, or 0 if not currently locked down. + */ + private long lockdownTime = 0; + /** + * Timestamp of when the current lockdown started, or null if not currently locked down. + */ + private Date lockdownStartTime = null; + /** + * Clock instance used for time, injectable for testing. + */ + private final Clock clock; + + /** + * Construct a LockdownManager using the default system clock. + */ + public LockdownManager() { + this(new SystemClock()); + } + + /** + * Construct a LockdownManager using the provided clock. + * + * @param clock Clock object to use for lockdown logic + */ + public LockdownManager(Clock clock) { + this.clock = clock; + } + + /** + * Returns true if the system is in a lockdown. + * + * @return true if the system is in a lockdown, otherwise false + */ + public synchronized boolean isLockedDown() { + return lockdownStartTime != null && (clock.millis() - lockdownStartTime.getTime()) < lockdownTime; + } + + /** + * Reset the lockdown state, disabling lockdown and setting the backoff time to zero. + */ + public synchronized void resetState() { + lockdownTime = 0; + lockdownStartTime = null; + } + + /** + * Enable lockdown if it's not already enabled, using the recommended time + * from the provided {@link ConnectionException}, if any. + * + * @param connectionException ConnectionException to check for a recommended + * lockdown time, may be null + */ + public synchronized void setState(ConnectionException connectionException) { + // If we are already in a lockdown state, don't change anything + if (isLockedDown()) { + return; + } + + if (connectionException != null && connectionException.getRecommendedLockdownTime() != null) { + lockdownTime = connectionException.getRecommendedLockdownTime(); + } else if (lockdownTime != 0) { + lockdownTime = lockdownTime * 2; + } else { + lockdownTime = baseLockdownTime; + } + + lockdownTime = Math.min(maxLockdownTime, lockdownTime); + lockdownStartTime = clock.date(); + } + + public synchronized void setBaseLockdownTime(long baseLockdownTime) { + this.baseLockdownTime = baseLockdownTime; + } + + public synchronized void setMaxLockdownTime(long maxLockdownTime) { + this.maxLockdownTime = maxLockdownTime; + } +} diff --git a/raven/src/main/java/com/getsentry/raven/connection/LockedDownException.java b/raven/src/main/java/com/getsentry/raven/connection/LockedDownException.java new file mode 100644 index 00000000000..78e1ec90375 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/connection/LockedDownException.java @@ -0,0 +1,17 @@ +package com.getsentry.raven.connection; + +/** + * Exception thrown when attempting to send Events while in a lockdown. + */ +public class LockedDownException extends RuntimeException { + + /** + * Construct a LockedDownException with a message. + * + * @param message Exception message. + */ + public LockedDownException(String message) { + super(message); + } + +} diff --git a/raven/src/main/java/com/getsentry/raven/dsn/Dsn.java b/raven/src/main/java/com/getsentry/raven/dsn/Dsn.java index 3d7ab7ed2d1..8ea4979215b 100644 --- a/raven/src/main/java/com/getsentry/raven/dsn/Dsn.java +++ b/raven/src/main/java/com/getsentry/raven/dsn/Dsn.java @@ -244,9 +244,9 @@ public Map getOptions() { } /** - * Creates the URI of the sentry server. + * Creates the URI of the Sentry server. * - * @return the URI of the sentry server. + * @return the URI of the Sentry server. */ public URI getUri() { return uri; diff --git a/raven/src/main/java/com/getsentry/raven/time/Clock.java b/raven/src/main/java/com/getsentry/raven/time/Clock.java index e11486b491f..f85fe9c44a0 100644 --- a/raven/src/main/java/com/getsentry/raven/time/Clock.java +++ b/raven/src/main/java/com/getsentry/raven/time/Clock.java @@ -5,7 +5,7 @@ /** * Clock interface, used to inject a Clock dependency so time can be adjusted in tests. */ -interface Clock { +public interface Clock { /** * Returns the Clock's time in milliseconds. diff --git a/raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java b/raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java index 130f79866c9..ed1cedd1f11 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java +++ b/raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java @@ -1,35 +1,38 @@ package com.getsentry.raven.connection; +import com.getsentry.raven.time.FixedClock; import mockit.*; import com.getsentry.raven.event.Event; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import java.util.Collections; +import java.util.Date; import java.util.HashSet; -import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; import static mockit.Deencapsulation.getField; import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.testng.AssertJUnit.fail; public class AbstractConnectionTest { + private static final Date FIXED_DATE = new Date(1483228800L); @Injectable private final String publicKey = "9bcf4a8c-f353-4f25-9dda-76a873fff905"; @Injectable private final String secretKey = "56a9d05e-9032-4fdd-8f67-867d526422f9"; @Tested private AbstractConnection abstractConnection = null; - @Injectable - private Lock mockLock = null; - @Injectable - private Condition mockCondition = null; - //Disable thread sleep during the tests - @Mocked("sleep") - private Thread mockThread = null; + private FixedClock fixedClock = new FixedClock(FIXED_DATE); + private LockdownManager lockdownManager = new LockdownManager(fixedClock); + + @BeforeMethod + public void setup() { + fixedClock = new FixedClock(FIXED_DATE); + lockdownManager = new LockdownManager(fixedClock); + } @Test public void testAuthHeader() throws Exception { @@ -43,9 +46,6 @@ public void testAuthHeader() throws Exception { @Test public void testSuccessfulSendCallsDoSend(@Injectable final Event mockEvent) throws Exception { - setField(abstractConnection, "lock", mockLock); - setField(abstractConnection, "condition", mockCondition); - abstractConnection.send(mockEvent); new Verifications() {{ @@ -55,8 +55,8 @@ public void testSuccessfulSendCallsDoSend(@Injectable final Event mockEvent) thr @Test public void testExceptionOnSendStartLockDown(@Injectable final Event mockEvent) throws Exception { - setField(abstractConnection, "lock", mockLock); - setField(abstractConnection, "condition", mockCondition); + setField(abstractConnection, "lockdownManager", lockdownManager); + new NonStrictExpectations() {{ abstractConnection.doSend((Event) any); result = new ConnectionException(); @@ -68,15 +68,22 @@ public void testExceptionOnSendStartLockDown(@Injectable final Event mockEvent) // ignore } - new Verifications() {{ - mockLock.lock(); - mockCondition.signalAll(); - mockLock.unlock(); - }}; + Date lockdownStartTime = getField(lockdownManager, "lockdownStartTime"); + assertThat(lockdownStartTime, is(FIXED_DATE)); + + // Send while in lockdown throws LockedDownException + try { + abstractConnection.send(mockEvent); + fail(); + } catch (LockedDownException e) { + // ignore + } } @Test - public void testLockDownDoublesTheWaitingTime(@Injectable final Event mockEvent) throws Exception { + public void testLockDownDoublesTheTime(@Injectable final Event mockEvent) throws Exception { + setField(abstractConnection, "lockdownManager", lockdownManager); + new NonStrictExpectations() {{ abstractConnection.doSend((Event) any); result = new ConnectionException(); @@ -88,16 +95,31 @@ public void testLockDownDoublesTheWaitingTime(@Injectable final Event mockEvent) // ignore } - long waitingTimeAfter = getField(abstractConnection, "waitingTime"); - assertThat(waitingTimeAfter, is(AbstractConnection.DEFAULT_BASE_WAITING_TIME * 2)); - new Verifications() {{ - Thread.sleep(AbstractConnection.DEFAULT_BASE_WAITING_TIME); - }}; + // Check for default lockdown time + long lockdownTimeAfter = getField(lockdownManager, "lockdownTime"); + assertThat(lockdownTimeAfter, is(LockdownManager.DEFAULT_BASE_LOCKDOWN_TIME)); + + // Roll forward by the base lockdown time, allowing the lockdown to retried + fixedClock.tick(LockdownManager.DEFAULT_BASE_LOCKDOWN_TIME, TimeUnit.MILLISECONDS); + + // Send a second event, doubling the lockdown + try { + abstractConnection.send(mockEvent); + } catch (Exception e) { + // ignore + } + + // Check for doubled lockdown time + long lockdownTimeAfter2 = getField(lockdownManager, "lockdownTime"); + assertThat(lockdownTimeAfter2, is(LockdownManager.DEFAULT_BASE_LOCKDOWN_TIME * 2)); } @Test public void testLockDownDoesntDoubleItAtMax(@Injectable final Event mockEvent) throws Exception { - setField(abstractConnection, "waitingTime", AbstractConnection.DEFAULT_MAX_WAITING_TIME); + setField(abstractConnection, "lockdownManager", lockdownManager); + setField(lockdownManager, "lockdownTime", LockdownManager.DEFAULT_MAX_LOCKDOWN_TIME); + setField(lockdownManager, "lockdownStartTime", fixedClock.date()); + new NonStrictExpectations() {{ abstractConnection.doSend((Event) any); result = new ConnectionException(); @@ -109,11 +131,8 @@ public void testLockDownDoesntDoubleItAtMax(@Injectable final Event mockEvent) t // ignore } - long waitingTimeAfter = getField(abstractConnection, "waitingTime"); - assertThat(waitingTimeAfter, is(AbstractConnection.DEFAULT_MAX_WAITING_TIME)); - new Verifications() {{ - Thread.sleep(AbstractConnection.DEFAULT_MAX_WAITING_TIME); - }}; + long lockdownTimeAfter = getField(lockdownManager, "lockdownTime"); + assertThat(lockdownTimeAfter, is(LockdownManager.DEFAULT_MAX_LOCKDOWN_TIME)); } @Test @@ -142,8 +161,26 @@ public void onFailure(Event event, Exception exception) { // ignore } - assertThat(callbackCalled.get(), is(true)); } + @Test + public void testRecommendedLockdownRespected(@Injectable final Event mockEvent) throws Exception { + setField(abstractConnection, "lockdownManager", lockdownManager); + + final long recommendedLockdownWaitTime = 12345L; + new NonStrictExpectations() {{ + abstractConnection.doSend((Event) any); + result = new ConnectionException("Message", null, recommendedLockdownWaitTime); + }}; + + try { + abstractConnection.send(mockEvent); + } catch (Exception e) { + // ignore + } + + long lockdownTimeAfter = getField(lockdownManager, "lockdownTime"); + assertThat(lockdownTimeAfter, is(recommendedLockdownWaitTime)); + } } diff --git a/raven/src/test/java/com/getsentry/raven/connection/BufferedConnectionTest.java b/raven/src/test/java/com/getsentry/raven/connection/BufferedConnectionTest.java index 24cef72821b..11586caa7cf 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/BufferedConnectionTest.java +++ b/raven/src/test/java/com/getsentry/raven/connection/BufferedConnectionTest.java @@ -4,6 +4,7 @@ import com.getsentry.raven.buffer.Buffer; import com.getsentry.raven.event.Event; import com.getsentry.raven.event.EventBuilder; +import com.getsentry.raven.time.FixedClock; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -11,15 +12,23 @@ import org.testng.collections.Sets; import java.io.IOException; +import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.core.Is.is; public class BufferedConnectionTest extends BaseTest { + private static final Date FIXED_DATE = new Date(1483228800L); + private FixedClock fixedClock = new FixedClock(FIXED_DATE); + private LockdownManager lockdownManager = new LockdownManager(fixedClock); + private Set bufferedEvents; private List sentEvents; private Buffer mockBuffer; @@ -33,9 +42,9 @@ public void setup() { sentEvents = Lists.newArrayList(); connectionUp = true; - mockConnection = new Connection() { + mockConnection = new AbstractConnection("public", "private") { @Override - public void send(Event event) throws ConnectionException { + protected void doSend(Event event) throws ConnectionException { if (connectionUp) { sentEvents.add(event); } else { @@ -54,6 +63,9 @@ public void close() throws IOException { } }; + fixedClock = new FixedClock(FIXED_DATE); + lockdownManager = new LockdownManager(fixedClock); + mockBuffer = new Buffer() { @Override public void add(Event event) { @@ -84,6 +96,8 @@ public void teardown() throws IOException { @Test public void test() throws Exception { + setField(mockConnection, "lockdownManager", lockdownManager); + Event event = new EventBuilder().build(); connectionUp = false; try { @@ -94,6 +108,18 @@ public void test() throws Exception { assertThat(bufferedEvents.size(), equalTo(1)); assertThat(bufferedEvents.iterator().next(), equalTo(event)); + // Attempt sending a second event (should be in lockdown) + Event event2 = new EventBuilder().build(); + try { + bufferedConnection.send(event2); + } catch (Exception e) { + + } + assertThat(bufferedEvents.size(), equalTo(2)); + + // End the lockdown + fixedClock.tick(LockdownManager.DEFAULT_MAX_LOCKDOWN_TIME, TimeUnit.MILLISECONDS); + connectionUp = true; waitUntilTrue(1000, new Callable() { @Override @@ -102,6 +128,7 @@ public Boolean call() throws Exception { } }); assertThat(bufferedEvents.size(), equalTo(0)); - assertThat(sentEvents.get(0), equalTo(event)); + assertThat(sentEvents.contains(event), is(true)); + assertThat(sentEvents.contains(event2), is(true)); } } diff --git a/raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java b/raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java index c81fd4e0c54..ddd4b72a9ec 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java @@ -21,6 +21,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.isEmptyOrNullString; import static org.hamcrest.Matchers.not; +import static org.testng.Assert.fail; public class HttpConnectionTest { @Injectable @@ -136,6 +137,26 @@ public void testHttpErrorThrowsAnException(@Injectable final Event mockEvent) th httpConnection.doSend(mockEvent); } + @Test + public void testRetryAfterHeader(@Injectable final Event mockEvent) throws Exception { + final String httpErrorMessage = "93e3ddb1-c4f3-46c3-9900-529de83678b7"; + new NonStrictExpectations() {{ + mockUrlConnection.getOutputStream(); + result = new IOException(); + mockUrlConnection.getErrorStream(); + result = new ByteArrayInputStream(httpErrorMessage.getBytes()); + mockUrlConnection.getHeaderField("Retry-After"); + result = "12345"; + }}; + + try { + httpConnection.doSend(mockEvent); + fail(); + } catch (ConnectionException e) { + assertThat(e.getRecommendedLockdownTime(), is(12345L * 1000L)); + } + } + @Test public void testApiUrlCreation(@Injectable final URI sentryUri) throws Exception { final String uri = "http://host/sentry/"; From 4354dd14608a60af65a09ec802725db0d945ebb8 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 2 Mar 2017 18:13:26 -0600 Subject: [PATCH 1516/2152] Only 'store' events in integration tests if the response is a 200. (#324) --- raven/src/test/java/com/getsentry/raven/BaseIT.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/raven/src/test/java/com/getsentry/raven/BaseIT.java b/raven/src/test/java/com/getsentry/raven/BaseIT.java index 95992bc4099..065578b17e0 100644 --- a/raven/src/test/java/com/getsentry/raven/BaseIT.java +++ b/raven/src/test/java/com/getsentry/raven/BaseIT.java @@ -63,9 +63,11 @@ public List getStoredEvents() { List events = new ArrayList<>(); for (ServeEvent serveEvent : wireMockRule.getAllServeEvents()) { - UnmarshalledEvent event = unmarshaller.unmarshal(new ByteArrayInputStream(serveEvent.getRequest().getBody())); - if (event != null) { - events.add(event); + if (serveEvent.getResponse().getStatus() == 200) { + UnmarshalledEvent event = unmarshaller.unmarshal(new ByteArrayInputStream(serveEvent.getRequest().getBody())); + if (event != null) { + events.add(event); + } } } From bda5af28b985b340b350daa5ac57adf409cf9887 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 3 Mar 2017 07:38:29 -0600 Subject: [PATCH 1517/2152] Fix RandomEventSampler logic. (#325) --- .../raven/connection/RandomEventSampler.java | 9 +++--- .../connection/RandomEventSamplerTest.java | 30 +++++++++++-------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/raven/src/main/java/com/getsentry/raven/connection/RandomEventSampler.java b/raven/src/main/java/com/getsentry/raven/connection/RandomEventSampler.java index b509d8ca7b2..adb4f31f824 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/RandomEventSampler.java +++ b/raven/src/main/java/com/getsentry/raven/connection/RandomEventSampler.java @@ -9,9 +9,7 @@ * sample rate to random bits from the Event's ID. */ public class RandomEventSampler implements EventSampler { - private static final int RATE_MULTIPLIER = 100; - - private int sampleRate; + private double sampleRate; private Random random; /** @@ -34,7 +32,7 @@ public RandomEventSampler(double sampleRate) { * @param random Random instance to use for sampling, useful for testing. */ public RandomEventSampler(double sampleRate, Random random) { - this.sampleRate = (int) (sampleRate * RATE_MULTIPLIER); + this.sampleRate = sampleRate; this.random = random; } @@ -46,6 +44,7 @@ public RandomEventSampler(double sampleRate, Random random) { */ @Override public boolean shouldSendEvent(Event event) { - return Math.abs(random.nextInt()) % RATE_MULTIPLIER < sampleRate; + double randomDouble = random.nextDouble(); + return sampleRate >= Math.abs(randomDouble); } } diff --git a/raven/src/test/java/com/getsentry/raven/connection/RandomEventSamplerTest.java b/raven/src/test/java/com/getsentry/raven/connection/RandomEventSamplerTest.java index 624a6299356..b638043a60b 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/RandomEventSamplerTest.java +++ b/raven/src/test/java/com/getsentry/raven/connection/RandomEventSamplerTest.java @@ -13,24 +13,30 @@ public class RandomEventSamplerTest { private Event event = new EventBuilder().build(); - private Random seededRandom = new Random(); - @BeforeMethod - public void setup() { - // set our Random to a known seed such that nextInt % 100 == -25 - seededRandom.setSeed(1); + private void testRandomEventSampler(double sampleRate, double fakeRandom, boolean expected) { + RandomEventSampler randomEventSampler = new RandomEventSampler(sampleRate, new FakeRandom(fakeRandom)); + assertThat(randomEventSampler.shouldSendEvent(event), is(expected)); } @Test public void testShouldSend() { - RandomEventSampler randomEventSampler = new RandomEventSampler(0.5, seededRandom); - assertThat(randomEventSampler.shouldSendEvent(event), is(true)); + testRandomEventSampler(0.8, 0.75, true); + testRandomEventSampler(1.0, 0.99, true); + testRandomEventSampler(0.1, 0.75, false); + testRandomEventSampler(0.0, 1.0, false); } - @Test - public void testShouldNotSend() { - RandomEventSampler randomEventSampler = new RandomEventSampler(0.1, seededRandom); - assertThat(randomEventSampler.shouldSendEvent(event), is(false)); - } + private class FakeRandom extends Random { + private double fakeRandom; + FakeRandom(double FakeRandom) { + fakeRandom = FakeRandom; + } + + @Override + public double nextDouble() { + return fakeRandom; + } + } } From e9b55a99595b59383edfa876b595a6935c474b26 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 3 Mar 2017 13:23:24 -0600 Subject: [PATCH 1518/2152] Add lockdownLogger so that lockdown messages can be squelched. (#323) --- raven/src/main/java/com/getsentry/raven/Raven.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/raven/src/main/java/com/getsentry/raven/Raven.java b/raven/src/main/java/com/getsentry/raven/Raven.java index a4045065a07..62c7a57055f 100644 --- a/raven/src/main/java/com/getsentry/raven/Raven.java +++ b/raven/src/main/java/com/getsentry/raven/Raven.java @@ -1,6 +1,7 @@ package com.getsentry.raven; import com.getsentry.raven.connection.Connection; +import com.getsentry.raven.connection.LockedDownException; import com.getsentry.raven.environment.RavenEnvironment; import com.getsentry.raven.event.Event; import com.getsentry.raven.event.EventBuilder; @@ -23,6 +24,9 @@ */ public class Raven { private static final Logger logger = LoggerFactory.getLogger(Raven.class); + // CHECKSTYLE.OFF: ConstantName + private static final Logger lockdownLogger = LoggerFactory.getLogger(Raven.class + ".lockdown"); + // CHECKSTYLE.ON: ConstantName /** * The most recently constructed Raven instance, used by static helper methods like {@link Raven#capture(Event)}. */ @@ -93,6 +97,8 @@ public void runBuilderHelpers(EventBuilder eventBuilder) { public void sendEvent(Event event) { try { connection.send(event); + } catch (LockedDownException e) { + lockdownLogger.warn("The connection to Sentry is currently locked down.", e); } catch (Exception e) { logger.error("An exception occurred while sending the event to Sentry.", e); } finally { From c36be42ab871b901e7e413f8fe7a282c4f19c0ce Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 6 Mar 2017 12:39:35 -0600 Subject: [PATCH 1519/2152] Install source jar when doing local install. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 47a6a91d413..2ec0ea3fc86 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ verify: test: verify install: - $(MVN) install -Dcheckstyle.skip=true -DskipTests + $(MVN) source:jar install -Dcheckstyle.skip=true -DskipTests clean: $(MVN) clean From 3ed7a35716f97bbcc187295c08ce8c525ac2bd0b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 6 Mar 2017 13:50:27 -0600 Subject: [PATCH 1520/2152] Adjust Makefile for macOS. --- Makefile | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 2ec0ea3fc86..68e9b0038ec 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,13 @@ +# Temporary: Required for macOS release +# export GPG_TTY=`tty` +# eval $(gpg-agent --daemon) + .PHONY: checkstyle compile test install clean prepare prepareDocs prepareMvn prepareChanges perform verify +# TODO: Fix to work between macOS and Linux MVN=mvn -e +ECHO=gecho +SED=gsed all: checkstyle test install @@ -23,14 +30,14 @@ clean: prepareDocs: # Store previously released version - $(eval PREVIOUS_RELEASE=$(shell grep -E "^Version" CHANGES | head -2 | tail -1 | sed -e 's/Version //')) + $(eval PREVIOUS_RELEASE=$(shell grep -E "^Version" CHANGES | head -2 | tail -1 | $(SED) -e 's/Version //')) @echo Previous release: $(PREVIOUS_RELEASE) # Store release project version - $(eval RELEASE_VERSION=$(shell mvn help:evaluate -Dexpression=project.version | grep -Ev '^\[' | sed -e 's/-SNAPSHOT//')) + $(eval RELEASE_VERSION=$(shell mvn help:evaluate -Dexpression=project.version | grep -Ev '^\[' | $(SED) -e 's/-SNAPSHOT//')) @echo This release: $(RELEASE_VERSION) # Fix released version in documentation @echo Fixing documentation versions - find . \( -name '*.md' -or -name '*.rst' \) -exec sed -i -e 's/$(PREVIOUS_RELEASE)/$(RELEASE_VERSION)/g' {} \; + find . \( -name '*.md' -or -name '*.rst' \) -exec $(SED) -i -e 's/$(PREVIOUS_RELEASE)/$(RELEASE_VERSION)/g' {} \; # Commit documentation changes @echo Committing documentation version changes git commit -a -m 'Bump docs to $(RELEASE_VERSION)' @@ -41,13 +48,13 @@ prepareMvn: prepareChanges: # Store new project version - $(eval DEV_VERSION=$(shell mvn help:evaluate -Dexpression=project.version | grep -Ev '^\[' | sed -e 's/-SNAPSHOT//')) + $(eval DEV_VERSION=$(shell mvn help:evaluate -Dexpression=project.version | grep -Ev '^\[' | $(SED) -e 's/-SNAPSHOT//')) @echo Development version: $(DEV_VERSION) # Store enough dashes to go under "Version X.Y.Z", accounting for changes in the $VERSION length $(eval DASHES=$(shell python -c 'print("-" * (8 + len("$(DEV_VERSION)")))')) # Add new Version section to the top of the CHANGES file @echo Updating CHANGES file - echo -e "Version $(DEV_VERSION)\n$(DASHES)\n\n-\n" > CHANGES.new && cat CHANGES >> CHANGES.new && mv CHANGES.new CHANGES + $(ECHO) -e "Version $(DEV_VERSION)\n$(DASHES)\n\n-\n" > CHANGES.new && cat CHANGES >> CHANGES.new && mv CHANGES.new CHANGES git add CHANGES git commit -m "Bump CHANGES to $(DEV_VERSION)" From 8709ef2edc04ab3505b8e4fa21c376426b26e33d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 6 Mar 2017 13:53:03 -0600 Subject: [PATCH 1521/2152] Bump docs to 7.8.3 --- docs/modules/android.rst | 4 ++-- docs/modules/appengine.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/raven.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index be7d3781ac3..3c4b2f9f63b 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -8,9 +8,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-android:7.8.2' + compile 'com.getsentry.raven:raven-android:7.8.3' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index de7a7bf6c5c..759235acd06 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: com.getsentry.raven raven-appengine - 7.8.2 + 7.8.3 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-appengine:7.8.2' + compile 'com.getsentry.raven:raven-appengine:7.8.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-appengine" % "7.8.2" + libraryDependencies += "com.getsentry.raven" % "raven-appengine" % "7.8.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index a74c6e7900f..9f355a2c0df 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-log4j - 7.8.2 + 7.8.3 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-log4j:7.8.2' + compile 'com.getsentry.raven:raven-log4j:7.8.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-log4j" % "7.8.2" + libraryDependencies += "com.getsentry.raven" % "raven-log4j" % "7.8.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 74025050200..b4e89e68e80 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-log4j2 - 7.8.2 + 7.8.3 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-log4j2:7.8.2' + compile 'com.getsentry.raven:raven-log4j2:7.8.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-log4j2" % "7.8.2" + libraryDependencies += "com.getsentry.raven" % "raven-log4j2" % "7.8.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 916e25830a1..2fdb2513bef 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-logback - 7.8.2 + 7.8.3 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-logback:7.8.2' + compile 'com.getsentry.raven:raven-logback:7.8.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-logback" % "7.8.2" + libraryDependencies += "com.getsentry.raven" % "raven-logback" % "7.8.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index 71db9fd41f7..da8a6b1d84d 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -18,22 +18,22 @@ Using Maven: com.getsentry.raven raven - 7.8.2 + 7.8.3 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven:7.8.2' + compile 'com.getsentry.raven:raven:7.8.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.2" + libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index f8a75125eaf..3c95078b0fd 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -15,22 +15,22 @@ Using Maven: com.getsentry.raven raven - 7.8.2 + 7.8.3 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven:7.8.2' + compile 'com.getsentry.raven:raven:7.8.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.2" + libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 6d7280ed06114ef383a9c49cbad72ebbae689f3b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 6 Mar 2017 13:54:12 -0600 Subject: [PATCH 1522/2152] [maven-release-plugin] prepare release v7.8.3 --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 5fde186869d..0a6b3813687 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.8.3-SNAPSHOT + 7.8.3 pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v7.8.3 https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index 8d5238bfab4..d04caacd7ae 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.3-SNAPSHOT + 7.8.3 raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 9c9922a5f30..869f53c9475 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.8.3-SNAPSHOT + 7.8.3 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index f0d96f28d09..3bb9820a9b1 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.3-SNAPSHOT + 7.8.3 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 85193eca6fe..162c3af4b74 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.3-SNAPSHOT + 7.8.3 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index ea1bf0c863c..dddf12cc5a7 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.3-SNAPSHOT + 7.8.3 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 7eb46ec57c5..f116e3d8aa3 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.3-SNAPSHOT + 7.8.3 raven From ae2a2d3a4d99615b38864e2d55c5b5127a67ec16 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 6 Mar 2017 13:54:13 -0600 Subject: [PATCH 1523/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 0a6b3813687..23f5c036e3b 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.8.3 + 7.8.4-SNAPSHOT pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v7.8.3 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index d04caacd7ae..cfebfe7ed7b 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.3 + 7.8.4-SNAPSHOT raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 869f53c9475..580e423e53f 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.8.3 + 7.8.4-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 3bb9820a9b1..8f140de41f0 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.3 + 7.8.4-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 162c3af4b74..2af26fd886d 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.3 + 7.8.4-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index dddf12cc5a7..c42aa8f1e37 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.3 + 7.8.4-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index f116e3d8aa3..1919cb4feb7 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.3 + 7.8.4-SNAPSHOT raven From 4343971e4d3c2c0217ab8a6dc1b62246a42ae814 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 6 Mar 2017 13:54:16 -0600 Subject: [PATCH 1524/2152] Bump CHANGES to 7.8.4 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 435af29bc8a..38bc5f5d7e1 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 7.8.4 +------------- + +- + Version 7.8.3 ------------- From 04f71ce59d1317cc6ec05b26563b7ccb40569a1a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 6 Mar 2017 16:49:38 -0600 Subject: [PATCH 1525/2152] Add contexts field support to Events. (#321) * Add contexts field support to Events. * Allow all value types. * Fix CHANGES * Make inner context maps immutable. * Fix type. --- CHANGES | 2 +- .../com/getsentry/raven/RavenContext.java | 2 + .../java/com/getsentry/raven/event/Event.java | 12 ++++++ .../getsentry/raven/event/EventBuilder.java | 24 +++++++++--- .../raven/marshaller/json/JsonMarshaller.java | 25 ++++++++++++- .../marshaller/json/JsonMarshallerTest.java | 37 ++++++++++++++++--- .../unmarshaller/event/UnmarshalledEvent.java | 5 +++ .../json/jsonmarshallertest/testContexts.json | 34 +++++++++++++++++ 8 files changed, 127 insertions(+), 14 deletions(-) create mode 100644 raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testContexts.json diff --git a/CHANGES b/CHANGES index 38bc5f5d7e1..da3df981602 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 7.8.4 ------------- -- +- Add ``contexts`` field support to Events. Version 7.8.3 ------------- diff --git a/raven/src/main/java/com/getsentry/raven/RavenContext.java b/raven/src/main/java/com/getsentry/raven/RavenContext.java index 2bccccee94b..615a2c2172d 100644 --- a/raven/src/main/java/com/getsentry/raven/RavenContext.java +++ b/raven/src/main/java/com/getsentry/raven/RavenContext.java @@ -1,4 +1,5 @@ package com.getsentry.raven; + import com.getsentry.raven.event.Breadcrumb; import com.getsentry.raven.event.User; import com.getsentry.raven.util.CircularFifoQueue; @@ -8,6 +9,7 @@ import java.util.Iterator; import java.util.List; import java.util.UUID; + /** * RavenContext is used to hold {@link ThreadLocal} context data (such as * {@link Breadcrumb}s) so that data may be collected in different parts diff --git a/raven/src/main/java/com/getsentry/raven/event/Event.java b/raven/src/main/java/com/getsentry/raven/event/Event.java index d8f7a682ec6..57ec748e06e 100644 --- a/raven/src/main/java/com/getsentry/raven/event/Event.java +++ b/raven/src/main/java/com/getsentry/raven/event/Event.java @@ -76,6 +76,10 @@ public class Event implements Serializable { * List of Breadcrumb objects related to the event. */ private List breadcrumbs = new ArrayList<>(); + /** + * Map of map of context objects related to the event. + */ + private Map> contexts = new HashMap<>(); /** * Identifies the version of the application. */ @@ -201,6 +205,14 @@ void setBreadcrumbs(List breadcrumbs) { this.breadcrumbs = breadcrumbs; } + public Map> getContexts() { + return contexts; + } + + public void setContexts(Map> contexts) { + this.contexts = contexts; + } + public Map getTags() { return tags; } diff --git a/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java b/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java index 212a429518c..bc8a583449a 100644 --- a/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java +++ b/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java @@ -7,11 +7,7 @@ import java.net.InetAddress; import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; @@ -114,6 +110,13 @@ private static void makeImmutable(Event event) { // Make the breadcrumbs unmodifiable event.setBreadcrumbs(Collections.unmodifiableList(event.getBreadcrumbs())); + // Make the contexts unmodifiable + Map> tempContexts = new HashMap<>(); + for (Map.Entry> contextEntry : event.getContexts().entrySet()) { + tempContexts.put(contextEntry.getKey(), Collections.unmodifiableMap(contextEntry.getValue())); + } + event.setContexts(Collections.unmodifiableMap(tempContexts)); + // Make the extra properties unmodifiable (everything in it is still mutable though) event.setExtra(Collections.unmodifiableMap(event.getExtra())); @@ -280,6 +283,17 @@ public EventBuilder withBreadcrumbs(List breadcrumbs) { return this; } + /** + * Adds a map of map of context objects to the event. + * + * @param contexts map of map of contexts + * @return the current {@code EventBuilder} for chained calls. + */ + public EventBuilder withContexts(Map> contexts) { + event.setContexts(contexts); + return this; + } + /** * Sets the serverName in the event. * diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java index 6ae5c03535d..3914c77505b 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java @@ -65,6 +65,10 @@ public class JsonMarshaller implements Marshaller { * List of breadcrumbs for this event. */ public static final String BREADCRUMBS = "breadcrumbs"; + /** + * Map of map of contexts for this event. + */ + public static final String CONTEXTS = "contexts"; /** * Identifies the host client from which the event was recorded. */ @@ -172,6 +176,7 @@ private void writeContent(JsonGenerator generator, Event event) throws IOExcepti writeSdk(generator, event.getSdkName(), event.getSdkVersion()); writeTags(generator, event.getTags()); writeBreadcumbs(generator, event.getBreadcrumbs()); + writeContexts(generator, event.getContexts()); generator.writeStringField(SERVER_NAME, event.getServerName()); generator.writeStringField(RELEASE, event.getRelease()); generator.writeStringField(ENVIRONMENT, event.getEnvironment()); @@ -276,7 +281,7 @@ private void writeTags(JsonGenerator generator, Map tags) throws @SuppressWarnings("checkstyle:magicnumber") private void writeBreadcumbs(JsonGenerator generator, List breadcrumbs) throws IOException { - if (breadcrumbs.size() < 1) { + if (breadcrumbs.isEmpty()) { return; } @@ -299,7 +304,7 @@ private void writeBreadcumbs(JsonGenerator generator, List breadcrum if (breadcrumb.getCategory() != null) { generator.writeStringField("category", breadcrumb.getCategory()); } - if (breadcrumb.getData() != null && breadcrumb.getData().size() > 0) { + if (breadcrumb.getData() != null && !breadcrumb.getData().isEmpty()) { generator.writeObjectFieldStart("data"); for (Map.Entry entry : breadcrumb.getData().entrySet()) { generator.writeStringField(entry.getKey(), entry.getValue()); @@ -312,6 +317,22 @@ private void writeBreadcumbs(JsonGenerator generator, List breadcrum generator.writeEndObject(); } + private void writeContexts(JsonGenerator generator, Map> contexts) throws IOException { + if (contexts.isEmpty()) { + return; + } + + generator.writeObjectFieldStart(CONTEXTS); + for (Map.Entry> contextEntry : contexts.entrySet()) { + generator.writeObjectFieldStart(contextEntry.getKey()); + for (Map.Entry innerContextEntry : contextEntry.getValue().entrySet()) { + generator.writeObjectField(innerContextEntry.getKey(), innerContextEntry.getValue()); + } + generator.writeEndObject(); + } + generator.writeEndObject(); + } + /** * Trims a message, ensuring that the maximum length {@link #maxMessageLength} isn't reached. * diff --git a/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java index b88c40893d6..de92f2ac0d0 100644 --- a/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java @@ -12,12 +12,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.UUID; +import java.util.*; import static com.getsentry.raven.marshaller.json.JsonComparisonUtil.*; import static org.hamcrest.MatcherAssert.assertThat; @@ -267,6 +262,36 @@ public void testEventBreadcrumbsWrittenProperly() throws Exception { assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testBreadcrumbs.json"))); } + @Test + public void testEventContextsWrittenProperly() throws Exception { + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); + + final HashMap> contexts = new HashMap<>(); + HashMap context1 = new HashMap<>(); + context1.put("context1key1", "context1value1"); + context1.put("context1key2", 12); + context1.put("context1key3", 1.3); + context1.put("context1key4", true); + + HashMap context2 = new HashMap<>(); + context2.put("context2key1", "context2value1"); + context2.put("context2key2", 22); + context2.put("context2key3", 2.3); + context2.put("context2key4", false); + + contexts.put("context1", context1); + contexts.put("context2", context2); + + new NonStrictExpectations() {{ + mockEvent.getContexts(); + result = contexts; + }}; + + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); + + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testContexts.json"))); + } + @Test public void testEventChecksumWrittenProperly(@Injectable("1234567890abcdef") final String mockChecksum) throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); diff --git a/raven/src/test/java/com/getsentry/raven/unmarshaller/event/UnmarshalledEvent.java b/raven/src/test/java/com/getsentry/raven/unmarshaller/event/UnmarshalledEvent.java index 71926d2b68b..d78dde387aa 100644 --- a/raven/src/test/java/com/getsentry/raven/unmarshaller/event/UnmarshalledEvent.java +++ b/raven/src/test/java/com/getsentry/raven/unmarshaller/event/UnmarshalledEvent.java @@ -1,6 +1,7 @@ package com.getsentry.raven.unmarshaller.event; import com.fasterxml.jackson.annotation.JsonProperty; +import com.getsentry.raven.event.Breadcrumb; import com.getsentry.raven.unmarshaller.event.interfaces.ExceptionInterface; import com.getsentry.raven.unmarshaller.event.interfaces.MessageInterface; import com.getsentry.raven.unmarshaller.event.interfaces.StackTraceInterface; @@ -40,6 +41,10 @@ public class UnmarshalledEvent { private Map modules; @JsonProperty(value = "extra") private Map extras; + @JsonProperty(value = "breadcrumbs") + private List breadcrumbs; + @JsonProperty(value = "contexts") + private Map> contexts; @JsonProperty(value = "sentry.interfaces.Message") private MessageInterface messageInterface; private List exceptionInterfaces; diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testContexts.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testContexts.json new file mode 100644 index 00000000000..4094bf88507 --- /dev/null +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testContexts.json @@ -0,0 +1,34 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "release": null, + "environment": null, + "extra": {}, + "checksum": null, + "contexts": + { + "context1": { + "context1key1": "context1value1", + "context1key2": 12, + "context1key3": 1.3, + "context1key4": true + }, + "context2": { + "context2key1": "context2value1", + "context2key2": 22, + "context2key3": 2.3, + "context2key4": false + } + }, + "sdk": { + "name": null, + "version": null + } +} From aa5a1b83e9b99fb060db494521980004739c8bc4 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Mar 2017 10:06:52 -0600 Subject: [PATCH 1526/2152] Add small explainer of Android features, add way to disable buffering. (#332) --- docs/modules/android.rst | 14 +++++++ .../raven/android/AndroidRavenFactory.java | 5 ++- .../getsentry/raven/DefaultRavenFactory.java | 41 +++++++++++++++---- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 3c4b2f9f63b..9c08615d65e 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -1,6 +1,20 @@ Android ======= +Features +-------- + +The Raven Android SDK is built on top of the main Java SDK and supports all of the same +features, `configuration options `_, and more. + +Events will be `buffered to disk `_ +(in the application's cache directory) by default. This allows events to be sent at a +later time if the device does not have connectivity when an event is created. This can +be disabled by setting the DSN option ``raven.buffer.enabled`` to ``false``. + +An ``UncaughtExceptionHandler`` is configured so that crash events will be +stored to disk and sent the next time the application is run. + Installation ------------ diff --git a/raven-android/src/main/java/com/getsentry/raven/android/AndroidRavenFactory.java b/raven-android/src/main/java/com/getsentry/raven/android/AndroidRavenFactory.java index c69e9e75c17..9c2f9244df4 100644 --- a/raven-android/src/main/java/com/getsentry/raven/android/AndroidRavenFactory.java +++ b/raven-android/src/main/java/com/getsentry/raven/android/AndroidRavenFactory.java @@ -48,8 +48,9 @@ public com.getsentry.raven.Raven createRavenInstance(Dsn dsn) { @Override protected Buffer getBuffer(Dsn dsn) { File bufferDir; - if (dsn.getOptions().get(BUFFER_DIR_OPTION) != null) { - bufferDir = new File(dsn.getOptions().get(BUFFER_DIR_OPTION)); + String bufferDirOpt = dsn.getOptions().get(BUFFER_DIR_OPTION); + if (bufferDirOpt != null) { + bufferDir = new File(bufferDirOpt); } else { bufferDir = new File(ctx.getCacheDir().getAbsolutePath(), DEFAULT_BUFFER_DIR); } diff --git a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java index f8b4b1e2f53..c285dfda80e 100644 --- a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java @@ -53,6 +53,15 @@ public class DefaultRavenFactory extends RavenFactory { * Default timeout of an HTTP connection to Sentry. */ public static final int TIMEOUT_DEFAULT = (int) TimeUnit.SECONDS.toMillis(1); + /** + * Option to enable or disable Event buffering. A buffering directory is also required. + * This setting is mostly useful on Android where a buffering directory is set by default. + */ + public static final String BUFFER_ENABLED_OPTION = "raven.buffer.enabled"; + /** + * Default value for whether buffering is enabled (if a directory is also provided). + */ + public static final boolean BUFFER_ENABLED_DEFAULT = true; /** * Option to buffer events to disk when network is down. */ @@ -218,15 +227,17 @@ protected Connection createConnection(Dsn dsn) { throw new IllegalStateException("Couldn't create a connection for the protocol '" + protocol + "'"); } - Buffer eventBuffer = getBuffer(dsn); BufferedConnection bufferedConnection = null; - if (eventBuffer != null) { - long flushtime = getBufferFlushtime(dsn); - boolean gracefulShutdown = getBufferedConnectionGracefulShutdownEnabled(dsn); - Long shutdownTimeout = getBufferedConnectionShutdownTimeout(dsn); - bufferedConnection = new BufferedConnection(connection, eventBuffer, flushtime, gracefulShutdown, - shutdownTimeout); - connection = bufferedConnection; + if (getBufferEnabled(dsn)) { + Buffer eventBuffer = getBuffer(dsn); + if (eventBuffer != null) { + long flushtime = getBufferFlushtime(dsn); + boolean gracefulShutdown = getBufferedConnectionGracefulShutdownEnabled(dsn); + Long shutdownTimeout = getBufferedConnectionShutdownTimeout(dsn); + bufferedConnection = new BufferedConnection(connection, eventBuffer, flushtime, gracefulShutdown, + shutdownTimeout); + connection = bufferedConnection; + } } // Enable async unless its value is 'false'. @@ -595,6 +606,20 @@ protected int getTimeout(Dsn dsn) { return Util.parseInteger(dsn.getOptions().get(TIMEOUT_OPTION), TIMEOUT_DEFAULT); } + /** + * Whether or not buffering is enabled. + * + * @param dsn Sentry server DSN which may contain options. + * @return Whether or not buffering is enabled. + */ + protected boolean getBufferEnabled(Dsn dsn) { + String bufferEnabled = dsn.getOptions().get(BUFFER_ENABLED_OPTION); + if (bufferEnabled != null) { + return Boolean.parseBoolean(bufferEnabled); + } + return BUFFER_ENABLED_DEFAULT; + } + /** * Get the {@link Buffer} where events are stored when network is down. * From 08a3f43f1dda2a4150dd186f8e5eaf973311167e Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Mar 2017 12:28:08 -0600 Subject: [PATCH 1527/2152] Remove unused Maven Jetty plugin. --- raven-android/pom.xml | 4 ---- raven-log4j/pom.xml | 4 ---- raven-log4j2/pom.xml | 4 ---- raven-logback/pom.xml | 4 ---- 4 files changed, 16 deletions(-) diff --git a/raven-android/pom.xml b/raven-android/pom.xml index cfebfe7ed7b..a1c9ddb1f05 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -103,10 +103,6 @@ org.apache.maven.plugins maven-dependency-plugin - - org.eclipse.jetty - jetty-maven-plugin - org.apache.maven.plugins maven-checkstyle-plugin diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 8f140de41f0..1c1d80a8557 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -116,10 +116,6 @@ org.apache.maven.plugins maven-dependency-plugin - - org.eclipse.jetty - jetty-maven-plugin - org.apache.maven.plugins maven-checkstyle-plugin diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 2af26fd886d..1c5a3bfcb5f 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -114,10 +114,6 @@ org.apache.maven.plugins maven-dependency-plugin - - org.eclipse.jetty - jetty-maven-plugin - org.apache.maven.plugins maven-checkstyle-plugin diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index c42aa8f1e37..f4243c49a4d 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -107,10 +107,6 @@ org.apache.maven.plugins maven-dependency-plugin - - org.eclipse.jetty - jetty-maven-plugin - org.apache.maven.plugins maven-checkstyle-plugin From a74e8d74dbcadc0b7cf0098d01728677ee2cfe6f Mon Sep 17 00:00:00 2001 From: zliau Date: Fri, 10 Mar 2017 15:22:15 -0500 Subject: [PATCH 1528/2152] Use lockdown logger in AsyncConnection. (thanks zliau) --- .../com/getsentry/raven/connection/AsyncConnection.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java b/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java index 81cd1dcc18e..40c039276d5 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java @@ -1,5 +1,6 @@ package com.getsentry.raven.connection; +import com.getsentry.raven.Raven; import com.getsentry.raven.environment.RavenEnvironment; import com.getsentry.raven.event.Event; import org.slf4j.Logger; @@ -19,6 +20,9 @@ */ public class AsyncConnection implements Connection { private static final Logger logger = LoggerFactory.getLogger(AsyncConnection.class); + // CHECKSTYLE.OFF: ConstantName + private static final Logger lockdownLogger = LoggerFactory.getLogger(Raven.class + ".lockdown"); + // CHECKSTYLE.ON: ConstantName /** * Timeout of the {@link #executorService}, in milliseconds. */ @@ -165,6 +169,8 @@ public void run() { try { // The current thread is managed by raven actualConnection.send(event); + } catch (LockedDownException e) { + lockdownLogger.warn("The connection to Sentry is currently locked down.", e); } catch (Exception e) { logger.error("An exception occurred while sending the event to Sentry.", e); } finally { From 8da4c55840632515dae55e057f611bcb815d9d24 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Mar 2017 14:57:43 -0600 Subject: [PATCH 1529/2152] Note lockdown logger in CHANGES. --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index da3df981602..5332c23182a 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,8 @@ Version 7.8.4 ------------- - Add ``contexts`` field support to Events. +- Add ``com.getsentry.raven.Raven.lockdown`` logger that can be disabled to ignore warning messages + on each attempted Event send when Raven is in lockdown mode. Version 7.8.3 ------------- From 8b0342b9677fd686ea49d936a34717fe1ce9cf77 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Mar 2017 14:58:57 -0600 Subject: [PATCH 1530/2152] Bump docs to 7.8.4 --- docs/modules/android.rst | 4 ++-- docs/modules/appengine.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/raven.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 9c08615d65e..53a8dcdf287 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -22,9 +22,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-android:7.8.3' + compile 'com.getsentry.raven:raven-android:7.8.4' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 759235acd06..ffe86a02fff 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: com.getsentry.raven raven-appengine - 7.8.3 + 7.8.4 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-appengine:7.8.3' + compile 'com.getsentry.raven:raven-appengine:7.8.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-appengine" % "7.8.3" + libraryDependencies += "com.getsentry.raven" % "raven-appengine" % "7.8.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 9f355a2c0df..6cdfa0e6fdc 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-log4j - 7.8.3 + 7.8.4 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-log4j:7.8.3' + compile 'com.getsentry.raven:raven-log4j:7.8.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-log4j" % "7.8.3" + libraryDependencies += "com.getsentry.raven" % "raven-log4j" % "7.8.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index b4e89e68e80..5bcc3aee0e3 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-log4j2 - 7.8.3 + 7.8.4 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-log4j2:7.8.3' + compile 'com.getsentry.raven:raven-log4j2:7.8.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-log4j2" % "7.8.3" + libraryDependencies += "com.getsentry.raven" % "raven-log4j2" % "7.8.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 2fdb2513bef..d67c182674e 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-logback - 7.8.3 + 7.8.4 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-logback:7.8.3' + compile 'com.getsentry.raven:raven-logback:7.8.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-logback" % "7.8.3" + libraryDependencies += "com.getsentry.raven" % "raven-logback" % "7.8.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index da8a6b1d84d..205e8907506 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -18,22 +18,22 @@ Using Maven: com.getsentry.raven raven - 7.8.3 + 7.8.4 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven:7.8.3' + compile 'com.getsentry.raven:raven:7.8.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.3" + libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index 3c95078b0fd..bd442ab4c17 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -15,22 +15,22 @@ Using Maven: com.getsentry.raven raven - 7.8.3 + 7.8.4 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven:7.8.3' + compile 'com.getsentry.raven:raven:7.8.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.3" + libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From ff174d2aec24959fd5e9292813d2302b91c79afe Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Mar 2017 15:00:10 -0600 Subject: [PATCH 1531/2152] [maven-release-plugin] prepare release v7.8.4 --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 23f5c036e3b..d60aa41caf7 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.8.4-SNAPSHOT + 7.8.4 pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v7.8.4 https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index a1c9ddb1f05..14c9fb94113 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.4-SNAPSHOT + 7.8.4 raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 580e423e53f..3ce9c33b478 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.8.4-SNAPSHOT + 7.8.4 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 1c1d80a8557..103649fd5b0 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.4-SNAPSHOT + 7.8.4 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 1c5a3bfcb5f..863f275b9db 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.4-SNAPSHOT + 7.8.4 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index f4243c49a4d..06c632983ff 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.4-SNAPSHOT + 7.8.4 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 1919cb4feb7..8fdf73b8824 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.4-SNAPSHOT + 7.8.4 raven From d58388c2f1fd1434e8290059731fec88e824043c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Mar 2017 15:00:10 -0600 Subject: [PATCH 1532/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index d60aa41caf7..bb7f842dc41 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.8.4 + 7.8.5-SNAPSHOT pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v7.8.4 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index 14c9fb94113..372a33929b7 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.4 + 7.8.5-SNAPSHOT raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 3ce9c33b478..0851a41b5fd 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.8.4 + 7.8.5-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 103649fd5b0..053ec0d8657 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.4 + 7.8.5-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 863f275b9db..c4ff2e7e006 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.4 + 7.8.5-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 06c632983ff..db7376bfb5a 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.4 + 7.8.5-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 8fdf73b8824..b82d2fec822 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.4 + 7.8.5-SNAPSHOT raven From 8b4edd3cc299c46961317bcb8d1a56467acd9b57 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Mar 2017 15:00:13 -0600 Subject: [PATCH 1533/2152] Bump CHANGES to 7.8.5 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 5332c23182a..3f76995b617 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 7.8.5 +------------- + +- + Version 7.8.4 ------------- From 9a197f0723273a121bc95ab1fbf1a8e9a089cfc2 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Mar 2017 15:35:21 -0600 Subject: [PATCH 1534/2152] Add a default user id on Android. (#333) --- .../android/event/helper/AndroidEventBuilderHelper.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java b/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java index 299218ce4f0..c27c578e713 100644 --- a/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java +++ b/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java @@ -2,10 +2,12 @@ import android.content.Context; import android.content.pm.PackageManager; +import android.provider.Settings; import android.util.Log; import com.getsentry.raven.environment.RavenEnvironment; import com.getsentry.raven.event.EventBuilder; import com.getsentry.raven.event.helper.EventBuilderHelper; +import com.getsentry.raven.event.interfaces.UserInterface; /** * EventBuilderHelper that makes use of Android Context to populate some Event fields. @@ -37,6 +39,13 @@ public void helpBuildingEvent(EventBuilder eventBuilder) { } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Couldn't find package version: " + e); } + + String androidId = Settings.Secure.getString(ctx.getContentResolver(), Settings.Secure.ANDROID_ID); + if (androidId != null && !androidId.trim().equals("")) { + UserInterface userInterface = new UserInterface("android:" + androidId, null, null, null); + // set user interface but *don't* replace if it's already there + eventBuilder.withSentryInterface(userInterface, false); + } } } From 4bb2c39514c38609a6f60c277a02b1a2d5c2f893 Mon Sep 17 00:00:00 2001 From: zliau Date: Mon, 13 Mar 2017 14:18:13 -0400 Subject: [PATCH 1535/2152] Use Raven.class.getName for lockdown logger (#338) --- raven/src/main/java/com/getsentry/raven/Raven.java | 2 +- .../java/com/getsentry/raven/connection/AsyncConnection.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/raven/src/main/java/com/getsentry/raven/Raven.java b/raven/src/main/java/com/getsentry/raven/Raven.java index 62c7a57055f..f4bf8e9b932 100644 --- a/raven/src/main/java/com/getsentry/raven/Raven.java +++ b/raven/src/main/java/com/getsentry/raven/Raven.java @@ -25,7 +25,7 @@ public class Raven { private static final Logger logger = LoggerFactory.getLogger(Raven.class); // CHECKSTYLE.OFF: ConstantName - private static final Logger lockdownLogger = LoggerFactory.getLogger(Raven.class + ".lockdown"); + private static final Logger lockdownLogger = LoggerFactory.getLogger(Raven.class.getName() + ".lockdown"); // CHECKSTYLE.ON: ConstantName /** * The most recently constructed Raven instance, used by static helper methods like {@link Raven#capture(Event)}. diff --git a/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java b/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java index 40c039276d5..42f755ab763 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java @@ -21,7 +21,7 @@ public class AsyncConnection implements Connection { private static final Logger logger = LoggerFactory.getLogger(AsyncConnection.class); // CHECKSTYLE.OFF: ConstantName - private static final Logger lockdownLogger = LoggerFactory.getLogger(Raven.class + ".lockdown"); + private static final Logger lockdownLogger = LoggerFactory.getLogger(Raven.class.getName() + ".lockdown"); // CHECKSTYLE.ON: ConstantName /** * Timeout of the {@link #executorService}, in milliseconds. From 10ac4a5a6959ccd2c23a239a9646297477dd2334 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 13 Mar 2017 15:10:53 -0500 Subject: [PATCH 1536/2152] Add optional 'body' field to HttpInterface. (#334) --- .../raven/event/interfaces/HttpInterface.java | 22 ++++++++++++- .../marshaller/json/HttpInterfaceBinding.java | 33 ++++++++++++++----- .../raven/marshaller/json/JsonMarshaller.java | 19 ++--------- .../json/MessageInterfaceBinding.java | 22 +++---------- .../java/com/getsentry/raven/util/Util.java | 20 +++++++++++ .../json/HttpInterfaceBindingTest.java | 2 ++ .../raven/marshaller/json/Http1.json | 4 ++- 7 files changed, 77 insertions(+), 45 deletions(-) diff --git a/raven/src/main/java/com/getsentry/raven/event/interfaces/HttpInterface.java b/raven/src/main/java/com/getsentry/raven/event/interfaces/HttpInterface.java index 2df8c4c3e4f..8ef96aeb5da 100644 --- a/raven/src/main/java/com/getsentry/raven/event/interfaces/HttpInterface.java +++ b/raven/src/main/java/com/getsentry/raven/event/interfaces/HttpInterface.java @@ -32,6 +32,7 @@ public class HttpInterface implements SentryInterface { private final String authType; private final String remoteUser; private final Map> headers; + private final String body; /** * This constructor is for compatibility reasons and should not be used. @@ -43,12 +44,23 @@ public HttpInterface(HttpServletRequest request) { } /** - * Creates a an HTTP element for an {@link com.getsentry.raven.event.Event}. + * Creates an HTTP element for an {@link com.getsentry.raven.event.Event}. * * @param request Captured HTTP request to send to Sentry. * @param remoteAddressResolver RemoteAddressResolver */ public HttpInterface(HttpServletRequest request, RemoteAddressResolver remoteAddressResolver) { + this(request, remoteAddressResolver, null); + } + + /** + * Creates an HTTP element for an {@link com.getsentry.raven.event.Event}. + * + * @param request Captured HTTP request to send to Sentry. + * @param remoteAddressResolver RemoteAddressResolver + * @param body HTTP request body (optional) + */ + public HttpInterface(HttpServletRequest request, RemoteAddressResolver remoteAddressResolver, String body) { this.requestUrl = request.getRequestURL().toString(); this.method = request.getMethod(); this.parameters = new HashMap<>(); @@ -79,6 +91,7 @@ public HttpInterface(HttpServletRequest request, RemoteAddressResolver remoteAdd for (String headerName : Collections.list(request.getHeaderNames())) { this.headers.put(headerName, Collections.list(request.getHeaders(headerName))); } + this.body = body; } @Override @@ -150,6 +163,10 @@ public String getRemoteUser() { return remoteUser; } + public String getBody() { + return body; + } + public Map> getHeaders() { return Collections.unmodifiableMap(headers); } @@ -226,6 +243,9 @@ public boolean equals(Object o) { if (serverName != null ? !serverName.equals(that.serverName) : that.serverName != null) { return false; } + if (body != null ? !body.equals(that.body) : that.body != null) { + return false; + } return true; } diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/HttpInterfaceBinding.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/HttpInterfaceBinding.java index 4f0c9ff4b16..bd75fe1c150 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/HttpInterfaceBinding.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/HttpInterfaceBinding.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.getsentry.raven.event.interfaces.HttpInterface; +import com.getsentry.raven.util.Util; import java.io.IOException; import java.util.Collection; @@ -11,9 +12,18 @@ * Binding system allowing to convert an {@link HttpInterface} into a JSON stream. */ public class HttpInterfaceBinding implements InterfaceBinding { + /** + * Maximum length for the HTTP request body. + * + * See the SDK reference + * for more information. + */ + public static final int MAX_BODY_LENGTH = 2048; + private static final String URL = "url"; private static final String METHOD = "method"; private static final String DATA = "data"; + private static final String BODY = "body"; private static final String QUERY_STRING = "query_string"; private static final String COOKIES = "cookies"; private static final String HEADERS = "headers"; @@ -36,7 +46,7 @@ public void writeInterface(JsonGenerator generator, HttpInterface httpInterface) generator.writeStringField(URL, httpInterface.getRequestUrl()); generator.writeStringField(METHOD, httpInterface.getMethod()); generator.writeFieldName(DATA); - writeData(generator, httpInterface.getParameters()); + writeData(generator, httpInterface.getParameters(), httpInterface.getBody()); generator.writeStringField(QUERY_STRING, httpInterface.getQueryString()); generator.writeFieldName(COOKIES); writeCookies(generator, httpInterface.getCookies()); @@ -91,19 +101,26 @@ private void writeCookies(JsonGenerator generator, Map cookies) generator.writeEndObject(); } - private void writeData(JsonGenerator generator, Map> parameterMap) throws IOException { - if (parameterMap == null) { + private void writeData(JsonGenerator generator, + Map> parameterMap, + String body) throws IOException { + if (parameterMap == null && body == null) { generator.writeNull(); return; } generator.writeStartObject(); - for (Map.Entry> parameter : parameterMap.entrySet()) { - generator.writeArrayFieldStart(parameter.getKey()); - for (String parameterValue : parameter.getValue()) { - generator.writeString(parameterValue); + if (body != null) { + generator.writeStringField(BODY, Util.trimString(body, MAX_BODY_LENGTH)); + } + if (parameterMap != null) { + for (Map.Entry> parameter : parameterMap.entrySet()) { + generator.writeArrayFieldStart(parameter.getKey()); + for (String parameterValue : parameter.getValue()) { + generator.writeString(parameterValue); + } + generator.writeEndArray(); } - generator.writeEndArray(); } generator.writeEndObject(); } diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java index 3914c77505b..bfccbd6d220 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java @@ -8,6 +8,7 @@ import com.getsentry.raven.event.Event; import com.getsentry.raven.event.interfaces.SentryInterface; import com.getsentry.raven.marshaller.Marshaller; +import com.getsentry.raven.util.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -167,7 +168,7 @@ private void writeContent(JsonGenerator generator, Event event) throws IOExcepti generator.writeStartObject(); generator.writeStringField(EVENT_ID, formatId(event.getId())); - generator.writeStringField(MESSAGE, trimMessage(event.getMessage())); + generator.writeStringField(MESSAGE, Util.trimString(event.getMessage(), maxMessageLength)); generator.writeStringField(TIMESTAMP, ISO_FORMAT.get().format(event.getTimestamp())); generator.writeStringField(LEVEL, formatLevel(event.getLevel())); generator.writeStringField(LOGGER, event.getLogger()); @@ -333,22 +334,6 @@ private void writeContexts(JsonGenerator generator, Map maxMessageLength) { - return message.substring(0, maxMessageLength); - } else { - return message; - } - } - /** * Formats the {@code UUID} to send only the 32 necessary characters. * diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java index 57efedcea50..6c24e5216ca 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.getsentry.raven.event.interfaces.MessageInterface; +import com.getsentry.raven.util.Util; import java.io.IOException; @@ -38,33 +39,18 @@ public MessageInterfaceBinding(int maxMessageLength) { this.maxMessageLength = maxMessageLength; } - /** - * Trims a message, ensuring that the maximum length {@link #maxMessageLength} isn't reached. - * - * @param message message to format. - * @return trimmed message (shortened if necessary). - */ - private String trimMessage(String message) { - if (message == null) { - return null; - } else if (message.length() > maxMessageLength) { - return message.substring(0, maxMessageLength); - } else { - return message; - } - } - @Override public void writeInterface(JsonGenerator generator, MessageInterface messageInterface) throws IOException { generator.writeStartObject(); - generator.writeStringField(MESSAGE_PARAMETER, trimMessage(messageInterface.getMessage())); + generator.writeStringField(MESSAGE_PARAMETER, Util.trimString(messageInterface.getMessage(), maxMessageLength)); generator.writeArrayFieldStart(PARAMS_PARAMETER); for (String parameter : messageInterface.getParameters()) { generator.writeString(parameter); } generator.writeEndArray(); if (messageInterface.getFormatted() != null) { - generator.writeStringField(FORMATTED_PARAMETER, trimMessage(messageInterface.getFormatted())); + generator.writeStringField(FORMATTED_PARAMETER, + Util.trimString(messageInterface.getFormatted(), maxMessageLength)); } generator.writeEndObject(); } diff --git a/raven/src/main/java/com/getsentry/raven/util/Util.java b/raven/src/main/java/com/getsentry/raven/util/Util.java index 209266759b2..ea7c84d5862 100644 --- a/raven/src/main/java/com/getsentry/raven/util/Util.java +++ b/raven/src/main/java/com/getsentry/raven/util/Util.java @@ -109,4 +109,24 @@ public static Double parseDouble(String value, Double defaultValue) { } return Double.parseDouble(value); } + + /** + * Trims a String, ensuring that the maximum length isn't reached. + * + * @param string string to trim + * @param maxMessageLength maximum length of the string + * @return trimmed string + */ + public static String trimString(String string, int maxMessageLength) { + if (string == null) { + return null; + } else if (string.length() > maxMessageLength) { + // CHECKSTYLE.OFF: MagicNumber + return string.substring(0, maxMessageLength - 3) + "..."; + // CHECKSTYLE.ON: MagicNumber + } else { + return string; + } + } + } diff --git a/raven/src/test/java/com/getsentry/raven/marshaller/json/HttpInterfaceBindingTest.java b/raven/src/test/java/com/getsentry/raven/marshaller/json/HttpInterfaceBindingTest.java index c5f16c9d05d..ca4a1ebdeaa 100644 --- a/raven/src/test/java/com/getsentry/raven/marshaller/json/HttpInterfaceBindingTest.java +++ b/raven/src/test/java/com/getsentry/raven/marshaller/json/HttpInterfaceBindingTest.java @@ -55,6 +55,8 @@ public void testHeaders() throws Exception { result = "5.6.7.8"; mockMessageInterface.getLocalName(); result = "local-name"; + mockMessageInterface.getBody(); + result = "body"; }}; interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockMessageInterface); diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/Http1.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/Http1.json index d0bf64965cf..54eb1822034 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/Http1.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/Http1.json @@ -1,7 +1,9 @@ { "url": "http://host/url", "method": "GET", - "data": {}, + "data": { + "body": "body" + }, "query_string": "query", "cookies": { "Cookie1": "Value1" From f5fa2a3424367fc13d29361e25d0db0efd068119 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 13 Mar 2017 15:22:28 -0500 Subject: [PATCH 1537/2152] Update CHANGES. --- CHANGES | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 3f76995b617..72996a7c221 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,8 @@ Version 7.8.5 ------------- -- +- Add optional ``body`` field to HttpInterface. +- Fix name of the lockdown logger. Version 7.8.4 ------------- From bf5ebfa740f4309a63edae2ebcd4aac26d3ff6cd Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 13 Mar 2017 15:22:51 -0500 Subject: [PATCH 1538/2152] Bump docs to 7.8.5 --- docs/modules/android.rst | 4 ++-- docs/modules/appengine.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/raven.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 53a8dcdf287..1a5e2e81e14 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -22,9 +22,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-android:7.8.4' + compile 'com.getsentry.raven:raven-android:7.8.5' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index ffe86a02fff..353820175ae 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: com.getsentry.raven raven-appengine - 7.8.4 + 7.8.5 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-appengine:7.8.4' + compile 'com.getsentry.raven:raven-appengine:7.8.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-appengine" % "7.8.4" + libraryDependencies += "com.getsentry.raven" % "raven-appengine" % "7.8.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 6cdfa0e6fdc..3174d1abbb9 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-log4j - 7.8.4 + 7.8.5 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-log4j:7.8.4' + compile 'com.getsentry.raven:raven-log4j:7.8.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-log4j" % "7.8.4" + libraryDependencies += "com.getsentry.raven" % "raven-log4j" % "7.8.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 5bcc3aee0e3..98ee10c3c39 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-log4j2 - 7.8.4 + 7.8.5 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-log4j2:7.8.4' + compile 'com.getsentry.raven:raven-log4j2:7.8.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-log4j2" % "7.8.4" + libraryDependencies += "com.getsentry.raven" % "raven-log4j2" % "7.8.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index d67c182674e..5f5cfd2c836 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-logback - 7.8.4 + 7.8.5 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-logback:7.8.4' + compile 'com.getsentry.raven:raven-logback:7.8.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-logback" % "7.8.4" + libraryDependencies += "com.getsentry.raven" % "raven-logback" % "7.8.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index 205e8907506..147f9d76280 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -18,22 +18,22 @@ Using Maven: com.getsentry.raven raven - 7.8.4 + 7.8.5 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven:7.8.4' + compile 'com.getsentry.raven:raven:7.8.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.4" + libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index bd442ab4c17..d556c8ed71c 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -15,22 +15,22 @@ Using Maven: com.getsentry.raven raven - 7.8.4 + 7.8.5 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven:7.8.4' + compile 'com.getsentry.raven:raven:7.8.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.4" + libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From fa4a5f29db97853beaa662216fb016c861e32795 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 13 Mar 2017 15:24:13 -0500 Subject: [PATCH 1539/2152] [maven-release-plugin] prepare release v7.8.5 --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index bb7f842dc41..d4f30d41974 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.8.5-SNAPSHOT + 7.8.5 pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v7.8.5 https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index 372a33929b7..6298eae7e77 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.5-SNAPSHOT + 7.8.5 raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 0851a41b5fd..9546611c12a 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.8.5-SNAPSHOT + 7.8.5 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 053ec0d8657..a36682b8a08 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.5-SNAPSHOT + 7.8.5 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index c4ff2e7e006..2d5b59130b5 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.5-SNAPSHOT + 7.8.5 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index db7376bfb5a..99bcb19203a 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.5-SNAPSHOT + 7.8.5 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index b82d2fec822..57ced16a8a4 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.5-SNAPSHOT + 7.8.5 raven From 1c3499f7e0a3806cc0917a3745b4dad9a123db55 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 13 Mar 2017 15:24:13 -0500 Subject: [PATCH 1540/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index d4f30d41974..bb525b77a66 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.8.5 + 7.8.6-SNAPSHOT pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v7.8.5 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index 6298eae7e77..4648f552196 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.5 + 7.8.6-SNAPSHOT raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 9546611c12a..c8123adcbe0 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.8.5 + 7.8.6-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index a36682b8a08..9b5a82e9cff 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.5 + 7.8.6-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 2d5b59130b5..30b14971fc6 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.5 + 7.8.6-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 99bcb19203a..a5d47a28a46 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.5 + 7.8.6-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 57ced16a8a4..463a2e20165 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.5 + 7.8.6-SNAPSHOT raven From 9e70e7322b804f58fb6ee7362b42e8a89801f875 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 13 Mar 2017 15:24:16 -0500 Subject: [PATCH 1541/2152] Bump CHANGES to 7.8.6 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 72996a7c221..00491f5bae9 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 7.8.6 +------------- + +- + Version 7.8.5 ------------- From 90fb5a1a0dd93c9ba990f62af94fd6f4d267dd9f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 14 Mar 2017 14:43:47 -0500 Subject: [PATCH 1542/2152] Bump slf4j, log4j2 and logback. (#339) --- pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index bb525b77a66..58308e25409 100644 --- a/pom.xml +++ b/pom.xml @@ -116,7 +116,7 @@ 7 - 1.7.22 + 1.7.24 3.0.1 2.7.6 1.14 diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 30b14971fc6..41c51a0e784 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -15,7 +15,7 @@ log4j2 appender allowing to send logs to the Raven-Java client. - 2.8 + 2.8.1 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index a5d47a28a46..a37687c4799 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -15,7 +15,7 @@ Logback appender allowing to send logs to the Raven-Java client. - 1.1.9 + 1.2.1 From 13c0ab44833ed001d0d4afd0005e7ea710abc178 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 17 Mar 2017 08:35:30 -0500 Subject: [PATCH 1543/2152] Bump Jackson. (#343) --- CHANGES | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 00491f5bae9..0b1b74ebefa 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 7.8.6 ------------- -- +- Updated Jackson, slf4j, log4j2 and logback dependencies. Version 7.8.5 ------------- diff --git a/pom.xml b/pom.xml index 58308e25409..7b8dfd1978c 100644 --- a/pom.xml +++ b/pom.xml @@ -118,7 +118,7 @@ 1.7.24 3.0.1 - 2.7.6 + 2.8.7 1.14 6.9.10 1.3 From 00914162324ea534f210375de01b1a5bda5cb814 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 17 Mar 2017 17:11:30 -0500 Subject: [PATCH 1544/2152] Automatically collect device information in ``contexts`` field on Android. (#322) --- CHANGES | 1 + docs/modules/android.rst | 5 + .../com/getsentry/raven/android/Util.java | 2 + .../helper/AndroidEventBuilderHelper.java | 380 ++++++++++++++++++ 4 files changed, 388 insertions(+) diff --git a/CHANGES b/CHANGES index 0b1b74ebefa..1a17cf12aa7 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ Version 7.8.6 ------------- +- Automatically collect device information in ``contexts`` field on Android. - Updated Jackson, slf4j, log4j2 and logback dependencies. Version 7.8.5 diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 1a5e2e81e14..5c7d4ef2a78 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -15,6 +15,11 @@ be disabled by setting the DSN option ``raven.buffer.enabled`` to ``false``. An ``UncaughtExceptionHandler`` is configured so that crash events will be stored to disk and sent the next time the application is run. +The ``AndroidEventBuilderHelper`` is enabled by default, which will automatically +enrich events with data about the current state of the device, such as memory usage, +storage usage, display resolution, connectivity, battery level, model, Android version, +whether the device is rooted or not, etc. + Installation ------------ diff --git a/raven-android/src/main/java/com/getsentry/raven/android/Util.java b/raven-android/src/main/java/com/getsentry/raven/android/Util.java index 65fa6b1192e..67534cbe66b 100644 --- a/raven-android/src/main/java/com/getsentry/raven/android/Util.java +++ b/raven-android/src/main/java/com/getsentry/raven/android/Util.java @@ -10,6 +10,8 @@ */ public final class Util { + private static final String TAG = Util.class.getName(); + /** * Hide constructor. */ diff --git a/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java b/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java index c27c578e713..5f4a9e51177 100644 --- a/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java +++ b/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java @@ -1,14 +1,32 @@ package com.getsentry.raven.android.event.helper; +import android.app.ActivityManager; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.os.BatteryManager; +import android.os.Build; +import android.os.Environment; +import android.os.StatFs; import android.provider.Settings; +import android.util.DisplayMetrics; import android.util.Log; +import com.getsentry.raven.android.Util; import com.getsentry.raven.environment.RavenEnvironment; import com.getsentry.raven.event.EventBuilder; import com.getsentry.raven.event.helper.EventBuilderHelper; import com.getsentry.raven.event.interfaces.UserInterface; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.Map; + +import static android.content.Context.ACTIVITY_SERVICE; + /** * EventBuilderHelper that makes use of Android Context to populate some Event fields. */ @@ -19,6 +37,9 @@ public class AndroidEventBuilderHelper implements EventBuilderHelper { */ public static final String TAG = AndroidEventBuilderHelper.class.getName(); + private static final Boolean IS_EMULATOR = isEmulator(); + private static final String KERNEL_VERSION = getKernelVersion(); + private Context ctx; /** @@ -46,6 +67,365 @@ public void helpBuildingEvent(EventBuilder eventBuilder) { // set user interface but *don't* replace if it's already there eventBuilder.withSentryInterface(userInterface, false); } + + eventBuilder.withContexts(getContexts()); + } + + private Map> getContexts() { + Map> contexts = new HashMap<>(); + Map deviceMap = new HashMap<>(); + Map osMap = new HashMap<>(); + contexts.put("os", osMap); + contexts.put("device", deviceMap); + + // Device + deviceMap.put("manufacturer", Build.MANUFACTURER); + deviceMap.put("brand", Build.BRAND); + deviceMap.put("model", Build.MODEL); + deviceMap.put("family", getFamily()); + deviceMap.put("model_id", Build.ID); + deviceMap.put("battery_level", getBatteryLevel(ctx)); + deviceMap.put("orientation", getOrientation(ctx)); + deviceMap.put("simulator", IS_EMULATOR); + deviceMap.put("arch", Build.CPU_ABI); + deviceMap.put("storage_size", getTotalInternalStorage()); + deviceMap.put("free_storage", getUnusedInternalStorage()); + deviceMap.put("external_storage_size", getTotalExternalStorage()); + deviceMap.put("external_free_storage", getUnusedExternalStorage()); + deviceMap.put("charging", isCharging(ctx)); + deviceMap.put("online", Util.isConnected(ctx)); + + DisplayMetrics displayMetrics = getDisplayMetrics(ctx); + if (displayMetrics != null) { + int largestSide = Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels); + int smallestSide = Math.min(displayMetrics.widthPixels, displayMetrics.heightPixels); + String resolution = Integer.toString(largestSide) + "x" + Integer.toString(smallestSide); + deviceMap.put("screen_resolution", resolution); + deviceMap.put("screen_density", displayMetrics.density); + deviceMap.put("screen_dpi", displayMetrics.densityDpi); + } + + ActivityManager.MemoryInfo memInfo = getMemInfo(ctx); + if (memInfo != null) { + deviceMap.put("free_memory", memInfo.availMem); + deviceMap.put("memory_size", memInfo.totalMem); + deviceMap.put("low_memory", memInfo.lowMemory); + } + + // Operating System + osMap.put("name", "Android"); + osMap.put("version", Build.VERSION.RELEASE); + osMap.put("build", Build.DISPLAY); + osMap.put("kernel_version", KERNEL_VERSION); + osMap.put("rooted", isRooted()); + + return contexts; + } + + /** + * Fake the device family by using the first word in the Build.MODEL. Works + * well in most cases... "Nexus 6P" -> "Nexus", "Galaxy S7" -> "Galaxy". + * + * @return family name of the device, as best we can tell + */ + private static String getFamily() { + try { + return Build.MODEL.split(" ")[0]; + } catch (Exception e) { + Log.e(TAG, "Error getting device family.", e); + return null; + } + } + + /** + * Check whether the application is running in an emulator. http://stackoverflow.com/a/21505193 + * + * @return true if the application is running in an emulator, false otherwise + */ + private static Boolean isEmulator() { + try { + return Build.FINGERPRINT.startsWith("generic") + || Build.FINGERPRINT.startsWith("unknown") + || Build.MODEL.contains("google_sdk") + || Build.MODEL.contains("Emulator") + || Build.MODEL.contains("Android SDK built for x86") + || Build.MANUFACTURER.contains("Genymotion") + || (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) + || "google_sdk".equals(Build.PRODUCT); + } catch (Exception e) { + Log.e(TAG, "Error checking whether application is running in an emulator.", e); + return null; + } + } + + /** + * Get MemoryInfo object representing the memory state of the application. + * + * @param ctx Android application context + * @return MemoryInfo object representing the memory state of the application + */ + private static ActivityManager.MemoryInfo getMemInfo(Context ctx) { + try { + ActivityManager actManager = (ActivityManager) ctx.getSystemService(ACTIVITY_SERVICE); + ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); + actManager.getMemoryInfo(memInfo); + return memInfo; + } catch (Exception e) { + Log.e(TAG, "Error getting MemoryInfo.", e); + return null; + } + } + + /** + * Get the device's current screen orientation. + * + * @param ctx Android application context + * @return the device's current screen orientation, or null if unknown + */ + private static String getOrientation(Context ctx) { + try { + String o; + switch (ctx.getResources().getConfiguration().orientation) { + case android.content.res.Configuration.ORIENTATION_LANDSCAPE: + o = "landscape"; + break; + case android.content.res.Configuration.ORIENTATION_PORTRAIT: + o = "portrait"; + break; + default: + o = null; + break; + } + return o; + } catch (Exception e) { + Log.e(TAG, "Error getting device orientation.", e); + return null; + } + } + + /** + * Get the device's current battery level (as a percentage of total). + * + * @param ctx Android application context + * @return the device's current battery level (as a percentage of total), or null if unknown + */ + private static Float getBatteryLevel(Context ctx) { + try { + Intent intent = ctx.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + if (intent == null) { + return null; + } + + int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); + int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); + + if (level == -1 || scale == -1) { + return null; + } + + // CHECKSTYLE.OFF: MagicNumber + float percentMultiplier = 100.0f; + // CHECKSTYLE.ON: MagicNumber + + return ((float) level / (float) scale) * percentMultiplier; + } catch (Exception e) { + Log.e(TAG, "Error getting device battery level.", e); + return null; + } + } + + /** + * Checks whether or not the device is currently plugged in and charging, or null if unknown. + * + * @param ctx Android application context + * @return whether or not the device is currently plugged in and charging, or null if unknown + */ + private static Boolean isCharging(Context ctx) { + try { + Intent intent = ctx.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + if (intent == null) { + return null; + } + + int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); + return plugged == BatteryManager.BATTERY_PLUGGED_AC || plugged == BatteryManager.BATTERY_PLUGGED_USB; + } catch (Exception e) { + Log.e(TAG, "Error getting device charging state.", e); + return null; + } + } + + /** + * Get the device's current kernel version, as a string (from uname -a). + * + * @return the device's current kernel version, as a string (from uname -a) + */ + private static String getKernelVersion() { + String errorMsg = "Exception while attempting to read kernel information"; + BufferedReader br = null; + try { + Process p = Runtime.getRuntime().exec("uname -a"); + if (p.waitFor() == 0) { + br = new BufferedReader(new InputStreamReader(p.getInputStream())); + return br.readLine(); + } + } catch (Exception e) { + Log.e(TAG, errorMsg, e); + } finally { + if (br != null) { + try { + br.close(); + } catch (IOException ioe) { + Log.e(TAG, errorMsg, ioe); + } + } + } + + return null; + } + + /** + * Attempt to discover if this device is currently rooted. From: + * https://stackoverflow.com/questions/1101380/determine-if-running-on-a-rooted-device + * + * @return true if heuristics show the device is probably rooted, otherwise false + */ + private static Boolean isRooted() { + if (android.os.Build.TAGS != null && android.os.Build.TAGS.contains("test-keys")) { + return true; + } + + String[] probableRootPaths = { + "/data/local/bin/su", + "/data/local/su", + "/data/local/xbin/su", + "/sbin/su", + "/su/bin", + "/su/bin/su", + "/system/app/SuperSU", + "/system/app/SuperSU.apk", + "/system/app/Superuser", + "/system/app/Superuser.apk", + "/system/bin/failsafe/su", + "/system/bin/su", + "/system/sd/xbin/su", + "/system/xbin/daemonsu", + "/system/xbin/su" + }; + + for (String probableRootPath : probableRootPaths) { + try { + if (new File(probableRootPath).exists()) { + return true; + } + } catch (Exception e) { + Log.e(TAG, "Exception while attempting to detect whether the device is rooted", e); + } + } + + return false; + } + + private static boolean isExternalStorageMounted() { + return Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED) + && !Environment.isExternalStorageEmulated(); + } + + /** + * Get the unused amount of internal storage, in bytes. + * + * @return the unused amount of internal storage, in bytes + */ + private static Long getUnusedInternalStorage() { + try { + File path = Environment.getDataDirectory(); + StatFs stat = new StatFs(path.getPath()); + long blockSize = stat.getBlockSize(); + long availableBlocks = stat.getAvailableBlocks(); + return availableBlocks * blockSize; + } catch (Exception e) { + Log.e(TAG, "Error getting unused internal storage amount.", e); + return null; + } + } + + /** + * Get the total amount of internal storage, in bytes. + * + * @return the total amount of internal storage, in bytes + */ + private static Long getTotalInternalStorage() { + try { + File path = Environment.getDataDirectory(); + StatFs stat = new StatFs(path.getPath()); + long blockSize = stat.getBlockSize(); + long totalBlocks = stat.getBlockCount(); + return totalBlocks * blockSize; + } catch (Exception e) { + Log.e(TAG, "Error getting total internal storage amount.", e); + return null; + } + } + + /** + * Get the unused amount of external storage, in bytes, or null if no external storage + * is mounted. + * + * @return the unused amount of external storage, in bytes, or null if no external storage + * is mounted + */ + private static Long getUnusedExternalStorage() { + try { + if (isExternalStorageMounted()) { + File path = Environment.getExternalStorageDirectory(); + StatFs stat = new StatFs(path.getPath()); + long blockSize = stat.getBlockSize(); + long availableBlocks = stat.getAvailableBlocks(); + return availableBlocks * blockSize; + } + } catch (Exception e) { + Log.e(TAG, "Error getting unused external storage amount.", e); + } + + return null; + } + + /** + * Get the total amount of external storage, in bytes, or null if no external storage + * is mounted. + * + * @return the total amount of external storage, in bytes, or null if no external storage + * is mounted + */ + private static Long getTotalExternalStorage() { + try { + if (isExternalStorageMounted()) { + File path = Environment.getExternalStorageDirectory(); + StatFs stat = new StatFs(path.getPath()); + long blockSize = stat.getBlockSize(); + long totalBlocks = stat.getBlockCount(); + return totalBlocks * blockSize; + } + } catch (Exception e) { + Log.e(TAG, "Error getting total external storage amount.", e); + } + + return null; + } + + /** + * Get the DisplayMetrics object for the current application. + * + * @param ctx Android application context + * @return the DisplayMetrics object for the current application + */ + private static DisplayMetrics getDisplayMetrics(Context ctx) { + try { + return ctx.getResources().getDisplayMetrics(); + } catch (Exception e) { + Log.e(TAG, "Error getting DisplayMetrics.", e); + return null; + } } } From 9f6767c9eb551e2b618fbb08cb39f7bfd7e0dbbe Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 21 Mar 2017 10:57:31 -0500 Subject: [PATCH 1545/2152] Bump docs to 7.8.6 --- docs/modules/android.rst | 4 ++-- docs/modules/appengine.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/raven.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 5c7d4ef2a78..00130adb237 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -27,9 +27,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-android:7.8.5' + compile 'com.getsentry.raven:raven-android:7.8.6' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 353820175ae..8a380be4a16 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: com.getsentry.raven raven-appengine - 7.8.5 + 7.8.6 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-appengine:7.8.5' + compile 'com.getsentry.raven:raven-appengine:7.8.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-appengine" % "7.8.5" + libraryDependencies += "com.getsentry.raven" % "raven-appengine" % "7.8.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 3174d1abbb9..f64b821b5d1 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-log4j - 7.8.5 + 7.8.6 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-log4j:7.8.5' + compile 'com.getsentry.raven:raven-log4j:7.8.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-log4j" % "7.8.5" + libraryDependencies += "com.getsentry.raven" % "raven-log4j" % "7.8.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 98ee10c3c39..c3b4ce14a0f 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-log4j2 - 7.8.5 + 7.8.6 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-log4j2:7.8.5' + compile 'com.getsentry.raven:raven-log4j2:7.8.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-log4j2" % "7.8.5" + libraryDependencies += "com.getsentry.raven" % "raven-log4j2" % "7.8.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 5f5cfd2c836..852d13023bb 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-logback - 7.8.5 + 7.8.6 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-logback:7.8.5' + compile 'com.getsentry.raven:raven-logback:7.8.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-logback" % "7.8.5" + libraryDependencies += "com.getsentry.raven" % "raven-logback" % "7.8.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index 147f9d76280..42052a6693b 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -18,22 +18,22 @@ Using Maven: com.getsentry.raven raven - 7.8.5 + 7.8.6 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven:7.8.5' + compile 'com.getsentry.raven:raven:7.8.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.5" + libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index d556c8ed71c..e98f663a5f1 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -15,22 +15,22 @@ Using Maven: com.getsentry.raven raven - 7.8.5 + 7.8.6 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven:7.8.5' + compile 'com.getsentry.raven:raven:7.8.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.5" + libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 827e4cfff3e78ab08e1765e7902e2c6ee607847d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 21 Mar 2017 10:58:43 -0500 Subject: [PATCH 1546/2152] [maven-release-plugin] prepare release v7.8.6 --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 7b8dfd1978c..f63e55e622e 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.8.6-SNAPSHOT + 7.8.6 pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v7.8.6 https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index 4648f552196..cda6e68eb27 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.6-SNAPSHOT + 7.8.6 raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index c8123adcbe0..fb553142327 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.8.6-SNAPSHOT + 7.8.6 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 9b5a82e9cff..fdfc9b9a338 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.6-SNAPSHOT + 7.8.6 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 41c51a0e784..173a0f027c1 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.6-SNAPSHOT + 7.8.6 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index a37687c4799..f9b2b290b1c 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.6-SNAPSHOT + 7.8.6 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 463a2e20165..d70849a92d9 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.6-SNAPSHOT + 7.8.6 raven From d7dac47890b8086b65736ba3e1476589acb7fa8b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 21 Mar 2017 10:58:43 -0500 Subject: [PATCH 1547/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index f63e55e622e..76f01733bfc 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.8.6 + 7.8.7-SNAPSHOT pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v7.8.6 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index cda6e68eb27..8ad25dc134f 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.6 + 7.8.7-SNAPSHOT raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index fb553142327..930109531c1 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.8.6 + 7.8.7-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index fdfc9b9a338..4bfb0bff282 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.6 + 7.8.7-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 173a0f027c1..a1069dd7770 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.6 + 7.8.7-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index f9b2b290b1c..bef0dbff574 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.6 + 7.8.7-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index d70849a92d9..e5aa6c4b7dc 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.6 + 7.8.7-SNAPSHOT raven From 5736fdae7ff74617dab62fe0a12fe0cfd111fc33 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 21 Mar 2017 10:58:46 -0500 Subject: [PATCH 1548/2152] Bump CHANGES to 7.8.7 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 1a17cf12aa7..50a7b0f585d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 7.8.7 +------------- + +- + Version 7.8.6 ------------- From 718ab35ddc8f951030636d5735621bd24c42df84 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 17 Mar 2017 08:44:14 -0500 Subject: [PATCH 1549/2152] Drop deprecated (#340) --- docs/config.rst | 3 +- .../main/java/com/getsentry/raven/Raven.java | 18 -- .../raven/connection/AbstractConnection.java | 22 --- .../raven/connection/HttpConnection.java | 13 -- .../getsentry/raven/event/EventBuilder.java | 183 ------------------ .../java/com/getsentry/raven/RavenTest.java | 13 -- .../raven/connection/HttpConnectionTest.java | 12 +- 7 files changed, 9 insertions(+), 255 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 22ff2cfee66..91e883b1bb6 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -380,8 +380,7 @@ Implementation @Override public Raven createRavenInstance(Dsn dsn) { - Raven raven = new Raven(); - raven.setConnection(createConnection(dsn)); + Raven raven = new Raven(createConnection(dsn)); /* Create and use the ForwardedAddressResolver, which will use the diff --git a/raven/src/main/java/com/getsentry/raven/Raven.java b/raven/src/main/java/com/getsentry/raven/Raven.java index f4bf8e9b932..4f736a28be2 100644 --- a/raven/src/main/java/com/getsentry/raven/Raven.java +++ b/raven/src/main/java/com/getsentry/raven/Raven.java @@ -50,20 +50,6 @@ protected RavenContext initialValue() { } }; - /** - * Constructs a Raven instance. - * - * Note that the most recently constructed instance is stored statically so it can be used with - * the static helper methods. - * - * @deprecated in favor of {@link Raven#Raven(Connection)} because until you call - * {@link Raven#setConnection(Connection)} this instance will throw exceptions when used. - */ - @Deprecated - public Raven() { - stored = this; - } - /** * Constructs a Raven instance using the provided connection. * @@ -183,10 +169,6 @@ public void closeConnection() { } } - public void setConnection(Connection connection) { - this.connection = connection; - } - public RavenContext getContext() { return context.get(); } diff --git a/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java b/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java index 9c5ff40f9fa..64ffe33f7d7 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java @@ -96,28 +96,6 @@ public final void send(Event event) throws ConnectionException { */ protected abstract void doSend(Event event) throws ConnectionException; - /** - * Set the maximum waiting time for a lockdown, in milliseconds. - * - * @param maxWaitingTime maximum waiting time for a lockdown, in milliseconds. - * @deprecated slated for removal - */ - @Deprecated - public void setMaxWaitingTime(long maxWaitingTime) { - lockdownManager.setMaxLockdownTime(maxWaitingTime); - } - - /** - * Set the base waiting time for a lockdown, in milliseconds. - * - * @param baseWaitingTime base waiting time for a lockdown, in milliseconds. - * @deprecated slated for removal - */ - @Deprecated - public void setBaseWaitingTime(long baseWaitingTime) { - lockdownManager.setBaseLockdownTime(baseWaitingTime); - } - /** * Add a callback that is called when an exception occurs while attempting to * send events to the Sentry server. diff --git a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java b/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java index decccea1712..d9f9e2f81f6 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java +++ b/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java @@ -73,19 +73,6 @@ public boolean verify(String hostname, SSLSession sslSession) { */ private boolean bypassSecurity = false; - /** - * Creates an HTTP connection to a Sentry server. - * - * @param sentryUrl URL to the Sentry API. - * @param publicKey public key of the current project. - * @param secretKey private key of the current project. - * @deprecated use the more explicit constructor below - */ - @Deprecated - public HttpConnection(URL sentryUrl, String publicKey, String secretKey) { - this(sentryUrl, publicKey, secretKey, null, null); - } - /** * Creates an HTTP connection to a Sentry server. * diff --git a/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java b/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java index bc8a583449a..2c8d800852f 100644 --- a/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java +++ b/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java @@ -423,189 +423,6 @@ public String toString() { + '}'; } - - /** - * Sets the message in the event. - * - * @param message message of the event. - * @return the current {@code EventBuilder} for chained calls. - * @deprecated Use {@link #withMessage(String)} instead. - */ - @Deprecated - public EventBuilder setMessage(String message) { - return withMessage(message); - } - - /** - * Sets the timestamp in the event. - * - * @param timestamp timestamp of the event. - * @return the current {@code EventBuilder} for chained calls. - * @deprecated Use {@link #withTimestamp(Date)} instead. - */ - @Deprecated - public EventBuilder setTimestamp(Date timestamp) { - return withTimestamp(timestamp); - } - - /** - * Sets the log level in the event. - * - * @param level log level of the event. - * @return the current {@code EventBuilder} for chained calls. - * @deprecated Use {@link #withLevel(Event.Level)} instead. - */ - @Deprecated - public EventBuilder setLevel(Event.Level level) { - return withLevel(level); - } - - /** - * Sets the logger in the event. - * - * @param logger logger of the event. - * @return the current {@code EventBuilder} for chained calls. - * @deprecated Use {@link #withLogger(String)} instead. - */ - @Deprecated - public EventBuilder setLogger(String logger) { - return withLogger(logger); - } - - /** - * Sets the platform in the event. - * - * @param platform platform of the event. - * @return the current {@code EventBuilder} for chained calls. - * @deprecated Use {@link #withPlatform(String)} instead. - */ - @Deprecated - public EventBuilder setPlatform(String platform) { - return withPlatform(platform); - } - - /** - * Sets the culprit in the event based on a {@link StackTraceElement}. - * - * @param frame stack frame during which the event was captured. - * @return the current {@code EventBuilder} for chained calls. - * @deprecated Use {@link #withCulprit(StackTraceElement)} instead. - */ - @Deprecated - public EventBuilder setCulprit(StackTraceElement frame) { - return withCulprit(frame); - } - - /** - * Sets the culprit in the event. - * - * @param culprit culprit. - * @return the current {@code EventBuilder} for chained calls. - * @deprecated Use {@link #withCulprit(String)} instead. - */ - @Deprecated - public EventBuilder setCulprit(String culprit) { - return withCulprit(culprit); - } - - /** - * Adds a tag to an event. - *

    - * This allows to set a tag value in different contexts. - * - * @param tagKey name of the tag. - * @param tagValue value of the tag. - * @return the current {@code EventBuilder} for chained calls. - * @deprecated use {@link #withTag(String, String)} instead. - */ - @Deprecated - public EventBuilder addTag(String tagKey, String tagValue) { - return withTag(tagKey, tagValue); - } - - /** - * Sets the serverName in the event. - * - * @param serverName name of the server responsible for the event. - * @return the current {@code EventBuilder} for chained calls. - * @deprecated use {@link #withServerName(String)} instead. - */ - @Deprecated - public EventBuilder setServerName(String serverName) { - return withServerName(serverName); - } - - /** - * Adds an extra property to the event. - * - * @param extraName name of the extra property. - * @param extraValue value of the extra property. - * @return the current {@code EventBuilder} for chained calls. - * @deprecated use {@link #withExtra(String, Object)} instead. - */ - @Deprecated - public EventBuilder addExtra(String extraName, Object extraValue) { - return withExtra(extraName, extraValue); - } - - /** - * Generates a checksum from a given content and set it to the current event. - * - * @param contentToChecksum content to checksum. - * @return the current {@code EventBuilder} for chained calls. - * @deprecated use {@link #withChecksumFor(String)} instead. - */ - @Deprecated - public EventBuilder generateChecksum(String contentToChecksum) { - return withChecksumFor(contentToChecksum); - } - - /** - * Sets the checksum for the current event. - *

    - * It's recommended to rely instead on the checksum system provided by Sentry. - * - * @param checksum checksum for the event. - * @return the current {@code EventBuilder} for chained calls. - * @deprecated use {@link #withChecksum(String)} instead. - */ - @Deprecated - public EventBuilder setChecksum(String checksum) { - return withChecksum(checksum); - } - - /** - * Adds a {@link SentryInterface} to the event. - *

    - * If a {@code SentryInterface} with the same interface name has already been added, the new one will replace - * the old one. - * - * @param sentryInterface sentry interface to add to the event. - * @return the current {@code EventBuilder} for chained calls. - * @deprecated use {@link #withSentryInterface(SentryInterface)} instead. - */ - @Deprecated - public EventBuilder addSentryInterface(SentryInterface sentryInterface) { - return withSentryInterface(sentryInterface); - } - - /** - * Adds a {@link SentryInterface} to the event. - *

    - * Checks whether or not the entry already exists, and replaces it only if {@code replace} is true. - * - * @param sentryInterface sentry interface to add to the event. - * @param replace If true and a Sentry Interface with the same name has already been added it will be - * replaced. - * If false the statement will be ignored. - * @return the current {@code EventBuilder} for chained calls. - * @deprecated use {@link #withSentryInterface(SentryInterface, boolean)} instead. - */ - @Deprecated - public EventBuilder addSentryInterface(SentryInterface sentryInterface, boolean replace) { - return withSentryInterface(sentryInterface, replace); - } - /** * Time sensitive cache in charge of keeping track of the hostname. *

    diff --git a/raven/src/test/java/com/getsentry/raven/RavenTest.java b/raven/src/test/java/com/getsentry/raven/RavenTest.java index 694a13f1571..a8b0a22ab67 100644 --- a/raven/src/test/java/com/getsentry/raven/RavenTest.java +++ b/raven/src/test/java/com/getsentry/raven/RavenTest.java @@ -101,19 +101,6 @@ public void testSendException() throws Exception { }}; } - @Test - public void testChangeConnection(@Injectable final Connection mockNewConnection) throws Exception { - raven.setConnection(mockNewConnection); - - raven.sendEvent(mockEvent); - - new Verifications() {{ - mockConnection.send((Event) any); - times = 0; - mockNewConnection.send(mockEvent); - }}; - } - @Test public void testAddRemoveBuilderHelpers(@Injectable final EventBuilderHelper mockBuilderHelper) throws Exception { assertThat(raven.getBuilderHelpers(), not(contains(mockBuilderHelper))); diff --git a/raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java b/raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java index ddd4b72a9ec..51764077e26 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java +++ b/raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.Proxy; import java.net.URI; import java.net.URL; @@ -28,6 +29,10 @@ public class HttpConnectionTest { private final String publicKey = "6cc48e8f-380c-44cc-986b-f566247a2af5"; @Injectable private final String secretKey = "e30cca23-3f97-470b-a8c2-e29b33dd25e0"; + @Injectable + private Proxy proxy = null; + @Injectable + private EventSampler eventSampler = null; @Tested private HttpConnection httpConnection = null; @Injectable @@ -35,8 +40,6 @@ public class HttpConnectionTest { @Injectable private Marshaller mockMarshaller = null; @Injectable - private int timeout = 12; - @Injectable private URL mockUrl = null; @Injectable private OutputStream mockOutputStream = null; @@ -46,7 +49,9 @@ public class HttpConnectionTest { @BeforeMethod public void setUp() throws Exception { new NonStrictExpectations() {{ - mockUrl.openConnection(); + eventSampler.shouldSendEvent((Event) any); + result = true; + mockUrl.openConnection((Proxy) any); result = mockUrlConnection; mockUrlConnection.getOutputStream(); result = mockOutputStream; @@ -117,7 +122,6 @@ public void testContentMarshalled(@Injectable final Event mockEvent) throws Exce public void testAuthHeaderSent(@Injectable final Event mockEvent) throws Exception { httpConnection.send(mockEvent); - new Verifications() {{ mockUrlConnection.setRequestProperty("User-Agent", RavenEnvironment.getRavenName()); mockUrlConnection.setRequestProperty("X-Sentry-Auth", httpConnection.getAuthHeader()); From ddb0bba5725b5ecb4f6ba7a0ac87bfdaf737b342 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 17 Mar 2017 13:02:30 -0500 Subject: [PATCH 1550/2152] Note Android SDK application size increase. --- docs/modules/android.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 00130adb237..8c3077606a4 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,6 +6,8 @@ Features The Raven Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. +Adding version ``8.0.0`` of the Android SDK to a sample application that doesn't even use +Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ (in the application's cache directory) by default. This allows events to be sent at a From 7b3c4d8a5be19a7ee9b95fbd40ed7fe5ac6f003a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 20 Mar 2017 12:48:14 -0500 Subject: [PATCH 1551/2152] Drop needless javax.annotations.Nullable usages. (#352) --- pom.xml | 6 ------ raven/pom.xml | 5 ----- .../getsentry/raven/event/interfaces/MessageInterface.java | 3 +-- raven/src/main/java/com/getsentry/raven/util/Util.java | 3 +-- 4 files changed, 2 insertions(+), 15 deletions(-) diff --git a/pom.xml b/pom.xml index 76f01733bfc..5dc5553f8ac 100644 --- a/pom.xml +++ b/pom.xml @@ -117,7 +117,6 @@ 1.7.24 - 3.0.1 2.8.7 1.14 6.9.10 @@ -145,11 +144,6 @@ slf4j-api ${slf4j.version} - - com.google.code.findbugs - jsr305 - ${findbugs.version} - com.fasterxml.jackson.core jackson-core diff --git a/raven/pom.xml b/raven/pom.xml index e5aa6c4b7dc..c9eb9c8ede1 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -23,11 +23,6 @@ org.slf4j slf4j-api - - com.google.code.findbugs - jsr305 - provided - com.fasterxml.jackson.core jackson-core diff --git a/raven/src/main/java/com/getsentry/raven/event/interfaces/MessageInterface.java b/raven/src/main/java/com/getsentry/raven/event/interfaces/MessageInterface.java index fcf08651d45..bed4abb2260 100644 --- a/raven/src/main/java/com/getsentry/raven/event/interfaces/MessageInterface.java +++ b/raven/src/main/java/com/getsentry/raven/event/interfaces/MessageInterface.java @@ -1,6 +1,5 @@ package com.getsentry.raven.event.interfaces; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -31,7 +30,7 @@ public class MessageInterface implements SentryInterface { public static final String MESSAGE_INTERFACE = "sentry.interfaces.Message"; private final String message; private final List parameters; - @Nullable private final String formatted; + private final String formatted; /** * Creates a non parametrised message. diff --git a/raven/src/main/java/com/getsentry/raven/util/Util.java b/raven/src/main/java/com/getsentry/raven/util/Util.java index ea7c84d5862..c964349b932 100644 --- a/raven/src/main/java/com/getsentry/raven/util/Util.java +++ b/raven/src/main/java/com/getsentry/raven/util/Util.java @@ -1,6 +1,5 @@ package com.getsentry.raven.util; -import javax.annotation.Nullable; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -24,7 +23,7 @@ private Util() { * @param string a string reference to check * @return {@code true} if the string is null or is the empty string */ - public static boolean isNullOrEmpty(@Nullable String string) { + public static boolean isNullOrEmpty(String string) { return string == null || string.length() == 0; // string.isEmpty() in Java 6 } From 20466d333e25a6f4c25a58ca1b51fe97be93a905 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 28 Mar 2017 13:27:33 -0500 Subject: [PATCH 1552/2152] Minor reorganization/cleanup of Android documentation. (#357) --- docs/modules/android.rst | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 8c3077606a4..6a24e28671f 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -36,32 +36,42 @@ For other dependency managers see the `central Maven repository - - - -Your application must also have permission to access the internet in order to send -event to the Sentry server. In your ``AndroidManifest.xml``: +Your application must have permission to access the internet in order to send +events to the Sentry server. In your ``AndroidManifest.xml``: .. sourcecode:: xml -Then, in your application's ``onCreate``, initialize the Raven client: +Then initialize the Raven client in your application's main ``onCreate`` method: .. sourcecode:: java import com.getsentry.raven.android.Raven; - // "this" should be a reference to your main Activity - Raven.init(this.getApplicationContext()); + public class MainActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Context ctx = this.getApplicationContext(); + // Use the Sentry DSN (client key) from the Project Settings page on Sentry + String sentryDsn = "https://publicKey:secretKey@host:port/1?options"; + + Raven.init(ctx, sentryDsn); + } + } + +You can also configure your Sentry DSN (client key) in your ``AndroidManifest.xml``: + +.. sourcecode:: xml + + + + Now you can use ``Raven`` to capture events anywhere in your application: From 651df95c07ca702326e4dc5e796f5e5099337bcb Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 28 Mar 2017 13:37:17 -0500 Subject: [PATCH 1553/2152] Made some Appender initialization lazy, fixes 2 bugs. (#355) --- CHANGES | 6 +- .../getsentry/raven/log4j/SentryAppender.java | 71 +++++++++++++++---- .../raven/log4j2/SentryAppender.java | 66 ++++++++++++++--- .../raven/logback/SentryAppender.java | 67 ++++++++++++++--- .../getsentry/raven/DefaultRavenFactory.java | 2 +- .../getsentry/raven/jul/SentryHandler.java | 70 ++++++++++++++---- 6 files changed, 235 insertions(+), 47 deletions(-) diff --git a/CHANGES b/CHANGES index 50a7b0f585d..d5539a9e52a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,9 @@ -Version 7.8.7 +Version 8.0.0 ------------- -- +- Fix so that system properties and environment variables always override hardcoded logger configuration for settings + such as release, environment, etc. +- Fix Raven initialization so that slf4j doesn't emit log replay warning on startup. Version 7.8.6 ------------- diff --git a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java index fca09daaf8b..440f96581fc 100644 --- a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java @@ -84,18 +84,15 @@ public class SentryAppender extends AppenderSkeleton { * Might be empty in which case no mapped tags are set. */ protected Set extraTags = Collections.emptySet(); + /** + * Used for lazy initialization of appender state, see {@link #lazyInit()}. + */ + private volatile boolean initialized = false; /** * Creates an instance of SentryAppender. */ public SentryAppender() { - setRavenFactory(Lookup.lookup("ravenFactory")); - setRelease(Lookup.lookup("release")); - setEnvironment(Lookup.lookup("environment")); - setServerName(Lookup.lookup("serverName")); - setTags(Lookup.lookup("tags")); - setExtraTags(Lookup.lookup("extraTags")); - this.addFilter(new DropRavenFilter()); } @@ -109,6 +106,58 @@ public SentryAppender(Raven raven) { this.raven = raven; } + /** + * Do some appender initialization *after* instance construction, so that we don't + * log in the constructor (which can cause annoying messages) and so that system + * properties and environment variables override hardcoded appender configuration. + */ + @SuppressWarnings("checkstyle:hiddenfield") + private void lazyInit() { + if (raven == null) { + initRaven(); + } + + if (!initialized) { + synchronized (this) { + if (!initialized) { + try { + String ravenFactory = Lookup.lookup("ravenFactory"); + if (ravenFactory != null) { + setRavenFactory(ravenFactory); + } + + String release = Lookup.lookup("release"); + if (release != null) { + setRelease(release); + } + + String environment = Lookup.lookup("environment"); + if (environment != null) { + setEnvironment(environment); + } + + String serverName = Lookup.lookup("serverName"); + if (serverName != null) { + setServerName(serverName); + } + + String tags = Lookup.lookup("tags"); + if (tags != null) { + setTags(tags); + } + + String extraTags = Lookup.lookup("extraTags"); + if (extraTags != null) { + setExtraTags(extraTags); + } + } finally { + initialized = true; + } + } + } + } + } + /** * Transforms a {@link Level} into an {@link Event.Level}. * @@ -147,9 +196,7 @@ protected static StackTraceElement asStackTraceElement(LocationInfo location) { public void activateOptions() { super.activateOptions(); - if (raven == null) { - initRaven(); - } + lazyInit(); } /** @@ -180,9 +227,7 @@ protected void append(LoggingEvent loggingEvent) { RavenEnvironment.startManagingThread(); try { - if (raven == null) { - initRaven(); - } + lazyInit(); Event event = buildEvent(loggingEvent); raven.sendEvent(event); } catch (Exception e) { diff --git a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java index fe18c8a3ab5..8f1dac53f05 100644 --- a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java @@ -102,6 +102,10 @@ public class SentryAppender extends AbstractAppender { *

    */ protected Set extraTags = Collections.emptySet(); + /** + * Used for lazy initialization of appender state, see {@link #lazyInit()}. + */ + private volatile boolean initialized = false; /** * Creates an instance of SentryAppender. @@ -128,12 +132,6 @@ public SentryAppender(Raven raven) { */ protected SentryAppender(String name, Filter filter) { super(name, filter, null, true); - setRavenFactory(Lookup.lookup("ravenFactory")); - setRelease(Lookup.lookup("release")); - setEnvironment(Lookup.lookup("environment")); - setServerName(Lookup.lookup("serverName")); - setTags(Lookup.lookup("tags")); - setExtraTags(Lookup.lookup("extraTags")); this.addFilter(new DropRavenFilter()); } @@ -189,6 +187,58 @@ public static SentryAppender createAppender(@PluginAttribute("name") final Strin return sentryAppender; } + /** + * Do some appender initialization *after* instance construction, so that we don't + * log in the constructor (which can cause annoying messages) and so that system + * properties and environment variables override hardcoded appender configuration. + */ + @SuppressWarnings("checkstyle:hiddenfield") + private void lazyInit() { + if (raven == null) { + initRaven(); + } + + if (!initialized) { + synchronized (this) { + if (!initialized) { + try { + String ravenFactory = Lookup.lookup("ravenFactory"); + if (ravenFactory != null) { + setRavenFactory(ravenFactory); + } + + String release = Lookup.lookup("release"); + if (release != null) { + setRelease(release); + } + + String environment = Lookup.lookup("environment"); + if (environment != null) { + setEnvironment(environment); + } + + String serverName = Lookup.lookup("serverName"); + if (serverName != null) { + setServerName(serverName); + } + + String tags = Lookup.lookup("tags"); + if (tags != null) { + setTags(tags); + } + + String extraTags = Lookup.lookup("extraTags"); + if (extraTags != null) { + setExtraTags(extraTags); + } + } finally { + initialized = true; + } + } + } + } + } + /** * Transforms a {@link Level} into an {@link Event.Level}. * @@ -242,9 +292,7 @@ public void append(LogEvent logEvent) { RavenEnvironment.startManagingThread(); try { - if (raven == null) { - initRaven(); - } + lazyInit(); Event event = buildEvent(logEvent); raven.sendEvent(event); } catch (Exception e) { diff --git a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java index 21162ef3df4..b8ef953879d 100644 --- a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java @@ -96,18 +96,15 @@ public class SentryAppender extends AppenderBase { * Extras to use as tags. */ protected Set extraTags = Collections.emptySet(); + /** + * Used for lazy initialization of appender state, see {@link #lazyInit()}. + */ + private volatile boolean initialized = false; /** * Creates an instance of SentryAppender. */ public SentryAppender() { - setRavenFactory(Lookup.lookup("ravenFactory")); - setRelease(Lookup.lookup("release")); - setEnvironment(Lookup.lookup("environment")); - setServerName(Lookup.lookup("serverName")); - setTags(Lookup.lookup("tags")); - setExtraTags(Lookup.lookup("extraTags")); - this.addFilter(new DropRavenFilter()); } @@ -121,6 +118,58 @@ public SentryAppender(Raven raven) { this.raven = raven; } + /** + * Do some appender initialization *after* instance construction, so that we don't + * log in the constructor (which can cause annoying messages) and so that system + * properties and environment variables override hardcoded appender configuration. + */ + @SuppressWarnings("checkstyle:hiddenfield") + private void lazyInit() { + if (raven == null) { + initRaven(); + } + + if (!initialized) { + synchronized (this) { + if (!initialized) { + try { + String ravenFactory = Lookup.lookup("ravenFactory"); + if (ravenFactory != null) { + setRavenFactory(ravenFactory); + } + + String release = Lookup.lookup("release"); + if (release != null) { + setRelease(release); + } + + String environment = Lookup.lookup("environment"); + if (environment != null) { + setEnvironment(environment); + } + + String serverName = Lookup.lookup("serverName"); + if (serverName != null) { + setServerName(serverName); + } + + String tags = Lookup.lookup("tags"); + if (tags != null) { + setTags(tags); + } + + String extraTags = Lookup.lookup("extraTags"); + if (extraTags != null) { + setExtraTags(extraTags); + } + } finally { + initialized = true; + } + } + } + } + } + /** * Extracts message parameters into a List of Strings. *

    @@ -177,9 +226,7 @@ protected void append(ILoggingEvent iLoggingEvent) { return; } - if (raven == null) { - initRaven(); - } + lazyInit(); Event event = buildEvent(iLoggingEvent); raven.sendEvent(event); } catch (Exception e) { diff --git a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java index c285dfda80e..8f0625d2968 100644 --- a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java @@ -198,7 +198,7 @@ public Raven createRavenInstance(Dsn dsn) { raven.addBuilderHelper(new HttpEventBuilderHelper()); } catch (ClassNotFoundException e) { logger.debug("The current environment doesn't provide access to servlets," - + "or provides an unsupported version."); + + " or provides an unsupported version."); } raven.addBuilderHelper(new ContextBuilderHelper(raven)); return raven; diff --git a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java index ca2352bb783..44cc6964743 100644 --- a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java +++ b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java @@ -53,7 +53,6 @@ public class SentryHandler extends Handler { * false. */ protected boolean printfStyle; - /** * Name of the {@link RavenFactory} being used. *

    @@ -82,25 +81,21 @@ public class SentryHandler extends Handler { * Tags to add to every event. */ protected Map tags = Collections.emptyMap(); - /** * Set of tags to look for in the MDC. These will be added as tags to be sent to sentry. *

    * Might be empty in which case no mapped tags are set. */ protected Set extraTags = Collections.emptySet(); + /** + * Used for lazy initialization of appender state, see {@link #lazyInit()}. + */ + private volatile boolean initialized = false; /** * Creates an instance of SentryHandler. */ public SentryHandler() { - setRavenFactory(Lookup.lookup("ravenFactory")); - setRelease(Lookup.lookup("release")); - setEnvironment(Lookup.lookup("environment")); - setServerName(Lookup.lookup("serverName")); - setTags(Lookup.lookup("tags")); - setExtraTags(Lookup.lookup("extraTags")); - retrieveProperties(); this.setFilter(new DropRavenFilter()); } @@ -115,6 +110,59 @@ public SentryHandler(Raven raven) { this.raven = raven; } + /** + * Do some appender initialization *after* instance construction, so that we don't + * log in the constructor (which can cause annoying messages) and so that system + * properties and environment variables override hardcoded appender configuration. + */ + @SuppressWarnings("checkstyle:hiddenfield") + private void lazyInit() { + if (raven == null) { + initRaven(); + } + + if (!initialized) { + synchronized (this) { + if (!initialized) { + + try { + String ravenFactory = Lookup.lookup("ravenFactory"); + if (ravenFactory != null) { + setRavenFactory(ravenFactory); + } + + String release = Lookup.lookup("release"); + if (release != null) { + setRelease(release); + } + + String environment = Lookup.lookup("environment"); + if (environment != null) { + setEnvironment(environment); + } + + String serverName = Lookup.lookup("serverName"); + if (serverName != null) { + setServerName(serverName); + } + + String tags = Lookup.lookup("tags"); + if (tags != null) { + setTags(tags); + } + + String extraTags = Lookup.lookup("extraTags"); + if (extraTags != null) { + setExtraTags(extraTags); + } + } finally { + initialized = true; + } + } + } + } + } + /** * Transforms a {@link Level} into an {@link Event.Level}. * @@ -197,9 +245,7 @@ public void publish(LogRecord record) { RavenEnvironment.startManagingThread(); try { - if (raven == null) { - initRaven(); - } + lazyInit(); Event event = buildEvent(record); raven.sendEvent(event); } catch (Exception e) { From 8db0f82a33b140642356f17833158bed5f2ed1e9 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 28 Mar 2017 13:42:16 -0500 Subject: [PATCH 1554/2152] Change from 'in_app' blacklist to 'in_app' whitelist. (#344) --- CHANGES | 3 ++ docs/config.rst | 49 ++++++++----------- .../getsentry/raven/DefaultRavenFactory.java | 43 +++++++++------- .../json/StackTraceInterfaceBinding.java | 21 ++++---- .../raven/marshaller/json/StackTrace1.json | 2 +- .../raven/marshaller/json/StackTrace2.json | 2 +- .../raven/marshaller/json/StackTrace3.json | 4 +- 7 files changed, 63 insertions(+), 61 deletions(-) diff --git a/CHANGES b/CHANGES index d5539a9e52a..442f98900b8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ Version 8.0.0 ------------- +- Remove ``DefaultRavenFactory.getNotInAppFrames()`` blacklist in favor of a new whitelist method, + ``DefaultRavenFactory.getInAppFrames(Dsn dsn)``. +- Add ``raven.stacktrace.app.packages`` DSN option for configuring your application's package prefixes. - Fix so that system properties and environment variables always override hardcoded logger configuration for settings such as release, environment, etc. - Fix Raven initialization so that slf4j doesn't emit log replay warning on startup. diff --git a/docs/config.rst b/docs/config.rst index 91e883b1bb6..a6b50714d4a 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -262,20 +262,30 @@ This option takes a number from 0.0 to 1.0, representing the percent of events to allow through to server (from 0% to 100%). By default all events will be sent to the Sentry server. -Inapp Classes -~~~~~~~~~~~~~ +"In Application" Stack Frames +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sentry differentiates stack frames that are directly related to your application +("in application") from stack frames that come from other packages such as the +standard library, frameworks, or other dependencies. The difference +is visible in the Sentry web interface where only the "in application" frames are +displayed by default. + +You can configure which package prefixes your application uses with the +``raven.stacktrace.app.packages`` option, which takes a comma separated list. + +:: + + http://public:private@host:port/1?raven.stacktrace.app.packages=com.mycompany,com.other.name -Sentry differentiate ``in_app`` stack frames (which are directly related to your application) -and the "not ``in_app``" ones. -This difference is visible in the Sentry web interface where only the ``in_app`` -frames are displayed by default. +*Changed in version 8.0:* Raven formerly supported a package blacklist but +now only supports the package whitelist described above. Same Frame as Enclosing Exception ````````````````````````````````` -Raven can use the ``in_app`` system to hide frames in the context of chained exceptions. - -Usually when a StackTrace is printed, the result looks like this: +Raven can use the "in application" system to hide frames in chained exceptions. Usually when a +StackTrace is printed, the result looks like this: :: @@ -296,31 +306,12 @@ Usually when a StackTrace is printed, the result looks like this: Some frames are replaced by the ``... N more`` line as they are the same frames as in the enclosing exception. -To enable a similar behaviour from Raven use the ``raven.stacktrace.hidecommon`` option. +To enable a similar behaviour in Raven use the ``raven.stacktrace.hidecommon`` option. :: http://public:private@host:port/1?raven.stacktrace.hidecommon -Hide Frames Based on the Class Name -``````````````````````````````````` - -Raven can also mark some frames as ``in_app`` based on the name of the class. - -This can be used to hide parts of the stacktrace that are irrelevant to the problem -for example the stack frames in the ``java.util`` package will not help determining -what the problem was and will just create a longer stacktrace. - -Currently this is not configurable and some packages are ignored by default: - -- ``com.sun.*`` -- ``java.*`` -- ``javax.*`` -- ``org.omg.*`` -- ``sun.*`` -- ``junit.*`` -- ``com.intellij.rt.*`` - Compression ~~~~~~~~~~~ diff --git a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java index 8f0625d2968..dfd06240a3a 100644 --- a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java @@ -18,10 +18,7 @@ import java.net.Authenticator; import java.net.Proxy; import java.net.URL; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; @@ -144,6 +141,11 @@ public class DefaultRavenFactory extends RavenFactory { * Default timeout of the {@link AsyncConnection} executor, in milliseconds. */ public static final long ASYNC_SHUTDOWN_TIMEOUT_DEFAULT = TimeUnit.SECONDS.toMillis(1); + /** + * Option for which package prefixes are part of the user's application code, as a single + * comma separated string. + */ + public static final String IN_APP_FRAMES_OPTION = "raven.stacktrace.app.packages"; /** * Option for whether to hide common stackframes with enclosing exceptions. */ @@ -357,7 +359,7 @@ protected Marshaller createMarshaller(Dsn dsn) { StackTraceInterfaceBinding stackTraceBinding = new StackTraceInterfaceBinding(); // Enable common frames hiding unless its value is 'false'. stackTraceBinding.setRemoveCommonFramesWithEnclosing(getHideCommonFramesEnabled(dsn)); - stackTraceBinding.setNotInAppFrames(getNotInAppFrames()); + stackTraceBinding.setInAppFrames(getInAppFrames(dsn)); marshaller.addInterfaceBinding(StackTraceInterface.class, stackTraceBinding); marshaller.addInterfaceBinding(ExceptionInterface.class, new ExceptionInterfaceBinding(stackTraceBinding)); @@ -375,21 +377,26 @@ protected Marshaller createMarshaller(Dsn dsn) { } /** - * Provides a list of package names to consider as "not in-app". + * Returns the list of package names to consider "in-app". *

    - * Those packages will be used with the {@link StackTraceInterface} to hide frames that aren't a part of - * the main application. + * Those packages will be used with the {@link StackTraceInterface} to show frames that are a part of + * the main application in the Sentry UI by default. * - * @return the list of "not in-app" packages. - */ - protected Collection getNotInAppFrames() { - return Arrays.asList("com.sun.", - "java.", - "javax.", - "org.omg.", - "sun.", - "junit.", - "com.intellij.rt."); + * @param dsn Sentry server DSN which may contain options. + * @return the list of package names to consider "in-app". + */ + protected Collection getInAppFrames(Dsn dsn) { + if (!dsn.getOptions().containsKey(IN_APP_FRAMES_OPTION)) { + return Collections.emptyList(); + } + + ArrayList inAppPackages = new ArrayList<>(); + for (String inAppPackage : dsn.getOptions().get(IN_APP_FRAMES_OPTION).split(",")) { + if (!inAppPackage.trim().equals("")) { + inAppPackages.add(inAppPackage); + } + } + return inAppPackages; } /** diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/StackTraceInterfaceBinding.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/StackTraceInterfaceBinding.java index 80c36e2f375..4179fdaaaef 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/StackTraceInterfaceBinding.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/StackTraceInterfaceBinding.java @@ -22,7 +22,7 @@ public class StackTraceInterfaceBinding implements InterfaceBinding notInAppFrames = Collections.emptyList(); + private Collection inAppFrames = Collections.emptyList(); private boolean removeCommonFramesWithEnclosing = true; /** @@ -35,21 +35,22 @@ private void writeFrame(JsonGenerator generator, StackTraceElement stackTraceEle generator.writeStartObject(); generator.writeStringField(FILENAME_PARAMETER, stackTraceElement.getFileName()); generator.writeStringField(MODULE_PARAMETER, stackTraceElement.getClassName()); - generator.writeBooleanField(IN_APP_PARAMETER, !(removeCommonFramesWithEnclosing && commonWithEnclosing) - && isFrameInApp(stackTraceElement)); + boolean inApp = !(removeCommonFramesWithEnclosing && commonWithEnclosing) && isFrameInApp(stackTraceElement); + generator.writeBooleanField(IN_APP_PARAMETER, inApp); generator.writeStringField(FUNCTION_PARAMETER, stackTraceElement.getMethodName()); generator.writeNumberField(LINE_NO_PARAMETER, stackTraceElement.getLineNumber()); generator.writeEndObject(); } private boolean isFrameInApp(StackTraceElement stackTraceElement) { - //TODO: A set is absolutely not efficient here, a Trie could be a better solution. - for (String notInAppFrame : notInAppFrames) { - if (stackTraceElement.getClassName().startsWith(notInAppFrame)) { - return false; + // TODO: A linear search is not efficient here, a Trie could be a better solution. + for (String inAppFrame : inAppFrames) { + String className = stackTraceElement.getClassName(); + if (className.startsWith(inAppFrame)) { + return true; } } - return true; + return false; } @Override @@ -73,7 +74,7 @@ public void setRemoveCommonFramesWithEnclosing(boolean removeCommonFramesWithEnc this.removeCommonFramesWithEnclosing = removeCommonFramesWithEnclosing; } - public void setNotInAppFrames(Collection notInAppFrames) { - this.notInAppFrames = notInAppFrames; + public void setInAppFrames(Collection inAppFrames) { + this.inAppFrames = inAppFrames; } } diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/StackTrace1.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/StackTrace1.json index bda24011872..12311ce1368 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/StackTrace1.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/StackTrace1.json @@ -1,7 +1,7 @@ {"frames": [ { "module": "31b26f01-9b97-442b-9f36-8a317f94ad76", - "in_app": true, + "in_app": false, "function": "0cce55c9-478f-4386-8ede-4b6f000da3e6", "lineno": 1, "filename": "File.java" diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/StackTrace2.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/StackTrace2.json index f67b43d6650..2b1d621fa76 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/StackTrace2.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/StackTrace2.json @@ -8,7 +8,7 @@ }, { "module": "", - "in_app": true, + "in_app": false, "function": "", "filename": "File.java", "lineno": 0 diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/StackTrace3.json b/raven/src/test/resources/com/getsentry/raven/marshaller/json/StackTrace3.json index fcd2eba3f26..2b1d621fa76 100644 --- a/raven/src/test/resources/com/getsentry/raven/marshaller/json/StackTrace3.json +++ b/raven/src/test/resources/com/getsentry/raven/marshaller/json/StackTrace3.json @@ -1,14 +1,14 @@ {"frames": [ { "module": "", - "in_app": true, + "in_app": false, "function": "", "filename": "File.java", "lineno": 0 }, { "module": "", - "in_app": true, + "in_app": false, "function": "", "filename": "File.java", "lineno": 0 From 76020e24ff1fe208cb64119614f1f2fa49ff9812 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 30 Mar 2017 13:37:49 -0500 Subject: [PATCH 1555/2152] =?UTF-8?q?Add=20ContextManager=20interface=20so?= =?UTF-8?q?=20that=20the=20idea=20of=20'current=20context'=20ca=E2=80=A6?= =?UTF-8?q?=20(#345)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../raven/android/AndroidRavenFactory.java | 7 + .../getsentry/raven/DefaultRavenFactory.java | 17 +- .../main/java/com/getsentry/raven/Raven.java | 27 +-- .../com/getsentry/raven/RavenContext.java | 183 ------------------ .../com/getsentry/raven/context/Context.java | 124 ++++++++++++ .../raven/context/ContextManager.java | 20 ++ .../context/SingletonContextManager.java | 19 ++ .../context/ThreadLocalContextManager.java | 24 +++ .../getsentry/raven/event/Breadcrumbs.java | 6 +- .../event/helper/ContextBuilderHelper.java | 10 +- .../servlet/RavenServletRequestListener.java | 2 +- ...RavenContextTest.java => ContextTest.java} | 31 +-- .../java/com/getsentry/raven/RavenTest.java | 10 + .../EventSendFailureCallbackTest.java | 9 - .../getsentry/raven/event/BreadcrumbTest.java | 10 + .../com/getsentry/raven/event/UserTest.java | 11 +- 16 files changed, 267 insertions(+), 243 deletions(-) delete mode 100644 raven/src/main/java/com/getsentry/raven/RavenContext.java create mode 100644 raven/src/main/java/com/getsentry/raven/context/Context.java create mode 100644 raven/src/main/java/com/getsentry/raven/context/ContextManager.java create mode 100644 raven/src/main/java/com/getsentry/raven/context/SingletonContextManager.java create mode 100644 raven/src/main/java/com/getsentry/raven/context/ThreadLocalContextManager.java rename raven/src/test/java/com/getsentry/raven/{RavenContextTest.java => ContextTest.java} (73%) diff --git a/raven-android/src/main/java/com/getsentry/raven/android/AndroidRavenFactory.java b/raven-android/src/main/java/com/getsentry/raven/android/AndroidRavenFactory.java index 9c2f9244df4..ac07e2edbf6 100644 --- a/raven-android/src/main/java/com/getsentry/raven/android/AndroidRavenFactory.java +++ b/raven-android/src/main/java/com/getsentry/raven/android/AndroidRavenFactory.java @@ -6,6 +6,8 @@ import com.getsentry.raven.android.event.helper.AndroidEventBuilderHelper; import com.getsentry.raven.buffer.Buffer; import com.getsentry.raven.buffer.DiskBuffer; +import com.getsentry.raven.context.ContextManager; +import com.getsentry.raven.context.SingletonContextManager; import com.getsentry.raven.dsn.Dsn; import java.io.File; @@ -59,4 +61,9 @@ protected Buffer getBuffer(Dsn dsn) { return new DiskBuffer(bufferDir, getBufferSize(dsn)); } + @Override + protected ContextManager getContextManager(Dsn dsn) { + return new SingletonContextManager(); + } + } diff --git a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java index dfd06240a3a..cc554175d5d 100644 --- a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java +++ b/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java @@ -3,6 +3,8 @@ import com.getsentry.raven.buffer.Buffer; import com.getsentry.raven.buffer.DiskBuffer; import com.getsentry.raven.connection.*; +import com.getsentry.raven.context.ContextManager; +import com.getsentry.raven.context.ThreadLocalContextManager; import com.getsentry.raven.dsn.Dsn; import com.getsentry.raven.event.helper.ContextBuilderHelper; import com.getsentry.raven.event.helper.HttpEventBuilderHelper; @@ -191,7 +193,7 @@ public class DefaultRavenFactory extends RavenFactory { @Override public Raven createRavenInstance(Dsn dsn) { - Raven raven = new Raven(createConnection(dsn)); + Raven raven = new Raven(createConnection(dsn), getContextManager(dsn)); try { // `ServletRequestListener` was added in the Servlet 2.4 API, and // is used as part of the `HttpEventBuilderHelper`, see: @@ -376,6 +378,19 @@ protected Marshaller createMarshaller(Dsn dsn) { return marshaller; } + /** + * Returns the {@link ContextManager} to use for locating and storing data that is context specific, + * such as {@link com.getsentry.raven.event.Breadcrumb}s. + *

    + * Defaults to {@link ThreadLocalContextManager}. + * + * @param dsn Sentry server DSN which may contain options. + * @return the {@link ContextManager} to use. + */ + protected ContextManager getContextManager(Dsn dsn) { + return new ThreadLocalContextManager(); + } + /** * Returns the list of package names to consider "in-app". *

    diff --git a/raven/src/main/java/com/getsentry/raven/Raven.java b/raven/src/main/java/com/getsentry/raven/Raven.java index 4f736a28be2..926abd059be 100644 --- a/raven/src/main/java/com/getsentry/raven/Raven.java +++ b/raven/src/main/java/com/getsentry/raven/Raven.java @@ -2,6 +2,8 @@ import com.getsentry.raven.connection.Connection; import com.getsentry.raven.connection.LockedDownException; +import com.getsentry.raven.context.Context; +import com.getsentry.raven.context.ContextManager; import com.getsentry.raven.environment.RavenEnvironment; import com.getsentry.raven.event.Event; import com.getsentry.raven.event.EventBuilder; @@ -41,25 +43,23 @@ public class Raven { */ private final Set builderHelpers = Collections.newSetFromMap(new ConcurrentHashMap()); - private final ThreadLocal context = new ThreadLocal() { - @Override - protected RavenContext initialValue() { - RavenContext ctx = new RavenContext(); - ctx.activate(); - return ctx; - } - }; - + /** + * The {@link ContextManager} to use for locating and storing data that is context specific, + * such as {@link com.getsentry.raven.event.Breadcrumb}s. + */ + private final ContextManager contextManager; /** * Constructs a Raven instance using the provided connection. * * Note that the most recently constructed instance is stored statically so it can be used with * the static helper methods. * - * @param connection Underlying Connection instance to use for sending events + * @param connection Underlying {@link Connection} instance to use for sending events + * @param contextManager {@link ContextManager} instance to use for storing contextual data */ - public Raven(Connection connection) { + public Raven(Connection connection, ContextManager contextManager) { this.connection = connection; + this.contextManager = contextManager; stored = this; } @@ -169,8 +169,8 @@ public void closeConnection() { } } - public RavenContext getContext() { - return context.get(); + public Context getContext() { + return contextManager.getContext(); } @Override @@ -178,6 +178,7 @@ public String toString() { return "Raven{" + "name=" + RavenEnvironment.getRavenName() + ", connection=" + connection + + ", contextManager=" + contextManager + '}'; } diff --git a/raven/src/main/java/com/getsentry/raven/RavenContext.java b/raven/src/main/java/com/getsentry/raven/RavenContext.java deleted file mode 100644 index 615a2c2172d..00000000000 --- a/raven/src/main/java/com/getsentry/raven/RavenContext.java +++ /dev/null @@ -1,183 +0,0 @@ -package com.getsentry.raven; - -import com.getsentry.raven.event.Breadcrumb; -import com.getsentry.raven.event.User; -import com.getsentry.raven.util.CircularFifoQueue; -import java.util.ArrayList; -import java.util.Collection; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.List; -import java.util.UUID; - -/** - * RavenContext is used to hold {@link ThreadLocal} context data (such as - * {@link Breadcrumb}s) so that data may be collected in different parts - * of an application and then sent together when an exception occurs. - */ -public class RavenContext implements AutoCloseable { - /** - * Thread local set of active context objects. Note that an {@link IdentityHashMap} - * is used instead of a Set because there is no identity-set in the Java - * standard library. - * - * A set of active contexts is required in order to support running multiple Raven - * clients within a single process. In *most* cases this set will contain a single - * active context object. - * - * This must be static and {@link ThreadLocal} so that users can retrieve any active - * context objects globally, without passing context objects all the way down their - * stacks. See {@link com.getsentry.raven.event.Breadcrumbs} for an example of how this may be used. - */ - private static ThreadLocal> activeContexts = - new ThreadLocal>() { - @Override - protected IdentityHashMap initialValue() { - return new IdentityHashMap<>(); - } - }; - - /** - * The number of {@link Breadcrumb}s to keep in the ring buffer by default. - */ - private static final int DEFAULT_BREADCRUMB_LIMIT = 100; - - /** - * UUID of the last event sent to the Sentry server, if any. - */ - private UUID lastEventId; - - /** - * Ring buffer of {@link Breadcrumb} objects. - */ - private CircularFifoQueue breadcrumbs; - - /** - * User active in the current context, if any. - */ - private User user; - - /** - * Create a new (empty) RavenContext object with the default Breadcrumb limit. - */ - public RavenContext() { - this(DEFAULT_BREADCRUMB_LIMIT); - } - - /** - * Create a new (empty) RavenContext object with the specified Breadcrumb limit. - * - * @param breadcrumbLimit Number of Breadcrumb objects to retain in ring buffer. - */ - public RavenContext(int breadcrumbLimit) { - breadcrumbs = new CircularFifoQueue<>(breadcrumbLimit); - } - - /** - * Add this context to the active contexts for this thread. - */ - public void activate() { - activeContexts.get().put(this, this); - } - - /** - * Remove this context from the active contexts for this thread. - */ - public void deactivate() { - activeContexts.get().remove(this); - } - - /** - * Clear state from this context. - */ - public void clear() { - breadcrumbs.clear(); - lastEventId = null; - user = null; - } - - /** - * Calls deactivate, used by try-with-resources ({@link AutoCloseable}). - */ - @Override - public void close() { - deactivate(); - } - - /** - * Returns all active contexts for the current thread. - * - * @return List of active {@link RavenContext} objects. - */ - public static List getActiveContexts() { - Collection ravenContexts = activeContexts.get().values(); - List list = new ArrayList<>(ravenContexts.size()); - list.addAll(ravenContexts); - return list; - } - - /** - * Return {@link Breadcrumb}s attached to this RavenContext. - * - * @return Iterator of {@link Breadcrumb}s. - */ - public Iterator getBreadcrumbs() { - return breadcrumbs.iterator(); - } - - /** - * Record a single {@link Breadcrumb} into this context. - * - * @param breadcrumb Breadcrumb object to record - */ - public void recordBreadcrumb(Breadcrumb breadcrumb) { - breadcrumbs.add(breadcrumb); - } - - /** - * Store the UUID of the last sent event by this thread, useful for handling user feedback. - * - * @param id UUID of the last event sent by this thread. - */ - public void setLastEventId(UUID id) { - lastEventId = id; - } - - /** - * Get the UUID of the last event sent by this thread, useful for handling user feedback. - * - * Returns null if no event has been sent by this thread or if the event has been - * cleared. For example the RavenServletRequestListener clears the thread's RavenContext - * at the end of each request. - * - * @return UUID of the last event sent by this thread. - */ - public UUID getLastEventId() { - return lastEventId; - } - - /** - * Store the current user in the context. - * - * @param user User to store in context. - */ - public void setUser(User user) { - this.user = user; - } - - /** - * Clears the current user set on this context. - */ - public void clearUser() { - setUser(null); - } - - /** - * Gets the current user from the context. - * - * @return User currently stored in context. - */ - public User getUser() { - return user; - } -} diff --git a/raven/src/main/java/com/getsentry/raven/context/Context.java b/raven/src/main/java/com/getsentry/raven/context/Context.java new file mode 100644 index 00000000000..5ecde6fb1e1 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/context/Context.java @@ -0,0 +1,124 @@ +package com.getsentry.raven.context; + +import com.getsentry.raven.event.Breadcrumb; +import com.getsentry.raven.event.User; +import com.getsentry.raven.util.CircularFifoQueue; +import java.util.Iterator; +import java.util.UUID; + +/** + * Context is used to hold context data (such as {@link Breadcrumb}s) + * so that data may be collected in different parts of an application and + * then sent together when an exception occurs. + */ +public class Context { + /** + * The number of {@link Breadcrumb}s to keep in the ring buffer by default. + */ + private static final int DEFAULT_BREADCRUMB_LIMIT = 100; + + /** + * UUID of the last event sent to the Sentry server, if any. + */ + private UUID lastEventId; + + /** + * Ring buffer of {@link Breadcrumb} objects. + */ + private CircularFifoQueue breadcrumbs; + + /** + * User active in the current context, if any. + */ + private User user; + + /** + * Create a new (empty) Context object with the default Breadcrumb limit. + */ + public Context() { + this(DEFAULT_BREADCRUMB_LIMIT); + } + + /** + * Create a new (empty) Context object with the specified Breadcrumb limit. + * + * @param breadcrumbLimit Number of Breadcrumb objects to retain in ring buffer. + */ + public Context(int breadcrumbLimit) { + breadcrumbs = new CircularFifoQueue<>(breadcrumbLimit); + } + + /** + * Clear state from this context. + */ + public void clear() { + breadcrumbs.clear(); + lastEventId = null; + user = null; + } + + /** + * Return {@link Breadcrumb}s attached to this Context. + * + * @return Iterator of {@link Breadcrumb}s. + */ + public Iterator getBreadcrumbs() { + return breadcrumbs.iterator(); + } + + /** + * Record a single {@link Breadcrumb} into this context. + * + * @param breadcrumb Breadcrumb object to record + */ + public void recordBreadcrumb(Breadcrumb breadcrumb) { + breadcrumbs.add(breadcrumb); + } + + /** + * Store the UUID of the last sent event by this thread, useful for handling user feedback. + * + * @param id UUID of the last event sent by this thread. + */ + public void setLastEventId(UUID id) { + lastEventId = id; + } + + /** + * Get the UUID of the last event sent by this thread, useful for handling user feedback. + * + * Returns null if no event has been sent by this thread or if the event has been + * cleared. For example the RavenServletRequestListener clears the thread's Context + * at the end of each request. + * + * @return UUID of the last event sent by this thread. + */ + public UUID getLastEventId() { + return lastEventId; + } + + /** + * Store the current user in the context. + * + * @param user User to store in context. + */ + public void setUser(User user) { + this.user = user; + } + + /** + * Clears the current user set on this context. + */ + public void clearUser() { + setUser(null); + } + + /** + * Gets the current user from the context. + * + * @return User currently stored in context. + */ + public User getUser() { + return user; + } +} diff --git a/raven/src/main/java/com/getsentry/raven/context/ContextManager.java b/raven/src/main/java/com/getsentry/raven/context/ContextManager.java new file mode 100644 index 00000000000..4b4ba08dffa --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/context/ContextManager.java @@ -0,0 +1,20 @@ +package com.getsentry.raven.context; + +/** + * Context manager implementations define how context-specific data, such as + * {@link com.getsentry.raven.event.Breadcrumb}s are coupled to the idea of + * a "context." What "context" means depends on the application. For example, + * most web applications would define a single request/response cycle as a context, + * while an Android application would define the entire application run as a + * single context. + */ +public interface ContextManager { + + /** + * Returns the proper {@link Context} instance for the given application context. + * + * @return the proper {@link Context} instance for the given application context. + */ + Context getContext(); + +} diff --git a/raven/src/main/java/com/getsentry/raven/context/SingletonContextManager.java b/raven/src/main/java/com/getsentry/raven/context/SingletonContextManager.java new file mode 100644 index 00000000000..b7c31a3a2bb --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/context/SingletonContextManager.java @@ -0,0 +1,19 @@ +package com.getsentry.raven.context; + +/** + * {@link ContextManager} that maintains a single {@link Context} instance + * across the entire application. + */ +public class SingletonContextManager implements ContextManager { + private final Context context = new Context(); + + /** + * Returns a singleton {@link Context} instance. Useful for single-user + * applications. + * + * @return a singleton {@link Context} instance. + */ + public Context getContext() { + return context; + } +} diff --git a/raven/src/main/java/com/getsentry/raven/context/ThreadLocalContextManager.java b/raven/src/main/java/com/getsentry/raven/context/ThreadLocalContextManager.java new file mode 100644 index 00000000000..19e45272797 --- /dev/null +++ b/raven/src/main/java/com/getsentry/raven/context/ThreadLocalContextManager.java @@ -0,0 +1,24 @@ +package com.getsentry.raven.context; + +/** + * A {@link ContextManager} that returns a unique {@link Context} instance per thread. + */ +public class ThreadLocalContextManager implements ContextManager { + private final ThreadLocal context = new ThreadLocal() { + @Override + protected Context initialValue() { + return new Context(); + } + }; + + /** + * Returns a unique {@link Context} instance per thread. Useful for + * applications that use a single thread to server a user's request. + * + * @return a unique {@link Context} instance per thread. + */ + public Context getContext() { + return context.get(); + } + +} diff --git a/raven/src/main/java/com/getsentry/raven/event/Breadcrumbs.java b/raven/src/main/java/com/getsentry/raven/event/Breadcrumbs.java index f81080f78ea..755801fc0d0 100644 --- a/raven/src/main/java/com/getsentry/raven/event/Breadcrumbs.java +++ b/raven/src/main/java/com/getsentry/raven/event/Breadcrumbs.java @@ -1,6 +1,6 @@ package com.getsentry.raven.event; -import com.getsentry.raven.RavenContext; +import com.getsentry.raven.Raven; /** * Helpers for dealing with {@link Breadcrumb}s. @@ -20,9 +20,7 @@ private Breadcrumbs() { * @param breadcrumb Breadcrumb to record */ public static void record(Breadcrumb breadcrumb) { - for (RavenContext context : RavenContext.getActiveContexts()) { - context.recordBreadcrumb(breadcrumb); - } + Raven.getStoredInstance().getContext().recordBreadcrumb(breadcrumb); } } diff --git a/raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java b/raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java index 7a621f85027..cd84f5b9de9 100644 --- a/raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java +++ b/raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java @@ -1,7 +1,7 @@ package com.getsentry.raven.event.helper; import com.getsentry.raven.Raven; -import com.getsentry.raven.RavenContext; +import com.getsentry.raven.context.Context; import com.getsentry.raven.event.Breadcrumb; import com.getsentry.raven.event.EventBuilder; import com.getsentry.raven.event.User; @@ -13,19 +13,19 @@ /** * {@link EventBuilderHelper} that extracts and sends any data attached to the - * provided {@link Raven}'s {@link com.getsentry.raven.RavenContext}. + * provided {@link Raven}'s {@link Context}. */ public class ContextBuilderHelper implements EventBuilderHelper { /** - * Raven object where the RavenContext comes from. + * Raven object where the Context comes from. */ private Raven raven; /** * {@link EventBuilderHelper} that extracts context data from the provided {@link Raven} client. * - * @param raven Raven client which holds RavenContext to be used. + * @param raven Raven client which holds Context to be used. */ public ContextBuilderHelper(Raven raven) { this.raven = raven; @@ -34,7 +34,7 @@ public ContextBuilderHelper(Raven raven) { @Override public void helpBuildingEvent(EventBuilder eventBuilder) { List breadcrumbs = new ArrayList<>(); - RavenContext context = raven.getContext(); + Context context = raven.getContext(); Iterator breadcrumbIterator = context.getBreadcrumbs(); while (breadcrumbIterator.hasNext()) { diff --git a/raven/src/main/java/com/getsentry/raven/servlet/RavenServletRequestListener.java b/raven/src/main/java/com/getsentry/raven/servlet/RavenServletRequestListener.java index 60f3004e504..4af257509c4 100644 --- a/raven/src/main/java/com/getsentry/raven/servlet/RavenServletRequestListener.java +++ b/raven/src/main/java/com/getsentry/raven/servlet/RavenServletRequestListener.java @@ -33,7 +33,7 @@ public void requestDestroyed(ServletRequestEvent servletRequestEvent) { raven.getContext().clear(); } } catch (Exception e) { - logger.error("Error clearing RavenContext state.", e); + logger.error("Error clearing Context state.", e); } } diff --git a/raven/src/test/java/com/getsentry/raven/RavenContextTest.java b/raven/src/test/java/com/getsentry/raven/ContextTest.java similarity index 73% rename from raven/src/test/java/com/getsentry/raven/RavenContextTest.java rename to raven/src/test/java/com/getsentry/raven/ContextTest.java index a175e2b7a86..16f84bff69e 100644 --- a/raven/src/test/java/com/getsentry/raven/RavenContextTest.java +++ b/raven/src/test/java/com/getsentry/raven/ContextTest.java @@ -1,5 +1,6 @@ package com.getsentry.raven; +import com.getsentry.raven.context.Context; import com.getsentry.raven.event.Breadcrumb; import com.getsentry.raven.event.BreadcrumbBuilder; import com.getsentry.raven.event.User; @@ -11,36 +12,14 @@ import java.util.List; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.emptyCollectionOf; import static org.hamcrest.Matchers.equalTo; @Test(singleThreaded = true) -public class RavenContextTest { - - @Test - public void testActivateDeactivate() { - for (RavenContext context : RavenContext.getActiveContexts()) { - context.deactivate(); - } - - RavenContext context = new RavenContext(); - - assertThat(RavenContext.getActiveContexts(), emptyCollectionOf(RavenContext.class)); - - context.activate(); - - List match = new ArrayList<>(1); - match.add(context); - assertThat(RavenContext.getActiveContexts(), equalTo(match)); - - context.deactivate(); - - assertThat(RavenContext.getActiveContexts(), emptyCollectionOf(RavenContext.class)); - } +public class ContextTest { @Test public void testBreadcrumbs() { - RavenContext context = new RavenContext(); + Context context = new Context(); Breadcrumb breadcrumb = new BreadcrumbBuilder() .setLevel("info") @@ -63,7 +42,7 @@ public void testBreadcrumbs() { @Test public void breadcrumbLimit() { - RavenContext context = new RavenContext(1); + Context context = new Context(1); Breadcrumb breadcrumb1 = new BreadcrumbBuilder() .setLevel("info") @@ -94,7 +73,7 @@ public void breadcrumbLimit() { @Test public void testUser() { - RavenContext context = new RavenContext(); + Context context = new Context(); User user = new UserBuilder() .setEmail("test@example.com") diff --git a/raven/src/test/java/com/getsentry/raven/RavenTest.java b/raven/src/test/java/com/getsentry/raven/RavenTest.java index a8b0a22ab67..42909e8eead 100644 --- a/raven/src/test/java/com/getsentry/raven/RavenTest.java +++ b/raven/src/test/java/com/getsentry/raven/RavenTest.java @@ -1,5 +1,7 @@ package com.getsentry.raven; +import com.getsentry.raven.context.ContextManager; +import com.getsentry.raven.context.SingletonContextManager; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; @@ -9,6 +11,7 @@ import com.getsentry.raven.event.EventBuilder; import com.getsentry.raven.event.helper.EventBuilderHelper; import com.getsentry.raven.event.interfaces.ExceptionInterface; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.IOException; @@ -22,10 +25,17 @@ public class RavenTest { @Injectable private Connection mockConnection = null; @Injectable + private ContextManager contextManager = new SingletonContextManager(); + @Injectable private Event mockEvent = null; @Injectable private EventBuilderHelper mockEventBuilderHelper = null; + @BeforeMethod + public void setup() { + contextManager.getContext().clear(); + } + @Test public void testSendEvent() throws Exception { raven.sendEvent(mockEvent); diff --git a/raven/src/test/java/com/getsentry/raven/connection/EventSendFailureCallbackTest.java b/raven/src/test/java/com/getsentry/raven/connection/EventSendFailureCallbackTest.java index 170c5e8a794..ad211ed991d 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/EventSendFailureCallbackTest.java +++ b/raven/src/test/java/com/getsentry/raven/connection/EventSendFailureCallbackTest.java @@ -2,10 +2,8 @@ import com.getsentry.raven.DefaultRavenFactory; import com.getsentry.raven.Raven; -import com.getsentry.raven.RavenContext; import com.getsentry.raven.dsn.Dsn; import com.getsentry.raven.event.Event; -import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; import java.util.concurrent.atomic.AtomicBoolean; @@ -15,13 +13,6 @@ public class EventSendFailureCallbackTest { - @AfterMethod - private void afterMethod() { - for (RavenContext ctx : RavenContext.getActiveContexts()) { - ctx.deactivate(); - } - } - @Test public void testSimpleCallback() { final AtomicBoolean flag = new AtomicBoolean(false); diff --git a/raven/src/test/java/com/getsentry/raven/event/BreadcrumbTest.java b/raven/src/test/java/com/getsentry/raven/event/BreadcrumbTest.java index bcdf1109c35..62d125af96f 100644 --- a/raven/src/test/java/com/getsentry/raven/event/BreadcrumbTest.java +++ b/raven/src/test/java/com/getsentry/raven/event/BreadcrumbTest.java @@ -2,10 +2,13 @@ import com.getsentry.raven.Raven; import com.getsentry.raven.connection.Connection; +import com.getsentry.raven.context.ContextManager; +import com.getsentry.raven.context.SingletonContextManager; import com.getsentry.raven.event.helper.ContextBuilderHelper; import mockit.Injectable; import mockit.Tested; import mockit.Verifications; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.ArrayList; @@ -19,6 +22,13 @@ public class BreadcrumbTest { @Injectable private Connection mockConnection = null; + @Injectable + private ContextManager contextManager = new SingletonContextManager(); + + @BeforeMethod + public void setup() { + contextManager.getContext().clear(); + } @Test public void testBreadcrumbsViaContextRecording() { diff --git a/raven/src/test/java/com/getsentry/raven/event/UserTest.java b/raven/src/test/java/com/getsentry/raven/event/UserTest.java index 25dddcdadd2..cebeac78c85 100644 --- a/raven/src/test/java/com/getsentry/raven/event/UserTest.java +++ b/raven/src/test/java/com/getsentry/raven/event/UserTest.java @@ -2,11 +2,14 @@ import com.getsentry.raven.Raven; import com.getsentry.raven.connection.Connection; +import com.getsentry.raven.context.ContextManager; +import com.getsentry.raven.context.SingletonContextManager; import com.getsentry.raven.event.helper.ContextBuilderHelper; import com.getsentry.raven.event.interfaces.UserInterface; import mockit.Injectable; import mockit.Tested; import mockit.Verifications; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import static org.hamcrest.MatcherAssert.assertThat; @@ -15,9 +18,15 @@ public class UserTest { @Tested private Raven raven = null; - @Injectable private Connection mockConnection = null; + @Injectable + private ContextManager contextManager = new SingletonContextManager(); + + @BeforeMethod + public void setup() { + contextManager.getContext().clear(); + } @Test public void testUserPropagation() { From 6b97c8ca9ed0cb9fa170a5e015e081ae783fa360 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 30 Mar 2017 15:22:52 -0500 Subject: [PATCH 1556/2152] Populate new `app` context map, use versionName for release. (#359) --- .../helper/AndroidEventBuilderHelper.java | 75 +++++++++++++++++-- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java b/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java index 5f4a9e51177..ab19202ec5c 100644 --- a/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java +++ b/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java @@ -4,6 +4,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.BatteryManager; import android.os.Build; @@ -22,6 +24,8 @@ import java.io.File; import java.io.IOException; import java.io.InputStreamReader; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -54,11 +58,9 @@ public AndroidEventBuilderHelper(Context ctx) { @Override public void helpBuildingEvent(EventBuilder eventBuilder) { eventBuilder.withSdkName(RavenEnvironment.SDK_NAME + ":android"); - try { - int versionCode = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0).versionCode; - eventBuilder.withRelease(Integer.toString(versionCode)); - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "Couldn't find package version: " + e); + PackageInfo packageInfo = getPackageInfo(ctx); + if (packageInfo != null) { + eventBuilder.withRelease(packageInfo.versionName); } String androidId = Settings.Secure.getString(ctx.getContentResolver(), Settings.Secure.ANDROID_ID); @@ -75,8 +77,10 @@ private Map> getContexts() { Map> contexts = new HashMap<>(); Map deviceMap = new HashMap<>(); Map osMap = new HashMap<>(); + Map appMap = new HashMap<>(); contexts.put("os", osMap); contexts.put("device", deviceMap); + contexts.put("app", appMap); // Device deviceMap.put("manufacturer", Build.MANUFACTURER); @@ -119,9 +123,35 @@ private Map> getContexts() { osMap.put("kernel_version", KERNEL_VERSION); osMap.put("rooted", isRooted()); + // App + PackageInfo packageInfo = getPackageInfo(ctx); + if (packageInfo != null) { + appMap.put("app_version", packageInfo.versionName); + appMap.put("app_build", packageInfo.versionCode); + appMap.put("app_identifier", packageInfo.packageName); + } + + appMap.put("app_name", getApplicationName(ctx)); + appMap.put("app_start_time", stringifyDate(new Date())); + return contexts; } + /** + * Return the Application's PackageInfo if possible, or null. + * + * @param ctx Android application context + * @return the Application's PackageInfo if possible, or null + */ + private static PackageInfo getPackageInfo(Context ctx) { + try { + return ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Error getting package info.", e); + return null; + } + } + /** * Fake the device family by using the first word in the Build.MODEL. Works * well in most cases... "Nexus 6P" -> "Nexus", "Galaxy S7" -> "Galaxy". @@ -428,4 +458,39 @@ private static DisplayMetrics getDisplayMetrics(Context ctx) { } } + /** + * Formats the given Date object into an ISO8601 String. Note that SimpleDateFormat isn't + * thread safe, and so we build one every time. + * + * @param date Date to format as ISO8601 + * @return String representing the provided Date in ISO8601 format + */ + private static String stringifyDate(Date date) { + return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(date); + } + + /** + * Get the human-facing Application name. + * + * @param ctx Android application context + * @return Application name + */ + private static String getApplicationName(Context ctx) { + try { + ApplicationInfo applicationInfo = ctx.getApplicationInfo(); + int stringId = applicationInfo.labelRes; + if (stringId == 0) { + if (applicationInfo.nonLocalizedLabel != null) { + return applicationInfo.nonLocalizedLabel.toString(); + } + } else { + return ctx.getString(stringId); + } + } catch (Exception e) { + Log.e(TAG, "Error getting application name.", e); + } + + return null; + } + } From 09a6a9f63376d0fb2264b8cceafe85cb4cc05999 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 4 Apr 2017 17:10:23 -0500 Subject: [PATCH 1557/2152] Changed ``Breadcrumb`` and `BreadcrumbBuilder`` to use enums for some fields. (#362) --- CHANGES | 1 + .../com/getsentry/raven/event/Breadcrumb.java | 102 +++++++++++++++--- .../raven/event/BreadcrumbBuilder.java | 12 +-- .../raven/marshaller/json/JsonMarshaller.java | 4 +- .../java/com/getsentry/raven/ContextTest.java | 6 +- .../raven/buffer/DiskBufferTest.java | 6 +- .../getsentry/raven/event/BreadcrumbTest.java | 6 +- .../marshaller/json/JsonMarshallerTest.java | 4 +- 8 files changed, 104 insertions(+), 37 deletions(-) diff --git a/CHANGES b/CHANGES index 442f98900b8..0a41359defb 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,7 @@ Version 8.0.0 - Fix so that system properties and environment variables always override hardcoded logger configuration for settings such as release, environment, etc. - Fix Raven initialization so that slf4j doesn't emit log replay warning on startup. +- Changed ``Breadcrumb`` and `BreadcrumbBuilder`` to use enums for some fields. Version 7.8.6 ------------- diff --git a/raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java b/raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java index 60e5e91696c..462166dd300 100644 --- a/raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java +++ b/raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java @@ -14,7 +14,7 @@ public class Breadcrumb implements Serializable { /** * (Optional) Type of the breadcrumb. */ - private final String type; + private final Type type; /** * Timestamp when the breadcrumb occurred. */ @@ -22,7 +22,7 @@ public class Breadcrumb implements Serializable { /** * Level of the breadcrumb. */ - private final String level; + private final Level level; /** * Message of the breadcrumb. */ @@ -36,26 +36,102 @@ public class Breadcrumb implements Serializable { */ private final Map data; + /** + * Possible choices for the level field. + */ + public enum Level { + /** + * DEBUG level. + */ + DEBUG("debug"), + + /** + * INFO level. + */ + INFO("info"), + + /** + * WARNING level. + */ + WARNING("warning"), + + /** + * ERROR level. + */ + ERROR("error"), + + /** + * CRITICAL level. + */ + CRITICAL("critical"); + + private final String value; + + /** + * Construct a {@link Level} with the value to serialize with. + * + * @param value Value to use for serialization. + */ + Level(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + /** + * Possible choices for the type field. + */ + public enum Type { + /** + * DEFAULT type. + */ + DEFAULT("default"), + + /** + * HTTP type. + */ + HTTP("http"), + + /** + * NAVIGATION type. + */ + NAVIGATION("navigation"); + + private final String value; + + /** + * Construct a {@link Type} with the value to serialize with. + * + * @param value Value to use for serialization. + */ + Type(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + /** * Create an immutable {@link Breadcrumb} object. * - * @param type String + * @param type Type * @param timestamp Date - * @param level String + * @param level Level * @param message String * @param category String * @param data Map of String to String */ - Breadcrumb(String type, Date timestamp, String level, String message, + Breadcrumb(Type type, Date timestamp, Level level, String message, String category, Map data) { if (timestamp == null) { timestamp = new Date(); } - checkNotNull(level, "level"); - checkNotNull(category, "category"); - if (message == null && (data == null || data.size() < 1)) { throw new IllegalArgumentException("one of 'message' or 'data' must be set"); } @@ -68,13 +144,7 @@ public class Breadcrumb implements Serializable { this.data = data; } - private void checkNotNull(String str, String name) { - if (str == null) { - throw new IllegalArgumentException("field '" + name + "' is required but got null"); - } - } - - public String getType() { + public Type getType() { return type; } @@ -82,7 +152,7 @@ public Date getTimestamp() { return timestamp; } - public String getLevel() { + public Level getLevel() { return level; } diff --git a/raven/src/main/java/com/getsentry/raven/event/BreadcrumbBuilder.java b/raven/src/main/java/com/getsentry/raven/event/BreadcrumbBuilder.java index dbd5cf02f67..cd929d2293c 100644 --- a/raven/src/main/java/com/getsentry/raven/event/BreadcrumbBuilder.java +++ b/raven/src/main/java/com/getsentry/raven/event/BreadcrumbBuilder.java @@ -8,9 +8,9 @@ */ public class BreadcrumbBuilder { - private String type; + private Breadcrumb.Type type; private Date timestamp; - private String level; + private Breadcrumb.Level level; private String message; private String category; private Map data; @@ -18,10 +18,10 @@ public class BreadcrumbBuilder { /** * Type of the {@link Breadcrumb}. * - * @param newType String + * @param newType {@link Breadcrumb.Type} * @return current BreadcrumbBuilder */ - public BreadcrumbBuilder setType(String newType) { + public BreadcrumbBuilder setType(Breadcrumb.Type newType) { this.type = newType; return this; } @@ -40,10 +40,10 @@ public BreadcrumbBuilder setTimestamp(Date newTimestamp) { /** * Level of the {@link Breadcrumb}. * - * @param newLevel String + * @param newLevel {@link Breadcrumb.Level} * @return current BreadcrumbBuilder */ - public BreadcrumbBuilder setLevel(String newLevel) { + public BreadcrumbBuilder setLevel(Breadcrumb.Level newLevel) { this.level = newLevel; return this; } diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java index bfccbd6d220..ba79ff08f6a 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java +++ b/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java @@ -294,10 +294,10 @@ private void writeBreadcumbs(JsonGenerator generator, List breadcrum generator.writeNumberField("timestamp", breadcrumb.getTimestamp().getTime() / 1000); if (breadcrumb.getType() != null) { - generator.writeStringField("type", breadcrumb.getType()); + generator.writeStringField("type", breadcrumb.getType().getValue()); } if (breadcrumb.getLevel() != null) { - generator.writeStringField("level", breadcrumb.getLevel()); + generator.writeStringField("level", breadcrumb.getLevel().getValue()); } if (breadcrumb.getMessage() != null) { generator.writeStringField("message", breadcrumb.getMessage()); diff --git a/raven/src/test/java/com/getsentry/raven/ContextTest.java b/raven/src/test/java/com/getsentry/raven/ContextTest.java index 16f84bff69e..029e132ee30 100644 --- a/raven/src/test/java/com/getsentry/raven/ContextTest.java +++ b/raven/src/test/java/com/getsentry/raven/ContextTest.java @@ -22,7 +22,7 @@ public void testBreadcrumbs() { Context context = new Context(); Breadcrumb breadcrumb = new BreadcrumbBuilder() - .setLevel("info") + .setLevel(Breadcrumb.Level.INFO) .setCategory("foo") .setMessage("test") .build(); @@ -45,12 +45,12 @@ public void breadcrumbLimit() { Context context = new Context(1); Breadcrumb breadcrumb1 = new BreadcrumbBuilder() - .setLevel("info") + .setLevel(Breadcrumb.Level.INFO) .setCategory("foo") .setMessage("test1") .build(); Breadcrumb breadcrumb2 = new BreadcrumbBuilder() - .setLevel("info") + .setLevel(Breadcrumb.Level.INFO) .setCategory("foo") .setMessage("test2") .build(); diff --git a/raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java b/raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java index 86f395a9be5..5c8b3854917 100644 --- a/raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java +++ b/raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java @@ -36,11 +36,7 @@ public void teardown() { @Test public void testAddAndDiscard() throws IOException { - Breadcrumb breadcrumb = new BreadcrumbBuilder() - .setLevel("INFO") - .setCategory("CATEGORY") - .setMessage("MESSAGE") - .build(); + Breadcrumb breadcrumb = new BreadcrumbBuilder().setMessage("MESSAGE").build(); List breadcrumbs = Lists.newArrayList(); breadcrumbs.add(breadcrumb); diff --git a/raven/src/test/java/com/getsentry/raven/event/BreadcrumbTest.java b/raven/src/test/java/com/getsentry/raven/event/BreadcrumbTest.java index 62d125af96f..dc720da18e9 100644 --- a/raven/src/test/java/com/getsentry/raven/event/BreadcrumbTest.java +++ b/raven/src/test/java/com/getsentry/raven/event/BreadcrumbTest.java @@ -35,7 +35,7 @@ public void testBreadcrumbsViaContextRecording() { raven.addBuilderHelper(new ContextBuilderHelper(raven)); final Breadcrumb breadcrumb = new BreadcrumbBuilder() - .setLevel("info") + .setLevel(Breadcrumb.Level.INFO) .setMessage("message") .setCategory("step") .build(); @@ -58,7 +58,7 @@ public void testBreadcrumbsViaEventBuilder() { raven.addBuilderHelper(new ContextBuilderHelper(raven)); final Breadcrumb breadcrumb = new BreadcrumbBuilder() - .setLevel("info") + .setLevel(Breadcrumb.Level.INFO) .setMessage("message") .setCategory("step") .build(); @@ -83,7 +83,7 @@ public void testBreadcrumbsViaEvent() { raven.addBuilderHelper(new ContextBuilderHelper(raven)); final Breadcrumb breadcrumb = new BreadcrumbBuilder() - .setLevel("info") + .setLevel(Breadcrumb.Level.INFO) .setMessage("message") .setCategory("step") .build(); diff --git a/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java b/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java index de92f2ac0d0..c60aa68ab47 100644 --- a/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java +++ b/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java @@ -237,13 +237,13 @@ public void testEventBreadcrumbsWrittenProperly() throws Exception { Breadcrumb breadcrumb1 = new BreadcrumbBuilder() .setTimestamp(new Date(1463169342000L)) - .setLevel("info") + .setLevel(Breadcrumb.Level.INFO) .setCategory("foo") .setMessage("test1") .build(); Breadcrumb breadcrumb2 = new BreadcrumbBuilder() .setTimestamp(new Date(1463169343000L)) - .setLevel("info") + .setLevel(Breadcrumb.Level.INFO) .setCategory("foo") .setMessage("test2") .build(); From 9b82be1690e0f9921ed61a89112db5643a3048f3 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 5 Apr 2017 15:09:41 -0500 Subject: [PATCH 1558/2152] Add Breadcrumb documentation. (#364) --- docs/context.rst | 70 ++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 5 +-- docs/modules/android.rst | 5 +++ docs/modules/log4j.rst | 11 +++++++ docs/modules/log4j2.rst | 11 +++++++ docs/modules/logback.rst | 11 +++++++ docs/modules/raven.rst | 11 +++++++ docs/usage.rst | 11 +++++++ 8 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 docs/context.rst diff --git a/docs/context.rst b/docs/context.rst new file mode 100644 index 00000000000..a962bef0bbb --- /dev/null +++ b/docs/context.rst @@ -0,0 +1,70 @@ +Context & Breadcrumbs +===================== + +The Java SDK implements the idea of a "context" to support attaching additional +information to events, such as breadcrumbs. A context may refer to a single +request to a web framework, to the entire lifetime of an Android application, +or something else that better suits your application's needs. + +There is no single definition of context that applies to every application, +for this reason a specific implementation must be chosen depending on what your +application does and how it is structured. By default Raven uses a +``ThreadLocalContextManager`` that maintains a single ``Context`` instance per thread. +This is useful for frameworks that use one thread per user request such as those based +on synchronous servlet APIs. Raven also installs a ``ServletRequestListener`` that will +clear the thread's context after each servlet request finishes. + +Raven defaults to the ``SingletonContextManager`` on Android, which maintains a single +context instance for all threads for the lifetime of the application. + +As of version ``8.0.0`` to override the ``ContextManager`` you will need to override +the ``getContextManager`` method in the ``DefaultRavenFactory``. A simpler API will likely +be provided in the future. + +Using Breadcrumbs +----------------- + +Breadcrumbs can be used to describe actions that occurred in your application leading +up to an event being sent. For example, whether external API requests were made, +or whether a user clicked on something in an Android application. + +Once a Raven instance has been initialized, either via a logging framework or manually, +you can begin recording breadcrumbs. By default the last 100 breadcrumbs for a given +context instance will be stored and sent with future events. + +.. sourcecode:: java + + import com.getsentry.raven.Raven; + import com.getsentry.raven.context.Context; + import com.getsentry.raven.event.BreadcrumbBuilder; + import com.getsentry.raven.event.Breadcrumbs; + import com.getsentry.raven.event.UserBuilder; + + public void example() { + // Record a breadcrumb without having to look up the context instance manually + Breadcrumbs.record( + new BreadcrumbBuilder().setMessage("User did something specific again!").build() + ); + + // ... or retrieve and manipulate the context instance manually + + // Retrieve the stored Raven instance + Raven raven = Raven.getStoredInstance(); + + // Get the current context instance + Context context = raven.getContext(); + + // Set the current User in the context + context.setUser( + new UserBuilder().setUsername("user1").build() + ); + + // Record a breadcrumb in the context + context.recordBreadcrumb( + new BreadcrumbBuilder().setMessage("User did something specific!").build() + ); + + // Clear the context, useful if you need to add hooks in a framework + // to empty context between requests + context.clear() + } \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index dc990c3ab19..f42e77021e8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,7 +10,7 @@ Java ==== -Raven for Java (``raven-java``) is the official Java client for Sentry. At its core it provides +Raven for Java (``raven-java``) is the official Java SDK for Sentry. At its core it provides a raw client for sending events to Sentry, but it is highly recommended that you use one of the included library or framework integrations listed below if at all possible. @@ -19,6 +19,7 @@ use one of the included library or framework integrations listed below if at all :titlesonly: config + context usage modules/index @@ -29,4 +30,4 @@ Resources: * `Code `_ * `Mailing List `_ * `IRC `_ (irc.freenode.net, #sentry) -* `Travis CI `_ \ No newline at end of file +* `Travis CI `_ diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 6a24e28671f..3b159a01fb3 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -80,6 +80,11 @@ Now you can use ``Raven`` to capture events anywhere in your application: // Send a simple event to the Sentry server Raven.capture("Error message"); + // Set a breadcrumb that will be sent with the next event(s) + Breadcrumbs.record( + new BreadcrumbBuilder().setMessage("User made an action").build() + ); + try { something() } catch (Exception e) { diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index f64b821b5d1..54281dd4ab1 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -277,6 +277,17 @@ In Practice logger.error("This is a test"); } + void logWithBreadcrumbs() { + // Record a breadcrumb that will be sent with the next event(s), + // by default the last 100 breadcrumbs are kept. + Breadcrumbs.record( + new BreadcrumbBuilder().setMessage("User made an action").build() + ); + + // This sends a simple event to Sentry + logger.error("This is a test"); + } + void logWithExtras() { // MDC extras MDC.put("extra_key", "extra_value"); diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index c3b4ce14a0f..36310962e57 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -204,6 +204,17 @@ In Practice logger.error("This is a test"); } + void logWithBreadcrumbs() { + // Record a breadcrumb that will be sent with the next event(s), + // by default the last 100 breadcrumbs are kept. + Breadcrumbs.record( + new BreadcrumbBuilder().setMessage("User made an action").build() + ); + + // This sends a simple event to Sentry + logger.error("This is a test"); + } + void logWithTag() { // This sends an event with a tag named 'log4j2-Marker' to Sentry logger.error(MARKER, "This is a test"); diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 852d13023bb..998bffac2cc 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -211,6 +211,17 @@ In Practice logger.error("This is a test"); } + void logWithBreadcrumbs() { + // Record a breadcrumb that will be sent with the next event(s), + // by default the last 100 breadcrumbs are kept. + Breadcrumbs.record( + new BreadcrumbBuilder().setMessage("User made an action").build() + ); + + // This sends a simple event to Sentry + logger.error("This is a test"); + } + void logWithTag() { // This sends an event with a tag named 'logback-Marker' to Sentry logger.info(MARKER, "This is a test"); diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index 42052a6693b..df1745d6bea 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -156,6 +156,17 @@ In Practice logger.error(Level.INFO, "This is a test"); } + void logWithBreadcrumbs() { + // Record a breadcrumb that will be sent with the next event(s), + // by default the last 100 breadcrumbs are kept. + Breadcrumbs.record( + new BreadcrumbBuilder().setMessage("User made an action").build() + ); + + // This sends a simple event to Sentry + logger.error("This is a test"); + } + void logException() { try { unsafeMethod(); diff --git a/docs/usage.rst b/docs/usage.rst index e98f663a5f1..415403513ec 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -65,6 +65,17 @@ of the send methods it provides. raven.sendMessage("This is a test"); } + void logWithBreadcrumbs() { + // Record a breadcrumb that will be sent with the next event(s), + // by default the last 100 breadcrumbs are kept. + Breadcrumbs.record( + new BreadcrumbBuilder().setMessage("User made an action").build() + ); + + // This sends a simple event to Sentry + raven.sendMessage("This is a test"); + } + void logException() { try { unsafeMethod(); From 48360e4e0cc91c1927fe75b4f60fdbee38832773 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 5 Apr 2017 15:19:18 -0500 Subject: [PATCH 1559/2152] Bump to 8.0.0-SNAPSHOT --- pom.xml | 2 +- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 5dc5553f8ac..45af6f441a1 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 7.8.7-SNAPSHOT + 8.0.0-SNAPSHOT pom Raven-Java diff --git a/raven-android/pom.xml b/raven-android/pom.xml index 8ad25dc134f..bcc17ee5bd8 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.7-SNAPSHOT + 8.0.0-SNAPSHOT raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 930109531c1..dae8961ed21 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 7.8.7-SNAPSHOT + 8.0.0-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 4bfb0bff282..3e19a57339a 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.7-SNAPSHOT + 8.0.0-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index a1069dd7770..1c37d422e1a 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.7-SNAPSHOT + 8.0.0-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index bef0dbff574..7bce397db6f 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.7-SNAPSHOT + 8.0.0-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index c9eb9c8ede1..efd8d6d38f5 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 7.8.7-SNAPSHOT + 8.0.0-SNAPSHOT raven From 799a51b60086bda3c26ed2628742eb18935c6f9d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 5 Apr 2017 15:20:32 -0500 Subject: [PATCH 1560/2152] Bump docs to 8.0.0 --- docs/modules/android.rst | 4 ++-- docs/modules/appengine.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/raven.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 3b159a01fb3..be7b865bc2a 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-android:7.8.6' + compile 'com.getsentry.raven:raven-android:8.0.0' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 8a380be4a16..9120d846786 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: com.getsentry.raven raven-appengine - 7.8.6 + 8.0.0 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-appengine:7.8.6' + compile 'com.getsentry.raven:raven-appengine:8.0.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-appengine" % "7.8.6" + libraryDependencies += "com.getsentry.raven" % "raven-appengine" % "8.0.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 54281dd4ab1..50c9f2db4b7 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-log4j - 7.8.6 + 8.0.0 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-log4j:7.8.6' + compile 'com.getsentry.raven:raven-log4j:8.0.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-log4j" % "7.8.6" + libraryDependencies += "com.getsentry.raven" % "raven-log4j" % "8.0.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 36310962e57..256f1f72a76 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-log4j2 - 7.8.6 + 8.0.0 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-log4j2:7.8.6' + compile 'com.getsentry.raven:raven-log4j2:8.0.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-log4j2" % "7.8.6" + libraryDependencies += "com.getsentry.raven" % "raven-log4j2" % "8.0.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 998bffac2cc..3e487ecb52b 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-logback - 7.8.6 + 8.0.0 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-logback:7.8.6' + compile 'com.getsentry.raven:raven-logback:8.0.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-logback" % "7.8.6" + libraryDependencies += "com.getsentry.raven" % "raven-logback" % "8.0.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index df1745d6bea..e19ad0bfd25 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -18,22 +18,22 @@ Using Maven: com.getsentry.raven raven - 7.8.6 + 8.0.0 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven:7.8.6' + compile 'com.getsentry.raven:raven:8.0.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.6" + libraryDependencies += "com.getsentry.raven" % "raven" % "8.0.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index 415403513ec..3479371fde5 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -15,22 +15,22 @@ Using Maven: com.getsentry.raven raven - 7.8.6 + 8.0.0 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven:7.8.6' + compile 'com.getsentry.raven:raven:8.0.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven" % "7.8.6" + libraryDependencies += "com.getsentry.raven" % "raven" % "8.0.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 1341cbe9c320658eb18a529a7d1db2c15da0b4dd Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 5 Apr 2017 15:22:03 -0500 Subject: [PATCH 1561/2152] [maven-release-plugin] prepare release v8.0.0 --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 45af6f441a1..9e9fb20841a 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 8.0.0-SNAPSHOT + 8.0.0 pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v8.0.0 https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index bcc17ee5bd8..a21d4127c59 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.0-SNAPSHOT + 8.0.0 raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index dae8961ed21..4838ea49f2e 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 8.0.0-SNAPSHOT + 8.0.0 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 3e19a57339a..501248344c6 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.0-SNAPSHOT + 8.0.0 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 1c37d422e1a..bae1e45b2ab 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.0-SNAPSHOT + 8.0.0 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 7bce397db6f..30956a3d7b4 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.0-SNAPSHOT + 8.0.0 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index efd8d6d38f5..090e32e324e 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.0-SNAPSHOT + 8.0.0 raven From d141559114bd87e83fab2867b74003a5c6457acd Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 5 Apr 2017 15:22:03 -0500 Subject: [PATCH 1562/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 9e9fb20841a..4083618edd9 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 8.0.0 + 8.0.1-SNAPSHOT pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v8.0.0 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index a21d4127c59..de1f0ebd8c6 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.0 + 8.0.1-SNAPSHOT raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 4838ea49f2e..8218d2332d9 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 8.0.0 + 8.0.1-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 501248344c6..9002daca737 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.0 + 8.0.1-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index bae1e45b2ab..f5733e2c015 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.0 + 8.0.1-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 30956a3d7b4..d81d279ffcc 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.0 + 8.0.1-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 090e32e324e..7cc0cabb54a 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.0 + 8.0.1-SNAPSHOT raven From 63dbeb61aca857cbbd7900c4f4721d0d522f24fb Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 5 Apr 2017 15:22:06 -0500 Subject: [PATCH 1563/2152] Bump CHANGES to 8.0.1 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 0a41359defb..79409e06062 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 8.0.1 +------------- + +- + Version 8.0.0 ------------- From 8972a8f0733041f58d6856734c8aa80954e7b14a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 7 Apr 2017 12:57:09 -0500 Subject: [PATCH 1564/2152] Fix message interface errors in log4j2 integration. (#367) Apparently getFormat() can (and does) return an empty string in some cases. This would cause a `null` format string with parameters and display as an Event/JSON issue in the Sentry UI. This fixes the log4j2 integration to only include the parameterized message information if getFormat() returns something useful. --- CHANGES | 2 +- .../main/java/com/getsentry/raven/log4j2/SentryAppender.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 79409e06062..a9e08a5aa80 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 8.0.1 ------------- -- +- Fix message interface errors in log4j2 integration. Version 8.0.0 ------------- diff --git a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java index 8f1dac53f05..d9aceb4cdd6 100644 --- a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java @@ -347,7 +347,9 @@ protected Event buildEvent(LogEvent event) { eventBuilder.withEnvironment(environment.trim()); } - if (!eventMessage.getFormattedMessage().equals(eventMessage.getFormat())) { + if (eventMessage.getFormat() != null + && !eventMessage.getFormat().equals("") + && !eventMessage.getFormattedMessage().equals(eventMessage.getFormat())) { eventBuilder.withSentryInterface(new MessageInterface( eventMessage.getFormat(), formatMessageParameters(eventMessage.getParameters()), From d7626ceab7aa3ca16f20dc75fd3f0cb4c28dad36 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 7 Apr 2017 15:30:41 -0500 Subject: [PATCH 1565/2152] Fix setting the ``RavenFactory`` via Java System Properties or environment variables. (#369) --- CHANGES | 1 + .../java/com/getsentry/raven/log4j/SentryAppender.java | 8 ++++---- .../java/com/getsentry/raven/log4j2/SentryAppender.java | 8 ++++---- .../java/com/getsentry/raven/logback/SentryAppender.java | 8 ++++---- .../main/java/com/getsentry/raven/jul/SentryHandler.java | 8 ++++---- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index a9e08a5aa80..fa4b8494503 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ Version 8.0.1 ------------- +- Fix setting the ``RavenFactory`` via Java System Properties or environment variables. - Fix message interface errors in log4j2 integration. Version 8.0.0 diff --git a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java index 440f96581fc..66ae9b2e753 100644 --- a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java +++ b/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java @@ -113,10 +113,6 @@ public SentryAppender(Raven raven) { */ @SuppressWarnings("checkstyle:hiddenfield") private void lazyInit() { - if (raven == null) { - initRaven(); - } - if (!initialized) { synchronized (this) { if (!initialized) { @@ -156,6 +152,10 @@ private void lazyInit() { } } } + + if (raven == null) { + initRaven(); + } } /** diff --git a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java index d9aceb4cdd6..2eec0351f0b 100644 --- a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java +++ b/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java @@ -194,10 +194,6 @@ public static SentryAppender createAppender(@PluginAttribute("name") final Strin */ @SuppressWarnings("checkstyle:hiddenfield") private void lazyInit() { - if (raven == null) { - initRaven(); - } - if (!initialized) { synchronized (this) { if (!initialized) { @@ -237,6 +233,10 @@ private void lazyInit() { } } } + + if (raven == null) { + initRaven(); + } } /** diff --git a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java index b8ef953879d..e8d102d7c75 100644 --- a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java +++ b/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java @@ -125,10 +125,6 @@ public SentryAppender(Raven raven) { */ @SuppressWarnings("checkstyle:hiddenfield") private void lazyInit() { - if (raven == null) { - initRaven(); - } - if (!initialized) { synchronized (this) { if (!initialized) { @@ -168,6 +164,10 @@ private void lazyInit() { } } } + + if (raven == null) { + initRaven(); + } } /** diff --git a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java index 44cc6964743..e8ba2b14451 100644 --- a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java +++ b/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java @@ -117,10 +117,6 @@ public SentryHandler(Raven raven) { */ @SuppressWarnings("checkstyle:hiddenfield") private void lazyInit() { - if (raven == null) { - initRaven(); - } - if (!initialized) { synchronized (this) { if (!initialized) { @@ -161,6 +157,10 @@ private void lazyInit() { } } } + + if (raven == null) { + initRaven(); + } } /** From 9c1e97ef95c4fe8cd2cb79cd7dc8682433793cb8 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 7 Apr 2017 15:32:04 -0500 Subject: [PATCH 1566/2152] Bump docs to 8.0.1 --- docs/context.rst | 2 +- docs/modules/android.rst | 6 +++--- docs/modules/appengine.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/raven.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 8 files changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/context.rst b/docs/context.rst index a962bef0bbb..d507f0474ec 100644 --- a/docs/context.rst +++ b/docs/context.rst @@ -17,7 +17,7 @@ clear the thread's context after each servlet request finishes. Raven defaults to the ``SingletonContextManager`` on Android, which maintains a single context instance for all threads for the lifetime of the application. -As of version ``8.0.0`` to override the ``ContextManager`` you will need to override +As of version ``8.0.1`` to override the ``ContextManager`` you will need to override the ``getContextManager`` method in the ``DefaultRavenFactory``. A simpler API will likely be provided in the future. diff --git a/docs/modules/android.rst b/docs/modules/android.rst index be7b865bc2a..ab126514af0 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Raven Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``8.0.0`` of the Android SDK to a sample application that doesn't even use +Adding version ``8.0.1`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-android:8.0.0' + compile 'com.getsentry.raven:raven-android:8.0.1' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 9120d846786..7aeac80d9a1 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: com.getsentry.raven raven-appengine - 8.0.0 + 8.0.1 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-appengine:8.0.0' + compile 'com.getsentry.raven:raven-appengine:8.0.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-appengine" % "8.0.0" + libraryDependencies += "com.getsentry.raven" % "raven-appengine" % "8.0.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 50c9f2db4b7..0c9e8996dc0 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-log4j - 8.0.0 + 8.0.1 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-log4j:8.0.0' + compile 'com.getsentry.raven:raven-log4j:8.0.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-log4j" % "8.0.0" + libraryDependencies += "com.getsentry.raven" % "raven-log4j" % "8.0.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 256f1f72a76..8caa772670e 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-log4j2 - 8.0.0 + 8.0.1 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-log4j2:8.0.0' + compile 'com.getsentry.raven:raven-log4j2:8.0.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-log4j2" % "8.0.0" + libraryDependencies += "com.getsentry.raven" % "raven-log4j2" % "8.0.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 3e487ecb52b..1905685a2fa 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-logback - 8.0.0 + 8.0.1 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-logback:8.0.0' + compile 'com.getsentry.raven:raven-logback:8.0.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-logback" % "8.0.0" + libraryDependencies += "com.getsentry.raven" % "raven-logback" % "8.0.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index e19ad0bfd25..d3d520c7c63 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -18,22 +18,22 @@ Using Maven: com.getsentry.raven raven - 8.0.0 + 8.0.1 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven:8.0.0' + compile 'com.getsentry.raven:raven:8.0.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven" % "8.0.0" + libraryDependencies += "com.getsentry.raven" % "raven" % "8.0.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index 3479371fde5..fcf71c3c010 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -15,22 +15,22 @@ Using Maven: com.getsentry.raven raven - 8.0.0 + 8.0.1 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven:8.0.0' + compile 'com.getsentry.raven:raven:8.0.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven" % "8.0.0" + libraryDependencies += "com.getsentry.raven" % "raven" % "8.0.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From c85881ba930c5bb9641c165ed5e4a2581c1e7ac9 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 7 Apr 2017 15:33:35 -0500 Subject: [PATCH 1567/2152] [maven-release-plugin] prepare release v8.0.1 --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 4083618edd9..200f04fccc8 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 8.0.1-SNAPSHOT + 8.0.1 pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v8.0.1 https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index de1f0ebd8c6..1a6a48220d8 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.1-SNAPSHOT + 8.0.1 raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 8218d2332d9..04762295607 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 8.0.1-SNAPSHOT + 8.0.1 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 9002daca737..8ea179d3275 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.1-SNAPSHOT + 8.0.1 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index f5733e2c015..4be73f0237d 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.1-SNAPSHOT + 8.0.1 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index d81d279ffcc..4715aeb136e 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.1-SNAPSHOT + 8.0.1 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 7cc0cabb54a..48425db4051 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.1-SNAPSHOT + 8.0.1 raven From d02a95a330dbe134c2f1c82c10c9c17223ae14e8 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 7 Apr 2017 15:33:36 -0500 Subject: [PATCH 1568/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 200f04fccc8..bda3f22133d 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 8.0.1 + 8.0.2-SNAPSHOT pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v8.0.1 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index 1a6a48220d8..66b3be7b702 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.1 + 8.0.2-SNAPSHOT raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 04762295607..8a9e6a38f47 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 8.0.1 + 8.0.2-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 8ea179d3275..744211da190 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.1 + 8.0.2-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 4be73f0237d..879a65f268d 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.1 + 8.0.2-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 4715aeb136e..e45d7e22141 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.1 + 8.0.2-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 48425db4051..eec05b76781 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.1 + 8.0.2-SNAPSHOT raven From 63b4c792c51921b00319891e45101762614cfb4f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 7 Apr 2017 15:33:38 -0500 Subject: [PATCH 1569/2152] Bump CHANGES to 8.0.2 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index fa4b8494503..4c362d91d13 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 8.0.2 +------------- + +- + Version 8.0.1 ------------- From 67221eb2d0f3306a2fcec857b7ebff02be692ec5 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 10 Apr 2017 10:25:20 -0500 Subject: [PATCH 1570/2152] Add user breadcrumb type. (thanks NLthijs48) --- CHANGES | 2 +- .../main/java/com/getsentry/raven/event/Breadcrumb.java | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 4c362d91d13..f703dbff264 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 8.0.2 ------------- -- +- Add user breadcrumb type. (thanks NLthijs48) Version 8.0.1 ------------- diff --git a/raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java b/raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java index 462166dd300..62f771ad569 100644 --- a/raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java +++ b/raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java @@ -97,7 +97,13 @@ public enum Type { /** * NAVIGATION type. */ - NAVIGATION("navigation"); + NAVIGATION("navigation"), + + /** + * USER type. + */ + USER("user"); + private final String value; From 8e95b9ed882a753bfda8c55b5dc1853d97f8e8a1 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 13 Apr 2017 09:30:08 -0700 Subject: [PATCH 1571/2152] Add getter for underlying ``Event`` in ``EventBuilder`` so that helpers can read fields. (thanks anjo-swe) --- CHANGES | 1 + .../src/main/java/com/getsentry/raven/event/EventBuilder.java | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index f703dbff264..10d703b1945 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 8.0.2 ------------- - Add user breadcrumb type. (thanks NLthijs48) +- Add getter for underlying ``Event`` in ``EventBuilder`` so that helpers can read fields. (thanks anjo-swe) Version 8.0.1 ------------- diff --git a/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java b/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java index 2c8d800852f..bb0fea27854 100644 --- a/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java +++ b/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java @@ -415,6 +415,10 @@ public synchronized Event build() { return event; } + public Event getEvent() { + return event; + } + @Override public String toString() { return "EventBuilder{" From 2233bd062c4c50ceec5dd44f2f2da50f7d39397c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 13 Apr 2017 09:31:26 -0700 Subject: [PATCH 1572/2152] Bump docs to 8.0.2 --- docs/context.rst | 2 +- docs/modules/android.rst | 6 +++--- docs/modules/appengine.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/raven.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 8 files changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/context.rst b/docs/context.rst index d507f0474ec..4a01350a57e 100644 --- a/docs/context.rst +++ b/docs/context.rst @@ -17,7 +17,7 @@ clear the thread's context after each servlet request finishes. Raven defaults to the ``SingletonContextManager`` on Android, which maintains a single context instance for all threads for the lifetime of the application. -As of version ``8.0.1`` to override the ``ContextManager`` you will need to override +As of version ``8.0.2`` to override the ``ContextManager`` you will need to override the ``getContextManager`` method in the ``DefaultRavenFactory``. A simpler API will likely be provided in the future. diff --git a/docs/modules/android.rst b/docs/modules/android.rst index ab126514af0..6f92de76e5f 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Raven Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``8.0.1`` of the Android SDK to a sample application that doesn't even use +Adding version ``8.0.2`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-android:8.0.1' + compile 'com.getsentry.raven:raven-android:8.0.2' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 7aeac80d9a1..5e86bdd4468 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: com.getsentry.raven raven-appengine - 8.0.1 + 8.0.2 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-appengine:8.0.1' + compile 'com.getsentry.raven:raven-appengine:8.0.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-appengine" % "8.0.1" + libraryDependencies += "com.getsentry.raven" % "raven-appengine" % "8.0.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 0c9e8996dc0..b00464b027c 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-log4j - 8.0.1 + 8.0.2 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-log4j:8.0.1' + compile 'com.getsentry.raven:raven-log4j:8.0.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-log4j" % "8.0.1" + libraryDependencies += "com.getsentry.raven" % "raven-log4j" % "8.0.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 8caa772670e..d2ba15d422a 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-log4j2 - 8.0.1 + 8.0.2 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-log4j2:8.0.1' + compile 'com.getsentry.raven:raven-log4j2:8.0.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-log4j2" % "8.0.1" + libraryDependencies += "com.getsentry.raven" % "raven-log4j2" % "8.0.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 1905685a2fa..7ad75b577ff 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -19,22 +19,22 @@ Using Maven: com.getsentry.raven raven-logback - 8.0.1 + 8.0.2 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-logback:8.0.1' + compile 'com.getsentry.raven:raven-logback:8.0.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-logback" % "8.0.1" + libraryDependencies += "com.getsentry.raven" % "raven-logback" % "8.0.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index d3d520c7c63..35088a7fbac 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -18,22 +18,22 @@ Using Maven: com.getsentry.raven raven - 8.0.1 + 8.0.2 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven:8.0.1' + compile 'com.getsentry.raven:raven:8.0.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven" % "8.0.1" + libraryDependencies += "com.getsentry.raven" % "raven" % "8.0.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index fcf71c3c010..11075b2b3a9 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -15,22 +15,22 @@ Using Maven: com.getsentry.raven raven - 8.0.1 + 8.0.2 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven:8.0.1' + compile 'com.getsentry.raven:raven:8.0.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven" % "8.0.1" + libraryDependencies += "com.getsentry.raven" % "raven" % "8.0.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From beb4b23389a5f63f7348c09f4d152d7e9e937118 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 13 Apr 2017 09:32:46 -0700 Subject: [PATCH 1573/2152] [maven-release-plugin] prepare release v8.0.2 --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index bda3f22133d..006f464cd7b 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 8.0.2-SNAPSHOT + 8.0.2 pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v8.0.2 https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index 66b3be7b702..e78992f7f14 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.2-SNAPSHOT + 8.0.2 raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index 8a9e6a38f47..bba964af69d 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 8.0.2-SNAPSHOT + 8.0.2 raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index 744211da190..e228e6a7a8c 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.2-SNAPSHOT + 8.0.2 raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 879a65f268d..3c4596fdabf 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.2-SNAPSHOT + 8.0.2 raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index e45d7e22141..37cfeb80687 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.2-SNAPSHOT + 8.0.2 raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index eec05b76781..5c3161e7685 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.2-SNAPSHOT + 8.0.2 raven From e2fdbacc749f2c794d25e50ca83bc5f30d3d934a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 13 Apr 2017 09:32:46 -0700 Subject: [PATCH 1574/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- raven-android/pom.xml | 2 +- raven-appengine/pom.xml | 2 +- raven-log4j/pom.xml | 2 +- raven-log4j2/pom.xml | 2 +- raven-logback/pom.xml | 2 +- raven/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 006f464cd7b..2eb9bfca53e 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.getsentry.raven raven-all - 8.0.2 + 8.0.3-SNAPSHOT pom Raven-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v8.0.2 + HEAD https://github.com/${github.repo}/issues diff --git a/raven-android/pom.xml b/raven-android/pom.xml index e78992f7f14..77f2e5b6874 100644 --- a/raven-android/pom.xml +++ b/raven-android/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.2 + 8.0.3-SNAPSHOT raven-android diff --git a/raven-appengine/pom.xml b/raven-appengine/pom.xml index bba964af69d..0f33935142e 100644 --- a/raven-appengine/pom.xml +++ b/raven-appengine/pom.xml @@ -4,7 +4,7 @@ com.getsentry.raven raven-all - 8.0.2 + 8.0.3-SNAPSHOT raven-appengine diff --git a/raven-log4j/pom.xml b/raven-log4j/pom.xml index e228e6a7a8c..420c937ce49 100644 --- a/raven-log4j/pom.xml +++ b/raven-log4j/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.2 + 8.0.3-SNAPSHOT raven-log4j diff --git a/raven-log4j2/pom.xml b/raven-log4j2/pom.xml index 3c4596fdabf..428bbc12f05 100644 --- a/raven-log4j2/pom.xml +++ b/raven-log4j2/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.2 + 8.0.3-SNAPSHOT raven-log4j2 diff --git a/raven-logback/pom.xml b/raven-logback/pom.xml index 37cfeb80687..5653ac180ef 100644 --- a/raven-logback/pom.xml +++ b/raven-logback/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.2 + 8.0.3-SNAPSHOT raven-logback diff --git a/raven/pom.xml b/raven/pom.xml index 5c3161e7685..07809e8ebe0 100644 --- a/raven/pom.xml +++ b/raven/pom.xml @@ -5,7 +5,7 @@ com.getsentry.raven raven-all - 8.0.2 + 8.0.3-SNAPSHOT raven From cef220bdf354787873cbc699bf0b45b4ef69ba79 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 13 Apr 2017 09:32:49 -0700 Subject: [PATCH 1575/2152] Bump CHANGES to 8.0.3 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 10d703b1945..77252d970a7 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 8.0.3 +------------- + +- + Version 8.0.2 ------------- From af5196bd2a2531d4a3a74b51aeb64319c82c4ef6 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 19 Apr 2017 13:57:31 -0500 Subject: [PATCH 1576/2152] Move to io.sentry group, sentry-java artifact, 1.0.0 version. (#377) --- CHANGES | 5 +- LICENSE | 2 +- README.md | 12 +- docs/conf.py | 14 +- docs/config.rst | 137 +++++++------ docs/context.rst | 31 ++- docs/index.rst | 12 +- docs/modules/android.rst | 26 +-- docs/modules/appengine.rst | 32 ++-- docs/modules/index.rst | 4 +- docs/modules/log4j.rst | 62 +++--- docs/modules/log4j2.rst | 54 +++--- docs/modules/logback.rst | 52 ++--- docs/modules/raven.rst | 64 +++---- docs/sentry-doc-config.json | 6 +- docs/usage.rst | 84 ++++---- pom.xml | 28 +-- .../raven/android/AndroidRavenFactory.java | 69 ------- .../services/com.getsentry.raven.RavenFactory | 1 - .../com/getsentry/raven/RavenFactory.java | 180 ------------------ .../raven/environment/RavenEnvironment.java | 97 ---------- .../RavenServletContainerInitializer.java | 16 -- .../services/com.getsentry.raven.RavenFactory | 1 - .../javax.servlet.ServletContainerInitializer | 1 - .../environment/RavenEnvironmentTest.java | 62 ------ raven/src/test/resources/jndi.properties | 1 - .../resources/logging-integration.properties | 12 -- {raven-android => sentry-android}/README.md | 2 +- {raven-android => sentry-android}/pom.xml | 25 +-- .../sentry/android/AndroidSentryFactory.java | 69 +++++++ .../main/java/io/sentry/android/Sentry.java | 110 +++++------ .../SentryUncaughtExceptionHandler.java | 18 +- .../main/java/io/sentry}/android/Util.java | 8 +- .../helper/AndroidEventBuilderHelper.java | 14 +- .../java/io/sentry}/android/AndroidTest.java | 8 +- .../test/java/io/sentry/android/SentryIT.java | 6 +- .../io/sentry/android/SentryITActivity.java | 16 +- .../README.md | 2 +- {raven-appengine => sentry-appengine}/pom.xml | 14 +- .../appengine/AppEngineSentryFactory.java | 32 ++-- .../connection/AppEngineAsyncConnection.java | 16 +- .../helper/AppEngineEventBuilderHelper.java | 6 +- .../META-INF/services/io.sentry.SentryFactory | 1 + .../appengine/AppEngineSentryFactoryTest.java | 36 ++-- .../AppEngineAsyncConnectionTest.java | 8 +- .../AppEngineEventBuilderHelperTest.java | 4 +- .../src/test/resources/logback-test.xml | 2 +- {raven-log4j => sentry-log4j}/README.md | 2 +- {raven-log4j => sentry-log4j}/pom.xml | 18 +- .../java/io/sentry}/log4j/SentryAppender.java | 102 +++++----- .../io/sentry}/log4j/MockUpErrorHandler.java | 2 +- .../log4j/SentryAppenderCloseTest.java | 32 ++-- .../sentry}/log4j/SentryAppenderDsnTest.java | 26 +-- .../SentryAppenderEventBuildingTest.java | 48 ++--- .../log4j/SentryAppenderFailuresTest.java | 38 ++-- .../io/sentry}/log4j/SentryAppenderIT.java | 12 +- .../resources/log4j-integration.properties | 8 +- .../src/test/resources/log4j-test.properties | 2 +- {raven-log4j2 => sentry-log4j2}/README.md | 2 +- {raven-log4j2 => sentry-log4j2}/pom.xml | 18 +- .../io/sentry}/log4j2/SentryAppender.java | 118 ++++++------ .../io/sentry}/log4j2/MockUpErrorHandler.java | 2 +- .../log4j2/SentryAppenderCloseTest.java | 32 ++-- .../sentry}/log4j2/SentryAppenderDsnTest.java | 26 +-- .../SentryAppenderEventBuildingTest.java | 52 ++--- .../log4j2/SentryAppenderFailuresTest.java | 38 ++-- .../io/sentry}/log4j2/SentryAppenderIT.java | 12 +- .../src/test/resources/log4j2-integration.xml | 10 +- .../src/test/resources/log4j2-test.xml | 4 +- {raven-logback => sentry-logback}/README.md | 2 +- {raven-logback => sentry-logback}/pom.xml | 18 +- .../io/sentry}/logback/SentryAppender.java | 108 +++++------ .../sentry}/logback/MockUpLoggingEvent.java | 2 +- .../sentry}/logback/MockUpStatusPrinter.java | 2 +- .../logback/SentryAppenderCloseTest.java | 32 ++-- .../logback/SentryAppenderDsnTest.java | 26 +-- .../SentryAppenderEventBuildingTest.java | 72 +++---- .../SentryAppenderEventLevelFilterTest.java | 14 +- .../logback/SentryAppenderFailuresTest.java | 42 ++-- .../io/sentry}/logback/SentryAppenderIT.java | 12 +- .../test/resources/logback-integration.xml | 10 +- .../src/test/resources/logback-test.xml | 2 +- {raven => sentry}/README.md | 4 +- {raven => sentry}/pom.xml | 12 +- .../java/io/sentry/DefaultSentryFactory.java | 96 +++++----- .../src/main/java/io/sentry/Sentry.java | 72 +++---- .../main/java/io/sentry/SentryFactory.java | 180 ++++++++++++++++++ .../main/java/io/sentry}/buffer/Buffer.java | 8 +- .../java/io/sentry}/buffer/DiskBuffer.java | 6 +- .../java/io/sentry}/config/JndiLookup.java | 2 +- .../main/java/io/sentry}/config/Lookup.java | 4 +- .../connection/AbstractConnection.java | 8 +- .../sentry}/connection/AsyncConnection.java | 24 +-- .../connection/BufferedConnection.java | 18 +- .../io/sentry}/connection/Connection.java | 4 +- .../connection/ConnectionException.java | 4 +- .../io/sentry}/connection/EventSampler.java | 4 +- .../connection/EventSendFailureCallback.java | 4 +- .../io/sentry}/connection/HttpConnection.java | 10 +- .../sentry}/connection/LockdownManager.java | 6 +- .../connection/LockedDownException.java | 2 +- .../io/sentry}/connection/NoopConnection.java | 4 +- .../connection/OutputStreamConnection.java | 8 +- .../connection/ProxyAuthenticator.java | 2 +- .../connection/RandomEventSampler.java | 4 +- .../main/java/io/sentry}/context/Context.java | 10 +- .../io/sentry}/context/ContextManager.java | 4 +- .../context/SingletonContextManager.java | 2 +- .../context/ThreadLocalContextManager.java | 2 +- .../src/main/java/io/sentry}/dsn/Dsn.java | 4 +- .../io/sentry}/dsn/InvalidDsnException.java | 2 +- .../sentry/environment/SentryEnvironment.java | 97 ++++++++++ .../java/io/sentry}/event/Breadcrumb.java | 2 +- .../io/sentry}/event/BreadcrumbBuilder.java | 2 +- .../java/io/sentry}/event/Breadcrumbs.java | 6 +- .../src/main/java/io/sentry}/event/Event.java | 4 +- .../java/io/sentry}/event/EventBuilder.java | 10 +- .../src/main/java/io/sentry}/event/User.java | 2 +- .../java/io/sentry}/event/UserBuilder.java | 2 +- .../helper/BasicRemoteAddressResolver.java | 2 +- .../event/helper/ContextBuilderHelper.java | 30 +-- .../event/helper/EventBuilderHelper.java | 6 +- .../helper/ForwardedAddressResolver.java | 4 +- .../event/helper/HttpEventBuilderHelper.java | 12 +- .../event/helper/RemoteAddressResolver.java | 4 +- .../event/interfaces/ExceptionInterface.java | 2 +- .../event/interfaces/HttpInterface.java | 10 +- .../event/interfaces/MessageInterface.java | 14 +- .../event/interfaces/SentryException.java | 2 +- .../event/interfaces/SentryInterface.java | 2 +- .../event/interfaces/StackTraceInterface.java | 6 +- .../event/interfaces/UserInterface.java | 2 +- .../java/io/sentry}/jul/SentryHandler.java | 108 +++++------ .../io/sentry}/marshaller/Marshaller.java | 4 +- .../json/ExceptionInterfaceBinding.java | 8 +- .../marshaller/json/HttpInterfaceBinding.java | 6 +- .../marshaller/json/InterfaceBinding.java | 4 +- .../marshaller/json/JsonMarshaller.java | 18 +- .../json/MessageInterfaceBinding.java | 6 +- .../json/StackTraceInterfaceBinding.java | 4 +- .../marshaller/json/UserInterfaceBinding.java | 4 +- .../SentryServletContainerInitializer.java | 16 ++ .../servlet/SentryServletRequestListener.java | 16 +- .../src/main/java/io/sentry}/time/Clock.java | 2 +- .../main/java/io/sentry}/time/FixedClock.java | 2 +- .../java/io/sentry}/time/SystemClock.java | 2 +- .../src/main/java/io/sentry}/util/Base64.java | 4 +- .../io/sentry}/util/Base64OutputStream.java | 4 +- .../io/sentry}/util/CircularFifoQueue.java | 2 +- .../src/main/java/io/sentry}/util/Util.java | 4 +- .../META-INF/services/io.sentry.SentryFactory | 1 + .../javax.servlet.ServletContainerInitializer | 1 + .../main/resources/sentry-build.properties | 0 .../src/test/java/io/sentry}/BaseIT.java | 10 +- .../src/test/java/io/sentry}/BaseTest.java | 2 +- .../src/test/java/io/sentry}/ContextTest.java | 12 +- .../io/sentry/DefaultSentryFactoryTest.java | 8 +- .../java/io/sentry/SentryFactoryTest.java | 100 +++++----- .../src/test/java/io/sentry/SentryTest.java | 70 +++---- .../java/io/sentry}/buffer/BufferTest.java | 2 +- .../io/sentry}/buffer/DiskBufferTest.java | 12 +- .../connection/AbstractConnectionTest.java | 8 +- .../connection/AsyncConnectionTest.java | 20 +- .../connection/BufferedConnectionTest.java | 12 +- .../EventSendFailureCallbackTest.java | 26 +-- .../connection/HttpConnectionTest.java | 10 +- .../OutputStreamConnectionTest.java | 8 +- .../connection/RandomEventSamplerTest.java | 6 +- .../src/test/java/io/sentry}/dsn/DsnTest.java | 4 +- .../io/sentry}/dsn/InitialContextFactory.java | 2 +- .../environment/SentryEnvironmentTest.java | 62 ++++++ .../java/io/sentry}/event/BreadcrumbTest.java | 30 +-- .../event/EventBuilderHostnameCacheTest.java | 2 +- .../io/sentry}/event/EventBuilderTest.java | 4 +- .../test/java/io/sentry}/event/EventTest.java | 2 +- .../test/java/io/sentry}/event/UserTest.java | 24 +-- .../helper/HttpEventBuilderHelperTest.java | 28 +-- .../event/interfaces/HttpInterfaceTest.java | 2 +- .../interfaces/MessageInterfaceTest.java | 2 +- .../event/interfaces/SentryExceptionTest.java | 2 +- .../interfaces/StackTraceInterfaceTest.java | 2 +- .../event/interfaces/UserInterfaceTest.java | 2 +- .../helper/RemoteAddressResolverTest.java | 8 +- .../jul/SentryHandlerEventBuildingTest.java | 38 ++-- .../java/io/sentry}/jul/SentryHandlerIT.java | 12 +- .../UncloseableOutputStreamTest.java | 4 +- .../json/ExceptionInterfaceBindingTest.java | 16 +- .../json/HttpInterfaceBindingTest.java | 8 +- .../marshaller/json/JsonComparisonUtil.java | 2 +- .../marshaller/json/JsonMarshallerTest.java | 78 ++++---- .../json/MessageInterfaceBindingTest.java | 8 +- .../json/StackTraceInterfaceBindingTest.java | 12 +- .../json/UserInterfaceBindingTest.java | 12 +- ...SentryServletContainerInitializerTest.java | 12 +- .../SentryServletRequestListenerTest.java | 30 +-- .../test/java/io/sentry}/time/ClockTest.java | 2 +- .../java/io/sentry}/unmarshaller/Base64.java | 4 +- .../unmarshaller/Base64InputStream.java | 4 +- .../io/sentry}/unmarshaller/JsonDecoder.java | 4 +- .../unmarshaller/JsonUnmarshaller.java | 6 +- .../io/sentry}/unmarshaller/Unmarshaller.java | 6 +- .../unmarshaller/event/UnmarshalledEvent.java | 12 +- .../event/interfaces/ExceptionInterface.java | 4 +- .../event/interfaces/MessageInterface.java | 4 +- .../event/interfaces/StackTraceInterface.java | 4 +- .../sentry}/marshaller/json/Exception1.json | 0 .../sentry}/marshaller/json/Exception2.json | 0 .../sentry}/marshaller/json/Exception3.json | 0 .../io/sentry}/marshaller/json/Http1.json | 0 .../io/sentry}/marshaller/json/Message1.json | 0 .../sentry}/marshaller/json/StackTrace1.json | 0 .../sentry}/marshaller/json/StackTrace2.json | 0 .../sentry}/marshaller/json/StackTrace3.json | 0 .../io/sentry}/marshaller/json/User1.json | 0 .../jsonmarshallertest/testBreadcrumbs.json | 0 .../json/jsonmarshallertest/testChecksum.json | 0 .../json/jsonmarshallertest/testContexts.json | 0 .../json/jsonmarshallertest/testCulprit.json | 0 .../jsonmarshallertest/testEnvironment.json | 0 .../json/jsonmarshallertest/testEventId.json | 0 .../jsonmarshallertest/testExtraArray.json | 0 .../jsonmarshallertest/testExtraBoolean.json | 0 .../testExtraCustomValue.json | 0 .../jsonmarshallertest/testExtraIterable.json | 0 .../json/jsonmarshallertest/testExtraMap.json | 0 .../jsonmarshallertest/testExtraNull.json | 0 .../testExtraNullKeyMap.json | 0 .../jsonmarshallertest/testExtraNumber.json | 0 .../testExtraObjectKeyMap.json | 0 .../testExtraRecursiveArray.json | 0 .../testExtraRecursiveMap.json | 0 .../jsonmarshallertest/testExtraString.json | 0 .../jsonmarshallertest/testFingerprint.json | 0 .../testInterfaceBinding.json | 0 .../jsonmarshallertest/testLevelDebug.json | 0 .../jsonmarshallertest/testLevelError.json | 0 .../jsonmarshallertest/testLevelFatal.json | 0 .../jsonmarshallertest/testLevelInfo.json | 0 .../jsonmarshallertest/testLevelWarning.json | 0 .../json/jsonmarshallertest/testLogger.json | 0 .../json/jsonmarshallertest/testMessage.json | 0 .../json/jsonmarshallertest/testPlatform.json | 0 .../json/jsonmarshallertest/testRelease.json | 0 .../json/jsonmarshallertest/testSdk.json | 0 .../jsonmarshallertest/testServerName.json | 0 .../json/jsonmarshallertest/testTags.json | 0 .../jsonmarshallertest/testTimestamp.json | 0 sentry/src/test/resources/jndi.properties | 1 + .../resources/logging-integration.properties | 13 ++ .../test/resources/logging-test.properties | 2 +- .../test/resources/sentry-build.properties | 0 src/checkstyle/suppressions.xml | 4 +- src/site/site.xml | 31 --- 253 files changed, 2066 insertions(+), 2104 deletions(-) delete mode 100644 raven-android/src/main/java/com/getsentry/raven/android/AndroidRavenFactory.java delete mode 100644 raven-appengine/src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory delete mode 100644 raven/src/main/java/com/getsentry/raven/RavenFactory.java delete mode 100644 raven/src/main/java/com/getsentry/raven/environment/RavenEnvironment.java delete mode 100644 raven/src/main/java/com/getsentry/raven/servlet/RavenServletContainerInitializer.java delete mode 100644 raven/src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory delete mode 100644 raven/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer delete mode 100644 raven/src/test/java/com/getsentry/raven/environment/RavenEnvironmentTest.java delete mode 100644 raven/src/test/resources/jndi.properties delete mode 100644 raven/src/test/resources/logging-integration.properties rename {raven-android => sentry-android}/README.md (86%) rename {raven-android => sentry-android}/pom.xml (84%) create mode 100644 sentry-android/src/main/java/io/sentry/android/AndroidSentryFactory.java rename raven-android/src/main/java/com/getsentry/raven/android/Raven.java => sentry-android/src/main/java/io/sentry/android/Sentry.java (56%) rename raven-android/src/main/java/com/getsentry/raven/android/RavenUncaughtExceptionHandler.java => sentry-android/src/main/java/io/sentry/android/SentryUncaughtExceptionHandler.java (69%) rename {raven-android/src/main/java/com/getsentry/raven => sentry-android/src/main/java/io/sentry}/android/Util.java (88%) rename {raven-android/src/main/java/com/getsentry/raven => sentry-android/src/main/java/io/sentry}/android/event/helper/AndroidEventBuilderHelper.java (97%) rename {raven-android/src/test/java/com/getsentry/raven => sentry-android/src/test/java/io/sentry}/android/AndroidTest.java (73%) rename raven-android/src/test/java/com/getsentry/raven/android/RavenIT.java => sentry-android/src/test/java/io/sentry/android/SentryIT.java (82%) rename raven-android/src/test/java/com/getsentry/raven/android/RavenITActivity.java => sentry-android/src/test/java/io/sentry/android/SentryITActivity.java (64%) rename {raven-appengine => sentry-appengine}/README.md (85%) rename {raven-appengine => sentry-appengine}/pom.xml (80%) rename raven-appengine/src/main/java/com/getsentry/raven/appengine/AppEngineRavenFactory.java => sentry-appengine/src/main/java/io/sentry/appengine/AppEngineSentryFactory.java (62%) rename {raven-appengine/src/main/java/com/getsentry/raven => sentry-appengine/src/main/java/io/sentry}/appengine/connection/AppEngineAsyncConnection.java (91%) rename {raven-appengine/src/main/java/com/getsentry/raven => sentry-appengine/src/main/java/io/sentry}/appengine/event/helper/AppEngineEventBuilderHelper.java (87%) create mode 100644 sentry-appengine/src/main/resources/META-INF/services/io.sentry.SentryFactory rename raven-appengine/src/test/java/com/getsentry/raven/appengine/AppEngineRavenFactoryTest.java => sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryFactoryTest.java (61%) rename {raven-appengine/src/test/java/com/getsentry/raven => sentry-appengine/src/test/java/io/sentry}/appengine/connection/AppEngineAsyncConnectionTest.java (96%) rename {raven-appengine/src/test/java/com/getsentry/raven => sentry-appengine/src/test/java/io/sentry}/appengine/event/helper/AppEngineEventBuilderHelperTest.java (96%) rename {raven-appengine => sentry-appengine}/src/test/resources/logback-test.xml (74%) rename {raven-log4j => sentry-log4j}/README.md (87%) rename {raven-log4j => sentry-log4j}/pom.xml (89%) rename {raven-log4j/src/main/java/com/getsentry/raven => sentry-log4j/src/main/java/io/sentry}/log4j/SentryAppender.java (82%) rename {raven-log4j/src/test/java/com/getsentry/raven => sentry-log4j/src/test/java/io/sentry}/log4j/MockUpErrorHandler.java (95%) rename {raven-log4j/src/test/java/com/getsentry/raven => sentry-log4j/src/test/java/io/sentry}/log4j/SentryAppenderCloseTest.java (81%) rename {raven-log4j/src/test/java/com/getsentry/raven => sentry-log4j/src/test/java/io/sentry}/log4j/SentryAppenderDsnTest.java (72%) rename {raven-log4j/src/test/java/com/getsentry/raven => sentry-log4j/src/test/java/io/sentry}/log4j/SentryAppenderEventBuildingTest.java (89%) rename {raven-log4j/src/test/java/com/getsentry/raven => sentry-log4j/src/test/java/io/sentry}/log4j/SentryAppenderFailuresTest.java (65%) rename {raven-log4j/src/test/java/com/getsentry/raven => sentry-log4j/src/test/java/io/sentry}/log4j/SentryAppenderIT.java (78%) rename {raven-log4j => sentry-log4j}/src/test/resources/log4j-integration.properties (50%) rename {raven-log4j => sentry-log4j}/src/test/resources/log4j-test.properties (64%) rename {raven-log4j2 => sentry-log4j2}/README.md (84%) rename {raven-log4j2 => sentry-log4j2}/pom.xml (89%) rename {raven-log4j2/src/main/java/com/getsentry/raven => sentry-log4j2/src/main/java/io/sentry}/log4j2/SentryAppender.java (83%) rename {raven-log4j2/src/test/java/com/getsentry/raven => sentry-log4j2/src/test/java/io/sentry}/log4j2/MockUpErrorHandler.java (95%) rename {raven-log4j2/src/test/java/com/getsentry/raven => sentry-log4j2/src/test/java/io/sentry}/log4j2/SentryAppenderCloseTest.java (82%) rename {raven-log4j2/src/test/java/com/getsentry/raven => sentry-log4j2/src/test/java/io/sentry}/log4j2/SentryAppenderDsnTest.java (72%) rename {raven-log4j2/src/test/java/com/getsentry/raven => sentry-log4j2/src/test/java/io/sentry}/log4j2/SentryAppenderEventBuildingTest.java (88%) rename {raven-log4j2/src/test/java/com/getsentry/raven => sentry-log4j2/src/test/java/io/sentry}/log4j2/SentryAppenderFailuresTest.java (66%) rename {raven-log4j2/src/test/java/com/getsentry/raven => sentry-log4j2/src/test/java/io/sentry}/log4j2/SentryAppenderIT.java (78%) rename {raven-log4j2 => sentry-log4j2}/src/test/resources/log4j2-integration.xml (63%) rename {raven-log4j2 => sentry-log4j2}/src/test/resources/log4j2-test.xml (62%) rename {raven-logback => sentry-logback}/README.md (86%) rename {raven-logback => sentry-logback}/pom.xml (88%) rename {raven-logback/src/main/java/com/getsentry/raven => sentry-logback/src/main/java/io/sentry}/logback/SentryAppender.java (85%) rename {raven-logback/src/test/java/com/getsentry/raven => sentry-logback/src/test/java/io/sentry}/logback/MockUpLoggingEvent.java (99%) rename {raven-logback/src/test/java/com/getsentry/raven => sentry-logback/src/test/java/io/sentry}/logback/MockUpStatusPrinter.java (96%) rename {raven-logback/src/test/java/com/getsentry/raven => sentry-logback/src/test/java/io/sentry}/logback/SentryAppenderCloseTest.java (84%) rename {raven-logback/src/test/java/com/getsentry/raven => sentry-logback/src/test/java/io/sentry}/logback/SentryAppenderDsnTest.java (77%) rename {raven-logback/src/test/java/com/getsentry/raven => sentry-logback/src/test/java/io/sentry}/logback/SentryAppenderEventBuildingTest.java (85%) rename {raven-logback/src/test/java/com/getsentry/raven => sentry-logback/src/test/java/io/sentry}/logback/SentryAppenderEventLevelFilterTest.java (90%) rename {raven-logback/src/test/java/com/getsentry/raven => sentry-logback/src/test/java/io/sentry}/logback/SentryAppenderFailuresTest.java (71%) rename {raven-logback/src/test/java/com/getsentry/raven => sentry-logback/src/test/java/io/sentry}/logback/SentryAppenderIT.java (78%) rename {raven-logback => sentry-logback}/src/test/resources/logback-integration.xml (70%) rename {raven-logback => sentry-logback}/src/test/resources/logback-test.xml (74%) rename {raven => sentry}/README.md (56%) rename {raven => sentry}/pom.xml (95%) rename raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java => sentry/src/main/java/io/sentry/DefaultSentryFactory.java (89%) rename raven/src/main/java/com/getsentry/raven/Raven.java => sentry/src/main/java/io/sentry/Sentry.java (76%) create mode 100644 sentry/src/main/java/io/sentry/SentryFactory.java rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/buffer/Buffer.java (71%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/buffer/DiskBuffer.java (97%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/config/JndiLookup.java (97%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/config/Lookup.java (95%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/connection/AbstractConnection.java (94%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/connection/AsyncConnection.java (91%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/connection/BufferedConnection.java (95%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/connection/Connection.java (89%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/connection/ConnectionException.java (86%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/connection/EventSampler.java (84%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/connection/EventSendFailureCallback.java (85%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/connection/HttpConnection.java (96%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/connection/LockdownManager.java (96%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/connection/LockedDownException.java (89%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/connection/NoopConnection.java (84%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/connection/OutputStreamConnection.java (86%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/connection/ProxyAuthenticator.java (94%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/connection/RandomEventSampler.java (95%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/context/Context.java (91%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/context/ContextManager.java (84%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/context/SingletonContextManager.java (92%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/context/ThreadLocalContextManager.java (94%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/dsn/Dsn.java (99%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/dsn/InvalidDsnException.java (95%) create mode 100644 sentry/src/main/java/io/sentry/environment/SentryEnvironment.java rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/Breadcrumb.java (99%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/BreadcrumbBuilder.java (98%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/Breadcrumbs.java (74%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/Event.java (99%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/EventBuilder.java (98%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/User.java (96%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/UserBuilder.java (97%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/helper/BasicRemoteAddressResolver.java (92%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/helper/ContextBuilderHelper.java (65%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/helper/EventBuilderHelper.java (81%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/helper/ForwardedAddressResolver.java (93%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/helper/HttpEventBuilderHelper.java (85%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/helper/RemoteAddressResolver.java (79%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/interfaces/ExceptionInterface.java (97%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/interfaces/HttpInterface.java (95%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/interfaces/MessageInterface.java (86%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/interfaces/SentryException.java (99%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/interfaces/SentryInterface.java (87%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/interfaces/StackTraceInterface.java (92%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/event/interfaces/UserInterface.java (97%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/jul/SentryHandler.java (83%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/marshaller/Marshaller.java (95%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/marshaller/json/ExceptionInterfaceBinding.java (91%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/marshaller/json/HttpInterfaceBinding.java (97%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/marshaller/json/InterfaceBinding.java (87%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/marshaller/json/JsonMarshaller.java (97%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/marshaller/json/MessageInterfaceBinding.java (92%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/marshaller/json/StackTraceInterfaceBinding.java (96%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/marshaller/json/UserInterfaceBinding.java (90%) create mode 100644 sentry/src/main/java/io/sentry/servlet/SentryServletContainerInitializer.java rename raven/src/main/java/com/getsentry/raven/servlet/RavenServletRequestListener.java => sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java (73%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/time/Clock.java (92%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/time/FixedClock.java (96%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/time/SystemClock.java (90%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/util/Base64.java (99%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/util/Base64OutputStream.java (99%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/util/CircularFifoQueue.java (99%) rename {raven/src/main/java/com/getsentry/raven => sentry/src/main/java/io/sentry}/util/Util.java (98%) create mode 100644 sentry/src/main/resources/META-INF/services/io.sentry.SentryFactory create mode 100644 sentry/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer rename raven/src/main/resources/raven-build.properties => sentry/src/main/resources/sentry-build.properties (100%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/BaseIT.java (87%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/BaseTest.java (97%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/ContextTest.java (90%) rename raven/src/test/java/com/getsentry/raven/DefaultRavenFactoryTest.java => sentry/src/test/java/io/sentry/DefaultSentryFactoryTest.java (55%) rename raven/src/test/java/com/getsentry/raven/RavenFactoryTest.java => sentry/src/test/java/io/sentry/SentryFactoryTest.java (52%) rename raven/src/test/java/com/getsentry/raven/RavenTest.java => sentry/src/test/java/io/sentry/SentryTest.java (78%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/buffer/BufferTest.java (91%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/buffer/DiskBufferTest.java (91%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/connection/AbstractConnectionTest.java (97%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/connection/AsyncConnectionTest.java (90%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/connection/BufferedConnectionTest.java (94%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/connection/EventSendFailureCallbackTest.java (65%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/connection/HttpConnectionTest.java (95%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/connection/OutputStreamConnectionTest.java (86%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/connection/RandomEventSamplerTest.java (89%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/dsn/DsnTest.java (98%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/dsn/InitialContextFactory.java (93%) create mode 100644 sentry/src/test/java/io/sentry/environment/SentryEnvironmentTest.java rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/event/BreadcrumbTest.java (78%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/event/EventBuilderHostnameCacheTest.java (98%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/event/EventBuilderTest.java (99%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/event/EventTest.java (98%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/event/UserTest.java (73%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/event/helper/HttpEventBuilderHelperTest.java (77%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/event/interfaces/HttpInterfaceTest.java (99%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/event/interfaces/MessageInterfaceTest.java (97%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/event/interfaces/SentryExceptionTest.java (96%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/event/interfaces/StackTraceInterfaceTest.java (93%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/event/interfaces/UserInterfaceTest.java (95%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/helper/RemoteAddressResolverTest.java (90%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/jul/SentryHandlerEventBuildingTest.java (86%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/jul/SentryHandlerIT.java (77%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/marshaller/UncloseableOutputStreamTest.java (97%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/marshaller/json/ExceptionInterfaceBindingTest.java (90%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/marshaller/json/HttpInterfaceBindingTest.java (89%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/marshaller/json/JsonComparisonUtil.java (98%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/marshaller/json/JsonMarshallerTest.java (81%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/marshaller/json/MessageInterfaceBindingTest.java (83%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/marshaller/json/StackTraceInterfaceBindingTest.java (88%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/marshaller/json/UserInterfaceBindingTest.java (76%) rename raven/src/test/java/com/getsentry/raven/servlet/RavenServletContainerInitializerTest.java => sentry/src/test/java/io/sentry/servlet/SentryServletContainerInitializerTest.java (64%) rename raven/src/test/java/com/getsentry/raven/servlet/RavenServletRequestListenerTest.java => sentry/src/test/java/io/sentry/servlet/SentryServletRequestListenerTest.java (65%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/time/ClockTest.java (95%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/unmarshaller/Base64.java (99%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/unmarshaller/Base64InputStream.java (99%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/unmarshaller/JsonDecoder.java (99%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/unmarshaller/JsonUnmarshaller.java (88%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/unmarshaller/Unmarshaller.java (51%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/unmarshaller/event/UnmarshalledEvent.java (88%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/unmarshaller/event/interfaces/ExceptionInterface.java (85%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/unmarshaller/event/interfaces/MessageInterface.java (84%) rename {raven/src/test/java/com/getsentry/raven => sentry/src/test/java/io/sentry}/unmarshaller/event/interfaces/StackTraceInterface.java (94%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/Exception1.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/Exception2.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/Exception3.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/Http1.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/Message1.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/StackTrace1.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/StackTrace2.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/StackTrace3.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/User1.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testBreadcrumbs.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testChecksum.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testContexts.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testCulprit.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testEnvironment.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testEventId.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testExtraArray.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testExtraBoolean.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testExtraCustomValue.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testExtraIterable.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testExtraMap.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testExtraNull.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testExtraNumber.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testExtraString.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testFingerprint.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testInterfaceBinding.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testLevelDebug.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testLevelError.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testLevelFatal.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testLevelInfo.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testLevelWarning.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testLogger.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testMessage.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testPlatform.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testRelease.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testSdk.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testServerName.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testTags.json (100%) rename {raven/src/test/resources/com/getsentry/raven => sentry/src/test/resources/io/sentry}/marshaller/json/jsonmarshallertest/testTimestamp.json (100%) create mode 100644 sentry/src/test/resources/jndi.properties create mode 100644 sentry/src/test/resources/logging-integration.properties rename {raven => sentry}/src/test/resources/logging-test.properties (70%) rename raven/src/test/resources/raven-build.properties => sentry/src/test/resources/sentry-build.properties (100%) delete mode 100644 src/site/site.xml diff --git a/CHANGES b/CHANGES index 77252d970a7..26062fc1586 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,8 @@ -Version 8.0.3 +Version 1.0.0 ------------- -- +- Rename from ``raven-java`` to ``sentry-java``. +- Move from ``com.getsentry.raven`` package to ``io.sentry``. Version 8.0.2 ------------- diff --git a/LICENSE b/LICENSE index b6d765c9f6f..02802804714 100644 --- a/LICENSE +++ b/LICENSE @@ -8,6 +8,6 @@ Redistribution and use in source and binary forms, with or without modification, 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - 3. Neither the name of the Raven nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index f232acc3a3b..f8485f2180f 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ -# raven-java +# sentry-java -Raven is the Java SDK for [Sentry](https://sentry.io/). It provides out-of-the-box support for +This is the Java SDK for [Sentry](https://sentry.io/). It provides out-of-the-box support for many popular JVM based frameworks and libraries, including Android, Log4j, Logback, and more. -In most cases using one of the existing integrations is preferred, but Raven additionally provides +In most cases using one of the existing integrations is preferred, but Sentry additionally provides a low level client for manually building and sending events to Sentry that can be used in any JVM based application. ## Resources * [Documentation](https://docs.sentry.io/clients/java/) -* [Bug Tracker](http://github.com/getsentry/raven-java/issues) -* [Code](http://github.com/getsentry/raven-java) +* [Bug Tracker](http://github.com/getsentry/sentry-java/issues) +* [Code](http://github.com/getsentry/sentry-java) * [Mailing List](https://groups.google.com/group/getsentry) * [IRC](irc://irc.freenode.net/sentry) (irc.freenode.net, #sentry) -* [Travis CI](http://travis-ci.org/getsentry/raven-java) +* [Travis CI](http://travis-ci.org/getsentry/sentry-java) diff --git a/docs/conf.py b/docs/conf.py index 1a9d81148da..3cce7d56815 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# raven-java documentation build configuration file, created by +# sentry-java documentation build configuration file, created by # sphinx-quickstart on Mon Jan 21 21:04:27 2013. # # This file is execfile()d with the current directory set to its containing dir. @@ -40,7 +40,7 @@ master_doc = 'index' # General information about the project. -project = u'raven-java' +project = u'sentry-java' copyright = u'%s, Functional Software Inc.' % datetime.date.today().year # The version info for the project you're documenting, acts as replacement for @@ -166,7 +166,7 @@ #html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'raven-javadoc' +htmlhelp_basename = 'sentry-javadoc' # -- Options for LaTeX output -------------------------------------------------- @@ -185,7 +185,7 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'raven-java.tex', u'raven-java Documentation', + ('index', 'sentry-java.tex', u'sentry-java Documentation', u'Functional Software Inc.', 'manual'), ] @@ -215,7 +215,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'raven-java', u'raven-java Documentation', + ('index', 'sentry-java', u'sentry-java Documentation', [u'Functional Software Inc.'], 1) ] @@ -229,8 +229,8 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'raven-java', u'raven-java Documentation', - u'Functional Software Inc.', 'raven-java', 'One line description of project.', + ('index', 'sentry-java', u'sentry-java Documentation', + u'Functional Software Inc.', 'sentry-java', 'One line description of project.', 'Miscellaneous'), ] diff --git a/docs/config.rst b/docs/config.rst index a6b50714d4a..249f42a2079 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -1,13 +1,13 @@ Configuration ============= -**Note:** Raven's library and framework integration documentation explains how to to do -basic Raven configuration for each of the supported integrations. The configuration +**Note:** Sentry's library and framework integration documentation explains how to to do +basic Sentry configuration for each of the supported integrations. The configuration below is typically for more advanced use cases and can be used in combination any of the other -integrations *once you set Raven up with the integration*. Please check the integration +integrations *once you set Sentry up with the integration*. Please check the integration documentation before you attempt to do any advanced configuration. -Most of Raven's advanced configuration happens by setting options in your DSN, as seen below. +Most of Sentry's advanced configuration happens by setting options in your DSN, as seen below. Connection and Protocol ----------------------- @@ -84,7 +84,7 @@ useful inside shared application containers), :: - http://public:private@host:port/1?raven.http.proxy.host=proxy.example.com&raven.http.proxy.port=8080 + http://public:private@host:port/1?http.proxy.host=proxy.example.com&sentry.http.proxy.port=8080 Options ------- @@ -106,11 +106,11 @@ In order to avoid performance issues due to a large amount of logs being generated or a slow connection to the Sentry server, an asynchronous connection is set up, using a low priority thread pool to submit events to Sentry. -To disable the async mode, add ``raven.async=false`` to the DSN: +To disable the async mode, add ``sentry.async=false`` to the DSN: :: - http://public:private@host:port/1?raven.async=false + http://public:private@host:port/1?async=false Graceful Shutdown (Advanced) ```````````````````````````` @@ -118,30 +118,30 @@ Graceful Shutdown (Advanced) In order to shutdown the asynchronous connection gracefully, a ``ShutdownHook`` is created. By default, the asynchronous connection is given 1 second to shutdown gracefully, but this can be adjusted via -``raven.async.shutdowntimeout`` (represented in milliseconds): +``sentry.async.shutdowntimeout`` (represented in milliseconds): :: - http://public:private@host:port/1?raven.async.shutdowntimeout=5000 + http://public:private@host:port/1?async.shutdowntimeout=5000 The special value ``-1`` can be used to disable the timeout and wait indefinitely for the executor to terminate. The ``ShutdownHook`` could lead to memory leaks in an environment where -the life cycle of Raven doesn't match the life cycle of the JVM. +the life cycle of Sentry doesn't match the life cycle of the JVM. -An example would be in a JEE environment where the application using Raven +An example would be in a JEE environment where the application using Sentry could be deployed and undeployed regularly. To avoid this behaviour, it is possible to disable the graceful shutdown. This might lead to some log entries being lost if the log application -doesn't shut down the Raven instance nicely. +doesn't shut down the Sentry instance nicely. -The option to do so is ``raven.async.gracefulshutdown``: +The option to do so is ``sentry.async.gracefulshutdown``: :: - http://public:private@host:port/1?raven.async.gracefulshutdown=false + http://public:private@host:port/1?async.gracefulshutdown=false Queue Size (Advanced) ````````````````````` @@ -152,11 +152,11 @@ never sent to the Sentry server. Depending on the environment (if the memory is sparse) it is important to be able to control the size of that queue to avoid memory issues. -It is possible to set a maximum with the option ``raven.async.queuesize``: +It is possible to set a maximum with the option ``sentry.async.queuesize``: :: - http://public:private@host:port/1?raven.async.queuesize=100 + http://public:private@host:port/1?async.queuesize=100 This means that if the connection to the Sentry server is down, only the 100 most recent events will be stored and processed as soon as the server is back up. @@ -172,11 +172,11 @@ By default the thread pool used by the async connection contains one thread per processor available to the JVM. It's possible to manually set the number of threads (for example if you want -only one thread) with the option ``raven.async.threads``: +only one thread) with the option ``sentry.async.threads``: :: - http://public:private@host:port/1?raven.async.threads=1 + http://public:private@host:port/1?async.threads=1 Threads Priority (Advanced) ``````````````````````````` @@ -186,40 +186,40 @@ running smoothly, so the threads have a `minimal priority `_. It is possible to customise this value to increase the priority of those threads -with the option ``raven.async.priority``: +with the option ``sentry.async.priority``: :: - http://public:private@host:port/1?raven.async.priority=10 + http://public:private@host:port/1?async.priority=10 Buffering Events to Disk ~~~~~~~~~~~~~~~~~~~~~~~~ -Raven can be configured to write events to a specified directory on disk -anytime communication with the Sentry server fails with the ``raven.buffer.dir`` -option. If the directory doesn't exist, Raven will attempt to create it +Sentry can be configured to write events to a specified directory on disk +anytime communication with the Sentry server fails with the ``sentry.buffer.dir`` +option. If the directory doesn't exist, Sentry will attempt to create it on startup and may therefore need write permission on the parent directory. -Raven always requires write permission on the buffer directory itself. +Sentry always requires write permission on the buffer directory itself. :: - http://public:private@host:port/1?raven.buffer.dir=raven-events + http://public:private@host:port/1?buffer.dir=sentry-events The maximum number of events that will be stored on disk defaults to 50, -but can also be configured with the option ``raven.buffer.size``: +but can also be configured with the option ``sentry.buffer.size``: :: - http://public:private@host:port/1?raven.buffer.size=100 + http://public:private@host:port/1?buffer.size=100 If a buffer directory is provided, a background thread will periodically attempt to re-send the events that are found on disk. By default it will attempt to send events every 60 seconds. You can change this with the -``raven.buffer.flushtime`` option (in milliseconds): +``sentry.buffer.flushtime`` option (in milliseconds): :: - http://public:private@host:port/1?raven.buffer.flushtime=10000 + http://public:private@host:port/1?buffer.flushtime=10000 Graceful Shutdown (Advanced) ```````````````````````````` @@ -227,36 +227,36 @@ Graceful Shutdown (Advanced) In order to shutdown the buffer flushing thread gracefully, a ``ShutdownHook`` is created. By default, the buffer flushing thread is given 1 second to shutdown gracefully, but this can be adjusted via -``raven.buffer.shutdowntimeout`` (represented in milliseconds): +``sentry.buffer.shutdowntimeout`` (represented in milliseconds): :: - http://public:private@host:port/1?raven.buffer.shutdowntimeout=5000 + http://public:private@host:port/1?buffer.shutdowntimeout=5000 The special value ``-1`` can be used to disable the timeout and wait indefinitely for the executor to terminate. The ``ShutdownHook`` could lead to memory leaks in an environment where -the life cycle of Raven doesn't match the life cycle of the JVM. +the life cycle of Sentry doesn't match the life cycle of the JVM. -An example would be in a JEE environment where the application using Raven +An example would be in a JEE environment where the application using Sentry could be deployed and undeployed regularly. To avoid this behaviour, it is possible to disable the graceful shutdown -by setting the ``raven.buffer.gracefulshutdown`` option: +by setting the ``sentry.buffer.gracefulshutdown`` option: :: - http://public:private@host:port/1?raven.buffer.gracefulshutdown=false + http://public:private@host:port/1?buffer.gracefulshutdown=false Event Sampling ~~~~~~~~~~~~~~ -Raven can be configured to sample events with the ``raven.sample.rate`` option: +Sentry can be configured to sample events with the ``sentry.sample.rate`` option: :: - http://public:private@host:port/1?raven.sample.rate=0.75 + http://public:private@host:port/1?sample.rate=0.75 This option takes a number from 0.0 to 1.0, representing the percent of events to allow through to server (from 0% to 100%). By default all @@ -272,19 +272,16 @@ is visible in the Sentry web interface where only the "in application" frames ar displayed by default. You can configure which package prefixes your application uses with the -``raven.stacktrace.app.packages`` option, which takes a comma separated list. +``sentry.stacktrace.app.packages`` option, which takes a comma separated list. :: - http://public:private@host:port/1?raven.stacktrace.app.packages=com.mycompany,com.other.name - -*Changed in version 8.0:* Raven formerly supported a package blacklist but -now only supports the package whitelist described above. + http://public:private@host:port/1?stacktrace.app.packages=com.mycompany,com.other.name Same Frame as Enclosing Exception ````````````````````````````````` -Raven can use the "in application" system to hide frames in chained exceptions. Usually when a +Sentry can use the "in application" system to hide frames in chained exceptions. Usually when a StackTrace is printed, the result looks like this: :: @@ -306,11 +303,11 @@ StackTrace is printed, the result looks like this: Some frames are replaced by the ``... N more`` line as they are the same frames as in the enclosing exception. -To enable a similar behaviour in Raven use the ``raven.stacktrace.hidecommon`` option. +To enable a similar behaviour in Sentry use the ``sentry.stacktrace.hidecommon`` option. :: - http://public:private@host:port/1?raven.stacktrace.hidecommon + http://public:private@host:port/1?stacktrace.hidecommon Compression ~~~~~~~~~~~ @@ -325,41 +322,41 @@ limited connection, Sentry hosted on an external network), it can be useful to compress the data beforehand or not. It's possible to manually enable/disable the compression with the option -``raven.compression`` +``sentry.compression`` :: - http://public:private@host:port/1?raven.compression=false + http://public:private@host:port/1?compression=false Max Message Size ~~~~~~~~~~~~~~~~ By default only the first 1000 characters of a message will be sent to -the server. This can be changed with the ``raven.maxmessagelength`` option. +the server. This can be changed with the ``sentry.maxmessagelength`` option. :: - http://public:private@host:port/1?raven.maxmessagelength=1500 + http://public:private@host:port/1?maxmessagelength=1500 Timeout (Advanced) ~~~~~~~~~~~~~~~~~~ -A timeout is set to avoid blocking Raven threads because establishing a +A timeout is set to avoid blocking Sentry threads because establishing a connection is taking too long. -It's possible to manually set the timeout length with ``raven.timeout`` +It's possible to manually set the timeout length with ``sentry.timeout`` (in milliseconds): :: - http://public:private@host:port/1?raven.timeout=10000 + http://public:private@host:port/1?timeout=10000 -Custom RavenFactory -------------------- +Custom SentryFactory +-------------------- -At times, you may require custom functionality that is not included in ``raven-java`` -already. The most common way to do this is to create your own ``RavenFactory`` instance -as seen in the example below. Note that you'll also need to register it with Raven and +At times, you may require custom functionality that is not included in ``sentry-java`` +already. The most common way to do this is to create your own ``SentryFactory`` instance +as seen in the example below. Note that you'll also need to register it with Sentry and possibly configure your integration to use it, as shown below. Implementation @@ -367,25 +364,25 @@ Implementation .. sourcecode:: java - public class MyRavenFactory extends DefaultRavenFactory { + public class MySentryFactory extends DefaultSentryFactory { @Override - public Raven createRavenInstance(Dsn dsn) { - Raven raven = new Raven(createConnection(dsn)); + public Sentry createSentryInstance(Dsn dsn) { + Sentry sentry = new Sentry(createConnection(dsn)); /* Create and use the ForwardedAddressResolver, which will use the X-FORWARDED-FOR header for the remote address if it exists. */ ForwardedAddressResolver forwardedAddressResolver = new ForwardedAddressResolver(); - raven.addBuilderHelper(new HttpEventBuilderHelper(forwardedAddressResolver)); + sentry.addBuilderHelper(new HttpEventBuilderHelper(forwardedAddressResolver)); - return raven; + return sentry; } } -Next, you'll need to register your class with Raven in one of two ways. +Next, you'll need to register your class with Sentry in one of two ways. Registration ~~~~~~~~~~~~ @@ -394,15 +391,15 @@ Java ServiceLoader Provider (Recommended) ````````````````````````````````````````` You'll need to add a ``ServiceLoader`` provider file to your project at -``src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory`` that contains -the name of your class so that it will be considered as a candidate ``RavenFactory``. For an example, see -`how we configure the DefaultRavenFactory itself `_. +``src/main/resources/META-INF/services/io.sentry.SentryFactory`` that contains +the name of your class so that it will be considered as a candidate ``SentryFactory``. For an example, see +`how we configure the DefaultSentryFactory itself `_. Manual Registration ``````````````````` -You can also manually register your ``RavenFactory`` instance. If you are using -an integration that builds its own Raven client, such as a logging integration, this should +You can also manually register your ``SentryFactory`` instance. If you are using +an integration that builds its own Sentry client, such as a logging integration, this should be done early in your application lifecycle so that your factory is available the first time you attempt to send an event to the Sentry server. @@ -410,7 +407,7 @@ you attempt to send an event to the Sentry server. class MyApp { public static void main(String[] args) { - RavenFactory.registerFactory(new MyRavenFactory()); + SentryFactory.registerFactory(new MySentryFactory()); // ... your app code ... } } @@ -419,4 +416,4 @@ Configuration ~~~~~~~~~~~~~ Finally, see the documentation for the integration you use to find out how to -configure it to use your custom ``RavenFactory``. +configure it to use your custom ``SentryFactory``. diff --git a/docs/context.rst b/docs/context.rst index 4a01350a57e..85f68b1cc21 100644 --- a/docs/context.rst +++ b/docs/context.rst @@ -8,18 +8,17 @@ or something else that better suits your application's needs. There is no single definition of context that applies to every application, for this reason a specific implementation must be chosen depending on what your -application does and how it is structured. By default Raven uses a +application does and how it is structured. By default Sentry uses a ``ThreadLocalContextManager`` that maintains a single ``Context`` instance per thread. This is useful for frameworks that use one thread per user request such as those based -on synchronous servlet APIs. Raven also installs a ``ServletRequestListener`` that will +on synchronous servlet APIs. Sentry also installs a ``ServletRequestListener`` that will clear the thread's context after each servlet request finishes. -Raven defaults to the ``SingletonContextManager`` on Android, which maintains a single +Sentry defaults to the ``SingletonContextManager`` on Android, which maintains a single context instance for all threads for the lifetime of the application. -As of version ``8.0.2`` to override the ``ContextManager`` you will need to override -the ``getContextManager`` method in the ``DefaultRavenFactory``. A simpler API will likely -be provided in the future. +To override the ``ContextManager`` you will need to override the ``getContextManager`` +method in the ``DefaultSentryFactory``. A simpler API will likely be provided in the future. Using Breadcrumbs ----------------- @@ -28,17 +27,17 @@ Breadcrumbs can be used to describe actions that occurred in your application le up to an event being sent. For example, whether external API requests were made, or whether a user clicked on something in an Android application. -Once a Raven instance has been initialized, either via a logging framework or manually, +Once a Sentry instance has been initialized, either via a logging framework or manually, you can begin recording breadcrumbs. By default the last 100 breadcrumbs for a given context instance will be stored and sent with future events. .. sourcecode:: java - import com.getsentry.raven.Raven; - import com.getsentry.raven.context.Context; - import com.getsentry.raven.event.BreadcrumbBuilder; - import com.getsentry.raven.event.Breadcrumbs; - import com.getsentry.raven.event.UserBuilder; + import io.sentry.Sentry; + import io.sentry.context.Context; + import io.sentry.event.BreadcrumbBuilder; + import io.sentry.event.Breadcrumbs; + import io.sentry.event.UserBuilder; public void example() { // Record a breadcrumb without having to look up the context instance manually @@ -48,11 +47,11 @@ context instance will be stored and sent with future events. // ... or retrieve and manipulate the context instance manually - // Retrieve the stored Raven instance - Raven raven = Raven.getStoredInstance(); + // Retrieve the stored Sentry instance + Sentry sentry = Sentry.getStoredInstance(); // Get the current context instance - Context context = raven.getContext(); + Context context = sentry.getContext(); // Set the current User in the context context.setUser( @@ -67,4 +66,4 @@ context instance will be stored and sent with future events. // Clear the context, useful if you need to add hooks in a framework // to empty context between requests context.clear() - } \ No newline at end of file + } diff --git a/docs/index.rst b/docs/index.rst index f42e77021e8..27dfe41096f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,7 +1,7 @@ .. sentry:edition:: self - Raven Java - ========== + Sentry Java + =========== .. sentry:edition:: on-premise, hosted @@ -10,7 +10,7 @@ Java ==== -Raven for Java (``raven-java``) is the official Java SDK for Sentry. At its core it provides +Sentry for Java (``sentry-java``) is the official Java SDK for Sentry. At its core it provides a raw client for sending events to Sentry, but it is highly recommended that you use one of the included library or framework integrations listed below if at all possible. @@ -26,8 +26,8 @@ use one of the included library or framework integrations listed below if at all Resources: * `Documentation `_ -* `Bug Tracker `_ -* `Code `_ +* `Bug Tracker `_ +* `Code `_ * `Mailing List `_ * `IRC `_ (irc.freenode.net, #sentry) -* `Travis CI `_ +* `Travis CI `_ diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 6f92de76e5f..ed3f3e8b81f 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -4,15 +4,15 @@ Android Features -------- -The Raven Android SDK is built on top of the main Java SDK and supports all of the same +The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``8.0.2`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.0.0`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ (in the application's cache directory) by default. This allows events to be sent at a later time if the device does not have connectivity when an event is created. This can -be disabled by setting the DSN option ``raven.buffer.enabled`` to ``false``. +be disabled by setting the DSN option ``sentry.buffer.enabled`` to ``false``. An ``UncaughtExceptionHandler`` is configured so that crash events will be stored to disk and sent the next time the application is run. @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-android:8.0.2' + compile 'io.sentry:sentry-android:1.0.0' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- @@ -44,11 +44,11 @@ events to the Sentry server. In your ``AndroidManifest.xml``: -Then initialize the Raven client in your application's main ``onCreate`` method: +Then initialize the Sentry client in your application's main ``onCreate`` method: .. sourcecode:: java - import com.getsentry.raven.android.Raven; + import io.sentry.android.Sentry; public class MainActivity extends Activity { @Override @@ -59,7 +59,7 @@ Then initialize the Raven client in your application's main ``onCreate`` method: // Use the Sentry DSN (client key) from the Project Settings page on Sentry String sentryDsn = "https://publicKey:secretKey@host:port/1?options"; - Raven.init(ctx, sentryDsn); + Sentry.init(ctx, sentryDsn); } } @@ -69,16 +69,16 @@ You can also configure your Sentry DSN (client key) in your ``AndroidManifest.xm -Now you can use ``Raven`` to capture events anywhere in your application: +Now you can use ``Sentry`` to capture events anywhere in your application: .. sourcecode:: java // Send a simple event to the Sentry server - Raven.capture("Error message"); + Sentry.capture("Error message"); // Set a breadcrumb that will be sent with the next event(s) Breadcrumbs.record( @@ -89,11 +89,11 @@ Now you can use ``Raven`` to capture events anywhere in your application: something() } catch (Exception e) { // Send an exception event to the Sentry server - Raven.capture(e); + Sentry.capture(e); } // Or build an event manually EventBuilder eventBuilder = new EventBuilder() .withMessage("Exception caught") .withLevel(Event.Level.ERROR); - Raven.capture(eventBuilder.build()); + Sentry.capture(eventBuilder.build()); diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 5e86bdd4468..f7ab50283b7 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -1,12 +1,12 @@ Google App Engine ================= -The ``raven-appengine`` library provides `Google App Engine `_ -support for Raven via the `Task Queue API +The ``sentry-appengine`` library provides `Google App Engine `_ +support for Sentry via the `Task Queue API `_. The source can be found `on Github -`_. +`_. Installation ------------ @@ -16,31 +16,31 @@ Using Maven: .. sourcecode:: xml - com.getsentry.raven - raven-appengine - 8.0.2 + io.sentry + sentry-appengine + 1.0.0 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-appengine:8.0.2' + compile 'io.sentry:sentry-appengine:1.0.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-appengine" % "8.0.2" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.0.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- -This module provides a new ``RavenFactory`` implementation which replaces the default async -system with a Google App Engine compatible one. You'll need to configure Raven to use the -``com.getsentry.raven.appengine.AppEngineRavenFactory`` as its factory. +This module provides a new ``SentryFactory`` implementation which replaces the default async +system with a Google App Engine compatible one. You'll need to configure Sentry to use the +``io.sentry.appengine.AppEngineSentryFactory`` as its factory. The queue size and thread options will not be used as they are specific to the default Java threading system. @@ -49,9 +49,9 @@ Queue Name ---------- By default, the default task queue will be used, but it's possible to -specify which one will be used with the ``raven.async.gae.queuename`` option:: +specify which one will be used with the ``sentry.async.gae.queuename`` option:: - http://public:private@host:port/1?raven.async.gae.queuename=MyQueueName + http://public:private@host:port/1?async.gae.queuename=MyQueueName Connection Name --------------- @@ -61,6 +61,6 @@ application, it's important to be able to identify which connection should be used when processing the event. To do so, the GAE module will identify each connection based on an identifier either automatically generated or user defined. To manually set the connection identifier (only used -internally) use the option ``raven.async.gae.connectionid``:: +internally) use the option ``sentry.async.gae.connectionid``:: - http://public:private@host:port/1?raven.async.gae.connectionid=MyConnection + http://public:private@host:port/1?async.gae.connectionid=MyConnection diff --git a/docs/modules/index.rst b/docs/modules/index.rst index 225a4ed0bdf..884eb1362d6 100644 --- a/docs/modules/index.rst +++ b/docs/modules/index.rst @@ -1,7 +1,7 @@ Integrations ============ -The Raven Java SDK comes with support for some frameworks and libraries so that +The Sentry Java SDK comes with support for some frameworks and libraries so that you don't have to capture and send errors manually. .. toctree:: @@ -9,7 +9,7 @@ you don't have to capture and send errors manually. android appengine - raven + sentry log4j log4j2 logback diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index b00464b027c..7de908def21 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -1,13 +1,13 @@ Log4j 1.x ========= -The ``raven-log4j`` library provides `Log4j 1.x `_ -support for Raven via an `Appender +The ``sentry-log4j`` library provides `Log4j 1.x `_ +support for Sentry via an `Appender `_ that sends logged exceptions to Sentry. The source can be found `on Github -`_. +`_. Installation ------------ @@ -17,24 +17,24 @@ Using Maven: .. sourcecode:: xml - com.getsentry.raven - raven-log4j - 8.0.2 + io.sentry + sentry-log4j + 1.0.0 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-log4j:8.0.2' + compile 'io.sentry:sentry-log4j:1.0.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-log4j" % "8.0.2" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.0.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- @@ -58,7 +58,7 @@ Example configuration using the ``log4j.properties`` format: log4j.appender.Console.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5p: %m%n # Configure the Sentry appender, overriding the logging threshold to the WARN level - log4j.appender.Sentry=com.getsentry.raven.log4j.SentryAppender + log4j.appender.Sentry=io.sentry.log4j.SentryAppender log4j.appender.Sentry.threshold=WARN Alternatively, using the ``log4j.xml`` format: @@ -79,7 +79,7 @@ Alternatively, using the ``log4j.xml`` format: - + @@ -87,7 +87,7 @@ Alternatively, using the ``log4j.xml`` format: + of a non-Sentry logger that is set to a different logging threshold --> @@ -118,17 +118,17 @@ Or as Java System Properties: Configuration parameters follow: -======================= ======================= =============================== =========== -Environment variable Java System Property Example value Description -======================= ======================= =============================== =========== -``SENTRY_DSN`` ``sentry.dsn`` ``https://host:port/1?options`` Your Sentry DSN (client key), if left blank Raven will no-op -``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application -``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in -``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) -``SENTRY_RAVENFACTORY`` ``sentry.ravenfactory`` ``com.foo.RavenFactory`` Optional, select the ravenFactory class -``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags -``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC -======================= ======================= =============================== =========== +======================== ======================== =============================== =========== +Environment variable Java System Property Example value Description +======================== ======================== =============================== =========== +``SENTRY_DSN`` ``sentry.dsn`` ``https://host:port/1?options`` Your Sentry DSN (client key), if left blank Sentry will no-op +``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application +``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in +``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) +``SENTRY_SENTRYFACTORY`` ``sentry.sentryfactory`` ``com.foo.SentryFactory`` Optional, select the sentryFactory class +``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags +``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC +======================== ======================== =============================== =========== Configuration via Static File ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -150,7 +150,7 @@ Example configuration in the ``log4j.properties`` file: log4j.appender.Console.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5p: %m%n # Configure the Sentry appender, overriding the logging threshold to the WARN level - log4j.appender.Sentry=com.getsentry.raven.log4j.SentryAppender + log4j.appender.Sentry=io.sentry.log4j.SentryAppender log4j.appender.Sentry.threshold=WARN # Set the Sentry DSN @@ -165,8 +165,8 @@ Example configuration in the ``log4j.properties`` file: # Optional, override the server name (rather than looking it up dynamically) log4j.appender.Sentry.serverName=server1 - # Optional, select the ravenFactory class - log4j.appender.Sentry.ravenFactory=com.foo.RavenFactory + # Optional, select the sentryFactory class + log4j.appender.Sentry.sentryFactory=com.foo.SentryFactory # Optional, provide tags log4j.appender.Sentry.tags=tag1:value1,tag2:value2 @@ -192,7 +192,7 @@ Alternatively, using the ``log4j.xml`` format: - + @@ -210,8 +210,8 @@ Alternatively, using the ``log4j.xml`` format: - - + + @@ -221,7 +221,7 @@ Alternatively, using the ``log4j.xml`` format: + of a non-Sentry logger that is set to a different logging threshold --> @@ -314,6 +314,6 @@ In Practice Asynchronous Logging -------------------- -Raven uses asynchronous communication by default, and so it is unnecessary +Sentry uses asynchronous communication by default, and so it is unnecessary to use an `AsyncAppender `_. diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index d2ba15d422a..d8c5a31f901 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -1,13 +1,13 @@ Log4j 2.x ========= -The ``raven-log4j2`` library provides `Log4j 2.x `_ -support for Raven via an `Appender +The ``sentry-log4j2`` library provides `Log4j 2.x `_ +support for Sentry via an `Appender `_ that sends logged exceptions to Sentry. The source can be found `on Github -`_. +`_. Installation ------------ @@ -17,24 +17,24 @@ Using Maven: .. sourcecode:: xml - com.getsentry.raven - raven-log4j2 - 8.0.2 + io.sentry + sentry-log4j2 + 1.0.0 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-log4j2:8.0.2' + compile 'io.sentry:sentry-log4j2:1.0.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-log4j2" % "8.0.2" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.0.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- @@ -50,13 +50,13 @@ Example configuration using the ``log4j2.xml`` format: .. sourcecode:: xml - + - + @@ -92,17 +92,17 @@ Or as Java System Properties: Configuration parameters follow: -======================= ======================= =============================== =========== -Environment variable Java System Property Example value Description -======================= ======================= =============================== =========== -``SENTRY_DSN`` ``sentry.dsn`` ``https://host:port/1?options`` Your Sentry DSN (client key), if left blank Raven will no-op -``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application -``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in -``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) -``SENTRY_RAVENFACTORY`` ``sentry.ravenfactory`` ``com.foo.RavenFactory`` Optional, select the ravenFactory class -``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags -``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC -======================= ======================= =============================== =========== +======================== ======================== =============================== =========== +Environment variable Java System Property Example value Description +======================== ======================== =============================== =========== +``SENTRY_DSN`` ``sentry.dsn`` ``https://host:port/1?options`` Your Sentry DSN (client key), if left blank Sentry will no-op +``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application +``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in +``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) +``SENTRY_SENTRYFACTORY`` ``sentry.sentryfactory`` ``com.foo.SentryFactory`` Optional, select the sentryFactory class +``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags +``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC +======================== ======================== =============================== =========== Configuration via Static File ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -116,13 +116,13 @@ Example configuration in the ``log4j.properties`` file: .. sourcecode:: xml - + - + https://host:port/1?options @@ -135,15 +135,15 @@ Example configuration in the ``log4j.properties`` file: server1 - - com.foo.RavenFactory + + com.foo.SentryFactory tag1:value1,tag2:value2 foo,bar,baz - + diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 7ad75b577ff..64588c6f61a 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -1,13 +1,13 @@ Logback ======= -The ``raven-logback`` library provides `Logback `_ -support for Raven via an `Appender +The ``sentry-logback`` library provides `Logback `_ +support for Sentry via an `Appender `_ that sends logged exceptions to Sentry. The source can be found `on Github -`_. +`_. Installation ------------ @@ -17,24 +17,24 @@ Using Maven: .. sourcecode:: xml - com.getsentry.raven - raven-logback - 8.0.2 + io.sentry + sentry-logback + 1.0.0 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven-logback:8.0.2' + compile 'io.sentry:sentry-logback:1.0.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven-logback" % "8.0.2" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.0.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- @@ -58,14 +58,14 @@ Example configuration using the ``logback.xml`` format: - + WARN + of a non-Sentry logger that is set to a different logging threshold --> @@ -96,17 +96,17 @@ Or as Java System Properties: Configuration parameters follow: -======================= ======================= =============================== =========== -Environment variable Java System Property Example value Description -======================= ======================= =============================== =========== -``SENTRY_DSN`` ``sentry.dsn`` ``https://host:port/1?options`` Your Sentry DSN (client key), if left blank Raven will no-op -``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application -``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in -``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) -``SENTRY_RAVENFACTORY`` ``sentry.ravenfactory`` ``com.foo.RavenFactory`` Optional, select the ravenFactory class -``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags -``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC -======================= ======================= =============================== =========== +======================== ======================== =============================== =========== +Environment variable Java System Property Example value Description +======================== ======================== =============================== =========== +``SENTRY_DSN`` ``sentry.dsn`` ``https://host:port/1?options`` Your Sentry DSN (client key), if left blank Sentry will no-op +``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application +``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in +``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) +``SENTRY_SENTRYFACTORY`` ``sentry.sentryfactory`` ``com.foo.SentryFactory`` Optional, select the sentryFactory class +``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags +``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC +======================== ======================== =============================== =========== Configuration via Static File ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -128,7 +128,7 @@ Example configuration in the ``logback.xml`` file: - + WARN @@ -145,8 +145,8 @@ Example configuration in the ``logback.xml`` file: server1 - - com.foo.RavenFactory + + com.foo.SentryFactory tag1:value1,tag2:value2 @@ -156,7 +156,7 @@ Example configuration in the ``logback.xml`` file: + of a non-Sentry logger that is set to a different logging threshold --> diff --git a/docs/modules/raven.rst b/docs/modules/raven.rst index 35088a7fbac..c3e64906608 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/raven.rst @@ -1,12 +1,12 @@ java.util.logging ================= -The ``raven`` library provides a `java.util.logging Handler +The ``sentry`` library provides a `java.util.logging Handler `_ that sends logged exceptions to Sentry. -The source for ``raven-java`` can be found `on Github -`_. +The source for ``sentry-java`` can be found `on Github +`_. Installation ------------ @@ -16,24 +16,24 @@ Using Maven: .. sourcecode:: xml - com.getsentry.raven - raven - 8.0.2 + io.sentry + sentry + 1.0.0 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven:8.0.2' + compile 'io.sentry:sentry:1.0.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven" % "8.0.2" + libraryDependencies += "io.sentry" % "sentry" % "1.0.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- @@ -49,13 +49,13 @@ Example configuration using the ``logging.properties`` format: .. sourcecode:: ini # Enable the Console and Sentry handlers - handlers=java.util.logging.ConsoleHandler,com.getsentry.raven.jul.SentryHandler + handlers=java.util.logging.ConsoleHandler,io.sentry.jul.SentryHandler # Set the default log level to INFO .level=INFO # Override the Sentry handler log level to WARNING - com.getsentry.raven.jul.SentryHandler.level=WARNING + io.sentry.jul.SentryHandler.level=WARNING When starting your application, add the ``java.util.logging.config.file`` to the system properties, with the full path to the ``logging.properties`` as @@ -87,17 +87,17 @@ Or as Java System Properties: Configuration parameters follow: -======================= ======================= =============================== =========== -Environment variable Java System Property Example value Description -======================= ======================= =============================== =========== -``SENTRY_DSN`` ``sentry.dsn`` ``https://host:port/1?options`` Your Sentry DSN (client key), if left blank Raven will no-op -``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application -``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in -``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) -``SENTRY_RAVENFACTORY`` ``sentry.ravenfactory`` ``com.foo.RavenFactory`` Optional, select the ravenFactory class -``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags -``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC -======================= ======================= =============================== =========== +======================== ======================== =============================== =========== +Environment variable Java System Property Example value Description +======================== ======================== =============================== =========== +``SENTRY_DSN`` ``sentry.dsn`` ``https://host:port/1?options`` Your Sentry DSN (client key), if left blank Sentry will no-op +``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application +``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in +``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) +``SENTRY_SENTRYFACTORY`` ``sentry.sentryfactory`` ``com.foo.SentryFactory`` Optional, select the sentryFactory class +``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags +``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC +======================== ======================== =============================== =========== Configuration via Static File ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -111,34 +111,34 @@ Example configuration in the ``logging.properties`` file: .. sourcecode:: ini # Enable the Console and Sentry handlers - handlers=java.util.logging.ConsoleHandler, com.getsentry.raven.jul.SentryHandler + handlers=java.util.logging.ConsoleHandler, io.sentry.jul.SentryHandler # Set the default log level to INFO .level=INFO # Override the Sentry handler log level to WARNING - com.getsentry.raven.jul.SentryHandler.level=WARNING + io.sentry.jul.SentryHandler.level=WARNING # Set Sentry DSN - com.getsentry.raven.jul.SentryHandler.dsn=https://host:port/1?options + io.sentry.jul.SentryHandler.dsn=https://host:port/1?options # Optional, provide tags - com.getsentry.raven.jul.SentryHandler.tags=tag1:value1,tag2:value2 + io.sentry.jul.SentryHandler.tags=tag1:value1,tag2:value2 # Optional, provide release version of your application - com.getsentry.raven.jul.SentryHandler.release=1.0.0 + io.sentry.jul.SentryHandler.release=1.0.0 # Optional, provide environment your application is running in - com.getsentry.raven.jul.SentryHandler.environment=production + io.sentry.jul.SentryHandler.environment=production # Optional, override the server name (rather than looking it up dynamically) - com.getsentry.raven.jul.SentryHandler.serverName=server1 + io.sentry.jul.SentryHandler.serverName=server1 - # Optional, select the ravenFactory class - com.getsentry.raven.jul.SentryHandler.ravenFactory=com.foo.RavenFactory + # Optional, select the sentryFactory class + io.sentry.jul.SentryHandler.sentryFactory=com.foo.SentryFactory # Optional, provide tag names to be extracted from MDC - com.getsentry.raven.jul.SentryHandler.extraTags=foo,bar,baz + io.sentry.jul.SentryHandler.extraTags=foo,bar,baz In Practice ----------- diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json index af1dd53d421..1b12c2eb210 100644 --- a/docs/sentry-doc-config.json +++ b/docs/sentry-doc-config.json @@ -13,10 +13,10 @@ "java.logging": { "name": "java.util.logging", "type": "framework", - "doc_link": "modules/raven/", + "doc_link": "modules/sentry/", "wizard": [ - "modules/raven#installation", - "modules/raven#usage" + "modules/sentry#installation", + "modules/sentry#usage" ] }, "java.log4j": { diff --git a/docs/usage.rst b/docs/usage.rst index 11075b2b3a9..bd8048ddf41 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -2,7 +2,7 @@ Manual Usage ============ **Note:** The following page provides examples on how to configure and use -Raven directly. It is **highly recommended** that you use one of the provided +Sentry directly. It is **highly recommended** that you use one of the provided integrations instead if possible. Installation @@ -13,56 +13,56 @@ Using Maven: .. sourcecode:: xml - com.getsentry.raven - raven - 8.0.2 + io.sentry + sentry + 1.0.0 Using Gradle: .. sourcecode:: groovy - compile 'com.getsentry.raven:raven:8.0.2' + compile 'io.sentry:sentry:1.0.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "com.getsentry.raven" % "raven" % "8.0.2" + libraryDependencies += "io.sentry" % "sentry" % "1.0.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- -To report an event manually you need to construct a ``Raven`` instance and use one +To report an event manually you need to construct a ``Sentry`` instance and use one of the send methods it provides. .. sourcecode:: java - import com.getsentry.raven.Raven; - import com.getsentry.raven.RavenFactory; + import io.sentry.Sentry; + import io.sentry.SentryFactory; public class MyClass { - private static Raven raven; + private static Sentry sentry; public static void main(String... args) { // Creation of the client with a specific DSN String dsn = args[0]; - raven = RavenFactory.ravenInstance(dsn); + sentry = SentryFactory.sentryInstance(dsn); // Or, if you don't provide a DSN, - raven = RavenFactory.ravenInstance(); + sentry = SentryFactory.sentryInstance(); // It is also possible to use the DSN detection system, which // will check the environment variable "SENTRY_DSN" and the Java // System Property "sentry.dsn". - raven = RavenFactory.ravenInstance(); + sentry = SentryFactory.sentryInstance(); } void logSimpleMessage() { // This sends a simple event to Sentry - raven.sendMessage("This is a test"); + sentry.sendMessage("This is a test"); } void logWithBreadcrumbs() { @@ -73,7 +73,7 @@ of the send methods it provides. ); // This sends a simple event to Sentry - raven.sendMessage("This is a test"); + sentry.sendMessage("This is a test"); } void logException() { @@ -81,7 +81,7 @@ of the send methods it provides. unsafeMethod(); } catch (Exception e) { // This sends an exception event to Sentry - raven.sendException(e); + sentry.sendException(e); } } @@ -98,28 +98,28 @@ For more complex messages, you'll need to build an ``Event`` with the .. sourcecode:: java - import com.getsentry.raven.Raven; - import com.getsentry.raven.RavenFactory; - import com.getsentry.raven.event.Event; - import com.getsentry.raven.event.EventBuilder; - import com.getsentry.raven.event.interfaces.ExceptionInterface; - import com.getsentry.raven.event.interfaces.MessageInterface; + import io.sentry.Sentry; + import io.sentry.SentryFactory; + import io.sentry.event.Event; + import io.sentry.event.EventBuilder; + import io.sentry.event.interfaces.ExceptionInterface; + import io.sentry.event.interfaces.MessageInterface; public class MyClass { - private static Raven raven; + private static Sentry sentry; public static void main(String... args) { // Creation of the client with a specific DSN String dsn = args[0]; - raven = RavenFactory.ravenInstance(dsn); + sentry = SentryFactory.sentryInstance(dsn); // It is also possible to use the DSN detection system, which // will check the environment variable "SENTRY_DSN" and the Java // System Property "sentry.dsn". - raven = RavenFactory.ravenInstance(); + sentry = SentryFactory.sentryInstance(); - // Advanced: specify the ravenFactory used - raven = RavenFactory.ravenInstance(new Dsn(dsn), "com.getsentry.raven.DefaultRavenFactory"); + // Advanced: specify the sentryFactory used + sentry = SentryFactory.sentryInstance(new Dsn(dsn), "io.sentry.DefaultSentryFactory"); } void logSimpleMessage() { @@ -128,7 +128,7 @@ For more complex messages, you'll need to build an ``Event`` with the .withMessage("This is a test") .withLevel(Event.Level.INFO) .withLogger(MyClass.class.getName()); - raven.sendEvent(eventBuilder); + sentry.sendEvent(eventBuilder); } void logException() { @@ -141,7 +141,7 @@ For more complex messages, you'll need to build an ``Event`` with the .withLevel(Event.Level.ERROR) .withLogger(MyClass.class.getName()) .withSentryInterface(new ExceptionInterface(e)); - raven.sendEvent(eventBuilder); + sentry.sendEvent(eventBuilder); } } @@ -150,39 +150,39 @@ For more complex messages, you'll need to build an ``Event`` with the } } -Static Access to Raven ----------------------- +Static Access to Sentry +----------------------- -The most recently constructed ``Raven`` instance is stored statically so it may +The most recently constructed ``Sentry`` instance is stored statically so it may be used easily from anywhere in your application. .. sourcecode:: java - import com.getsentry.raven.Raven; - import com.getsentry.raven.RavenFactory; + import io.sentry.Sentry; + import io.sentry.SentryFactory; public class MyClass { public static void main(String... args) { - // Create a Raven instance - RavenFactory.ravenInstance(); + // Create a Sentry instance + SentryFactory.sentryInstance(); } public somewhereElse() { - // Use the Raven instance statically. Note that we are + // Use the Sentry instance statically. Note that we are // using the Class (and a static method) here - Raven.capture("Error message"); + Sentry.capture("Error message"); // Or pass it a throwable - Raven.capture(new Exception("Error message")); + Sentry.capture(new Exception("Error message")); // Or build an event yourself EventBuilder eventBuilder = new EventBuilder() .withMessage("Exception caught") .withLevel(Event.Level.ERROR); - Raven.capture(eventBuilder.build()); + Sentry.capture(eventBuilder.build()); } } -Note that a Raven instance *must* be created before you can use the ``Raven.capture`` +Note that a Sentry instance *must* be created before you can use the ``Sentry.capture`` method, otherwise a ``NullPointerException`` (with an explanation) will be thrown. diff --git a/pom.xml b/pom.xml index 2eb9bfca53e..a5181e5a5a1 100644 --- a/pom.xml +++ b/pom.xml @@ -8,12 +8,12 @@ 7 - com.getsentry.raven - raven-all - 8.0.3-SNAPSHOT + io.sentry + sentry-all + 1.0.0-SNAPSHOT pom - Raven-Java + Sentry-Java Sentry client and appenders for diverse logging frameworks. https://github.com/${github.repo} 2012 @@ -73,12 +73,12 @@ - raven - raven-android - raven-appengine - raven-log4j - raven-logback - raven-log4j2 + sentry + sentry-android + sentry-appengine + sentry-log4j + sentry-logback + sentry-log4j2 @@ -108,7 +108,7 @@ UTF-8 UTF-8 - getsentry/raven-java + getsentry/sentry-java github @@ -129,12 +129,12 @@ ${project.groupId} - raven + sentry ${project.version} ${project.groupId} - raven + sentry ${project.version} test-jar @@ -376,7 +376,7 @@ maven-javadoc-plugin 2.10.4 - com.getsentry.raven.util.base64* + io.sentry.util.base64* diff --git a/raven-android/src/main/java/com/getsentry/raven/android/AndroidRavenFactory.java b/raven-android/src/main/java/com/getsentry/raven/android/AndroidRavenFactory.java deleted file mode 100644 index ac07e2edbf6..00000000000 --- a/raven-android/src/main/java/com/getsentry/raven/android/AndroidRavenFactory.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.getsentry.raven.android; - -import android.content.Context; -import android.util.Log; -import com.getsentry.raven.*; -import com.getsentry.raven.android.event.helper.AndroidEventBuilderHelper; -import com.getsentry.raven.buffer.Buffer; -import com.getsentry.raven.buffer.DiskBuffer; -import com.getsentry.raven.context.ContextManager; -import com.getsentry.raven.context.SingletonContextManager; -import com.getsentry.raven.dsn.Dsn; - -import java.io.File; - -/** - * RavenFactory that handles Android-specific construction, like taking advantage - * of the Android Context instance. - */ -public class AndroidRavenFactory extends DefaultRavenFactory { - - /** - * Logger tag. - */ - public static final String TAG = AndroidRavenFactory.class.getName(); - /** - * Default Buffer directory name. - */ - private static final String DEFAULT_BUFFER_DIR = "raven-buffered-events"; - - private Context ctx; - - /** - * Construct an AndroidRavenFactory using the specified Android Context. - * - * @param ctx Android Context. - */ - public AndroidRavenFactory(Context ctx) { - this.ctx = ctx; - - Log.d(TAG, "Construction of Android Raven."); - } - - @Override - public com.getsentry.raven.Raven createRavenInstance(Dsn dsn) { - com.getsentry.raven.Raven ravenInstance = super.createRavenInstance(dsn); - ravenInstance.addBuilderHelper(new AndroidEventBuilderHelper(ctx)); - return ravenInstance; - } - - @Override - protected Buffer getBuffer(Dsn dsn) { - File bufferDir; - String bufferDirOpt = dsn.getOptions().get(BUFFER_DIR_OPTION); - if (bufferDirOpt != null) { - bufferDir = new File(bufferDirOpt); - } else { - bufferDir = new File(ctx.getCacheDir().getAbsolutePath(), DEFAULT_BUFFER_DIR); - } - - Log.d(TAG, "Using buffer dir: " + bufferDir.getAbsolutePath()); - return new DiskBuffer(bufferDir, getBufferSize(dsn)); - } - - @Override - protected ContextManager getContextManager(Dsn dsn) { - return new SingletonContextManager(); - } - -} diff --git a/raven-appengine/src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory b/raven-appengine/src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory deleted file mode 100644 index 2d9dfe00b06..00000000000 --- a/raven-appengine/src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory +++ /dev/null @@ -1 +0,0 @@ -com.getsentry.raven.appengine.AppEngineRavenFactory diff --git a/raven/src/main/java/com/getsentry/raven/RavenFactory.java b/raven/src/main/java/com/getsentry/raven/RavenFactory.java deleted file mode 100644 index a77e73b7f23..00000000000 --- a/raven/src/main/java/com/getsentry/raven/RavenFactory.java +++ /dev/null @@ -1,180 +0,0 @@ -package com.getsentry.raven; - -import com.getsentry.raven.dsn.Dsn; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.ServiceLoader; -import java.util.Set; - -/** - * Factory in charge of creating {@link Raven} instances. - *

    - * The factories register themselves through the {@link ServiceLoader} system. - */ -public abstract class RavenFactory { - private static final ServiceLoader AUTO_REGISTERED_FACTORIES = - ServiceLoader.load(RavenFactory.class, RavenFactory.class.getClassLoader()); - private static final Set MANUALLY_REGISTERED_FACTORIES = new HashSet<>(); - private static final Logger logger = LoggerFactory.getLogger(RavenFactory.class); - - /** - * Manually adds a RavenFactory to the system. - *

    - * Usually RavenFactories are automatically detected with the {@link ServiceLoader} system, but some systems - * such as Android do not provide a fully working ServiceLoader.
    - * If the factory isn't detected automatically, it's possible to add it through this method. - * - * @param ravenFactory ravenFactory to support. - */ - public static void registerFactory(RavenFactory ravenFactory) { - MANUALLY_REGISTERED_FACTORIES.add(ravenFactory); - } - - private static Iterable getRegisteredFactories() { - List ravenFactories = new LinkedList<>(); - ravenFactories.addAll(MANUALLY_REGISTERED_FACTORIES); - for (RavenFactory autoRegisteredFactory : AUTO_REGISTERED_FACTORIES) { - ravenFactories.add(autoRegisteredFactory); - } - return ravenFactories; - } - - /** - * Creates an instance of Raven using the DSN obtain through {@link com.getsentry.raven.dsn.Dsn#dsnLookup()}. - * - * @return an instance of Raven. - */ - public static Raven ravenInstance() { - return ravenInstance(Dsn.dsnLookup()); - } - - /** - * Creates an instance of Raven using the provided DSN. - * - * @param dsn Data Source Name of the Sentry server. - * @return an instance of Raven. - */ - public static Raven ravenInstance(String dsn) { - return ravenInstance(new Dsn(dsn)); - } - - /** - * Creates an instance of Raven using the provided DSN. - * - * @param dsn Data Source Name of the Sentry server. - * @return an instance of Raven. - */ - public static Raven ravenInstance(Dsn dsn) { - return ravenInstance(dsn, null); - } - - /** - * Creates an instance of Raven using the provided DSN and the specified factory. - * - * @param dsn Data Source Name of the Sentry server. - * @param ravenFactoryName name of the RavenFactory to use to generate an instance of Raven. - * @return an instance of Raven. - * @throws IllegalStateException when no instance of Raven has been created. - */ - public static Raven ravenInstance(Dsn dsn, String ravenFactoryName) { - logger.debug("Attempting to find a working RavenFactory"); - - // Loop through registered factories, keeping track of which classes we skip, which we try to instantiate, - // and the last exception thrown. - ArrayList skippedFactories = new ArrayList<>(); - ArrayList triedFactories = new ArrayList<>(); - RuntimeException lastExc = null; - - for (RavenFactory ravenFactory : getRegisteredFactories()) { - String name = ravenFactory.getClass().getName(); - if (ravenFactoryName != null && !ravenFactoryName.equals(name)) { - skippedFactories.add(name); - continue; - } - - logger.debug("Attempting to use '{}' as a RavenFactory.", ravenFactory); - triedFactories.add(name); - try { - Raven ravenInstance = ravenFactory.createRavenInstance(dsn); - logger.debug("The RavenFactory '{}' created an instance of Raven.", ravenFactory); - return ravenInstance; - } catch (RuntimeException e) { - lastExc = e; - logger.debug("The RavenFactory '{}' couldn't create an instance of Raven.", ravenFactory, e); - } - } - - if (ravenFactoryName != null && triedFactories.isEmpty()) { - try { - // see if the provided class exists on the classpath at all - Class.forName(ravenFactoryName); - logger.error( - "The RavenFactory class '{}' was found on your classpath but was not " - + "registered with Raven, see: " - + "https://github.com/getsentry/raven-java/#custom-ravenfactory", ravenFactoryName); - } catch (ClassNotFoundException e) { - logger.error("The RavenFactory class name '{}' was specified but " - + "the class was not found on your classpath.", ravenFactoryName); - } - } - - // Throw an IllegalStateException that attempts to be helpful. - StringBuilder sb = new StringBuilder(); - sb.append("Couldn't create a raven instance for: '"); - sb.append(dsn); - sb.append('\''); - if (ravenFactoryName != null) { - sb.append("; ravenFactoryName: "); - sb.append(ravenFactoryName); - - if (skippedFactories.isEmpty()) { - sb.append("; no skipped factories"); - } else { - sb.append("; skipped factories: "); - String delim = ""; - for (String skippedFactory : skippedFactories) { - sb.append(delim); - sb.append(skippedFactory); - delim = ", "; - } - } - } - - if (triedFactories.isEmpty()) { - sb.append("; no factories tried!"); - throw new IllegalStateException(sb.toString()); - } - - sb.append("; tried factories: "); - String delim = ""; - for (String triedFactory : triedFactories) { - sb.append(delim); - sb.append(triedFactory); - delim = ", "; - } - - sb.append("; cause contains exception thrown by the last factory tried."); - throw new IllegalStateException(sb.toString(), lastExc); - } - - /** - * Creates an instance of Raven given a DSN. - * - * @param dsn Data Source Name of the Sentry server. - * @return an instance of Raven. - * @throws RuntimeException when an instance couldn't be created. - */ - public abstract Raven createRavenInstance(Dsn dsn); - - @Override - public String toString() { - return "RavenFactory{" - + "name='" + this.getClass().getName() + '\'' - + '}'; - } -} diff --git a/raven/src/main/java/com/getsentry/raven/environment/RavenEnvironment.java b/raven/src/main/java/com/getsentry/raven/environment/RavenEnvironment.java deleted file mode 100644 index 62ddd1e5a6f..00000000000 --- a/raven/src/main/java/com/getsentry/raven/environment/RavenEnvironment.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.getsentry.raven.environment; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ResourceBundle; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Manages environment information on Raven. - *

    - * Manages information related to Raven Runtime such as the name of the library or - * whether or not the thread is managed by Raven. - */ -public final class RavenEnvironment { - /** - * Name of this SDK. - */ - public static final String SDK_NAME = "raven-java"; - /** - * Version of this SDK. - */ - public static final String SDK_VERSION = ResourceBundle.getBundle("raven-build").getString("build.name"); - /** - * Indicates whether the current thread is managed by raven or not. - */ - protected static final ThreadLocal RAVEN_THREAD = new ThreadLocal() { - @Override - protected AtomicInteger initialValue() { - return new AtomicInteger(); - } - }; - private static final Logger logger = LoggerFactory.getLogger(RavenEnvironment.class); - - private RavenEnvironment() { - } - - /** - * Sets the current thread as managed by Raven. - *

    - * The logs generated by Threads managed by Raven will not send logs to Sentry. - *

    - * Recommended usage: - *

    {@code
    -     * RavenEnvironment.startManagingThread();
    -     * try {
    -     *     // Some code that shouldn't generate Sentry logs.
    -     * } finally {
    -     *     RavenEnvironment.stopManagingThread();
    -     * }
    -     * }
    - */ - public static void startManagingThread() { - try { - if (isManagingThread()) { - logger.warn("Thread already managed by Raven"); - } - } finally { - RAVEN_THREAD.get().incrementAndGet(); - } - } - - /** - * Sets the current thread as not managed by Raven. - *

    - * The logs generated by Threads not managed by Raven will send logs to Sentry. - */ - public static void stopManagingThread() { - try { - if (!isManagingThread()) { - //Start managing the thread only to send the warning - startManagingThread(); - logger.warn("Thread not yet managed by Raven"); - } - } finally { - RAVEN_THREAD.get().decrementAndGet(); - } - } - - /** - * Checks whether the current thread is managed by Raven or not. - * - * @return {@code true} if the thread is managed by Raven, {@code false} otherwise. - */ - public static boolean isManagingThread() { - return RAVEN_THREAD.get().get() > 0; - } - - /** - * Returns sdk name+version string, used for HTTP User Agent, sentry_client, etc. - * - * @return Raven sdk string - */ - public static String getRavenName() { - return SDK_NAME + "/" + SDK_VERSION; - } -} diff --git a/raven/src/main/java/com/getsentry/raven/servlet/RavenServletContainerInitializer.java b/raven/src/main/java/com/getsentry/raven/servlet/RavenServletContainerInitializer.java deleted file mode 100644 index 793f3904cf1..00000000000 --- a/raven/src/main/java/com/getsentry/raven/servlet/RavenServletContainerInitializer.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.getsentry.raven.servlet; - -import javax.servlet.ServletContainerInitializer; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import java.util.Set; - -/** - * Servlet container initializer used to add the {@link RavenServletRequestListener} to the {@link ServletContext}. - */ -public class RavenServletContainerInitializer implements ServletContainerInitializer { - @Override - public void onStartup(Set> c, ServletContext ctx) throws ServletException { - ctx.addListener(RavenServletRequestListener.class); - } -} diff --git a/raven/src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory b/raven/src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory deleted file mode 100644 index 1dfcaa5e2ce..00000000000 --- a/raven/src/main/resources/META-INF/services/com.getsentry.raven.RavenFactory +++ /dev/null @@ -1 +0,0 @@ -com.getsentry.raven.DefaultRavenFactory diff --git a/raven/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/raven/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer deleted file mode 100644 index 7091d90b28e..00000000000 --- a/raven/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer +++ /dev/null @@ -1 +0,0 @@ -com.getsentry.raven.servlet.RavenServletContainerInitializer diff --git a/raven/src/test/java/com/getsentry/raven/environment/RavenEnvironmentTest.java b/raven/src/test/java/com/getsentry/raven/environment/RavenEnvironmentTest.java deleted file mode 100644 index 447194ff0d8..00000000000 --- a/raven/src/test/java/com/getsentry/raven/environment/RavenEnvironmentTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.getsentry.raven.environment; - -import org.testng.annotations.AfterMethod; -import org.testng.annotations.Test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -public class RavenEnvironmentTest { - @AfterMethod - public void tearDown() throws Exception { - RavenEnvironment.RAVEN_THREAD.remove(); - } - - @Test - public void testThreadNotManagedByDefault() throws Exception { - assertThat(RavenEnvironment.isManagingThread(), is(false)); - } - - @Test - public void testStartManagingThreadWorks() throws Exception { - RavenEnvironment.startManagingThread(); - - assertThat(RavenEnvironment.isManagingThread(), is(true)); - } - - @Test - public void testStartManagingAlreadyManagedThreadWorks() throws Exception { - RavenEnvironment.startManagingThread(); - - RavenEnvironment.startManagingThread(); - - assertThat(RavenEnvironment.isManagingThread(), is(true)); - } - - @Test - public void testStopManagingThreadWorks() throws Exception { - RavenEnvironment.startManagingThread(); - - RavenEnvironment.stopManagingThread(); - - assertThat(RavenEnvironment.isManagingThread(), is(false)); - } - - @Test - public void testStopManagingNonManagedThreadWorks() throws Exception { - RavenEnvironment.stopManagingThread(); - - assertThat(RavenEnvironment.isManagingThread(), is(false)); - } - - @Test - public void testThreadManagedTwiceNeedsToBeUnmanagedTwice() throws Exception { - RavenEnvironment.startManagingThread(); - RavenEnvironment.startManagingThread(); - - RavenEnvironment.stopManagingThread(); - assertThat(RavenEnvironment.isManagingThread(), is(true)); - RavenEnvironment.stopManagingThread(); - assertThat(RavenEnvironment.isManagingThread(), is(false)); - } -} diff --git a/raven/src/test/resources/jndi.properties b/raven/src/test/resources/jndi.properties deleted file mode 100644 index 6351768204a..00000000000 --- a/raven/src/test/resources/jndi.properties +++ /dev/null @@ -1 +0,0 @@ -java.naming.factory.initial=com.getsentry.raven.dsn.InitialContextFactory diff --git a/raven/src/test/resources/logging-integration.properties b/raven/src/test/resources/logging-integration.properties deleted file mode 100644 index 59f2f278827..00000000000 --- a/raven/src/test/resources/logging-integration.properties +++ /dev/null @@ -1,12 +0,0 @@ -java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter -java.util.logging.ConsoleHandler.level=INFO -java.util.logging.SimpleFormatter.format=[RAVEN] [%4$.4s] %3$s - %5$s%n - -com.getsentry.raven.jul.SentryHandler.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false -# Set Raven to WARNING level, as we recommend this as the lowest users go in their own configuration -com.getsentry.raven.jul.SentryHandler.level=WARNING - -handlers=java.util.logging.ConsoleHandler -.level=INFO - -jul.SentryHandlerIT.handlers=java.util.logging.ConsoleHandler, com.getsentry.raven.jul.SentryHandler diff --git a/raven-android/README.md b/sentry-android/README.md similarity index 86% rename from raven-android/README.md rename to sentry-android/README.md index a677e4fe59e..32aa7c84f6c 100644 --- a/raven-android/README.md +++ b/sentry-android/README.md @@ -1,3 +1,3 @@ -# raven-android +# sentry-android See the [Sentry documentation](https://docs.sentry.io/clients/java/modules/android/) for more information. diff --git a/raven-android/pom.xml b/sentry-android/pom.xml similarity index 84% rename from raven-android/pom.xml rename to sentry-android/pom.xml index 77f2e5b6874..c1ca5831e00 100644 --- a/raven-android/pom.xml +++ b/sentry-android/pom.xml @@ -3,16 +3,16 @@ 4.0.0 - com.getsentry.raven - raven-all - 8.0.3-SNAPSHOT + io.sentry + sentry-all + 1.0.0-SNAPSHOT - raven-android + sentry-android jar - Raven-Java for Android - Android Raven-Java client. + Sentry-Java for Android + Android Sentry-Java client. 4.1.1.4 @@ -24,7 +24,7 @@ ${project.groupId} - raven + sentry com.google.android @@ -34,12 +34,6 @@ - - junit - junit - ${junit.version} - test - org.robolectric robolectric @@ -65,6 +59,7 @@ junit junit + ${junit.version} test @@ -74,10 +69,10 @@ - + ${project.groupId} - raven + sentry test-jar test diff --git a/sentry-android/src/main/java/io/sentry/android/AndroidSentryFactory.java b/sentry-android/src/main/java/io/sentry/android/AndroidSentryFactory.java new file mode 100644 index 00000000000..190563474b0 --- /dev/null +++ b/sentry-android/src/main/java/io/sentry/android/AndroidSentryFactory.java @@ -0,0 +1,69 @@ +package io.sentry.android; + +import android.content.Context; +import android.util.Log; +import io.sentry.*; +import io.sentry.android.event.helper.AndroidEventBuilderHelper; +import io.sentry.buffer.Buffer; +import io.sentry.buffer.DiskBuffer; +import io.sentry.context.ContextManager; +import io.sentry.context.SingletonContextManager; +import io.sentry.dsn.Dsn; + +import java.io.File; + +/** + * SentryFactory that handles Android-specific construction, like taking advantage + * of the Android Context instance. + */ +public class AndroidSentryFactory extends DefaultSentryFactory { + + /** + * Logger tag. + */ + public static final String TAG = AndroidSentryFactory.class.getName(); + /** + * Default Buffer directory name. + */ + private static final String DEFAULT_BUFFER_DIR = "sentry-buffered-events"; + + private Context ctx; + + /** + * Construct an AndroidSentryFactory using the specified Android Context. + * + * @param ctx Android Context. + */ + public AndroidSentryFactory(Context ctx) { + this.ctx = ctx; + + Log.d(TAG, "Construction of Android Sentry."); + } + + @Override + public io.sentry.Sentry createSentryInstance(Dsn dsn) { + io.sentry.Sentry sentryInstance = super.createSentryInstance(dsn); + sentryInstance.addBuilderHelper(new AndroidEventBuilderHelper(ctx)); + return sentryInstance; + } + + @Override + protected Buffer getBuffer(Dsn dsn) { + File bufferDir; + String bufferDirOpt = dsn.getOptions().get(BUFFER_DIR_OPTION); + if (bufferDirOpt != null) { + bufferDir = new File(bufferDirOpt); + } else { + bufferDir = new File(ctx.getCacheDir().getAbsolutePath(), DEFAULT_BUFFER_DIR); + } + + Log.d(TAG, "Using buffer dir: " + bufferDir.getAbsolutePath()); + return new DiskBuffer(bufferDir, getBufferSize(dsn)); + } + + @Override + protected ContextManager getContextManager(Dsn dsn) { + return new SingletonContextManager(); + } + +} diff --git a/raven-android/src/main/java/com/getsentry/raven/android/Raven.java b/sentry-android/src/main/java/io/sentry/android/Sentry.java similarity index 56% rename from raven-android/src/main/java/com/getsentry/raven/android/Raven.java rename to sentry-android/src/main/java/io/sentry/android/Sentry.java index 575606dbf00..2abe7675fcf 100644 --- a/raven-android/src/main/java/com/getsentry/raven/android/Raven.java +++ b/sentry-android/src/main/java/io/sentry/android/Sentry.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.android; +package io.sentry.android; import android.Manifest; import android.content.Context; @@ -6,48 +6,48 @@ import android.content.pm.PackageManager; import android.text.TextUtils; import android.util.Log; -import com.getsentry.raven.DefaultRavenFactory; -import com.getsentry.raven.RavenFactory; -import com.getsentry.raven.dsn.Dsn; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.EventBuilder; +import io.sentry.DefaultSentryFactory; +import io.sentry.SentryFactory; +import io.sentry.dsn.Dsn; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; /** - * Android specific class to interface with Raven. Supplements the default Java classes + * Android specific class to interface with Sentry. Supplements the default Java classes * with Android specific state and features. */ -public final class Raven { +public final class Sentry { /** * Logger tag. */ - public static final String TAG = Raven.class.getName(); + public static final String TAG = Sentry.class.getName(); - private static volatile com.getsentry.raven.Raven raven; + private static volatile io.sentry.Sentry sentry; /** * Hide constructor. */ - private Raven() { + private Sentry() { } /** - * Initialize Raven using a DSN set in the AndroidManifest. + * Initialize Sentry using a DSN set in the AndroidManifest. * * @param ctx Android application ctx */ public static void init(Context ctx) { - init(ctx, getDefaultRavenFactory(ctx)); + init(ctx, getDefaultSentryFactory(ctx)); } /** - * Initialize Raven using a DSN set in the AndroidManifest. + * Initialize Sentry using a DSN set in the AndroidManifest. * * @param ctx Android application ctx - * @param ravenFactory the RavenFactory to be used to generate the Raven instance + * @param sentryFactory the SentryFactory to be used to generate the Sentry instance */ - public static void init(Context ctx, AndroidRavenFactory ravenFactory) { + public static void init(Context ctx, AndroidSentryFactory sentryFactory) { ctx = ctx.getApplicationContext(); String dsn = ""; @@ -56,63 +56,63 @@ public static void init(Context ctx, AndroidRavenFactory ravenFactory) { try { PackageManager packageManager = ctx.getPackageManager(); appInfo = packageManager.getApplicationInfo(ctx.getPackageName(), PackageManager.GET_META_DATA); - dsn = appInfo.metaData.getString("com.getsentry.raven.android.DSN"); + dsn = appInfo.metaData.getString("io.sentry.android.DSN"); } catch (PackageManager.NameNotFoundException e) { // skip } if (TextUtils.isEmpty(dsn)) { - throw new NullPointerException("Raven DSN is not set, you must provide it via" + throw new NullPointerException("Sentry DSN is not set, you must provide it via" + "the constructor or AndroidManifest."); } - init(ctx, dsn, ravenFactory); + init(ctx, dsn, sentryFactory); } /** - * Initialize Raven using a string DSN. + * Initialize Sentry using a string DSN. * * @param ctx Android application ctx * @param dsn Sentry DSN string */ public static void init(Context ctx, String dsn) { - init(ctx, new Dsn(dsn), getDefaultRavenFactory(ctx)); + init(ctx, new Dsn(dsn), getDefaultSentryFactory(ctx)); } /** - * Initialize Raven using a string DSN. + * Initialize Sentry using a string DSN. * * @param ctx Android application ctx * @param dsn Sentry DSN string - * @param ravenFactory the RavenFactory to be used to generate the Raven instance + * @param sentryFactory the SentryFactory to be used to generate the Sentry instance */ - public static void init(Context ctx, String dsn, AndroidRavenFactory ravenFactory) { - init(ctx, new Dsn(dsn), ravenFactory); + public static void init(Context ctx, String dsn, AndroidSentryFactory sentryFactory) { + init(ctx, new Dsn(dsn), sentryFactory); } /** - * Initialize Raven using a DSN object. + * Initialize Sentry using a DSN object. * * @param ctx Android application ctx * @param dsn Sentry DSN object */ public static void init(Context ctx, Dsn dsn) { - init(ctx, dsn, getDefaultRavenFactory(ctx)); + init(ctx, dsn, getDefaultSentryFactory(ctx)); } /** - * Initialize Raven using a DSN object. This is the 'main' initializer that other methods + * Initialize Sentry using a DSN object. This is the 'main' initializer that other methods * eventually call. * * @param ctx Android application ctx * @param dsn Sentry DSN object - * @param ravenFactory the RavenFactory to be used to generate the Raven instance + * @param sentryFactory the SentryFactory to be used to generate the Sentry instance */ - public static void init(Context ctx, Dsn dsn, AndroidRavenFactory ravenFactory) { - if (raven != null) { - Log.e(TAG, "Initializing Raven multiple times."); + public static void init(Context ctx, Dsn dsn, AndroidSentryFactory sentryFactory) { + if (sentry != null) { + Log.e(TAG, "Initializing Sentry multiple times."); // cleanup existing connections - raven.closeConnection(); + sentry.closeConnection(); } // Ensure we have the application context @@ -123,27 +123,27 @@ public static void init(Context ctx, Dsn dsn, AndroidRavenFactory ravenFactory) + " please add it to your AndroidManifest.xml"); } - Log.d(TAG, "Raven init with ctx='" + ctx.toString() + "' and dsn='" + dsn + "'"); + Log.d(TAG, "Sentry init with ctx='" + ctx.toString() + "' and dsn='" + dsn + "'"); String protocol = dsn.getProtocol(); if (!(protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https"))) { throw new IllegalArgumentException("Only 'http' or 'https' connections are supported in" - + " Raven Android, but received: " + protocol); + + " Sentry Android, but received: " + protocol); } - if ("false".equalsIgnoreCase(dsn.getOptions().get(DefaultRavenFactory.ASYNC_OPTION))) { - throw new IllegalArgumentException("Raven Android cannot use synchronous connections, remove '" - + DefaultRavenFactory.ASYNC_OPTION + "=false' from your DSN."); + if ("false".equalsIgnoreCase(dsn.getOptions().get(DefaultSentryFactory.ASYNC_OPTION))) { + throw new IllegalArgumentException("Sentry Android cannot use synchronous connections, remove '" + + DefaultSentryFactory.ASYNC_OPTION + "=false' from your DSN."); } - RavenFactory.registerFactory(ravenFactory); - raven = RavenFactory.ravenInstance(dsn); + SentryFactory.registerFactory(sentryFactory); + sentry = SentryFactory.sentryInstance(dsn); setupUncaughtExceptionHandler(); } - private static AndroidRavenFactory getDefaultRavenFactory(Context ctx) { - return new AndroidRavenFactory(ctx); + private static AndroidSentryFactory getDefaultSentryFactory(Context ctx) { + return new AndroidSentryFactory(ctx); } /** @@ -157,58 +157,58 @@ private static void setupUncaughtExceptionHandler() { } // don't double register - if (!(currentHandler instanceof RavenUncaughtExceptionHandler)) { + if (!(currentHandler instanceof SentryUncaughtExceptionHandler)) { // register as default exception handler Thread.setDefaultUncaughtExceptionHandler( - new RavenUncaughtExceptionHandler(currentHandler)); + new SentryUncaughtExceptionHandler(currentHandler)); } } /** - * Send an Event using the statically stored Raven instance. + * Send an Event using the statically stored Sentry instance. * * @param event Event to send to the Sentry server */ public static void capture(Event event) { - raven.sendEvent(event); + sentry.sendEvent(event); } /** - * Sends an exception (or throwable) to the Sentry server using the statically stored Raven instance. + * Sends an exception (or throwable) to the Sentry server using the statically stored Sentry instance. *

    * The exception will be logged at the {@link Event.Level#ERROR} level. * * @param throwable exception to send to Sentry. */ public static void capture(Throwable throwable) { - raven.sendException(throwable); + sentry.sendException(throwable); } /** - * Sends a message to the Sentry server using the statically stored Raven instance. + * Sends a message to the Sentry server using the statically stored Sentry instance. *

    * The message will be logged at the {@link Event.Level#INFO} level. * * @param message message to send to Sentry. */ public static void capture(String message) { - raven.sendMessage(message); + sentry.sendMessage(message); } /** - * Builds and sends an {@link Event} to the Sentry server using the statically stored Raven instance. + * Builds and sends an {@link Event} to the Sentry server using the statically stored Sentry instance. * * @param eventBuilder {@link EventBuilder} to send to Sentry. */ public static void capture(EventBuilder eventBuilder) { - raven.sendEvent(eventBuilder); + sentry.sendEvent(eventBuilder); } /** - * Clear statically stored Raven instance. Useful for tests. + * Clear statically stored Sentry instance. Useful for tests. */ - public static void clearStoredRaven() { - raven = null; + public static void clearStoredSentry() { + sentry = null; } } diff --git a/raven-android/src/main/java/com/getsentry/raven/android/RavenUncaughtExceptionHandler.java b/sentry-android/src/main/java/io/sentry/android/SentryUncaughtExceptionHandler.java similarity index 69% rename from raven-android/src/main/java/com/getsentry/raven/android/RavenUncaughtExceptionHandler.java rename to sentry-android/src/main/java/io/sentry/android/SentryUncaughtExceptionHandler.java index 7ab51ff9dc1..aee5a0e86c3 100644 --- a/raven-android/src/main/java/com/getsentry/raven/android/RavenUncaughtExceptionHandler.java +++ b/sentry-android/src/main/java/io/sentry/android/SentryUncaughtExceptionHandler.java @@ -1,20 +1,20 @@ -package com.getsentry.raven.android; +package io.sentry.android; import android.util.Log; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.EventBuilder; -import com.getsentry.raven.event.interfaces.ExceptionInterface; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; +import io.sentry.event.interfaces.ExceptionInterface; /** * Sends any uncaught exception to Sentry, then passes the exception on to the pre-existing * uncaught exception handler. */ -class RavenUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { +class SentryUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { /** * Logger tag. */ - private static final String TAG = RavenUncaughtExceptionHandler.class.getName(); + private static final String TAG = SentryUncaughtExceptionHandler.class.getName(); /** * Reference to the pre-existing uncaught exception handler. @@ -22,12 +22,12 @@ class RavenUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { private Thread.UncaughtExceptionHandler defaultExceptionHandler; /** - * Construct the {@link RavenUncaughtExceptionHandler}, storing the pre-existing uncaught exception + * Construct the {@link SentryUncaughtExceptionHandler}, storing the pre-existing uncaught exception * handler. * * @param defaultExceptionHandler pre-existing uncaught exception handler */ - RavenUncaughtExceptionHandler(Thread.UncaughtExceptionHandler defaultExceptionHandler) { + SentryUncaughtExceptionHandler(Thread.UncaughtExceptionHandler defaultExceptionHandler) { this.defaultExceptionHandler = defaultExceptionHandler; } @@ -48,7 +48,7 @@ public void uncaughtException(Thread thread, Throwable thrown) { .withSentryInterface(new ExceptionInterface(thrown)); try { - com.getsentry.raven.Raven.capture(eventBuilder); + io.sentry.Sentry.capture(eventBuilder); } catch (Exception e) { Log.e(TAG, "Error sending excepting to Sentry.", e); } diff --git a/raven-android/src/main/java/com/getsentry/raven/android/Util.java b/sentry-android/src/main/java/io/sentry/android/Util.java similarity index 88% rename from raven-android/src/main/java/com/getsentry/raven/android/Util.java rename to sentry-android/src/main/java/io/sentry/android/Util.java index 67534cbe66b..f0ad61b9593 100644 --- a/raven-android/src/main/java/com/getsentry/raven/android/Util.java +++ b/sentry-android/src/main/java/io/sentry/android/Util.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.android; +package io.sentry.android; import android.content.Context; import android.content.pm.PackageManager; @@ -6,7 +6,7 @@ import android.net.NetworkInfo; /** - * Raven Android utility methods. + * Sentry Android utility methods. */ public final class Util { @@ -45,10 +45,10 @@ public static boolean isConnected(Context ctx) { } /** - * Check whether Raven should attempt to send an event, or just immediately store it. + * Check whether Sentry should attempt to send an event, or just immediately store it. * * @param ctx Android application context - * @return true if Raven should attempt to send an event + * @return true if Sentry should attempt to send an event */ public static boolean shouldAttemptToSend(Context ctx) { if (!checkPermission(ctx, android.Manifest.permission.ACCESS_NETWORK_STATE)) { diff --git a/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java similarity index 97% rename from raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java rename to sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java index ab19202ec5c..78a2f07d370 100644 --- a/raven-android/src/main/java/com/getsentry/raven/android/event/helper/AndroidEventBuilderHelper.java +++ b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.android.event.helper; +package io.sentry.android.event.helper; import android.app.ActivityManager; import android.content.Context; @@ -14,11 +14,11 @@ import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; -import com.getsentry.raven.android.Util; -import com.getsentry.raven.environment.RavenEnvironment; -import com.getsentry.raven.event.EventBuilder; -import com.getsentry.raven.event.helper.EventBuilderHelper; -import com.getsentry.raven.event.interfaces.UserInterface; +import io.sentry.android.Util; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.EventBuilder; +import io.sentry.event.helper.EventBuilderHelper; +import io.sentry.event.interfaces.UserInterface; import java.io.BufferedReader; import java.io.File; @@ -57,7 +57,7 @@ public AndroidEventBuilderHelper(Context ctx) { @Override public void helpBuildingEvent(EventBuilder eventBuilder) { - eventBuilder.withSdkName(RavenEnvironment.SDK_NAME + ":android"); + eventBuilder.withSdkName(SentryEnvironment.SDK_NAME + ":android"); PackageInfo packageInfo = getPackageInfo(ctx); if (packageInfo != null) { eventBuilder.withRelease(packageInfo.versionName); diff --git a/raven-android/src/test/java/com/getsentry/raven/android/AndroidTest.java b/sentry-android/src/test/java/io/sentry/android/AndroidTest.java similarity index 73% rename from raven-android/src/test/java/com/getsentry/raven/android/AndroidTest.java rename to sentry-android/src/test/java/io/sentry/android/AndroidTest.java index 234a21a280a..786e940ac07 100644 --- a/raven-android/src/test/java/com/getsentry/raven/android/AndroidTest.java +++ b/sentry-android/src/test/java/io/sentry/android/AndroidTest.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.android; +package io.sentry.android; -import com.getsentry.raven.BaseIT; +import io.sentry.BaseIT; import org.junit.After; import org.junit.Before; @@ -15,7 +15,7 @@ public void setup() { @After public void tearDown() throws Exception { - Raven.clearStoredRaven(); + Sentry.clearStoredSentry(); } -} \ No newline at end of file +} diff --git a/raven-android/src/test/java/com/getsentry/raven/android/RavenIT.java b/sentry-android/src/test/java/io/sentry/android/SentryIT.java similarity index 82% rename from raven-android/src/test/java/com/getsentry/raven/android/RavenIT.java rename to sentry-android/src/test/java/io/sentry/android/SentryIT.java index e991b3e41c0..8edb35d7b1a 100644 --- a/raven-android/src/test/java/com/getsentry/raven/android/RavenIT.java +++ b/sentry-android/src/test/java/io/sentry/android/SentryIT.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.android; +package io.sentry.android; import org.junit.Assert; import org.junit.Test; @@ -9,14 +9,14 @@ import java.util.concurrent.Callable; @RunWith(RobolectricTestRunner.class) -public class RavenIT extends AndroidTest { +public class SentryIT extends AndroidTest { @Test public void test() throws Exception { verifyProject1PostRequestCount(0); verifyStoredEventCount(0); - RavenITActivity activity = Robolectric.setupActivity(RavenITActivity.class); + SentryITActivity activity = Robolectric.setupActivity(SentryITActivity.class); Assert.assertEquals(activity.getCustomFactoryUsed(), true); activity.sendEvent(); diff --git a/raven-android/src/test/java/com/getsentry/raven/android/RavenITActivity.java b/sentry-android/src/test/java/io/sentry/android/SentryITActivity.java similarity index 64% rename from raven-android/src/test/java/com/getsentry/raven/android/RavenITActivity.java rename to sentry-android/src/test/java/io/sentry/android/SentryITActivity.java index 99c1af96b96..e54d170b646 100644 --- a/raven-android/src/test/java/com/getsentry/raven/android/RavenITActivity.java +++ b/sentry-android/src/test/java/io/sentry/android/SentryITActivity.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.android; +package io.sentry.android; import android.app.Activity; import android.content.Context; @@ -6,17 +6,17 @@ import java.util.concurrent.atomic.AtomicBoolean; -public class RavenITActivity extends Activity { +public class SentryITActivity extends Activity { private AtomicBoolean customFactoryUsed = new AtomicBoolean(false); - class CustomAndroidRavenFactory extends AndroidRavenFactory { + class CustomAndroidSentryFactory extends AndroidSentryFactory { /** - * Construct an AndroidRavenFactory using the specified Android Context. + * Construct an AndroidSentryFactory using the specified Android Context. * * @param ctx Android Context. */ - public CustomAndroidRavenFactory(Context ctx) { + public CustomAndroidSentryFactory(Context ctx) { super(ctx); customFactoryUsed.set(true); } @@ -25,14 +25,14 @@ public CustomAndroidRavenFactory(Context ctx) { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Raven.init( + Sentry.init( this.getApplicationContext(), "http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1", - new CustomAndroidRavenFactory(getApplicationContext())); + new CustomAndroidSentryFactory(getApplicationContext())); } public void sendEvent() { - Raven.capture("sendEvent()"); + Sentry.capture("sendEvent()"); } public boolean getCustomFactoryUsed() { diff --git a/raven-appengine/README.md b/sentry-appengine/README.md similarity index 85% rename from raven-appengine/README.md rename to sentry-appengine/README.md index 1be84b4b932..319df7b3ce6 100644 --- a/raven-appengine/README.md +++ b/sentry-appengine/README.md @@ -1,3 +1,3 @@ -# raven-appengine +# sentry-appengine See the [Sentry documentation](https://docs.sentry.io/clients/java/modules/appengine/) for more information. diff --git a/raven-appengine/pom.xml b/sentry-appengine/pom.xml similarity index 80% rename from raven-appengine/pom.xml rename to sentry-appengine/pom.xml index 0f33935142e..222ac5dd3b8 100644 --- a/raven-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -2,16 +2,16 @@ 4.0.0 - com.getsentry.raven - raven-all - 8.0.3-SNAPSHOT + io.sentry + sentry-all + 1.0.0-SNAPSHOT - raven-appengine + sentry-appengine jar - Raven-Java for Google AppEngine - Raven module allowing to use GoogleApp Engine and its task queueing system. + Sentry-Java for Google AppEngine + Sentry module allowing to use GoogleApp Engine and its task queueing system. 1.9.49 @@ -20,7 +20,7 @@ ${project.groupId} - raven + sentry com.google.appengine diff --git a/raven-appengine/src/main/java/com/getsentry/raven/appengine/AppEngineRavenFactory.java b/sentry-appengine/src/main/java/io/sentry/appengine/AppEngineSentryFactory.java similarity index 62% rename from raven-appengine/src/main/java/com/getsentry/raven/appengine/AppEngineRavenFactory.java rename to sentry-appengine/src/main/java/io/sentry/appengine/AppEngineSentryFactory.java index d1dc86df182..cd548e641ac 100644 --- a/raven-appengine/src/main/java/com/getsentry/raven/appengine/AppEngineRavenFactory.java +++ b/sentry-appengine/src/main/java/io/sentry/appengine/AppEngineSentryFactory.java @@ -1,21 +1,21 @@ -package com.getsentry.raven.appengine; +package io.sentry.appengine; import com.google.appengine.api.utils.SystemProperty; -import com.getsentry.raven.DefaultRavenFactory; -import com.getsentry.raven.Raven; -import com.getsentry.raven.appengine.connection.AppEngineAsyncConnection; -import com.getsentry.raven.appengine.event.helper.AppEngineEventBuilderHelper; -import com.getsentry.raven.connection.Connection; -import com.getsentry.raven.dsn.Dsn; +import io.sentry.DefaultSentryFactory; +import io.sentry.Sentry; +import io.sentry.appengine.connection.AppEngineAsyncConnection; +import io.sentry.appengine.event.helper.AppEngineEventBuilderHelper; +import io.sentry.connection.Connection; +import io.sentry.dsn.Dsn; /** - * RavenFactory dedicated to create async connections within Google App Engine. + * SentryFactory dedicated to create async connections within Google App Engine. */ -public class AppEngineRavenFactory extends DefaultRavenFactory { +public class AppEngineSentryFactory extends DefaultSentryFactory { /** * Option for the queue name used in Google App Engine of threads assigned for the connection. */ - public static final String QUEUE_NAME = "raven.async.gae.queuename"; + public static final String QUEUE_NAME = "sentry.async.gae.queuename"; /** * Option to define the identifier of the async connection across every instance of the application. *

    @@ -27,13 +27,13 @@ public class AppEngineRavenFactory extends DefaultRavenFactory { * * @see AppEngineAsyncConnection */ - public static final String CONNECTION_IDENTIFIER = "raven.async.gae.connectionid"; + public static final String CONNECTION_IDENTIFIER = "sentry.async.gae.connectionid"; @Override - public Raven createRavenInstance(Dsn dsn) { - Raven ravenInstance = super.createRavenInstance(dsn); - ravenInstance.addBuilderHelper(new AppEngineEventBuilderHelper()); - return ravenInstance; + public Sentry createSentryInstance(Dsn dsn) { + Sentry sentryInstance = super.createSentryInstance(dsn); + sentryInstance.addBuilderHelper(new AppEngineEventBuilderHelper()); + return sentryInstance; } /** @@ -50,7 +50,7 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { if (dsn.getOptions().containsKey(CONNECTION_IDENTIFIER)) { connectionIdentifier = dsn.getOptions().get(CONNECTION_IDENTIFIER); } else { - connectionIdentifier = AppEngineRavenFactory.class.getCanonicalName() + dsn + SystemProperty.version.get(); + connectionIdentifier = AppEngineSentryFactory.class.getCanonicalName() + dsn + SystemProperty.version.get(); } AppEngineAsyncConnection asyncConnection = new AppEngineAsyncConnection(connectionIdentifier, connection); diff --git a/raven-appengine/src/main/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnection.java b/sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java similarity index 91% rename from raven-appengine/src/main/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnection.java rename to sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java index f5a6205d8bd..f383602608a 100644 --- a/raven-appengine/src/main/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnection.java +++ b/sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java @@ -1,12 +1,12 @@ -package com.getsentry.raven.appengine.connection; +package io.sentry.appengine.connection; -import com.getsentry.raven.connection.EventSendFailureCallback; +import io.sentry.connection.EventSendFailureCallback; import com.google.appengine.api.taskqueue.DeferredTask; import com.google.appengine.api.taskqueue.Queue; import com.google.appengine.api.taskqueue.QueueFactory; -import com.getsentry.raven.connection.Connection; -import com.getsentry.raven.environment.RavenEnvironment; -import com.getsentry.raven.event.Event; +import io.sentry.connection.Connection; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Event; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -119,9 +119,9 @@ private EventSubmitter(String connectionId, Event event) { @Override public void run() { setDoNotRetry(true); - RavenEnvironment.startManagingThread(); + SentryEnvironment.startManagingThread(); try { - // The current thread is managed by raven + // The current thread is managed by sentry AppEngineAsyncConnection connection = APP_ENGINE_ASYNC_CONNECTIONS.get(connectionId); if (connection == null) { logger.warn("Couldn't find the AppEngineAsyncConnection identified by '{}'. " @@ -132,7 +132,7 @@ public void run() { } catch (Exception e) { logger.error("An exception occurred while sending the event to Sentry.", e); } finally { - RavenEnvironment.stopManagingThread(); + SentryEnvironment.stopManagingThread(); } } } diff --git a/raven-appengine/src/main/java/com/getsentry/raven/appengine/event/helper/AppEngineEventBuilderHelper.java b/sentry-appengine/src/main/java/io/sentry/appengine/event/helper/AppEngineEventBuilderHelper.java similarity index 87% rename from raven-appengine/src/main/java/com/getsentry/raven/appengine/event/helper/AppEngineEventBuilderHelper.java rename to sentry-appengine/src/main/java/io/sentry/appengine/event/helper/AppEngineEventBuilderHelper.java index 9d984c6d412..a157fa37b55 100644 --- a/raven-appengine/src/main/java/com/getsentry/raven/appengine/event/helper/AppEngineEventBuilderHelper.java +++ b/sentry-appengine/src/main/java/io/sentry/appengine/event/helper/AppEngineEventBuilderHelper.java @@ -1,9 +1,9 @@ -package com.getsentry.raven.appengine.event.helper; +package io.sentry.appengine.event.helper; import com.google.appengine.api.utils.SystemProperty; import com.google.apphosting.api.ApiProxy; -import com.getsentry.raven.event.EventBuilder; -import com.getsentry.raven.event.helper.EventBuilderHelper; +import io.sentry.event.EventBuilder; +import io.sentry.event.helper.EventBuilderHelper; /** * EventBuildHelper defining Google App Engine specific properties (hostname). diff --git a/sentry-appengine/src/main/resources/META-INF/services/io.sentry.SentryFactory b/sentry-appengine/src/main/resources/META-INF/services/io.sentry.SentryFactory new file mode 100644 index 00000000000..2384481459b --- /dev/null +++ b/sentry-appengine/src/main/resources/META-INF/services/io.sentry.SentryFactory @@ -0,0 +1 @@ +io.sentry.appengine.AppEngineSentryFactory diff --git a/raven-appengine/src/test/java/com/getsentry/raven/appengine/AppEngineRavenFactoryTest.java b/sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryFactoryTest.java similarity index 61% rename from raven-appengine/src/test/java/com/getsentry/raven/appengine/AppEngineRavenFactoryTest.java rename to sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryFactoryTest.java index b05e76559b9..e149a62eddf 100644 --- a/raven-appengine/src/test/java/com/getsentry/raven/appengine/AppEngineRavenFactoryTest.java +++ b/sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryFactoryTest.java @@ -1,10 +1,10 @@ -package com.getsentry.raven.appengine; +package io.sentry.appengine; import mockit.*; -import com.getsentry.raven.RavenFactory; -import com.getsentry.raven.appengine.connection.AppEngineAsyncConnection; -import com.getsentry.raven.connection.Connection; -import com.getsentry.raven.dsn.Dsn; +import io.sentry.SentryFactory; +import io.sentry.appengine.connection.AppEngineAsyncConnection; +import io.sentry.connection.Connection; +import io.sentry.dsn.Dsn; import org.hamcrest.Matchers; import org.testng.annotations.Test; @@ -15,9 +15,9 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -public class AppEngineRavenFactoryTest { +public class AppEngineSentryFactoryTest { @Tested - private AppEngineRavenFactory appEngineRavenFactory; + private AppEngineSentryFactory appEngineSentryFactory; @Injectable private Connection mockConnection; @Injectable @@ -25,14 +25,14 @@ public class AppEngineRavenFactoryTest { @Test public void checkServiceLoaderProvidesFactory() throws Exception { - ServiceLoader ravenFactories = ServiceLoader.load(RavenFactory.class); + ServiceLoader sentryFactories = ServiceLoader.load(SentryFactory.class); - assertThat(ravenFactories, Matchers.hasItem(instanceOf(AppEngineRavenFactory.class))); + assertThat(sentryFactories, Matchers.hasItem(instanceOf(AppEngineSentryFactory.class))); } @Test - public void asyncConnectionCreatedByAppEngineRavenFactoryIsForAppEngine() throws Exception { - Connection connection = appEngineRavenFactory.createAsyncConnection(mockDsn, mockConnection); + public void asyncConnectionCreatedByAppEngineSentryFactoryIsForAppEngine() throws Exception { + Connection connection = appEngineSentryFactory.createAsyncConnection(mockDsn, mockConnection); assertThat(connection, is(instanceOf(AppEngineAsyncConnection.class))); } @@ -45,10 +45,10 @@ public void asyncConnectionWithoutConnectionIdGeneratesDefaultId() throws Except result = dnsString; }}; - appEngineRavenFactory.createAsyncConnection(mockDsn, mockConnection); + appEngineSentryFactory.createAsyncConnection(mockDsn, mockConnection); new Verifications() {{ - String connectionId = AppEngineRavenFactory.class.getCanonicalName() + dnsString; + String connectionId = AppEngineSentryFactory.class.getCanonicalName() + dnsString; new AppEngineAsyncConnection(connectionId, mockConnection); }}; } @@ -58,10 +58,10 @@ public void asyncConnectionWithConnectionIdUsesId( @Injectable("543afd41-379d-41cb-8c99-8ce73e83a0cc") final String connectionId) throws Exception { new NonStrictExpectations() {{ mockDsn.getOptions(); - result = Collections.singletonMap(AppEngineRavenFactory.CONNECTION_IDENTIFIER, connectionId); + result = Collections.singletonMap(AppEngineSentryFactory.CONNECTION_IDENTIFIER, connectionId); }}; - appEngineRavenFactory.createAsyncConnection(mockDsn, mockConnection); + appEngineSentryFactory.createAsyncConnection(mockDsn, mockConnection); new Verifications() {{ new AppEngineAsyncConnection(connectionId, mockConnection); @@ -71,7 +71,7 @@ public void asyncConnectionWithConnectionIdUsesId( @Test public void asyncConnectionWithoutQueueNameKeepsDefaultQueue( @Mocked final AppEngineAsyncConnection mockAppEngineAsyncConnection) throws Exception { - appEngineRavenFactory.createAsyncConnection(mockDsn, mockConnection); + appEngineSentryFactory.createAsyncConnection(mockDsn, mockConnection); new Verifications() {{ mockAppEngineAsyncConnection.setQueue(anyString); @@ -85,10 +85,10 @@ public void asyncConnectionWithQueueNameSetsQueue( @Injectable("queueName") final String mockQueueName) throws Exception { new NonStrictExpectations() {{ mockDsn.getOptions(); - result = Collections.singletonMap(AppEngineRavenFactory.QUEUE_NAME, mockQueueName); + result = Collections.singletonMap(AppEngineSentryFactory.QUEUE_NAME, mockQueueName); }}; - appEngineRavenFactory.createAsyncConnection(mockDsn, mockConnection); + appEngineSentryFactory.createAsyncConnection(mockDsn, mockConnection); new Verifications() {{ mockAppEngineAsyncConnection.setQueue(mockQueueName); diff --git a/raven-appengine/src/test/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnectionTest.java b/sentry-appengine/src/test/java/io/sentry/appengine/connection/AppEngineAsyncConnectionTest.java similarity index 96% rename from raven-appengine/src/test/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnectionTest.java rename to sentry-appengine/src/test/java/io/sentry/appengine/connection/AppEngineAsyncConnectionTest.java index 18d042b8579..f37f1571414 100644 --- a/raven-appengine/src/test/java/com/getsentry/raven/appengine/connection/AppEngineAsyncConnectionTest.java +++ b/sentry-appengine/src/test/java/io/sentry/appengine/connection/AppEngineAsyncConnectionTest.java @@ -1,10 +1,10 @@ -package com.getsentry.raven.appengine.connection; +package io.sentry.appengine.connection; import com.google.appengine.api.taskqueue.*; import mockit.*; -import com.getsentry.raven.connection.Connection; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.EventBuilder; +import io.sentry.connection.Connection; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; import org.hamcrest.Matchers; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; diff --git a/raven-appengine/src/test/java/com/getsentry/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java b/sentry-appengine/src/test/java/io/sentry/appengine/event/helper/AppEngineEventBuilderHelperTest.java similarity index 96% rename from raven-appengine/src/test/java/com/getsentry/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java rename to sentry-appengine/src/test/java/io/sentry/appengine/event/helper/AppEngineEventBuilderHelperTest.java index 4299f5b557a..04fdeec778e 100644 --- a/raven-appengine/src/test/java/com/getsentry/raven/appengine/event/helper/AppEngineEventBuilderHelperTest.java +++ b/sentry-appengine/src/test/java/io/sentry/appengine/event/helper/AppEngineEventBuilderHelperTest.java @@ -1,9 +1,9 @@ -package com.getsentry.raven.appengine.event.helper; +package io.sentry.appengine.event.helper; import com.google.appengine.api.utils.SystemProperty; import com.google.apphosting.api.ApiProxy; import mockit.*; -import com.getsentry.raven.event.EventBuilder; +import io.sentry.event.EventBuilder; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; diff --git a/raven-appengine/src/test/resources/logback-test.xml b/sentry-appengine/src/test/resources/logback-test.xml similarity index 74% rename from raven-appengine/src/test/resources/logback-test.xml rename to sentry-appengine/src/test/resources/logback-test.xml index bfd7282a648..93163fa0c98 100644 --- a/raven-appengine/src/test/resources/logback-test.xml +++ b/sentry-appengine/src/test/resources/logback-test.xml @@ -1,7 +1,7 @@ - [RAVEN] [%-5level] %logger{36} - %msg%n%nopex + [SENTRY] [%-5level] %logger{36} - %msg%n%nopex diff --git a/raven-log4j/README.md b/sentry-log4j/README.md similarity index 87% rename from raven-log4j/README.md rename to sentry-log4j/README.md index 498cff6884d..25dca01e77c 100644 --- a/raven-log4j/README.md +++ b/sentry-log4j/README.md @@ -1,3 +1,3 @@ -# raven-log4j +# sentry-log4j See the [Sentry documentation](https://docs.sentry.io/clients/java/modules/log4j/) for more information. diff --git a/raven-log4j/pom.xml b/sentry-log4j/pom.xml similarity index 89% rename from raven-log4j/pom.xml rename to sentry-log4j/pom.xml index 420c937ce49..197cd1d1b4b 100644 --- a/raven-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -3,16 +3,16 @@ 4.0.0 - com.getsentry.raven - raven-all - 8.0.3-SNAPSHOT + io.sentry + sentry-all + 1.0.0-SNAPSHOT - raven-log4j + sentry-log4j jar - Raven-Java for log4j - log4j appender allowing to send logs to the Raven-Java client. + Sentry-Java for log4j + log4j appender allowing to send logs to the Sentry-Java client. 1.2.17 @@ -21,7 +21,7 @@ ${project.groupId} - raven + sentry log4j @@ -62,10 +62,10 @@ - + ${project.groupId} - raven + sentry test-jar test diff --git a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java similarity index 82% rename from raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java rename to sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java index 66ae9b2e753..a725c8d62ef 100644 --- a/raven-log4j/src/main/java/com/getsentry/raven/log4j/SentryAppender.java +++ b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java @@ -1,16 +1,16 @@ -package com.getsentry.raven.log4j; - -import com.getsentry.raven.Raven; -import com.getsentry.raven.RavenFactory; -import com.getsentry.raven.config.Lookup; -import com.getsentry.raven.dsn.Dsn; -import com.getsentry.raven.dsn.InvalidDsnException; -import com.getsentry.raven.environment.RavenEnvironment; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.EventBuilder; -import com.getsentry.raven.event.interfaces.ExceptionInterface; -import com.getsentry.raven.event.interfaces.StackTraceInterface; -import com.getsentry.raven.util.Util; +package io.sentry.log4j; + +import io.sentry.Sentry; +import io.sentry.SentryFactory; +import io.sentry.config.Lookup; +import io.sentry.dsn.Dsn; +import io.sentry.dsn.InvalidDsnException; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; +import io.sentry.event.interfaces.ExceptionInterface; +import io.sentry.event.interfaces.StackTraceInterface; +import io.sentry.util.Util; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; import org.apache.log4j.spi.ErrorCode; @@ -35,13 +35,13 @@ public class SentryAppender extends AppenderSkeleton { /** * Name of the {@link Event#extra} property containing the Thread name. */ - public static final String THREAD_NAME = "Raven-Threadname"; + public static final String THREAD_NAME = "Sentry-Threadname"; /** - * Current instance of {@link Raven}. + * Current instance of {@link Sentry}. * - * @see #initRaven() + * @see #initSentry() */ - protected volatile Raven raven; + protected volatile Sentry sentry; /** * DSN property of the appender. *

    @@ -49,11 +49,11 @@ public class SentryAppender extends AppenderSkeleton { */ protected String dsn; /** - * Name of the {@link RavenFactory} being used. + * Name of the {@link SentryFactory} being used. *

    * Might be null in which case the factory should be defined automatically. */ - protected String ravenFactory; + protected String sentryFactory; /** * Identifies the version of the application. *

    @@ -93,17 +93,17 @@ public class SentryAppender extends AppenderSkeleton { * Creates an instance of SentryAppender. */ public SentryAppender() { - this.addFilter(new DropRavenFilter()); + this.addFilter(new DropSentryFilter()); } /** * Creates an instance of SentryAppender. * - * @param raven instance of Raven to use with this appender. + * @param sentry instance of Sentry to use with this appender. */ - public SentryAppender(Raven raven) { + public SentryAppender(Sentry sentry) { this(); - this.raven = raven; + this.sentry = sentry; } /** @@ -117,9 +117,9 @@ private void lazyInit() { synchronized (this) { if (!initialized) { try { - String ravenFactory = Lookup.lookup("ravenFactory"); - if (ravenFactory != null) { - setRavenFactory(ravenFactory); + String sentryFactory = Lookup.lookup("sentryFactory"); + if (sentryFactory != null) { + setSentryFactory(sentryFactory); } String release = Lookup.lookup("release"); @@ -153,8 +153,8 @@ private void lazyInit() { } } - if (raven == null) { - initRaven(); + if (sentry == null) { + initSentry(); } } @@ -162,7 +162,7 @@ private void lazyInit() { * Transforms a {@link Level} into an {@link Event.Level}. * * @param level original level as defined in log4j. - * @return log level used within raven. + * @return log level used within sentry. */ protected static Event.Level formatLevel(Level level) { if (level.isGreaterOrEqual(Level.FATAL)) { @@ -200,41 +200,41 @@ public void activateOptions() { } /** - * Initialises the Raven instance. + * Initialises the Sentry instance. */ - protected synchronized void initRaven() { + protected synchronized void initSentry() { try { if (dsn == null) { dsn = Dsn.dsnLookup(); } - raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + sentry = SentryFactory.sentryInstance(new Dsn(dsn), sentryFactory); } catch (InvalidDsnException e) { - getErrorHandler().error("An exception occurred during the retrieval of the DSN for Raven", e, + getErrorHandler().error("An exception occurred during the retrieval of the DSN for Sentry", e, ErrorCode.ADDRESS_PARSE_FAILURE); } catch (Exception e) { - getErrorHandler().error("An exception occurred during the creation of a Raven instance", e, + getErrorHandler().error("An exception occurred during the creation of a Sentry instance", e, ErrorCode.FILE_OPEN_FAILURE); } } @Override protected void append(LoggingEvent loggingEvent) { - // Do not log the event if the current thread is managed by raven - if (RavenEnvironment.isManagingThread()) { + // Do not log the event if the current thread is managed by sentry + if (SentryEnvironment.isManagingThread()) { return; } - RavenEnvironment.startManagingThread(); + SentryEnvironment.startManagingThread(); try { lazyInit(); Event event = buildEvent(loggingEvent); - raven.sendEvent(event); + sentry.sendEvent(event); } catch (Exception e) { - getErrorHandler().error("An exception occurred while creating a new event in Raven", e, + getErrorHandler().error("An exception occurred while creating a new event in Sentry", e, ErrorCode.WRITE_FAILURE); } finally { - RavenEnvironment.stopManagingThread(); + SentryEnvironment.stopManagingThread(); } } @@ -246,7 +246,7 @@ protected void append(LoggingEvent loggingEvent) { */ protected Event buildEvent(LoggingEvent loggingEvent) { EventBuilder eventBuilder = new EventBuilder() - .withSdkName(RavenEnvironment.SDK_NAME + ":log4j") + .withSdkName(SentryEnvironment.SDK_NAME + ":log4j") .withTimestamp(new Date(loggingEvent.getTimeStamp())) .withMessage(loggingEvent.getRenderedMessage()) .withLogger(loggingEvent.getLoggerName()) @@ -308,12 +308,12 @@ protected Event buildEvent(LoggingEvent loggingEvent) { eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); } - raven.runBuilderHelpers(eventBuilder); + sentry.runBuilderHelpers(eventBuilder); return eventBuilder.build(); } - public void setRavenFactory(String ravenFactory) { - this.ravenFactory = ravenFactory; + public void setSentryFactory(String sentryFactory) { + this.sentryFactory = sentryFactory; } public void setDsn(String dsn) { @@ -352,20 +352,20 @@ public void setExtraTags(String extraTags) { @Override public void close() { - RavenEnvironment.startManagingThread(); + SentryEnvironment.startManagingThread(); try { if (this.closed) { return; } this.closed = true; - if (raven != null) { - raven.closeConnection(); + if (sentry != null) { + sentry.closeConnection(); } } catch (Exception e) { - getErrorHandler().error("An exception occurred while closing the Raven connection", e, + getErrorHandler().error("An exception occurred while closing the Sentry connection", e, ErrorCode.CLOSE_FAILURE); } finally { - RavenEnvironment.stopManagingThread(); + SentryEnvironment.stopManagingThread(); } } @@ -374,11 +374,11 @@ public boolean requiresLayout() { return false; } - private class DropRavenFilter extends Filter { + private class DropSentryFilter extends Filter { @Override public int decide(LoggingEvent event) { String loggerName = event.getLoggerName(); - if (loggerName != null && loggerName.startsWith("com.getsentry.raven")) { + if (loggerName != null && loggerName.startsWith("io.sentry")) { return Filter.DENY; } return Filter.NEUTRAL; diff --git a/raven-log4j/src/test/java/com/getsentry/raven/log4j/MockUpErrorHandler.java b/sentry-log4j/src/test/java/io/sentry/log4j/MockUpErrorHandler.java similarity index 95% rename from raven-log4j/src/test/java/com/getsentry/raven/log4j/MockUpErrorHandler.java rename to sentry-log4j/src/test/java/io/sentry/log4j/MockUpErrorHandler.java index fe8bb77b37c..657979926df 100644 --- a/raven-log4j/src/test/java/com/getsentry/raven/log4j/MockUpErrorHandler.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/MockUpErrorHandler.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.log4j; +package io.sentry.log4j; import mockit.Mock; import mockit.MockUp; diff --git a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderCloseTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java similarity index 81% rename from raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderCloseTest.java rename to sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java index 723b6c6316c..a7915638c75 100644 --- a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderCloseTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java @@ -1,9 +1,9 @@ -package com.getsentry.raven.log4j; +package io.sentry.log4j; import mockit.*; -import com.getsentry.raven.Raven; -import com.getsentry.raven.RavenFactory; -import com.getsentry.raven.dsn.Dsn; +import io.sentry.Sentry; +import io.sentry.SentryFactory; +import io.sentry.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -13,10 +13,10 @@ public class SentryAppenderCloseTest { private MockUpErrorHandler mockUpErrorHandler; @Injectable - private Raven mockRaven = null; + private Sentry mockSentry = null; @SuppressWarnings("unused") - @Mocked("ravenInstance") - private RavenFactory mockRavenFactory = null; + @Mocked("sentryInstance") + private SentryFactory mockSentryFactory = null; @SuppressWarnings("unused") @Mocked("dsnLookup") private Dsn mockDsn = null; @@ -32,35 +32,35 @@ private void assertNoErrorsInErrorHandler() throws Exception { @Test public void testConnectionClosedWhenAppenderClosed() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven); + final SentryAppender sentryAppender = new SentryAppender(mockSentry); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.activateOptions(); sentryAppender.close(); new Verifications() {{ - mockRaven.closeConnection(); + mockSentry.closeConnection(); }}; assertNoErrorsInErrorHandler(); } @Test - public void testClosedIfRavenInstanceNotProvided() throws Exception { + public void testClosedIfSentryInstanceNotProvided() throws Exception { final String dsnUri = "protocol://public:private@host/1"; final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); new Expectations() {{ Dsn.dsnLookup(); result = dsnUri; - RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockRaven; + SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); + result = mockSentry; }}; sentryAppender.activateOptions(); sentryAppender.close(); new Verifications() {{ - mockRaven.closeConnection(); + mockSentry.closeConnection(); }}; assertNoErrorsInErrorHandler(); } @@ -71,7 +71,7 @@ public void testCloseDoNotFailIfInitFailed() throws Exception { final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); new NonStrictExpectations() {{ - RavenFactory.ravenInstance((Dsn) any, anyString); + SentryFactory.sentryInstance((Dsn) any, anyString); result = new UnsupportedOperationException(); }}; sentryAppender.activateOptions(); @@ -94,7 +94,7 @@ public void testCloseDoNotFailIfNoInit() @Test public void testCloseDoNotFailWhenMultipleCalls() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven); + final SentryAppender sentryAppender = new SentryAppender(mockSentry); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.activateOptions(); @@ -102,7 +102,7 @@ public void testCloseDoNotFailWhenMultipleCalls() throws Exception { sentryAppender.close(); new Verifications() {{ - mockRaven.closeConnection(); + mockSentry.closeConnection(); times = 1; }}; assertNoErrorsInErrorHandler(); diff --git a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderDsnTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderDsnTest.java similarity index 72% rename from raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderDsnTest.java rename to sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderDsnTest.java index 740a882c98d..02aaf8f3a5b 100644 --- a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderDsnTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderDsnTest.java @@ -1,12 +1,12 @@ -package com.getsentry.raven.log4j; +package io.sentry.log4j; import mockit.Expectations; import mockit.Injectable; import mockit.Mocked; import mockit.Tested; -import com.getsentry.raven.Raven; -import com.getsentry.raven.RavenFactory; -import com.getsentry.raven.dsn.Dsn; +import io.sentry.Sentry; +import io.sentry.SentryFactory; +import io.sentry.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -18,10 +18,10 @@ public class SentryAppenderDsnTest { private SentryAppender sentryAppender = null; private MockUpErrorHandler mockUpErrorHandler; @Injectable - private Raven mockRaven = null; + private Sentry mockSentry = null; @SuppressWarnings("unused") - @Mocked("ravenInstance") - private RavenFactory mockRavenFactory = null; + @Mocked("sentryInstance") + private SentryFactory mockSentryFactory = null; @SuppressWarnings("unused") @Mocked("dsnLookup") private Dsn mockDsn = null; @@ -43,11 +43,11 @@ public void testDsnDetected() throws Exception { new Expectations() {{ Dsn.dsnLookup(); result = dsnUri; - RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockRaven; + SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); + result = mockSentry; }}; - sentryAppender.initRaven(); + sentryAppender.initSentry(); assertNoErrorsInErrorHandler(); } @@ -57,11 +57,11 @@ public void testDsnProvided() throws Exception { final String dsnUri = "protocol://public:private@host/2"; sentryAppender.setDsn(dsnUri); new Expectations() {{ - RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockRaven; + SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); + result = mockSentry; }}; - sentryAppender.initRaven(); + sentryAppender.initSentry(); assertNoErrorsInErrorHandler(); } diff --git a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderEventBuildingTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java similarity index 89% rename from raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderEventBuildingTest.java rename to sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java index 62d48172382..9bd84d1f970 100644 --- a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java @@ -1,13 +1,13 @@ -package com.getsentry.raven.log4j; +package io.sentry.log4j; -import com.getsentry.raven.environment.RavenEnvironment; +import io.sentry.environment.SentryEnvironment; import mockit.*; -import com.getsentry.raven.Raven; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.EventBuilder; -import com.getsentry.raven.event.interfaces.ExceptionInterface; -import com.getsentry.raven.event.interfaces.SentryException; -import com.getsentry.raven.event.interfaces.StackTraceInterface; +import io.sentry.Sentry; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; +import io.sentry.event.interfaces.ExceptionInterface; +import io.sentry.event.interfaces.SentryException; +import io.sentry.event.interfaces.StackTraceInterface; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.spi.LocationInfo; @@ -31,14 +31,14 @@ public class SentryAppenderEventBuildingTest { private SentryAppender sentryAppender = null; private MockUpErrorHandler mockUpErrorHandler; @Injectable - private Raven mockRaven = null; + private Sentry mockSentry = null; @Injectable private Logger mockLogger = null; private String mockExtraTag = "a8e0ad33-3c11-4899-b8c7-c99926c6d7b8"; @BeforeMethod public void setUp() throws Exception { - sentryAppender = new SentryAppender(mockRaven); + sentryAppender = new SentryAppender(mockSentry); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.setExtraTags(mockExtraTag); @@ -65,13 +65,13 @@ public void testSimpleMessageLogging() throws Exception { new Verifications() {{ Event event; - mockRaven.runBuilderHelpers((EventBuilder) any); - mockRaven.sendEvent(event = withCapture()); + mockSentry.runBuilderHelpers((EventBuilder) any); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getMessage(), is(message)); assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); - assertThat(event.getSdkName(), is(RavenEnvironment.SDK_NAME + ":log4j")); + assertThat(event.getSdkName(), is(SentryEnvironment.SDK_NAME + ":log4j")); }}; assertNoErrorsInErrorHandler(); } @@ -93,7 +93,7 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getLevel(), is(expectedLevel)); }}; assertNoErrorsInErrorHandler(); @@ -107,7 +107,7 @@ public void testExceptionLogging() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); SentryException sentryException = exceptionInterface.getExceptions().getFirst(); @@ -127,7 +127,7 @@ public void testMdcAddedToExtra() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); }}; assertNoErrorsInErrorHandler(); @@ -142,7 +142,7 @@ public void testNdcAddedToExtra() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.LOG4J_NDC, ndcEntries)); }}; assertNoErrorsInErrorHandler(); @@ -171,7 +171,7 @@ public void testSourceUsedAsStacktrace(@Injectable final LocationInfo locationIn new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() .get(StackTraceInterface.STACKTRACE_INTERFACE); assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); @@ -204,7 +204,7 @@ public void testCulpritWithSource(@Injectable final LocationInfo locationInfo) t new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is("a.b(c:42)")); }}; assertNoErrorsInErrorHandler(); @@ -222,7 +222,7 @@ public void testCulpritWithoutSource() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is(loggerName)); }}; assertNoErrorsInErrorHandler(); @@ -238,7 +238,7 @@ public void testExtraTagObtainedFromMdc() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getTags().entrySet(), hasSize(1)); assertThat(event.getTags(), hasEntry(mockExtraTag, "ac84f38a-3889-41ed-9519-201402688abb")); assertThat(event.getExtra(), not(hasKey(mockExtraTag))); @@ -259,7 +259,7 @@ public void testExtraTagObtainedFromMdcConvertedToString(@Injectable final Objec new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getTags().entrySet(), hasSize(1)); assertThat(event.getTags(), hasEntry(mockExtraTag, "3c8981b4-01ad-47ec-8a3a-77a0bbcb42e2")); assertThat(event.getExtra(), not(hasKey(mockExtraTag))); @@ -276,7 +276,7 @@ public void testReleaseAddedToEvent() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getRelease(), is(release)); }}; assertNoErrorsInErrorHandler(); @@ -291,7 +291,7 @@ public void testEnvironmentAddedToEvent() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getEnvironment(), is(environment)); }}; assertNoErrorsInErrorHandler(); diff --git a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderFailuresTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java similarity index 65% rename from raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderFailuresTest.java rename to sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java index aed15a14529..f8269b3cec6 100644 --- a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderFailuresTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java @@ -1,11 +1,11 @@ -package com.getsentry.raven.log4j; +package io.sentry.log4j; import mockit.*; -import com.getsentry.raven.Raven; -import com.getsentry.raven.RavenFactory; -import com.getsentry.raven.dsn.Dsn; -import com.getsentry.raven.environment.RavenEnvironment; -import com.getsentry.raven.event.Event; +import io.sentry.Sentry; +import io.sentry.SentryFactory; +import io.sentry.dsn.Dsn; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Event; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.spi.LoggingEvent; @@ -20,25 +20,25 @@ public class SentryAppenderFailuresTest { private SentryAppender sentryAppender = null; private MockUpErrorHandler mockUpErrorHandler; @Injectable - private Raven mockRaven = null; + private Sentry mockSentry = null; @Injectable private Logger mockLogger = null; @SuppressWarnings("unused") - @Mocked("ravenInstance") - private RavenFactory mockRavenFactory = null; + @Mocked("sentryInstance") + private SentryFactory mockSentryFactory = null; @BeforeMethod public void setUp() throws Exception { - sentryAppender = new SentryAppender(mockRaven); + sentryAppender = new SentryAppender(mockSentry); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.activateOptions(); } @Test - public void testRavenFailureDoesNotPropagate() throws Exception { + public void testSentryFailureDoesNotPropagate() throws Exception { new NonStrictExpectations() {{ - mockRaven.sendEvent((Event) any); + mockSentry.sendEvent((Event) any); result = new UnsupportedOperationException(); }}; @@ -48,33 +48,33 @@ public void testRavenFailureDoesNotPropagate() throws Exception { } @Test - public void testRavenFactoryFailureDoesNotPropagate() throws Exception { + public void testSentryFactoryFailureDoesNotPropagate() throws Exception { new NonStrictExpectations() {{ - RavenFactory.ravenInstance((Dsn) any, anyString); + SentryFactory.sentryInstance((Dsn) any, anyString); result = new UnsupportedOperationException(); }}; SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.setDsn("protocol://public:private@host/1"); - sentryAppender.initRaven(); + sentryAppender.initSentry(); assertThat(mockUpErrorHandler.getErrorCount(), is(1)); } @Test - public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { - RavenEnvironment.startManagingThread(); + public void testAppendFailIfCurrentThreadSpawnedBySentry() throws Exception { + SentryEnvironment.startManagingThread(); try { sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); new Verifications() {{ - mockRaven.sendEvent((Event) any); + mockSentry.sendEvent((Event) any); times = 0; }}; assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } finally { - RavenEnvironment.stopManagingThread(); + SentryEnvironment.stopManagingThread(); } } } diff --git a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderIT.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderIT.java similarity index 78% rename from raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderIT.java rename to sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderIT.java index 2b07b5cfe5a..432d904a98e 100644 --- a/raven-log4j/src/test/java/com/getsentry/raven/log4j/SentryAppenderIT.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderIT.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.log4j; +package io.sentry.log4j; -import com.getsentry.raven.BaseIT; +import io.sentry.BaseIT; import org.apache.log4j.Logger; import org.junit.Before; import org.junit.Test; @@ -9,11 +9,11 @@ public class SentryAppenderIT extends BaseIT { /* - We filter out loggers that start with `com.getsentry.raven`, so we deliberately + We filter out loggers that start with `io.sentry`, so we deliberately use a custom logger name here. */ private static final Logger logger = Logger.getLogger("log4j.SentryAppenderIT"); - private static final Logger ravenLogger = Logger.getLogger(SentryAppenderIT.class); + private static final Logger sentryLogger = Logger.getLogger(SentryAppenderIT.class); @Before public void setup() { @@ -44,11 +44,11 @@ public void testChainedExceptions() throws Exception { } @Test - public void testNoRavenLogging() throws Exception { + public void testNoSentryLogging() throws Exception { verifyProject1PostRequestCount(0); verifyStoredEventCount(0); - ravenLogger.error("This is a test"); + sentryLogger.error("This is a test"); verifyProject1PostRequestCount(0); verifyStoredEventCount(0); diff --git a/raven-log4j/src/test/resources/log4j-integration.properties b/sentry-log4j/src/test/resources/log4j-integration.properties similarity index 50% rename from raven-log4j/src/test/resources/log4j-integration.properties rename to sentry-log4j/src/test/resources/log4j-integration.properties index d7e87d685c4..58ef7437c5d 100644 --- a/raven-log4j/src/test/resources/log4j-integration.properties +++ b/sentry-log4j/src/test/resources/log4j-integration.properties @@ -1,10 +1,10 @@ log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender log4j.appender.ConsoleAppender.layout=org.apache.log4j.EnhancedPatternLayout -log4j.appender.ConsoleAppender.layout.ConversionPattern=[RAVEN] [%-5p] %c - %m%n%throwable{none} +log4j.appender.ConsoleAppender.layout.ConversionPattern=[SENTRY] [%-5p] %c - %m%n%throwable{none} -log4j.appender.SentryAppender=com.getsentry.raven.log4j.SentryAppender -log4j.appender.SentryAppender.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false -# Set Raven to WARNING level, as we recommend this as the lowest users go in their own configuration +log4j.appender.SentryAppender=io.sentry.log4j.SentryAppender +log4j.appender.SentryAppender.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?async=false +# Set Sentry to WARNING level, as we recommend this as the lowest users go in their own configuration log4j.appender.SentryAppender.threshold=WARN log4j.rootLogger=INFO, ConsoleAppender diff --git a/raven-log4j/src/test/resources/log4j-test.properties b/sentry-log4j/src/test/resources/log4j-test.properties similarity index 64% rename from raven-log4j/src/test/resources/log4j-test.properties rename to sentry-log4j/src/test/resources/log4j-test.properties index 2bd86f9037c..5e3343e895f 100644 --- a/raven-log4j/src/test/resources/log4j-test.properties +++ b/sentry-log4j/src/test/resources/log4j-test.properties @@ -1,4 +1,4 @@ log4j.rootLogger=OFF, ConsoleAppender log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender log4j.appender.ConsoleAppender.layout=org.apache.log4j.EnhancedPatternLayout -log4j.appender.ConsoleAppender.layout.ConversionPattern=[RAVEN] [%-5p] %c - %m%n%throwable{none} +log4j.appender.ConsoleAppender.layout.ConversionPattern=[SENTRY] [%-5p] %c - %m%n%throwable{none} diff --git a/raven-log4j2/README.md b/sentry-log4j2/README.md similarity index 84% rename from raven-log4j2/README.md rename to sentry-log4j2/README.md index 879e7e67a18..1eced7fde3b 100644 --- a/raven-log4j2/README.md +++ b/sentry-log4j2/README.md @@ -1,3 +1,3 @@ -# raven-log4j 2.x +# sentry-log4j 2.x See the [Sentry documentation](https://docs.sentry.io/clients/java/modules/log4j2/) for more information. diff --git a/raven-log4j2/pom.xml b/sentry-log4j2/pom.xml similarity index 89% rename from raven-log4j2/pom.xml rename to sentry-log4j2/pom.xml index 428bbc12f05..443febce722 100644 --- a/raven-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -3,16 +3,16 @@ 4.0.0 - com.getsentry.raven - raven-all - 8.0.3-SNAPSHOT + io.sentry + sentry-all + 1.0.0-SNAPSHOT - raven-log4j2 + sentry-log4j2 jar - Raven-Java for log4j2 - log4j2 appender allowing to send logs to the Raven-Java client. + Sentry-Java for log4j2 + log4j2 appender allowing to send logs to the Sentry-Java client. 2.8.1 @@ -21,7 +21,7 @@ ${project.groupId} - raven + sentry org.apache.logging.log4j @@ -67,10 +67,10 @@ - + ${project.groupId} - raven + sentry test-jar test diff --git a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java similarity index 83% rename from raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java rename to sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java index 2eec0351f0b..27e3d0e4da2 100644 --- a/raven-log4j2/src/main/java/com/getsentry/raven/log4j2/SentryAppender.java +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -1,17 +1,17 @@ -package com.getsentry.raven.log4j2; - -import com.getsentry.raven.Raven; -import com.getsentry.raven.RavenFactory; -import com.getsentry.raven.config.Lookup; -import com.getsentry.raven.dsn.Dsn; -import com.getsentry.raven.dsn.InvalidDsnException; -import com.getsentry.raven.environment.RavenEnvironment; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.EventBuilder; -import com.getsentry.raven.event.interfaces.ExceptionInterface; -import com.getsentry.raven.event.interfaces.MessageInterface; -import com.getsentry.raven.event.interfaces.StackTraceInterface; -import com.getsentry.raven.util.Util; +package io.sentry.log4j2; + +import io.sentry.Sentry; +import io.sentry.SentryFactory; +import io.sentry.config.Lookup; +import io.sentry.dsn.Dsn; +import io.sentry.dsn.InvalidDsnException; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; +import io.sentry.event.interfaces.ExceptionInterface; +import io.sentry.event.interfaces.MessageInterface; +import io.sentry.event.interfaces.StackTraceInterface; +import io.sentry.util.Util; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.Filter; @@ -35,12 +35,12 @@ /** * Appender for log4j2 in charge of sending the logged events to a Sentry server. */ -@Plugin(name = "Raven", category = "Core", elementType = "appender", printObject = true) +@Plugin(name = "Sentry", category = "Core", elementType = "appender", printObject = true) public class SentryAppender extends AbstractAppender { /** * Default name for the appender. */ - public static final String APPENDER_NAME = "raven"; + public static final String APPENDER_NAME = "sentry"; /** * Name of the {@link Event#extra} property containing NDC details. */ @@ -52,13 +52,13 @@ public class SentryAppender extends AbstractAppender { /** * Name of the {@link Event#extra} property containing the Thread name. */ - public static final String THREAD_NAME = "Raven-Threadname"; + public static final String THREAD_NAME = "Sentry-Threadname"; /** - * Current instance of {@link Raven}. + * Current instance of {@link Sentry}. * - * @see #initRaven() + * @see #initSentry() */ - protected volatile Raven raven; + protected volatile Sentry sentry; /** * DSN property of the appender. *

    @@ -66,11 +66,11 @@ public class SentryAppender extends AbstractAppender { */ protected String dsn; /** - * Name of the {@link RavenFactory} being used. + * Name of the {@link SentryFactory} being used. *

    * Might be null in which case the factory should be defined automatically. */ - protected String ravenFactory; + protected String sentryFactory; /** * Identifies the version of the application. *

    @@ -117,11 +117,11 @@ public SentryAppender() { /** * Creates an instance of SentryAppender. * - * @param raven instance of Raven to use with this appender. + * @param sentry instance of Sentry to use with this appender. */ - public SentryAppender(Raven raven) { + public SentryAppender(Sentry sentry) { this(); - this.raven = raven; + this.sentry = sentry; } /** @@ -132,7 +132,7 @@ public SentryAppender(Raven raven) { */ protected SentryAppender(String name, Filter filter) { super(name, filter, null, true); - this.addFilter(new DropRavenFilter()); + this.addFilter(new DropSentryFilter()); } /** @@ -140,7 +140,7 @@ protected SentryAppender(String name, Filter filter) { * * @param name The name of the Appender. * @param dsn Data Source Name to access the Sentry server. - * @param ravenFactory Name of the factory to use to build the {@link Raven} instance. + * @param sentryFactory Name of the factory to use to build the {@link Sentry} instance. * @param release Release to be sent to Sentry. * @param environment Environment to be sent to Sentry. * @param serverName serverName to be sent to Sentry. @@ -153,7 +153,7 @@ protected SentryAppender(String name, Filter filter) { @SuppressWarnings("checkstyle:parameternumber") public static SentryAppender createAppender(@PluginAttribute("name") final String name, @PluginAttribute("dsn") final String dsn, - @PluginAttribute("ravenFactory") final String ravenFactory, + @PluginAttribute("sentryFactory") final String sentryFactory, @PluginAttribute("release") final String release, @PluginAttribute("environment") final String environment, @PluginAttribute("serverName") final String serverName, @@ -183,7 +183,7 @@ public static SentryAppender createAppender(@PluginAttribute("name") final Strin if (extraTags != null) { sentryAppender.setExtraTags(extraTags); } - sentryAppender.setRavenFactory(ravenFactory); + sentryAppender.setSentryFactory(sentryFactory); return sentryAppender; } @@ -198,9 +198,9 @@ private void lazyInit() { synchronized (this) { if (!initialized) { try { - String ravenFactory = Lookup.lookup("ravenFactory"); - if (ravenFactory != null) { - setRavenFactory(ravenFactory); + String sentryFactory = Lookup.lookup("sentryFactory"); + if (sentryFactory != null) { + setSentryFactory(sentryFactory); } String release = Lookup.lookup("release"); @@ -234,8 +234,8 @@ private void lazyInit() { } } - if (raven == null) { - initRaven(); + if (sentry == null) { + initSentry(); } } @@ -243,7 +243,7 @@ private void lazyInit() { * Transforms a {@link Level} into an {@link Event.Level}. * * @param level original level as defined in log4j2. - * @return log level used within raven. + * @return log level used within sentry. */ protected static Event.Level formatLevel(Level level) { if (level.isMoreSpecificThan(Level.FATAL)) { @@ -278,44 +278,44 @@ protected static List formatMessageParameters(Object[] parameters) { /** * {@inheritDoc} *

    - * The raven instance is set in this method instead of {@link #start()} in order to avoid substitute loggers - * being generated during the instantiation of {@link Raven}.
    + * The sentry instance is set in this method instead of {@link #start()} in order to avoid substitute loggers + * being generated during the instantiation of {@link Sentry}.
    * * @param logEvent The LogEvent. */ @Override public void append(LogEvent logEvent) { - // Do not log the event if the current thread is managed by raven - if (RavenEnvironment.isManagingThread()) { + // Do not log the event if the current thread is managed by sentry + if (SentryEnvironment.isManagingThread()) { return; } - RavenEnvironment.startManagingThread(); + SentryEnvironment.startManagingThread(); try { lazyInit(); Event event = buildEvent(logEvent); - raven.sendEvent(event); + sentry.sendEvent(event); } catch (Exception e) { - error("An exception occurred while creating a new event in Raven", logEvent, e); + error("An exception occurred while creating a new event in Sentry", logEvent, e); } finally { - RavenEnvironment.stopManagingThread(); + SentryEnvironment.stopManagingThread(); } } /** - * Initialises the Raven instance. + * Initialises the Sentry instance. */ - protected synchronized void initRaven() { + protected synchronized void initSentry() { try { if (dsn == null) { dsn = Dsn.dsnLookup(); } - raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + sentry = SentryFactory.sentryInstance(new Dsn(dsn), sentryFactory); } catch (InvalidDsnException e) { - error("An exception occurred during the retrieval of the DSN for Raven", e); + error("An exception occurred during the retrieval of the DSN for Sentry", e); } catch (Exception e) { - error("An exception occurred during the creation of a Raven instance", e); + error("An exception occurred during the creation of a Sentry instance", e); } } @@ -328,7 +328,7 @@ protected synchronized void initRaven() { protected Event buildEvent(LogEvent event) { Message eventMessage = event.getMessage(); EventBuilder eventBuilder = new EventBuilder() - .withSdkName(RavenEnvironment.SDK_NAME + ":log4j2") + .withSdkName(SentryEnvironment.SDK_NAME + ":log4j2") .withTimestamp(new Date(event.getTimeMillis())) .withMessage(eventMessage.getFormattedMessage()) .withLogger(event.getLoggerName()) @@ -392,7 +392,7 @@ protected Event buildEvent(LogEvent event) { eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); } - raven.runBuilderHelpers(eventBuilder); + sentry.runBuilderHelpers(eventBuilder); return eventBuilder.build(); } @@ -400,8 +400,8 @@ public void setDsn(String dsn) { this.dsn = dsn; } - public void setRavenFactory(String ravenFactory) { - this.ravenFactory = ravenFactory; + public void setSentryFactory(String sentryFactory) { + this.sentryFactory = sentryFactory; } public void setRelease(String release) { @@ -437,23 +437,23 @@ public void setExtraTags(String extraTags) { @Override public void stop() { - RavenEnvironment.startManagingThread(); + SentryEnvironment.startManagingThread(); try { if (!isStarted()) { return; } super.stop(); - if (raven != null) { - raven.closeConnection(); + if (sentry != null) { + sentry.closeConnection(); } } catch (Exception e) { - error("An exception occurred while closing the Raven connection", e); + error("An exception occurred while closing the Sentry connection", e); } finally { - RavenEnvironment.stopManagingThread(); + SentryEnvironment.stopManagingThread(); } } - private class DropRavenFilter extends AbstractFilter { + private class DropSentryFilter extends AbstractFilter { @Override public Result filter(Logger logger, Level level, Marker marker, String msg, Object... params) { @@ -476,7 +476,7 @@ public Result filter(LogEvent event) { } private Result filter(String loggerName) { - if (loggerName != null && loggerName.startsWith("com.getsentry.raven")) { + if (loggerName != null && loggerName.startsWith("io.sentry")) { return Result.DENY; } return Result.NEUTRAL; diff --git a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/MockUpErrorHandler.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/MockUpErrorHandler.java similarity index 95% rename from raven-log4j2/src/test/java/com/getsentry/raven/log4j2/MockUpErrorHandler.java rename to sentry-log4j2/src/test/java/io/sentry/log4j2/MockUpErrorHandler.java index 0f875c0d5f0..4a7c4ccd5d6 100644 --- a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/MockUpErrorHandler.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/MockUpErrorHandler.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.log4j2; +package io.sentry.log4j2; import mockit.Mock; import mockit.MockUp; diff --git a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderCloseTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java similarity index 82% rename from raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderCloseTest.java rename to sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java index 7243c872a85..a6b0dd760ac 100644 --- a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderCloseTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java @@ -1,9 +1,9 @@ -package com.getsentry.raven.log4j2; +package io.sentry.log4j2; import mockit.*; -import com.getsentry.raven.Raven; -import com.getsentry.raven.RavenFactory; -import com.getsentry.raven.dsn.Dsn; +import io.sentry.Sentry; +import io.sentry.SentryFactory; +import io.sentry.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -13,10 +13,10 @@ public class SentryAppenderCloseTest { private MockUpErrorHandler mockUpErrorHandler; @Injectable - private Raven mockRaven = null; + private Sentry mockSentry = null; @SuppressWarnings("unused") - @Mocked("ravenInstance") - private RavenFactory mockRavenFactory = null; + @Mocked("sentryInstance") + private SentryFactory mockSentryFactory = null; @SuppressWarnings("unused") @Mocked("dsnLookup") private Dsn mockDsn = null; @@ -32,28 +32,28 @@ private void assertNoErrorsInErrorHandler() throws Exception { @Test public void testConnectionClosedWhenAppenderStopped() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven); + final SentryAppender sentryAppender = new SentryAppender(mockSentry); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.start(); sentryAppender.stop(); new Verifications() {{ - mockRaven.closeConnection(); + mockSentry.closeConnection(); }}; assertNoErrorsInErrorHandler(); } @Test - public void testStopIfRavenInstanceNotProvided() throws Exception { + public void testStopIfSentryInstanceNotProvided() throws Exception { final String dsnUri = "protocol://public:private@host/1"; final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); new Expectations() {{ Dsn.dsnLookup(); result = dsnUri; - RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockRaven; + SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); + result = mockSentry; }}; sentryAppender.start(); sentryAppender.append(null); @@ -61,7 +61,7 @@ public void testStopIfRavenInstanceNotProvided() throws Exception { sentryAppender.stop(); new Verifications() {{ - mockRaven.closeConnection(); + mockSentry.closeConnection(); }}; //One error, because of the null event. assertThat(mockUpErrorHandler.getErrorCount(), is(1)); @@ -73,7 +73,7 @@ public void testStopDoNotFailIfInitFailed() throws Exception { final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); new NonStrictExpectations() {{ - RavenFactory.ravenInstance((Dsn) any, anyString); + SentryFactory.sentryInstance((Dsn) any, anyString); result = new UnsupportedOperationException(); }}; sentryAppender.start(); @@ -98,7 +98,7 @@ public void testStopDoNotFailIfNoInit() @Test public void testStopDoNotFailWhenMultipleCalls() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven); + final SentryAppender sentryAppender = new SentryAppender(mockSentry); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.start(); @@ -106,7 +106,7 @@ public void testStopDoNotFailWhenMultipleCalls() throws Exception { sentryAppender.stop(); new Verifications() {{ - mockRaven.closeConnection(); + mockSentry.closeConnection(); times = 1; }}; assertNoErrorsInErrorHandler(); diff --git a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderDsnTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderDsnTest.java similarity index 72% rename from raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderDsnTest.java rename to sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderDsnTest.java index 9a91186c208..00ebdc45632 100644 --- a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderDsnTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderDsnTest.java @@ -1,12 +1,12 @@ -package com.getsentry.raven.log4j2; +package io.sentry.log4j2; import mockit.Expectations; import mockit.Injectable; import mockit.Mocked; import mockit.Tested; -import com.getsentry.raven.Raven; -import com.getsentry.raven.RavenFactory; -import com.getsentry.raven.dsn.Dsn; +import io.sentry.Sentry; +import io.sentry.SentryFactory; +import io.sentry.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -18,10 +18,10 @@ public class SentryAppenderDsnTest { private SentryAppender sentryAppender = null; private MockUpErrorHandler mockUpErrorHandler = new MockUpErrorHandler(); @Injectable - private Raven mockRaven = null; + private Sentry mockSentry = null; @SuppressWarnings("unused") - @Mocked("ravenInstance") - private RavenFactory mockRavenFactory = null; + @Mocked("sentryInstance") + private SentryFactory mockSentryFactory = null; @SuppressWarnings("unused") @Mocked("dsnLookup") private Dsn mockDsn = null; @@ -42,11 +42,11 @@ public void testDsnDetected() throws Exception { new Expectations() {{ Dsn.dsnLookup(); result = dsnUri; - RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockRaven; + SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); + result = mockSentry; }}; - sentryAppender.initRaven(); + sentryAppender.initSentry(); assertNoErrorsInErrorHandler(); } @@ -56,11 +56,11 @@ public void testDsnProvided() throws Exception { final String dsnUri = "protocol://public:private@host/2"; sentryAppender.setDsn(dsnUri); new Expectations() {{ - RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockRaven; + SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); + result = mockSentry; }}; - sentryAppender.initRaven(); + sentryAppender.initSentry(); assertNoErrorsInErrorHandler(); } diff --git a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderEventBuildingTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java similarity index 88% rename from raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderEventBuildingTest.java rename to sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java index d621cf504d0..43a47293c9e 100644 --- a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java @@ -1,16 +1,16 @@ -package com.getsentry.raven.log4j2; +package io.sentry.log4j2; -import com.getsentry.raven.environment.RavenEnvironment; +import io.sentry.environment.SentryEnvironment; import mockit.Injectable; import mockit.Tested; import mockit.Verifications; -import com.getsentry.raven.Raven; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.EventBuilder; -import com.getsentry.raven.event.interfaces.ExceptionInterface; -import com.getsentry.raven.event.interfaces.MessageInterface; -import com.getsentry.raven.event.interfaces.SentryException; -import com.getsentry.raven.event.interfaces.StackTraceInterface; +import io.sentry.Sentry; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; +import io.sentry.event.interfaces.ExceptionInterface; +import io.sentry.event.interfaces.MessageInterface; +import io.sentry.event.interfaces.SentryException; +import io.sentry.event.interfaces.StackTraceInterface; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.ThreadContext; @@ -33,12 +33,12 @@ public class SentryAppenderEventBuildingTest { private SentryAppender sentryAppender = null; private MockUpErrorHandler mockUpErrorHandler; @Injectable - private Raven mockRaven = null; + private Sentry mockSentry = null; private String mockExtraTag = "d421627f-7a25-4d43-8210-140dfe73ff10"; @BeforeMethod public void setUp() throws Exception { - sentryAppender = new SentryAppender(mockRaven); + sentryAppender = new SentryAppender(mockSentry); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.setExtraTags(mockExtraTag); @@ -60,13 +60,13 @@ public void testSimpleMessageLogging() throws Exception { new Verifications() {{ Event event; - mockRaven.runBuilderHelpers((EventBuilder) any); - mockRaven.sendEvent(event = withCapture()); + mockSentry.runBuilderHelpers((EventBuilder) any); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getMessage(), is(message)); assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); - assertThat(event.getSdkName(), is(RavenEnvironment.SDK_NAME + ":log4j2")); + assertThat(event.getSdkName(), is(SentryEnvironment.SDK_NAME + ":log4j2")); }}; assertNoErrorsInErrorHandler(); } @@ -88,7 +88,7 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getLevel(), is(expectedLevel)); }}; assertNoErrorsInErrorHandler(); @@ -102,7 +102,7 @@ public void testExceptionLogging() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); SentryException sentryException = exceptionInterface.getExceptions().getFirst(); @@ -122,7 +122,7 @@ public void testLogParametrisedMessage() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); MessageInterface messageInterface = (MessageInterface) event.getSentryInterfaces() .get(MessageInterface.MESSAGE_INTERFACE); @@ -143,7 +143,7 @@ public void testMarkerAddedToTag() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getTags(), Matchers.hasEntry(SentryAppender.LOG4J_MARKER, markerName)); }}; assertNoErrorsInErrorHandler(); @@ -159,7 +159,7 @@ public void testMdcAddedToExtra() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); }}; assertNoErrorsInErrorHandler(); @@ -178,7 +178,7 @@ public void testNdcAddedToExtra() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat((List) event.getExtra().get(SentryAppender.LOG4J_NDC), equalTo(contextStack.asList())); }}; assertNoErrorsInErrorHandler(); @@ -194,7 +194,7 @@ public void testSourceUsedAsStacktrace() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() .get(StackTraceInterface.STACKTRACE_INTERFACE); assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); @@ -212,7 +212,7 @@ public void testCulpritWithSource() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is("a.b(c:42)")); }}; assertNoErrorsInErrorHandler(); @@ -226,7 +226,7 @@ public void testCulpritWithoutSource() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is(loggerName)); }}; assertNoErrorsInErrorHandler(); @@ -242,7 +242,7 @@ public void testExtraTagObtainedFromMdc() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getTags().entrySet(), hasSize(1)); assertThat(event.getTags(), hasEntry(mockExtraTag, "565940d2-f4a4-42f6-9496-42e3c7c85c43")); assertThat(event.getExtra(), not(hasKey(mockExtraTag))); @@ -260,7 +260,7 @@ public void testReleaseAddedToEvent() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getRelease(), is(release)); }}; assertNoErrorsInErrorHandler(); @@ -275,7 +275,7 @@ public void testEnvironmentAddedToEvent() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getEnvironment(), is(environment)); }}; assertNoErrorsInErrorHandler(); diff --git a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderFailuresTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java similarity index 66% rename from raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderFailuresTest.java rename to sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java index 3ef41d7a5d5..3d36b31e3ff 100644 --- a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderFailuresTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java @@ -1,14 +1,14 @@ -package com.getsentry.raven.log4j2; +package io.sentry.log4j2; import mockit.Injectable; import mockit.Mocked; import mockit.NonStrictExpectations; import mockit.Verifications; -import com.getsentry.raven.Raven; -import com.getsentry.raven.RavenFactory; -import com.getsentry.raven.dsn.Dsn; -import com.getsentry.raven.environment.RavenEnvironment; -import com.getsentry.raven.event.Event; +import io.sentry.Sentry; +import io.sentry.SentryFactory; +import io.sentry.dsn.Dsn; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Event; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.SimpleMessage; @@ -22,22 +22,22 @@ public class SentryAppenderFailuresTest { private SentryAppender sentryAppender; private MockUpErrorHandler mockUpErrorHandler; @Injectable - private Raven mockRaven = null; + private Sentry mockSentry = null; @SuppressWarnings("unused") - @Mocked("ravenInstance") - private RavenFactory mockRavenFactory = null; + @Mocked("sentryInstance") + private SentryFactory mockSentryFactory = null; @BeforeMethod public void setUp() throws Exception { - sentryAppender = new SentryAppender(mockRaven); + sentryAppender = new SentryAppender(mockSentry); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); } @Test - public void testRavenFailureDoesNotPropagate() throws Exception { + public void testSentryFailureDoesNotPropagate() throws Exception { new NonStrictExpectations() {{ - mockRaven.sendEvent((Event) any); + mockSentry.sendEvent((Event) any); result = new UnsupportedOperationException(); }}; @@ -47,33 +47,33 @@ public void testRavenFailureDoesNotPropagate() throws Exception { } @Test - public void testRavenFactoryFailureDoesNotPropagate() throws Exception { + public void testSentryFactoryFailureDoesNotPropagate() throws Exception { new NonStrictExpectations() {{ - RavenFactory.ravenInstance((Dsn) any, anyString); + SentryFactory.sentryInstance((Dsn) any, anyString); result = new UnsupportedOperationException(); }}; SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.setDsn("protocol://public:private@host/1"); - sentryAppender.initRaven(); + sentryAppender.initSentry(); assertThat(mockUpErrorHandler.getErrorCount(), is(1)); } @Test - public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { - RavenEnvironment.startManagingThread(); + public void testAppendFailIfCurrentThreadSpawnedBySentry() throws Exception { + SentryEnvironment.startManagingThread(); try { sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); new Verifications() {{ - mockRaven.sendEvent((Event) any); + mockSentry.sendEvent((Event) any); times = 0; }}; assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } finally { - RavenEnvironment.stopManagingThread(); + SentryEnvironment.stopManagingThread(); } } } diff --git a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderIT.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderIT.java similarity index 78% rename from raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderIT.java rename to sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderIT.java index 290dc80e6ca..fe717b1c1e2 100644 --- a/raven-log4j2/src/test/java/com/getsentry/raven/log4j2/SentryAppenderIT.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderIT.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.log4j2; +package io.sentry.log4j2; -import com.getsentry.raven.BaseIT; +import io.sentry.BaseIT; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.Before; @@ -10,11 +10,11 @@ public class SentryAppenderIT extends BaseIT { /* - We filter out loggers that start with `com.getsentry.raven`, so we deliberately + We filter out loggers that start with `io.sentry`, so we deliberately use a custom logger name here. */ private static final Logger logger = LogManager.getLogger("log4j2.SentryAppenderIT"); - private static final Logger ravenLogger = LogManager.getLogger(SentryAppenderIT.class); + private static final Logger sentryLogger = LogManager.getLogger(SentryAppenderIT.class); @Before public void setup() { @@ -45,11 +45,11 @@ public void testChainedExceptions() throws Exception { } @Test - public void testNoRavenLogging() throws Exception { + public void testNoSentryLogging() throws Exception { verifyProject1PostRequestCount(0); verifyStoredEventCount(0); - ravenLogger.error("This is a test"); + sentryLogger.error("This is a test"); verifyProject1PostRequestCount(0); verifyStoredEventCount(0); diff --git a/raven-log4j2/src/test/resources/log4j2-integration.xml b/sentry-log4j2/src/test/resources/log4j2-integration.xml similarity index 63% rename from raven-log4j2/src/test/resources/log4j2-integration.xml rename to sentry-log4j2/src/test/resources/log4j2-integration.xml index 79e8dda443f..f5fa96839d1 100644 --- a/raven-log4j2/src/test/resources/log4j2-integration.xml +++ b/sentry-log4j2/src/test/resources/log4j2-integration.xml @@ -1,12 +1,12 @@ - + - + - - http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false - + + http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?async=false + diff --git a/raven-log4j2/src/test/resources/log4j2-test.xml b/sentry-log4j2/src/test/resources/log4j2-test.xml similarity index 62% rename from raven-log4j2/src/test/resources/log4j2-test.xml rename to sentry-log4j2/src/test/resources/log4j2-test.xml index b4aca8db1df..85879247581 100644 --- a/raven-log4j2/src/test/resources/log4j2-test.xml +++ b/sentry-log4j2/src/test/resources/log4j2-test.xml @@ -1,8 +1,8 @@ - + - + diff --git a/raven-logback/README.md b/sentry-logback/README.md similarity index 86% rename from raven-logback/README.md rename to sentry-logback/README.md index 9a203a9d6e1..4c05c6c1279 100644 --- a/raven-logback/README.md +++ b/sentry-logback/README.md @@ -1,3 +1,3 @@ -# raven-logback +# sentry-logback See the [Sentry documentation](https://docs.sentry.io/clients/java/modules/logback/) for more information. diff --git a/raven-logback/pom.xml b/sentry-logback/pom.xml similarity index 88% rename from raven-logback/pom.xml rename to sentry-logback/pom.xml index 5653ac180ef..03b503399d6 100644 --- a/raven-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -3,16 +3,16 @@ 4.0.0 - com.getsentry.raven - raven-all - 8.0.3-SNAPSHOT + io.sentry + sentry-all + 1.0.0-SNAPSHOT - raven-logback + sentry-logback jar - Raven-Java for Logback - Logback appender allowing to send logs to the Raven-Java client. + Sentry-Java for Logback + Logback appender allowing to send logs to the Sentry-Java client. 1.2.1 @@ -21,7 +21,7 @@ ${project.groupId} - raven + sentry org.slf4j @@ -71,10 +71,10 @@ - + ${project.groupId} - raven + sentry test-jar test diff --git a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java similarity index 85% rename from raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java rename to sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index e8d102d7c75..b90a4b3e000 100644 --- a/raven-logback/src/main/java/com/getsentry/raven/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.logback; +package io.sentry.logback; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; @@ -7,19 +7,19 @@ import ch.qos.logback.core.AppenderBase; import ch.qos.logback.core.filter.Filter; import ch.qos.logback.core.spi.FilterReply; -import com.getsentry.raven.Raven; -import com.getsentry.raven.RavenFactory; -import com.getsentry.raven.config.Lookup; -import com.getsentry.raven.dsn.Dsn; -import com.getsentry.raven.dsn.InvalidDsnException; -import com.getsentry.raven.environment.RavenEnvironment; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.EventBuilder; -import com.getsentry.raven.event.interfaces.ExceptionInterface; -import com.getsentry.raven.event.interfaces.MessageInterface; -import com.getsentry.raven.event.interfaces.SentryException; -import com.getsentry.raven.event.interfaces.StackTraceInterface; -import com.getsentry.raven.util.Util; +import io.sentry.Sentry; +import io.sentry.SentryFactory; +import io.sentry.config.Lookup; +import io.sentry.dsn.Dsn; +import io.sentry.dsn.InvalidDsnException; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; +import io.sentry.event.interfaces.ExceptionInterface; +import io.sentry.event.interfaces.MessageInterface; +import io.sentry.event.interfaces.SentryException; +import io.sentry.event.interfaces.StackTraceInterface; +import io.sentry.util.Util; import java.util.ArrayDeque; import java.util.ArrayList; @@ -43,13 +43,13 @@ public class SentryAppender extends AppenderBase { /** * Name of the {@link Event#extra} property containing the Thread name. */ - public static final String THREAD_NAME = "Raven-Threadname"; + public static final String THREAD_NAME = "Sentry-Threadname"; /** - * Current instance of {@link Raven}. + * Current instance of {@link Sentry}. * - * @see #initRaven() + * @see #initSentry() */ - protected volatile Raven raven; + protected volatile Sentry sentry; /** * DSN property of the appender. *

    @@ -57,11 +57,11 @@ public class SentryAppender extends AppenderBase { */ protected String dsn; /** - * Name of the {@link RavenFactory} being used. + * Name of the {@link SentryFactory} being used. *

    * Might be null in which case the factory should be defined automatically. */ - protected String ravenFactory; + protected String sentryFactory; /** * Identifies the version of the application. *

    @@ -105,17 +105,17 @@ public class SentryAppender extends AppenderBase { * Creates an instance of SentryAppender. */ public SentryAppender() { - this.addFilter(new DropRavenFilter()); + this.addFilter(new DropSentryFilter()); } /** * Creates an instance of SentryAppender. * - * @param raven instance of Raven to use with this appender. + * @param sentry instance of Sentry to use with this appender. */ - public SentryAppender(Raven raven) { + public SentryAppender(Sentry sentry) { this(); - this.raven = raven; + this.sentry = sentry; } /** @@ -129,9 +129,9 @@ private void lazyInit() { synchronized (this) { if (!initialized) { try { - String ravenFactory = Lookup.lookup("ravenFactory"); - if (ravenFactory != null) { - setRavenFactory(ravenFactory); + String sentryFactory = Lookup.lookup("sentryFactory"); + if (sentryFactory != null) { + setSentryFactory(sentryFactory); } String release = Lookup.lookup("release"); @@ -165,8 +165,8 @@ private void lazyInit() { } } - if (raven == null) { - initRaven(); + if (sentry == null) { + initSentry(); } } @@ -190,7 +190,7 @@ protected static List formatMessageParameters(Object[] parameters) { * Transforms a {@link Level} into an {@link Event.Level}. * * @param level original level as defined in logback. - * @return log level used within raven. + * @return log level used within sentry. */ protected static Event.Level formatLevel(Level level) { if (level.isGreaterOrEqual(Level.ERROR)) { @@ -209,18 +209,18 @@ protected static Event.Level formatLevel(Level level) { /** * {@inheritDoc} *

    - * The raven instance is started in this method instead of {@link #start()} in order to avoid substitute loggers - * being generated during the instantiation of {@link Raven}.
    + * The sentry instance is started in this method instead of {@link #start()} in order to avoid substitute loggers + * being generated during the instantiation of {@link Sentry}.
    * More on www.slf4j.org/codes.html#substituteLogger */ @Override protected void append(ILoggingEvent iLoggingEvent) { - // Do not log the event if the current thread is managed by raven - if (RavenEnvironment.isManagingThread()) { + // Do not log the event if the current thread is managed by sentry + if (SentryEnvironment.isManagingThread()) { return; } - RavenEnvironment.startManagingThread(); + SentryEnvironment.startManagingThread(); try { if (minLevel != null && !iLoggingEvent.getLevel().isGreaterOrEqual(minLevel)) { return; @@ -228,28 +228,28 @@ protected void append(ILoggingEvent iLoggingEvent) { lazyInit(); Event event = buildEvent(iLoggingEvent); - raven.sendEvent(event); + sentry.sendEvent(event); } catch (Exception e) { - addError("An exception occurred while creating a new event in Raven", e); + addError("An exception occurred while creating a new event in Sentry", e); } finally { - RavenEnvironment.stopManagingThread(); + SentryEnvironment.stopManagingThread(); } } /** - * Initialises the Raven instance. + * Initialises the Sentry instance. */ - protected synchronized void initRaven() { + protected synchronized void initSentry() { try { if (dsn == null) { dsn = Dsn.dsnLookup(); } - raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + sentry = SentryFactory.sentryInstance(new Dsn(dsn), sentryFactory); } catch (InvalidDsnException e) { - addError("An exception occurred during the retrieval of the DSN for Raven", e); + addError("An exception occurred during the retrieval of the DSN for Sentry", e); } catch (Exception e) { - addError("An exception occurred during the creation of a Raven instance", e); + addError("An exception occurred during the creation of a Sentry instance", e); } } @@ -261,7 +261,7 @@ protected synchronized void initRaven() { */ protected Event buildEvent(ILoggingEvent iLoggingEvent) { EventBuilder eventBuilder = new EventBuilder() - .withSdkName(RavenEnvironment.SDK_NAME + ":logback") + .withSdkName(SentryEnvironment.SDK_NAME + ":logback") .withTimestamp(new Date(iLoggingEvent.getTimeStamp())) .withMessage(iLoggingEvent.getFormattedMessage()) .withLogger(iLoggingEvent.getLoggerName()) @@ -320,7 +320,7 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); } - raven.runBuilderHelpers(eventBuilder); + sentry.runBuilderHelpers(eventBuilder); return eventBuilder.build(); } @@ -425,8 +425,8 @@ public void setDsn(String dsn) { this.dsn = dsn; } - public void setRavenFactory(String ravenFactory) { - this.ravenFactory = ravenFactory; + public void setSentryFactory(String sentryFactory) { + this.sentryFactory = sentryFactory; } public void setRelease(String release) { @@ -465,27 +465,27 @@ public void setExtraTags(String extraTags) { @Override public void stop() { - RavenEnvironment.startManagingThread(); + SentryEnvironment.startManagingThread(); try { if (!isStarted()) { return; } super.stop(); - if (raven != null) { - raven.closeConnection(); + if (sentry != null) { + sentry.closeConnection(); } } catch (Exception e) { - addError("An exception occurred while closing the Raven connection", e); + addError("An exception occurred while closing the Sentry connection", e); } finally { - RavenEnvironment.stopManagingThread(); + SentryEnvironment.stopManagingThread(); } } - private class DropRavenFilter extends Filter { + private class DropSentryFilter extends Filter { @Override public FilterReply decide(ILoggingEvent event) { String loggerName = event.getLoggerName(); - if (loggerName != null && loggerName.startsWith("com.getsentry.raven")) { + if (loggerName != null && loggerName.startsWith("io.sentry")) { return FilterReply.DENY; } return FilterReply.NEUTRAL; diff --git a/raven-logback/src/test/java/com/getsentry/raven/logback/MockUpLoggingEvent.java b/sentry-logback/src/test/java/io/sentry/logback/MockUpLoggingEvent.java similarity index 99% rename from raven-logback/src/test/java/com/getsentry/raven/logback/MockUpLoggingEvent.java rename to sentry-logback/src/test/java/io/sentry/logback/MockUpLoggingEvent.java index 60cf2e7f40f..8cc6b2b1d9b 100644 --- a/raven-logback/src/test/java/com/getsentry/raven/logback/MockUpLoggingEvent.java +++ b/sentry-logback/src/test/java/io/sentry/logback/MockUpLoggingEvent.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.logback; +package io.sentry.logback; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; diff --git a/raven-logback/src/test/java/com/getsentry/raven/logback/MockUpStatusPrinter.java b/sentry-logback/src/test/java/io/sentry/logback/MockUpStatusPrinter.java similarity index 96% rename from raven-logback/src/test/java/com/getsentry/raven/logback/MockUpStatusPrinter.java rename to sentry-logback/src/test/java/io/sentry/logback/MockUpStatusPrinter.java index 482cf6b342b..e210ffd6ef1 100644 --- a/raven-logback/src/test/java/com/getsentry/raven/logback/MockUpStatusPrinter.java +++ b/sentry-logback/src/test/java/io/sentry/logback/MockUpStatusPrinter.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.logback; +package io.sentry.logback; import ch.qos.logback.core.status.Status; import ch.qos.logback.core.util.StatusPrinter; diff --git a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderCloseTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java similarity index 84% rename from raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderCloseTest.java rename to sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java index 282b00f7475..2563c72aa5f 100644 --- a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderCloseTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java @@ -1,12 +1,12 @@ -package com.getsentry.raven.logback; +package io.sentry.logback; import ch.qos.logback.core.BasicStatusManager; import ch.qos.logback.core.Context; import ch.qos.logback.core.status.OnConsoleStatusListener; import mockit.*; -import com.getsentry.raven.Raven; -import com.getsentry.raven.RavenFactory; -import com.getsentry.raven.dsn.Dsn; +import io.sentry.Sentry; +import io.sentry.SentryFactory; +import io.sentry.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -15,12 +15,12 @@ public class SentryAppenderCloseTest { @Injectable - private Raven mockRaven = null; + private Sentry mockSentry = null; @Injectable private Context mockContext = null; @SuppressWarnings("unused") - @Mocked("ravenInstance") - private RavenFactory mockRavenFactory = null; + @Mocked("sentryInstance") + private SentryFactory mockSentryFactory = null; @SuppressWarnings("unused") @Mocked("dsnLookup") private Dsn mockDsn = null; @@ -45,28 +45,28 @@ private void assertNoErrorsInStatusManager() throws Exception { @Test public void testConnectionClosedWhenAppenderStopped() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven); + final SentryAppender sentryAppender = new SentryAppender(mockSentry); sentryAppender.setContext(mockContext); sentryAppender.start(); sentryAppender.stop(); new Verifications() {{ - mockRaven.closeConnection(); + mockSentry.closeConnection(); }}; assertNoErrorsInStatusManager(); } @Test - public void testStopIfRavenInstanceNotProvided() throws Exception { + public void testStopIfSentryInstanceNotProvided() throws Exception { final String dsnUri = "protocol://public:private@host/1"; final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setContext(mockContext); new Expectations() {{ Dsn.dsnLookup(); result = dsnUri; - RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockRaven; + SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); + result = mockSentry; }}; sentryAppender.start(); sentryAppender.append(null); @@ -74,7 +74,7 @@ public void testStopIfRavenInstanceNotProvided() throws Exception { sentryAppender.stop(); new Verifications() {{ - mockRaven.closeConnection(); + mockSentry.closeConnection(); }}; //One error, because of the null event. assertThat(mockContext.getStatusManager().getCount(), is(1)); @@ -86,7 +86,7 @@ public void testStopDoNotFailIfInitFailed() throws Exception { final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setContext(mockContext); new NonStrictExpectations() {{ - RavenFactory.ravenInstance((Dsn) any, anyString); + SentryFactory.sentryInstance((Dsn) any, anyString); result = new UnsupportedOperationException(); }}; sentryAppender.start(); @@ -111,7 +111,7 @@ public void testStopDoNotFailIfNoInit() @Test public void testStopDoNotFailWhenMultipleCalls() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven); + final SentryAppender sentryAppender = new SentryAppender(mockSentry); sentryAppender.setContext(mockContext); sentryAppender.start(); @@ -119,7 +119,7 @@ public void testStopDoNotFailWhenMultipleCalls() throws Exception { sentryAppender.stop(); new Verifications() {{ - mockRaven.closeConnection(); + mockSentry.closeConnection(); times = 1; }}; assertNoErrorsInStatusManager(); diff --git a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderDsnTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderDsnTest.java similarity index 77% rename from raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderDsnTest.java rename to sentry-logback/src/test/java/io/sentry/logback/SentryAppenderDsnTest.java index 815262f51d0..52f7df9ae5c 100644 --- a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderDsnTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderDsnTest.java @@ -1,12 +1,12 @@ -package com.getsentry.raven.logback; +package io.sentry.logback; import ch.qos.logback.core.BasicStatusManager; import ch.qos.logback.core.Context; import ch.qos.logback.core.status.OnConsoleStatusListener; import mockit.*; -import com.getsentry.raven.Raven; -import com.getsentry.raven.RavenFactory; -import com.getsentry.raven.dsn.Dsn; +import io.sentry.Sentry; +import io.sentry.SentryFactory; +import io.sentry.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -17,12 +17,12 @@ public class SentryAppenderDsnTest { @Tested private SentryAppender sentryAppender = null; @Injectable - private Raven mockRaven = null; + private Sentry mockSentry = null; @Injectable private Context mockContext = null; @SuppressWarnings("unused") - @Mocked("ravenInstance") - private RavenFactory mockRavenFactory = null; + @Mocked("sentryInstance") + private SentryFactory mockSentryFactory = null; @SuppressWarnings("unused") @Mocked("dsnLookup") private Dsn mockDsn = null; @@ -54,11 +54,11 @@ public void testDsnDetected() throws Exception { new Expectations() {{ Dsn.dsnLookup(); result = dsnUri; - RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockRaven; + SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); + result = mockSentry; }}; - sentryAppender.initRaven(); + sentryAppender.initSentry(); assertNoErrorsInStatusManager(); } @@ -68,11 +68,11 @@ public void testDsnProvided() throws Exception { final String dsnUri = "protocol://public:private@host/2"; sentryAppender.setDsn(dsnUri); new Expectations() {{ - RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockRaven; + SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); + result = mockSentry; }}; - sentryAppender.initRaven(); + sentryAppender.initSentry(); assertNoErrorsInStatusManager(); } diff --git a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderEventBuildingTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java similarity index 85% rename from raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderEventBuildingTest.java rename to sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java index 8b0d87c675c..f0049e25579 100644 --- a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderEventBuildingTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java @@ -1,22 +1,22 @@ -package com.getsentry.raven.logback; +package io.sentry.logback; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.BasicStatusManager; import ch.qos.logback.core.Context; import ch.qos.logback.core.status.OnConsoleStatusListener; -import com.getsentry.raven.environment.RavenEnvironment; +import io.sentry.environment.SentryEnvironment; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; import mockit.Verifications; -import com.getsentry.raven.Raven; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.EventBuilder; -import com.getsentry.raven.event.interfaces.ExceptionInterface; -import com.getsentry.raven.event.interfaces.MessageInterface; -import com.getsentry.raven.event.interfaces.SentryException; -import com.getsentry.raven.event.interfaces.StackTraceInterface; +import io.sentry.Sentry; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; +import io.sentry.event.interfaces.ExceptionInterface; +import io.sentry.event.interfaces.MessageInterface; +import io.sentry.event.interfaces.SentryException; +import io.sentry.event.interfaces.StackTraceInterface; import org.hamcrest.Matchers; import org.slf4j.MarkerFactory; import org.testng.annotations.BeforeMethod; @@ -32,7 +32,7 @@ public class SentryAppenderEventBuildingTest { @Tested private SentryAppender sentryAppender = null; @Injectable - private Raven mockRaven = null; + private Sentry mockSentry = null; @Injectable private Context mockContext = null; private String mockExtraTag = "60f42409-c029-447d-816a-fb2722913c93"; @@ -41,7 +41,7 @@ public class SentryAppenderEventBuildingTest { @BeforeMethod public void setUp() throws Exception { new MockUpStatusPrinter(); - sentryAppender = new SentryAppender(mockRaven); + sentryAppender = new SentryAppender(mockSentry); sentryAppender.setContext(mockContext); sentryAppender.setExtraTags(mockExtraTag); sentryAppender.setMinLevel(mockMinLevel); @@ -74,13 +74,13 @@ public void testSimpleMessageLogging() throws Exception { new Verifications() {{ Event event; - mockRaven.runBuilderHelpers((EventBuilder) any); - mockRaven.sendEvent(event = withCapture()); + mockSentry.runBuilderHelpers((EventBuilder) any); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getMessage(), is(message)); assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); - assertThat(event.getSdkName(), is(RavenEnvironment.SDK_NAME + ":logback")); + assertThat(event.getSdkName(), is(SentryEnvironment.SDK_NAME + ":logback")); }}; assertNoErrorsInStatusManager(); } @@ -101,7 +101,7 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getLevel(), is(expectedLevel)); }}; assertNoErrorsInStatusManager(); @@ -115,8 +115,8 @@ public void testExceptionLogging() throws Exception { new Verifications() {{ Event event; - mockRaven.runBuilderHelpers((EventBuilder) any); - mockRaven.sendEvent(event = withCapture()); + mockSentry.runBuilderHelpers((EventBuilder) any); + mockSentry.sendEvent(event = withCapture()); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); SentryException sentryException = exceptionInterface.getExceptions().getFirst(); @@ -136,8 +136,8 @@ public void testLogParametrisedMessage() throws Exception { new Verifications() {{ Event event; - mockRaven.runBuilderHelpers((EventBuilder) any); - mockRaven.sendEvent(event = withCapture()); + mockSentry.runBuilderHelpers((EventBuilder) any); + mockSentry.sendEvent(event = withCapture()); MessageInterface messageInterface = (MessageInterface) event.getSentryInterfaces() .get(MessageInterface.MESSAGE_INTERFACE); @@ -159,8 +159,8 @@ public void testMarkerAddedToTag() throws Exception { new Verifications() {{ Event event; - mockRaven.runBuilderHelpers((EventBuilder) any); - mockRaven.sendEvent(event = withCapture()); + mockSentry.runBuilderHelpers((EventBuilder) any); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getTags(), Matchers.hasEntry(SentryAppender.LOGBACK_MARKER, markerName)); }}; assertNoErrorsInStatusManager(); @@ -176,8 +176,8 @@ public void testMdcAddedToExtra() throws Exception { new Verifications() {{ Event event; - mockRaven.runBuilderHelpers((EventBuilder) any); - mockRaven.sendEvent(event = withCapture()); + mockSentry.runBuilderHelpers((EventBuilder) any); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); }}; assertNoErrorsInStatusManager(); @@ -193,8 +193,8 @@ public void testContextPropertiesAddedToExtra() throws Exception { new Verifications() {{ Event event; - mockRaven.runBuilderHelpers((EventBuilder) any); - mockRaven.sendEvent(event = withCapture()); + mockSentry.runBuilderHelpers((EventBuilder) any); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); }}; assertNoErrorsInStatusManager(); @@ -213,8 +213,8 @@ public void testMdcTakesPrecedenceOverContextProperties() throws Exception { new Verifications() {{ Event event; - mockRaven.runBuilderHelpers((EventBuilder) any); - mockRaven.sendEvent(event = withCapture()); + mockSentry.runBuilderHelpers((EventBuilder) any); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(mdcKey, mdcValue)); }}; assertNoErrorsInStatusManager(); @@ -231,8 +231,8 @@ public void testSourceUsedAsStacktrace() throws Exception { new Verifications() {{ Event event; - mockRaven.runBuilderHelpers((EventBuilder) any); - mockRaven.sendEvent(event = withCapture()); + mockSentry.runBuilderHelpers((EventBuilder) any); + mockSentry.sendEvent(event = withCapture()); StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() .get(StackTraceInterface.STACKTRACE_INTERFACE); assertThat(stackTraceInterface.getStackTrace(), is(location)); @@ -250,8 +250,8 @@ public void testCulpritWithSource() throws Exception { new Verifications() {{ Event event; - mockRaven.runBuilderHelpers((EventBuilder) any); - mockRaven.sendEvent(event = withCapture()); + mockSentry.runBuilderHelpers((EventBuilder) any); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is("a.b(c:42)")); }}; assertNoErrorsInStatusManager(); @@ -265,8 +265,8 @@ public void testCulpritWithoutSource() throws Exception { new Verifications() {{ Event event; - mockRaven.runBuilderHelpers((EventBuilder) any); - mockRaven.sendEvent(event = withCapture()); + mockSentry.runBuilderHelpers((EventBuilder) any); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is(loggerName)); }}; assertNoErrorsInStatusManager(); @@ -283,7 +283,7 @@ public void testExtraTagObtainedFromMdc() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getTags().entrySet(), hasSize(1)); assertThat(event.getTags(), hasEntry(mockExtraTag, "47008f35-50c8-4e40-94ca-c8c1a3ddb729")); assertThat(event.getExtra(), not(hasKey(mockExtraTag))); @@ -301,7 +301,7 @@ public void testReleaseAddedToEvent() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getRelease(), is(release)); }}; assertNoErrorsInStatusManager(); @@ -316,7 +316,7 @@ public void testEnvironmentAddedToEvent() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getEnvironment(), is(environment)); }}; assertNoErrorsInStatusManager(); diff --git a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderEventLevelFilterTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java similarity index 90% rename from raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderEventLevelFilterTest.java rename to sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java index 6301e83f91b..b12cc59e8fe 100644 --- a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderEventLevelFilterTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java @@ -1,12 +1,12 @@ -package com.getsentry.raven.logback; +package io.sentry.logback; import ch.qos.logback.classic.Level; import ch.qos.logback.core.Context; import mockit.Injectable; import mockit.Tested; import mockit.Verifications; -import com.getsentry.raven.Raven; -import com.getsentry.raven.event.Event; +import io.sentry.Sentry; +import io.sentry.event.Event; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -18,14 +18,14 @@ public class SentryAppenderEventLevelFilterTest { @Tested private SentryAppender sentryAppender = null; @Injectable - private Raven mockRaven = null; + private Sentry mockSentry = null; @Injectable private Context mockContext = null; @BeforeMethod public void setUp() throws Exception { new MockUpStatusPrinter(); - sentryAppender = new SentryAppender(mockRaven); + sentryAppender = new SentryAppender(mockSentry); sentryAppender.setContext(mockContext); } @@ -53,7 +53,7 @@ public void testLevelFilter(final String minLevel, final Integer expectedEvents) sentryAppender.append(new MockUpLoggingEvent(null, null, Level.ERROR, null, null, null).getMockInstance()); new Verifications() {{ - mockRaven.sendEvent((Event) any); + mockSentry.sendEvent((Event) any); minTimes = expectedEvents; maxTimes = expectedEvents; }}; @@ -68,7 +68,7 @@ public void testDefaultLevelFilter() throws Exception { sentryAppender.append(new MockUpLoggingEvent(null, null, Level.ERROR, null, null, null).getMockInstance()); new Verifications() {{ - mockRaven.sendEvent((Event) any); + mockSentry.sendEvent((Event) any); minTimes = 5; maxTimes = 5; }}; diff --git a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderFailuresTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java similarity index 71% rename from raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderFailuresTest.java rename to sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java index 7156d512b27..8ffd76da07b 100644 --- a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderFailuresTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java @@ -1,15 +1,15 @@ -package com.getsentry.raven.logback; +package io.sentry.logback; import ch.qos.logback.classic.Level; import ch.qos.logback.core.BasicStatusManager; import ch.qos.logback.core.Context; import ch.qos.logback.core.status.OnConsoleStatusListener; import mockit.*; -import com.getsentry.raven.Raven; -import com.getsentry.raven.RavenFactory; -import com.getsentry.raven.dsn.Dsn; -import com.getsentry.raven.environment.RavenEnvironment; -import com.getsentry.raven.event.Event; +import io.sentry.Sentry; +import io.sentry.SentryFactory; +import io.sentry.dsn.Dsn; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Event; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -18,12 +18,12 @@ public class SentryAppenderFailuresTest { @Injectable - private Raven mockRaven = null; + private Sentry mockSentry = null; @Injectable private Context mockContext = null; @SuppressWarnings("unused") - @Mocked("ravenInstance") - private RavenFactory mockRavenFactory; + @Mocked("sentryInstance") + private SentryFactory mockSentryFactory; @BeforeMethod public void setUp() throws Exception { @@ -40,12 +40,12 @@ public void setUp() throws Exception { } @Test - public void testRavenFailureDoesNotPropagate() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockRaven); + public void testSentryFailureDoesNotPropagate() throws Exception { + final SentryAppender sentryAppender = new SentryAppender(mockSentry); sentryAppender.setContext(mockContext); sentryAppender.setMinLevel("ALL"); new NonStrictExpectations() {{ - mockRaven.sendEvent((Event) any); + mockSentry.sendEvent((Event) any); result = new UnsupportedOperationException(); }}; sentryAppender.start(); @@ -53,45 +53,45 @@ public void testRavenFailureDoesNotPropagate() throws Exception { sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null).getMockInstance()); new Verifications() {{ - mockRaven.sendEvent((Event) any); + mockSentry.sendEvent((Event) any); }}; assertThat(mockContext.getStatusManager().getCount(), is(1)); } @Test - public void testRavenFactoryFailureDoesNotPropagate() throws Exception { + public void testSentryFactoryFailureDoesNotPropagate() throws Exception { final String dsnUri = "proto://private:public@host/1"; final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setContext(mockContext); sentryAppender.setDsn(dsnUri); new Expectations() {{ - RavenFactory.ravenInstance(withEqual(new Dsn(dsnUri)), anyString); + SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); result = new UnsupportedOperationException(); }}; sentryAppender.start(); - sentryAppender.initRaven(); + sentryAppender.initSentry(); assertThat(mockContext.getStatusManager().getCount(), is(1)); } @Test - public void testAppendFailIfCurrentThreadSpawnedByRaven() throws Exception { - RavenEnvironment.startManagingThread(); + public void testAppendFailIfCurrentThreadSpawnedBySentry() throws Exception { + SentryEnvironment.startManagingThread(); try { - final SentryAppender sentryAppender = new SentryAppender(mockRaven); + final SentryAppender sentryAppender = new SentryAppender(mockSentry); sentryAppender.setContext(mockContext); sentryAppender.start(); sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null).getMockInstance()); new Verifications() {{ - mockRaven.sendEvent((Event) any); + mockSentry.sendEvent((Event) any); times = 0; }}; assertThat(mockContext.getStatusManager().getCount(), is(0)); } finally { - RavenEnvironment.stopManagingThread(); + SentryEnvironment.stopManagingThread(); } } } diff --git a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderIT.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderIT.java similarity index 78% rename from raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderIT.java rename to sentry-logback/src/test/java/io/sentry/logback/SentryAppenderIT.java index 4500b25326e..4bd7f717730 100644 --- a/raven-logback/src/test/java/com/getsentry/raven/logback/SentryAppenderIT.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderIT.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.logback; +package io.sentry.logback; -import com.getsentry.raven.BaseIT; +import io.sentry.BaseIT; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; @@ -10,11 +10,11 @@ public class SentryAppenderIT extends BaseIT { /* - We filter out loggers that start with `com.getsentry.raven`, so we deliberately + We filter out loggers that start with `io.sentry`, so we deliberately use a custom logger name here. */ private static final Logger logger = LoggerFactory.getLogger("logback.SentryAppenderIT"); - private static final Logger ravenLogger = LoggerFactory.getLogger(SentryAppenderIT.class); + private static final Logger sentryLogger = LoggerFactory.getLogger(SentryAppenderIT.class); @Before public void setup() { @@ -45,11 +45,11 @@ public void testChainedExceptions() throws Exception { } @Test - public void testNoRavenLogging() throws Exception { + public void testNoSentryLogging() throws Exception { verifyProject1PostRequestCount(0); verifyStoredEventCount(0); - ravenLogger.error("This is a test"); + sentryLogger.error("This is a test"); verifyProject1PostRequestCount(0); verifyStoredEventCount(0); diff --git a/raven-logback/src/test/resources/logback-integration.xml b/sentry-logback/src/test/resources/logback-integration.xml similarity index 70% rename from raven-logback/src/test/resources/logback-integration.xml rename to sentry-logback/src/test/resources/logback-integration.xml index cc44189820f..9b64b25b751 100644 --- a/raven-logback/src/test/resources/logback-integration.xml +++ b/sentry-logback/src/test/resources/logback-integration.xml @@ -1,20 +1,20 @@ - [RAVEN] [%-5level] %logger{36} - %msg%n%nopex + [SENTRY] [%-5level] %logger{36} - %msg%n%nopex - - http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?raven.async=false + + http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?async=false - + WARN - + diff --git a/raven-logback/src/test/resources/logback-test.xml b/sentry-logback/src/test/resources/logback-test.xml similarity index 74% rename from raven-logback/src/test/resources/logback-test.xml rename to sentry-logback/src/test/resources/logback-test.xml index bfd7282a648..93163fa0c98 100644 --- a/raven-logback/src/test/resources/logback-test.xml +++ b/sentry-logback/src/test/resources/logback-test.xml @@ -1,7 +1,7 @@ - [RAVEN] [%-5level] %logger{36} - %msg%n%nopex + [SENTRY] [%-5level] %logger{36} - %msg%n%nopex diff --git a/raven/README.md b/sentry/README.md similarity index 56% rename from raven/README.md rename to sentry/README.md index 7b34e2f3f20..8510d5c2f7d 100644 --- a/raven/README.md +++ b/sentry/README.md @@ -1,3 +1,3 @@ -# raven +# sentry -See the [Sentry documentation](https://docs.sentry.io/clients/java/modules/raven/) for more information. +See the [Sentry documentation](https://docs.sentry.io/clients/java/modules/sentry/) for more information. diff --git a/raven/pom.xml b/sentry/pom.xml similarity index 95% rename from raven/pom.xml rename to sentry/pom.xml index 07809e8ebe0..8db9e7c1390 100644 --- a/raven/pom.xml +++ b/sentry/pom.xml @@ -3,15 +3,15 @@ 4.0.0 - com.getsentry.raven - raven-all - 8.0.3-SNAPSHOT + io.sentry + sentry-all + 1.0.0-SNAPSHOT - raven + sentry jar - Raven-Java client + Sentry-Java client Sentry client written in Java. @@ -89,7 +89,7 @@ src/main/resources - raven-build.properties + sentry-build.properties true diff --git a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryFactory.java similarity index 89% rename from raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java rename to sentry/src/main/java/io/sentry/DefaultSentryFactory.java index cc554175d5d..c9b0a39e885 100644 --- a/raven/src/main/java/com/getsentry/raven/DefaultRavenFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryFactory.java @@ -1,17 +1,17 @@ -package com.getsentry.raven; - -import com.getsentry.raven.buffer.Buffer; -import com.getsentry.raven.buffer.DiskBuffer; -import com.getsentry.raven.connection.*; -import com.getsentry.raven.context.ContextManager; -import com.getsentry.raven.context.ThreadLocalContextManager; -import com.getsentry.raven.dsn.Dsn; -import com.getsentry.raven.event.helper.ContextBuilderHelper; -import com.getsentry.raven.event.helper.HttpEventBuilderHelper; -import com.getsentry.raven.event.interfaces.*; -import com.getsentry.raven.marshaller.Marshaller; -import com.getsentry.raven.marshaller.json.*; -import com.getsentry.raven.util.Util; +package io.sentry; + +import io.sentry.buffer.Buffer; +import io.sentry.buffer.DiskBuffer; +import io.sentry.connection.*; +import io.sentry.context.ContextManager; +import io.sentry.context.ThreadLocalContextManager; +import io.sentry.dsn.Dsn; +import io.sentry.event.helper.ContextBuilderHelper; +import io.sentry.event.helper.HttpEventBuilderHelper; +import io.sentry.event.interfaces.*; +import io.sentry.marshaller.Marshaller; +import io.sentry.marshaller.json.*; +import io.sentry.util.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,11 +25,11 @@ import java.util.concurrent.atomic.AtomicInteger; /** - * Default implementation of {@link RavenFactory}. + * Default implementation of {@link SentryFactory}. *

    * In most cases this is the implementation to use or extend for additional features. */ -public class DefaultRavenFactory extends RavenFactory { +public class DefaultSentryFactory extends SentryFactory { //TODO: Add support for tags set by default /** * Protocol setting to disable security checks over an SSL connection. @@ -38,16 +38,16 @@ public class DefaultRavenFactory extends RavenFactory { /** * Option for whether to compress requests sent to the Sentry Server. */ - public static final String COMPRESSION_OPTION = "raven.compression"; + public static final String COMPRESSION_OPTION = "compression"; /** * Option to set the maximum length of the message body in the requests to the * Sentry Server. */ - public static final String MAX_MESSAGE_LENGTH_OPTION = "raven.maxmessagelength"; + public static final String MAX_MESSAGE_LENGTH_OPTION = "maxmessagelength"; /** * Option to set a timeout for requests to the Sentry server, in milliseconds. */ - public static final String TIMEOUT_OPTION = "raven.timeout"; + public static final String TIMEOUT_OPTION = "timeout"; /** * Default timeout of an HTTP connection to Sentry. */ @@ -56,7 +56,7 @@ public class DefaultRavenFactory extends RavenFactory { * Option to enable or disable Event buffering. A buffering directory is also required. * This setting is mostly useful on Android where a buffering directory is set by default. */ - public static final String BUFFER_ENABLED_OPTION = "raven.buffer.enabled"; + public static final String BUFFER_ENABLED_OPTION = "buffer.enabled"; /** * Default value for whether buffering is enabled (if a directory is also provided). */ @@ -64,11 +64,11 @@ public class DefaultRavenFactory extends RavenFactory { /** * Option to buffer events to disk when network is down. */ - public static final String BUFFER_DIR_OPTION = "raven.buffer.dir"; + public static final String BUFFER_DIR_OPTION = "buffer.dir"; /** * Option for maximum number of events to cache offline when network is down. */ - public static final String BUFFER_SIZE_OPTION = "raven.buffer.size"; + public static final String BUFFER_SIZE_OPTION = "buffer.size"; /** * Default number of events to cache offline when network is down. */ @@ -76,7 +76,7 @@ public class DefaultRavenFactory extends RavenFactory { /** * Option for how long to wait between attempts to flush the disk buffer, in milliseconds. */ - public static final String BUFFER_FLUSHTIME_OPTION = "raven.buffer.flushtime"; + public static final String BUFFER_FLUSHTIME_OPTION = "buffer.flushtime"; /** * Default number of milliseconds between attempts to flush buffered events. */ @@ -84,11 +84,11 @@ public class DefaultRavenFactory extends RavenFactory { /** * Option to disable the graceful shutdown of the buffer flusher. */ - public static final String BUFFER_GRACEFUL_SHUTDOWN_OPTION = "raven.buffer.gracefulshutdown"; + public static final String BUFFER_GRACEFUL_SHUTDOWN_OPTION = "buffer.gracefulshutdown"; /** * Option for the graceful shutdown timeout of the buffer flushing executor, in milliseconds. */ - public static final String BUFFER_SHUTDOWN_TIMEOUT_OPTION = "raven.buffer.shutdowntimeout"; + public static final String BUFFER_SHUTDOWN_TIMEOUT_OPTION = "buffer.shutdowntimeout"; /** * Default timeout of the {@link BufferedConnection} shutdown, in milliseconds. */ @@ -96,27 +96,27 @@ public class DefaultRavenFactory extends RavenFactory { /** * Option for whether to send events asynchronously. */ - public static final String ASYNC_OPTION = "raven.async"; + public static final String ASYNC_OPTION = "async"; /** * Option to disable the graceful shutdown of the async connection. */ - public static final String ASYNC_GRACEFUL_SHUTDOWN_OPTION = "raven.async.gracefulshutdown"; + public static final String ASYNC_GRACEFUL_SHUTDOWN_OPTION = "async.gracefulshutdown"; /** * Option for the number of threads used for the async connection. */ - public static final String ASYNC_THREADS_OPTION = "raven.async.threads"; + public static final String ASYNC_THREADS_OPTION = "async.threads"; /** * Option for the priority of threads used for the async connection. */ - public static final String ASYNC_PRIORITY_OPTION = "raven.async.priority"; + public static final String ASYNC_PRIORITY_OPTION = "async.priority"; /** * Option for the maximum size of the async send queue. */ - public static final String ASYNC_QUEUE_SIZE_OPTION = "raven.async.queuesize"; + public static final String ASYNC_QUEUE_SIZE_OPTION = "async.queuesize"; /** * Option for what to do when the async executor queue is full. */ - public static final String ASYNC_QUEUE_OVERFLOW_OPTION = "raven.async.queue.overflow"; + public static final String ASYNC_QUEUE_OVERFLOW_OPTION = "async.queue.overflow"; /** * Async executor overflow behavior that will discard old events in the queue. */ @@ -138,7 +138,7 @@ public class DefaultRavenFactory extends RavenFactory { /** * Option for the graceful shutdown timeout of the async executor, in milliseconds. */ - public static final String ASYNC_SHUTDOWN_TIMEOUT_OPTION = "raven.async.shutdowntimeout"; + public static final String ASYNC_SHUTDOWN_TIMEOUT_OPTION = "async.shutdowntimeout"; /** * Default timeout of the {@link AsyncConnection} executor, in milliseconds. */ @@ -147,31 +147,31 @@ public class DefaultRavenFactory extends RavenFactory { * Option for which package prefixes are part of the user's application code, as a single * comma separated string. */ - public static final String IN_APP_FRAMES_OPTION = "raven.stacktrace.app.packages"; + public static final String IN_APP_FRAMES_OPTION = "stacktrace.app.packages"; /** * Option for whether to hide common stackframes with enclosing exceptions. */ - public static final String HIDE_COMMON_FRAMES_OPTION = "raven.stacktrace.hidecommon"; + public static final String HIDE_COMMON_FRAMES_OPTION = "stacktrace.hidecommon"; /** * Option for whether to sample events, allowing from 0.0 to 1.0 (0 to 100%) to be sent to the server. */ - public static final String SAMPLE_RATE_OPTION = "raven.sample.rate"; + public static final String SAMPLE_RATE_OPTION = "sample.rate"; /** * Option to set an HTTP proxy hostname for Sentry connections. */ - public static final String HTTP_PROXY_HOST_OPTION = "raven.http.proxy.host"; + public static final String HTTP_PROXY_HOST_OPTION = "http.proxy.host"; /** * Option to set an HTTP proxy port for Sentry connections. */ - public static final String HTTP_PROXY_PORT_OPTION = "raven.http.proxy.port"; + public static final String HTTP_PROXY_PORT_OPTION = "http.proxy.port"; /** * Option to set an HTTP proxy username for Sentry connections. */ - public static final String HTTP_PROXY_USER_OPTION = "raven.http.proxy.user"; + public static final String HTTP_PROXY_USER_OPTION = "http.proxy.user"; /** * Option to set an HTTP proxy password for Sentry connections. */ - public static final String HTTP_PROXY_PASS_OPTION = "raven.http.proxy.password"; + public static final String HTTP_PROXY_PASS_OPTION = "http.proxy.password"; /** * The default async queue size if none is provided. */ @@ -181,7 +181,7 @@ public class DefaultRavenFactory extends RavenFactory { */ public static final int HTTP_PROXY_PORT_DEFAULT = 80; - private static final Logger logger = LoggerFactory.getLogger(DefaultRavenFactory.class); + private static final Logger logger = LoggerFactory.getLogger(DefaultSentryFactory.class); private static final String FALSE = Boolean.FALSE.toString(); private static final Map REJECT_EXECUTION_HANDLERS = new HashMap<>(); @@ -192,20 +192,20 @@ public class DefaultRavenFactory extends RavenFactory { } @Override - public Raven createRavenInstance(Dsn dsn) { - Raven raven = new Raven(createConnection(dsn), getContextManager(dsn)); + public Sentry createSentryInstance(Dsn dsn) { + Sentry sentry = new Sentry(createConnection(dsn), getContextManager(dsn)); try { // `ServletRequestListener` was added in the Servlet 2.4 API, and // is used as part of the `HttpEventBuilderHelper`, see: // https://tomcat.apache.org/tomcat-5.5-doc/servletapi/ Class.forName("javax.servlet.ServletRequestListener", false, this.getClass().getClassLoader()); - raven.addBuilderHelper(new HttpEventBuilderHelper()); + sentry.addBuilderHelper(new HttpEventBuilderHelper()); } catch (ClassNotFoundException e) { logger.debug("The current environment doesn't provide access to servlets," + " or provides an unsupported version."); } - raven.addBuilderHelper(new ContextBuilderHelper(raven)); - return raven; + sentry.addBuilderHelper(new ContextBuilderHelper(sentry)); + return sentry; } /** @@ -347,7 +347,7 @@ protected Connection createStdOutConnection(Dsn dsn) { } /** - * Creates a JSON marshaller that will convert every {@link com.getsentry.raven.event.Event} in a format + * Creates a JSON marshaller that will convert every {@link io.sentry.event.Event} in a format * handled by the Sentry server. * * @param dsn Data Source Name of the Sentry server. @@ -380,7 +380,7 @@ protected Marshaller createMarshaller(Dsn dsn) { /** * Returns the {@link ContextManager} to use for locating and storing data that is context specific, - * such as {@link com.getsentry.raven.event.Breadcrumb}s. + * such as {@link io.sentry.event.Breadcrumb}s. *

    * Defaults to {@link ThreadLocalContextManager}. * @@ -683,7 +683,7 @@ protected static final class DaemonThreadFactory implements ThreadFactory { private DaemonThreadFactory(int priority) { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); - namePrefix = "raven-pool-" + POOL_NUMBER.getAndIncrement() + "-thread-"; + namePrefix = "sentry-pool-" + POOL_NUMBER.getAndIncrement() + "-thread-"; this.priority = priority; } diff --git a/raven/src/main/java/com/getsentry/raven/Raven.java b/sentry/src/main/java/io/sentry/Sentry.java similarity index 76% rename from raven/src/main/java/com/getsentry/raven/Raven.java rename to sentry/src/main/java/io/sentry/Sentry.java index 926abd059be..70c3862e533 100644 --- a/raven/src/main/java/com/getsentry/raven/Raven.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -1,14 +1,14 @@ -package com.getsentry.raven; - -import com.getsentry.raven.connection.Connection; -import com.getsentry.raven.connection.LockedDownException; -import com.getsentry.raven.context.Context; -import com.getsentry.raven.context.ContextManager; -import com.getsentry.raven.environment.RavenEnvironment; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.EventBuilder; -import com.getsentry.raven.event.helper.EventBuilderHelper; -import com.getsentry.raven.event.interfaces.ExceptionInterface; +package io.sentry; + +import io.sentry.connection.Connection; +import io.sentry.connection.LockedDownException; +import io.sentry.context.Context; +import io.sentry.context.ContextManager; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; +import io.sentry.event.helper.EventBuilderHelper; +import io.sentry.event.interfaces.ExceptionInterface; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,21 +18,21 @@ import java.util.concurrent.ConcurrentHashMap; /** - * Raven is a client for Sentry allowing to send an {@link Event} that will be processed and sent to a Sentry server. + * Sentry is a client for Sentry allowing to send an {@link Event} that will be processed and sent to a Sentry server. *

    - * It is recommended to create an instance of Raven through - * {@link RavenFactory#createRavenInstance(com.getsentry.raven.dsn.Dsn)}, this will use the best factory available to - * create a sensible instance of Raven. + * It is recommended to create an instance of Sentry through + * {@link SentryFactory#createSentryInstance(io.sentry.dsn.Dsn)}, this will use the best factory available to + * create a sensible instance of Sentry. */ -public class Raven { - private static final Logger logger = LoggerFactory.getLogger(Raven.class); +public class Sentry { + private static final Logger logger = LoggerFactory.getLogger(Sentry.class); // CHECKSTYLE.OFF: ConstantName - private static final Logger lockdownLogger = LoggerFactory.getLogger(Raven.class.getName() + ".lockdown"); + private static final Logger lockdownLogger = LoggerFactory.getLogger(Sentry.class.getName() + ".lockdown"); // CHECKSTYLE.ON: ConstantName /** - * The most recently constructed Raven instance, used by static helper methods like {@link Raven#capture(Event)}. + * The most recently constructed Sentry instance, used by static helper methods like {@link Sentry#capture(Event)}. */ - private static volatile Raven stored = null; + private static volatile Sentry stored = null; /** * The underlying {@link Connection} to use for sending events to Sentry. */ @@ -45,11 +45,11 @@ public class Raven { Collections.newSetFromMap(new ConcurrentHashMap()); /** * The {@link ContextManager} to use for locating and storing data that is context specific, - * such as {@link com.getsentry.raven.event.Breadcrumb}s. + * such as {@link io.sentry.event.Breadcrumb}s. */ private final ContextManager contextManager; /** - * Constructs a Raven instance using the provided connection. + * Constructs a Sentry instance using the provided connection. * * Note that the most recently constructed instance is stored statically so it can be used with * the static helper methods. @@ -57,7 +57,7 @@ public class Raven { * @param connection Underlying {@link Connection} instance to use for sending events * @param contextManager {@link ContextManager} instance to use for storing contextual data */ - public Raven(Connection connection, ContextManager contextManager) { + public Sentry(Connection connection, ContextManager contextManager) { this.connection = connection; this.contextManager = contextManager; stored = this; @@ -159,13 +159,13 @@ public Set getBuilderHelpers() { } /** - * Closes the connection for the Raven instance. + * Closes the connection for the Sentry instance. */ public void closeConnection() { try { connection.close(); } catch (IOException e) { - throw new RuntimeException("Couldn't close the Raven connection", e); + throw new RuntimeException("Couldn't close the Sentry connection", e); } } @@ -175,8 +175,8 @@ public Context getContext() { @Override public String toString() { - return "Raven{" - + "name=" + RavenEnvironment.getRavenName() + return "Sentry{" + + "name=" + SentryEnvironment.getSentryName() + ", connection=" + connection + ", contextManager=" + contextManager + '}'; @@ -187,24 +187,24 @@ public String toString() { // -------------------------------------------------------- /** - * Returns the last statically stored Raven instance or null if one has + * Returns the last statically stored Sentry instance or null if one has * never been stored. * - * @return statically stored {@link Raven} instance + * @return statically stored {@link Sentry} instance */ - public static Raven getStoredInstance() { + public static Sentry getStoredInstance() { return stored; } private static void verifyStoredInstance() { if (stored == null) { - throw new NullPointerException("No stored Raven instance is available to use." - + " You must construct a Raven instance before using the static Raven methods."); + throw new NullPointerException("No stored Sentry instance is available to use." + + " You must construct a Sentry instance before using the static Sentry methods."); } } /** - * Send an Event using the statically stored Raven instance. + * Send an Event using the statically stored Sentry instance. * * @param event Event to send to the Sentry server */ @@ -214,7 +214,7 @@ public static void capture(Event event) { } /** - * Sends an exception (or throwable) to the Sentry server using the statically stored Raven instance. + * Sends an exception (or throwable) to the Sentry server using the statically stored Sentry instance. *

    * The exception will be logged at the {@link Event.Level#ERROR} level. * @@ -226,7 +226,7 @@ public static void capture(Throwable throwable) { } /** - * Sends a message to the Sentry server using the statically stored Raven instance. + * Sends a message to the Sentry server using the statically stored Sentry instance. *

    * The message will be logged at the {@link Event.Level#INFO} level. * @@ -238,7 +238,7 @@ public static void capture(String message) { } /** - * Builds and sends an {@link Event} to the Sentry server using the statically stored Raven instance. + * Builds and sends an {@link Event} to the Sentry server using the statically stored Sentry instance. * * @param eventBuilder {@link EventBuilder} to send to Sentry. */ diff --git a/sentry/src/main/java/io/sentry/SentryFactory.java b/sentry/src/main/java/io/sentry/SentryFactory.java new file mode 100644 index 00000000000..dff773524e0 --- /dev/null +++ b/sentry/src/main/java/io/sentry/SentryFactory.java @@ -0,0 +1,180 @@ +package io.sentry; + +import io.sentry.dsn.Dsn; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.ServiceLoader; +import java.util.Set; + +/** + * Factory in charge of creating {@link Sentry} instances. + *

    + * The factories register themselves through the {@link ServiceLoader} system. + */ +public abstract class SentryFactory { + private static final ServiceLoader AUTO_REGISTERED_FACTORIES = + ServiceLoader.load(SentryFactory.class, SentryFactory.class.getClassLoader()); + private static final Set MANUALLY_REGISTERED_FACTORIES = new HashSet<>(); + private static final Logger logger = LoggerFactory.getLogger(SentryFactory.class); + + /** + * Manually adds a SentryFactory to the system. + *

    + * Usually SentryFactories are automatically detected with the {@link ServiceLoader} system, but some systems + * such as Android do not provide a fully working ServiceLoader.
    + * If the factory isn't detected automatically, it's possible to add it through this method. + * + * @param sentryFactory sentryFactory to support. + */ + public static void registerFactory(SentryFactory sentryFactory) { + MANUALLY_REGISTERED_FACTORIES.add(sentryFactory); + } + + private static Iterable getRegisteredFactories() { + List sentryFactories = new LinkedList<>(); + sentryFactories.addAll(MANUALLY_REGISTERED_FACTORIES); + for (SentryFactory autoRegisteredFactory : AUTO_REGISTERED_FACTORIES) { + sentryFactories.add(autoRegisteredFactory); + } + return sentryFactories; + } + + /** + * Creates an instance of Sentry using the DSN obtain through {@link io.sentry.dsn.Dsn#dsnLookup()}. + * + * @return an instance of Sentry. + */ + public static Sentry sentryInstance() { + return sentryInstance(Dsn.dsnLookup()); + } + + /** + * Creates an instance of Sentry using the provided DSN. + * + * @param dsn Data Source Name of the Sentry server. + * @return an instance of Sentry. + */ + public static Sentry sentryInstance(String dsn) { + return sentryInstance(new Dsn(dsn)); + } + + /** + * Creates an instance of Sentry using the provided DSN. + * + * @param dsn Data Source Name of the Sentry server. + * @return an instance of Sentry. + */ + public static Sentry sentryInstance(Dsn dsn) { + return sentryInstance(dsn, null); + } + + /** + * Creates an instance of Sentry using the provided DSN and the specified factory. + * + * @param dsn Data Source Name of the Sentry server. + * @param sentryFactoryName name of the SentryFactory to use to generate an instance of Sentry. + * @return an instance of Sentry. + * @throws IllegalStateException when no instance of Sentry has been created. + */ + public static Sentry sentryInstance(Dsn dsn, String sentryFactoryName) { + logger.debug("Attempting to find a working SentryFactory"); + + // Loop through registered factories, keeping track of which classes we skip, which we try to instantiate, + // and the last exception thrown. + ArrayList skippedFactories = new ArrayList<>(); + ArrayList triedFactories = new ArrayList<>(); + RuntimeException lastExc = null; + + for (SentryFactory sentryFactory : getRegisteredFactories()) { + String name = sentryFactory.getClass().getName(); + if (sentryFactoryName != null && !sentryFactoryName.equals(name)) { + skippedFactories.add(name); + continue; + } + + logger.debug("Attempting to use '{}' as a SentryFactory.", sentryFactory); + triedFactories.add(name); + try { + Sentry sentryInstance = sentryFactory.createSentryInstance(dsn); + logger.debug("The SentryFactory '{}' created an instance of Sentry.", sentryFactory); + return sentryInstance; + } catch (RuntimeException e) { + lastExc = e; + logger.debug("The SentryFactory '{}' couldn't create an instance of Sentry.", sentryFactory, e); + } + } + + if (sentryFactoryName != null && triedFactories.isEmpty()) { + try { + // see if the provided class exists on the classpath at all + Class.forName(sentryFactoryName); + logger.error( + "The SentryFactory class '{}' was found on your classpath but was not " + + "registered with Sentry, see: " + + "https://github.com/getsentry/sentry-java/#custom-sentryfactory", sentryFactoryName); + } catch (ClassNotFoundException e) { + logger.error("The SentryFactory class name '{}' was specified but " + + "the class was not found on your classpath.", sentryFactoryName); + } + } + + // Throw an IllegalStateException that attempts to be helpful. + StringBuilder sb = new StringBuilder(); + sb.append("Couldn't create a sentry instance for: '"); + sb.append(dsn); + sb.append('\''); + if (sentryFactoryName != null) { + sb.append("; sentryFactoryName: "); + sb.append(sentryFactoryName); + + if (skippedFactories.isEmpty()) { + sb.append("; no skipped factories"); + } else { + sb.append("; skipped factories: "); + String delim = ""; + for (String skippedFactory : skippedFactories) { + sb.append(delim); + sb.append(skippedFactory); + delim = ", "; + } + } + } + + if (triedFactories.isEmpty()) { + sb.append("; no factories tried!"); + throw new IllegalStateException(sb.toString()); + } + + sb.append("; tried factories: "); + String delim = ""; + for (String triedFactory : triedFactories) { + sb.append(delim); + sb.append(triedFactory); + delim = ", "; + } + + sb.append("; cause contains exception thrown by the last factory tried."); + throw new IllegalStateException(sb.toString(), lastExc); + } + + /** + * Creates an instance of Sentry given a DSN. + * + * @param dsn Data Source Name of the Sentry server. + * @return an instance of Sentry. + * @throws RuntimeException when an instance couldn't be created. + */ + public abstract Sentry createSentryInstance(Dsn dsn); + + @Override + public String toString() { + return "SentryFactory{" + + "name='" + this.getClass().getName() + '\'' + + '}'; + } +} diff --git a/raven/src/main/java/com/getsentry/raven/buffer/Buffer.java b/sentry/src/main/java/io/sentry/buffer/Buffer.java similarity index 71% rename from raven/src/main/java/com/getsentry/raven/buffer/Buffer.java rename to sentry/src/main/java/io/sentry/buffer/Buffer.java index ec0ec34153f..b353aa9f2aa 100644 --- a/raven/src/main/java/com/getsentry/raven/buffer/Buffer.java +++ b/sentry/src/main/java/io/sentry/buffer/Buffer.java @@ -1,12 +1,12 @@ -package com.getsentry.raven.buffer; +package io.sentry.buffer; -import com.getsentry.raven.event.Event; +import io.sentry.event.Event; import java.util.Iterator; /** - * Buffer that is called by a {@link com.getsentry.raven.connection.BufferedConnection} when an {@link Event} send - * fails with a {@link com.getsentry.raven.connection.ConnectionException}. + * Buffer that is called by a {@link io.sentry.connection.BufferedConnection} when an {@link Event} send + * fails with a {@link io.sentry.connection.ConnectionException}. */ public interface Buffer { /** diff --git a/raven/src/main/java/com/getsentry/raven/buffer/DiskBuffer.java b/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java similarity index 97% rename from raven/src/main/java/com/getsentry/raven/buffer/DiskBuffer.java rename to sentry/src/main/java/io/sentry/buffer/DiskBuffer.java index 50f9c2f3d1b..68b6e29432c 100644 --- a/raven/src/main/java/com/getsentry/raven/buffer/DiskBuffer.java +++ b/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.buffer; +package io.sentry.buffer; -import com.getsentry.raven.event.Event; +import io.sentry.event.Event; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,7 +21,7 @@ public class DiskBuffer implements Buffer { /** * File suffix added to all serialized event files. */ - public static final String FILE_SUFFIX = ".raven-event"; + public static final String FILE_SUFFIX = ".sentry-event"; private static final Logger logger = LoggerFactory.getLogger(DiskBuffer.class); diff --git a/raven/src/main/java/com/getsentry/raven/config/JndiLookup.java b/sentry/src/main/java/io/sentry/config/JndiLookup.java similarity index 97% rename from raven/src/main/java/com/getsentry/raven/config/JndiLookup.java rename to sentry/src/main/java/io/sentry/config/JndiLookup.java index f2ec1949528..8abdb862bd4 100644 --- a/raven/src/main/java/com/getsentry/raven/config/JndiLookup.java +++ b/sentry/src/main/java/io/sentry/config/JndiLookup.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.config; +package io.sentry.config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/raven/src/main/java/com/getsentry/raven/config/Lookup.java b/sentry/src/main/java/io/sentry/config/Lookup.java similarity index 95% rename from raven/src/main/java/com/getsentry/raven/config/Lookup.java rename to sentry/src/main/java/io/sentry/config/Lookup.java index ea71df9ef45..bf506ed539d 100644 --- a/raven/src/main/java/com/getsentry/raven/config/Lookup.java +++ b/sentry/src/main/java/io/sentry/config/Lookup.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.config; +package io.sentry.config; -import com.getsentry.raven.dsn.Dsn; +import io.sentry.dsn.Dsn; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java b/sentry/src/main/java/io/sentry/connection/AbstractConnection.java similarity index 94% rename from raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java rename to sentry/src/main/java/io/sentry/connection/AbstractConnection.java index 64ffe33f7d7..0e191c51a6d 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/AbstractConnection.java +++ b/sentry/src/main/java/io/sentry/connection/AbstractConnection.java @@ -1,7 +1,7 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; -import com.getsentry.raven.environment.RavenEnvironment; -import com.getsentry.raven.event.Event; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Event; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,7 +42,7 @@ protected AbstractConnection(String publicKey, String secretKey) { this.lockdownManager = new LockdownManager(); this.eventSendFailureCallbacks = new HashSet<>(); this.authHeader = "Sentry sentry_version=" + SENTRY_PROTOCOL_VERSION + "," - + "sentry_client=" + RavenEnvironment.getRavenName() + "," + + "sentry_client=" + SentryEnvironment.getSentryName() + "," + "sentry_key=" + publicKey + "," + "sentry_secret=" + secretKey; } diff --git a/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java b/sentry/src/main/java/io/sentry/connection/AsyncConnection.java similarity index 91% rename from raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java rename to sentry/src/main/java/io/sentry/connection/AsyncConnection.java index 42f755ab763..4373b7fd5d5 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/AsyncConnection.java +++ b/sentry/src/main/java/io/sentry/connection/AsyncConnection.java @@ -1,8 +1,8 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; -import com.getsentry.raven.Raven; -import com.getsentry.raven.environment.RavenEnvironment; -import com.getsentry.raven.event.Event; +import io.sentry.Sentry; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Event; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,7 +21,7 @@ public class AsyncConnection implements Connection { private static final Logger logger = LoggerFactory.getLogger(AsyncConnection.class); // CHECKSTYLE.OFF: ConstantName - private static final Logger lockdownLogger = LoggerFactory.getLogger(Raven.class.getName() + ".lockdown"); + private static final Logger lockdownLogger = LoggerFactory.getLogger(Sentry.class.getName() + ".lockdown"); // CHECKSTYLE.ON: ConstantName /** * Timeout of the {@link #executorService}, in milliseconds. @@ -153,7 +153,7 @@ private void doClose() throws IOException { } /** - * Simple runnable using the {@link #send(com.getsentry.raven.event.Event)} method of the + * Simple runnable using the {@link #send(io.sentry.event.Event)} method of the * {@link #actualConnection}. */ private final class EventSubmitter implements Runnable { @@ -165,16 +165,16 @@ private EventSubmitter(Event event) { @Override public void run() { - RavenEnvironment.startManagingThread(); + SentryEnvironment.startManagingThread(); try { - // The current thread is managed by raven + // The current thread is managed by sentry actualConnection.send(event); } catch (LockedDownException e) { lockdownLogger.warn("The connection to Sentry is currently locked down.", e); } catch (Exception e) { logger.error("An exception occurred while sending the event to Sentry.", e); } finally { - RavenEnvironment.stopManagingThread(); + SentryEnvironment.stopManagingThread(); } } } @@ -192,15 +192,15 @@ public void run() { return; } - RavenEnvironment.startManagingThread(); + SentryEnvironment.startManagingThread(); try { - // The current thread is managed by raven + // The current thread is managed by sentry logger.info("Automatic shutdown of the async connection"); AsyncConnection.this.doClose(); } catch (Exception e) { logger.error("An exception occurred while closing the connection.", e); } finally { - RavenEnvironment.stopManagingThread(); + SentryEnvironment.stopManagingThread(); } } } diff --git a/raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java b/sentry/src/main/java/io/sentry/connection/BufferedConnection.java similarity index 95% rename from raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java rename to sentry/src/main/java/io/sentry/connection/BufferedConnection.java index abfc2381d34..c5319233e32 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/BufferedConnection.java +++ b/sentry/src/main/java/io/sentry/connection/BufferedConnection.java @@ -1,8 +1,8 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; -import com.getsentry.raven.buffer.Buffer; -import com.getsentry.raven.environment.RavenEnvironment; -import com.getsentry.raven.event.Event; +import io.sentry.buffer.Buffer; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Event; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -204,7 +204,7 @@ private class Flusher implements Runnable { public void run() { logger.trace("Running Flusher"); - RavenEnvironment.startManagingThread(); + SentryEnvironment.startManagingThread(); try { Iterator events = buffer.getEvents(); while (events.hasNext() && !closed) { @@ -239,7 +239,7 @@ public void run() { } logger.trace("Flusher run exiting, no more events to send."); } finally { - RavenEnvironment.stopManagingThread(); + SentryEnvironment.stopManagingThread(); } } } @@ -257,15 +257,15 @@ public void run() { return; } - RavenEnvironment.startManagingThread(); + SentryEnvironment.startManagingThread(); try { - // The current thread is managed by raven + // The current thread is managed by sentry logger.info("Automatic shutdown of the buffered connection"); BufferedConnection.this.close(); } catch (Exception e) { logger.error("An exception occurred while closing the connection.", e); } finally { - RavenEnvironment.stopManagingThread(); + SentryEnvironment.stopManagingThread(); } } } diff --git a/raven/src/main/java/com/getsentry/raven/connection/Connection.java b/sentry/src/main/java/io/sentry/connection/Connection.java similarity index 89% rename from raven/src/main/java/com/getsentry/raven/connection/Connection.java rename to sentry/src/main/java/io/sentry/connection/Connection.java index bd163159e13..63bcd49f6e3 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/Connection.java +++ b/sentry/src/main/java/io/sentry/connection/Connection.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; -import com.getsentry.raven.event.Event; +import io.sentry.event.Event; import java.io.Closeable; diff --git a/raven/src/main/java/com/getsentry/raven/connection/ConnectionException.java b/sentry/src/main/java/io/sentry/connection/ConnectionException.java similarity index 86% rename from raven/src/main/java/com/getsentry/raven/connection/ConnectionException.java rename to sentry/src/main/java/io/sentry/connection/ConnectionException.java index 63e0f6e58c3..d56fd865ce4 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/ConnectionException.java +++ b/sentry/src/main/java/io/sentry/connection/ConnectionException.java @@ -1,7 +1,7 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; /** - * Exception thrown by a {@link com.getsentry.raven.connection.Connection} if something went wrong temporarily. + * Exception thrown by a {@link io.sentry.connection.Connection} if something went wrong temporarily. *

    * This allows connections to know when to back off for a while. */ diff --git a/raven/src/main/java/com/getsentry/raven/connection/EventSampler.java b/sentry/src/main/java/io/sentry/connection/EventSampler.java similarity index 84% rename from raven/src/main/java/com/getsentry/raven/connection/EventSampler.java rename to sentry/src/main/java/io/sentry/connection/EventSampler.java index e5c573015a2..3767143e594 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/EventSampler.java +++ b/sentry/src/main/java/io/sentry/connection/EventSampler.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; -import com.getsentry.raven.event.Event; +import io.sentry.event.Event; /** * Used by {@link HttpConnection} to decide whether a specific event should actually be diff --git a/raven/src/main/java/com/getsentry/raven/connection/EventSendFailureCallback.java b/sentry/src/main/java/io/sentry/connection/EventSendFailureCallback.java similarity index 85% rename from raven/src/main/java/com/getsentry/raven/connection/EventSendFailureCallback.java rename to sentry/src/main/java/io/sentry/connection/EventSendFailureCallback.java index 2bfa3490559..da57575122c 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/EventSendFailureCallback.java +++ b/sentry/src/main/java/io/sentry/connection/EventSendFailureCallback.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; -import com.getsentry.raven.event.Event; +import io.sentry.event.Event; /** * Callback that is called when an exception occurs while attempting to diff --git a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java b/sentry/src/main/java/io/sentry/connection/HttpConnection.java similarity index 96% rename from raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java rename to sentry/src/main/java/io/sentry/connection/HttpConnection.java index d9f9e2f81f6..d0535fb3fdc 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/HttpConnection.java +++ b/sentry/src/main/java/io/sentry/connection/HttpConnection.java @@ -1,8 +1,8 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; -import com.getsentry.raven.environment.RavenEnvironment; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.marshaller.Marshaller; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Event; +import io.sentry.marshaller.Marshaller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -125,7 +125,7 @@ protected HttpURLConnection getConnection() { connection.setRequestMethod("POST"); connection.setDoOutput(true); connection.setConnectTimeout(timeout); - connection.setRequestProperty(USER_AGENT, RavenEnvironment.getRavenName()); + connection.setRequestProperty(USER_AGENT, SentryEnvironment.getSentryName()); connection.setRequestProperty(SENTRY_AUTH, getAuthHeader()); return connection; } catch (IOException e) { diff --git a/raven/src/main/java/com/getsentry/raven/connection/LockdownManager.java b/sentry/src/main/java/io/sentry/connection/LockdownManager.java similarity index 96% rename from raven/src/main/java/com/getsentry/raven/connection/LockdownManager.java rename to sentry/src/main/java/io/sentry/connection/LockdownManager.java index 4cc7cd89c5d..2f414b582b6 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/LockdownManager.java +++ b/sentry/src/main/java/io/sentry/connection/LockdownManager.java @@ -1,7 +1,7 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; -import com.getsentry.raven.time.Clock; -import com.getsentry.raven.time.SystemClock; +import io.sentry.time.Clock; +import io.sentry.time.SystemClock; import java.util.Date; import java.util.concurrent.TimeUnit; diff --git a/raven/src/main/java/com/getsentry/raven/connection/LockedDownException.java b/sentry/src/main/java/io/sentry/connection/LockedDownException.java similarity index 89% rename from raven/src/main/java/com/getsentry/raven/connection/LockedDownException.java rename to sentry/src/main/java/io/sentry/connection/LockedDownException.java index 78e1ec90375..41f661db578 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/LockedDownException.java +++ b/sentry/src/main/java/io/sentry/connection/LockedDownException.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; /** * Exception thrown when attempting to send Events while in a lockdown. diff --git a/raven/src/main/java/com/getsentry/raven/connection/NoopConnection.java b/sentry/src/main/java/io/sentry/connection/NoopConnection.java similarity index 84% rename from raven/src/main/java/com/getsentry/raven/connection/NoopConnection.java rename to sentry/src/main/java/io/sentry/connection/NoopConnection.java index f492badd707..e1592ebcc72 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/NoopConnection.java +++ b/sentry/src/main/java/io/sentry/connection/NoopConnection.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; -import com.getsentry.raven.event.Event; +import io.sentry.event.Event; import java.io.IOException; diff --git a/raven/src/main/java/com/getsentry/raven/connection/OutputStreamConnection.java b/sentry/src/main/java/io/sentry/connection/OutputStreamConnection.java similarity index 86% rename from raven/src/main/java/com/getsentry/raven/connection/OutputStreamConnection.java rename to sentry/src/main/java/io/sentry/connection/OutputStreamConnection.java index 14a638bec0f..901ab4cfc37 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/OutputStreamConnection.java +++ b/sentry/src/main/java/io/sentry/connection/OutputStreamConnection.java @@ -1,7 +1,7 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.marshaller.Marshaller; +import io.sentry.event.Event; +import io.sentry.marshaller.Marshaller; import java.io.IOException; import java.io.OutputStream; @@ -28,7 +28,7 @@ public OutputStreamConnection(OutputStream outputStream) { @Override protected synchronized void doSend(Event event) throws ConnectionException { try { - outputStream.write("Raven event:\n".getBytes(UTF_8)); + outputStream.write("Sentry event:\n".getBytes(UTF_8)); marshaller.marshall(event, outputStream); outputStream.write("\n".getBytes(UTF_8)); outputStream.flush(); diff --git a/raven/src/main/java/com/getsentry/raven/connection/ProxyAuthenticator.java b/sentry/src/main/java/io/sentry/connection/ProxyAuthenticator.java similarity index 94% rename from raven/src/main/java/com/getsentry/raven/connection/ProxyAuthenticator.java rename to sentry/src/main/java/io/sentry/connection/ProxyAuthenticator.java index 08b787e3e91..a0aa536aeb7 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/ProxyAuthenticator.java +++ b/sentry/src/main/java/io/sentry/connection/ProxyAuthenticator.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; import java.net.Authenticator; import java.net.PasswordAuthentication; diff --git a/raven/src/main/java/com/getsentry/raven/connection/RandomEventSampler.java b/sentry/src/main/java/io/sentry/connection/RandomEventSampler.java similarity index 95% rename from raven/src/main/java/com/getsentry/raven/connection/RandomEventSampler.java rename to sentry/src/main/java/io/sentry/connection/RandomEventSampler.java index adb4f31f824..f6c047e35cb 100644 --- a/raven/src/main/java/com/getsentry/raven/connection/RandomEventSampler.java +++ b/sentry/src/main/java/io/sentry/connection/RandomEventSampler.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; -import com.getsentry.raven.event.Event; +import io.sentry.event.Event; import java.util.Random; diff --git a/raven/src/main/java/com/getsentry/raven/context/Context.java b/sentry/src/main/java/io/sentry/context/Context.java similarity index 91% rename from raven/src/main/java/com/getsentry/raven/context/Context.java rename to sentry/src/main/java/io/sentry/context/Context.java index 5ecde6fb1e1..4d9284991de 100644 --- a/raven/src/main/java/com/getsentry/raven/context/Context.java +++ b/sentry/src/main/java/io/sentry/context/Context.java @@ -1,8 +1,8 @@ -package com.getsentry.raven.context; +package io.sentry.context; -import com.getsentry.raven.event.Breadcrumb; -import com.getsentry.raven.event.User; -import com.getsentry.raven.util.CircularFifoQueue; +import io.sentry.event.Breadcrumb; +import io.sentry.event.User; +import io.sentry.util.CircularFifoQueue; import java.util.Iterator; import java.util.UUID; @@ -88,7 +88,7 @@ public void setLastEventId(UUID id) { * Get the UUID of the last event sent by this thread, useful for handling user feedback. * * Returns null if no event has been sent by this thread or if the event has been - * cleared. For example the RavenServletRequestListener clears the thread's Context + * cleared. For example the SentryServletRequestListener clears the thread's Context * at the end of each request. * * @return UUID of the last event sent by this thread. diff --git a/raven/src/main/java/com/getsentry/raven/context/ContextManager.java b/sentry/src/main/java/io/sentry/context/ContextManager.java similarity index 84% rename from raven/src/main/java/com/getsentry/raven/context/ContextManager.java rename to sentry/src/main/java/io/sentry/context/ContextManager.java index 4b4ba08dffa..f180a087a47 100644 --- a/raven/src/main/java/com/getsentry/raven/context/ContextManager.java +++ b/sentry/src/main/java/io/sentry/context/ContextManager.java @@ -1,8 +1,8 @@ -package com.getsentry.raven.context; +package io.sentry.context; /** * Context manager implementations define how context-specific data, such as - * {@link com.getsentry.raven.event.Breadcrumb}s are coupled to the idea of + * {@link io.sentry.event.Breadcrumb}s are coupled to the idea of * a "context." What "context" means depends on the application. For example, * most web applications would define a single request/response cycle as a context, * while an Android application would define the entire application run as a diff --git a/raven/src/main/java/com/getsentry/raven/context/SingletonContextManager.java b/sentry/src/main/java/io/sentry/context/SingletonContextManager.java similarity index 92% rename from raven/src/main/java/com/getsentry/raven/context/SingletonContextManager.java rename to sentry/src/main/java/io/sentry/context/SingletonContextManager.java index b7c31a3a2bb..ce43d4b362d 100644 --- a/raven/src/main/java/com/getsentry/raven/context/SingletonContextManager.java +++ b/sentry/src/main/java/io/sentry/context/SingletonContextManager.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.context; +package io.sentry.context; /** * {@link ContextManager} that maintains a single {@link Context} instance diff --git a/raven/src/main/java/com/getsentry/raven/context/ThreadLocalContextManager.java b/sentry/src/main/java/io/sentry/context/ThreadLocalContextManager.java similarity index 94% rename from raven/src/main/java/com/getsentry/raven/context/ThreadLocalContextManager.java rename to sentry/src/main/java/io/sentry/context/ThreadLocalContextManager.java index 19e45272797..2c505a14ebd 100644 --- a/raven/src/main/java/com/getsentry/raven/context/ThreadLocalContextManager.java +++ b/sentry/src/main/java/io/sentry/context/ThreadLocalContextManager.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.context; +package io.sentry.context; /** * A {@link ContextManager} that returns a unique {@link Context} instance per thread. diff --git a/raven/src/main/java/com/getsentry/raven/dsn/Dsn.java b/sentry/src/main/java/io/sentry/dsn/Dsn.java similarity index 99% rename from raven/src/main/java/com/getsentry/raven/dsn/Dsn.java rename to sentry/src/main/java/io/sentry/dsn/Dsn.java index 8ea4979215b..c2b2d9f29c0 100644 --- a/raven/src/main/java/com/getsentry/raven/dsn/Dsn.java +++ b/sentry/src/main/java/io/sentry/dsn/Dsn.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.dsn; +package io.sentry.dsn; -import com.getsentry.raven.config.Lookup; +import io.sentry.config.Lookup; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/raven/src/main/java/com/getsentry/raven/dsn/InvalidDsnException.java b/sentry/src/main/java/io/sentry/dsn/InvalidDsnException.java similarity index 95% rename from raven/src/main/java/com/getsentry/raven/dsn/InvalidDsnException.java rename to sentry/src/main/java/io/sentry/dsn/InvalidDsnException.java index b7a99076946..f097cdefaa6 100644 --- a/raven/src/main/java/com/getsentry/raven/dsn/InvalidDsnException.java +++ b/sentry/src/main/java/io/sentry/dsn/InvalidDsnException.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.dsn; +package io.sentry.dsn; /** * Exception thrown whenever the given {@link Dsn} as been detected as invalid. diff --git a/sentry/src/main/java/io/sentry/environment/SentryEnvironment.java b/sentry/src/main/java/io/sentry/environment/SentryEnvironment.java new file mode 100644 index 00000000000..dbe7d615508 --- /dev/null +++ b/sentry/src/main/java/io/sentry/environment/SentryEnvironment.java @@ -0,0 +1,97 @@ +package io.sentry.environment; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ResourceBundle; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Manages environment information on Sentry. + *

    + * Manages information related to Sentry Runtime such as the name of the library or + * whether or not the thread is managed by Sentry. + */ +public final class SentryEnvironment { + /** + * Name of this SDK. + */ + public static final String SDK_NAME = "sentry-java"; + /** + * Version of this SDK. + */ + public static final String SDK_VERSION = ResourceBundle.getBundle("sentry-build").getString("build.name"); + /** + * Indicates whether the current thread is managed by sentry or not. + */ + protected static final ThreadLocal SENTRY_THREAD = new ThreadLocal() { + @Override + protected AtomicInteger initialValue() { + return new AtomicInteger(); + } + }; + private static final Logger logger = LoggerFactory.getLogger(SentryEnvironment.class); + + private SentryEnvironment() { + } + + /** + * Sets the current thread as managed by Sentry. + *

    + * The logs generated by Threads managed by Sentry will not send logs to Sentry. + *

    + * Recommended usage: + *

    {@code
    +     * SentryEnvironment.startManagingThread();
    +     * try {
    +     *     // Some code that shouldn't generate Sentry logs.
    +     * } finally {
    +     *     SentryEnvironment.stopManagingThread();
    +     * }
    +     * }
    + */ + public static void startManagingThread() { + try { + if (isManagingThread()) { + logger.warn("Thread already managed by Sentry"); + } + } finally { + SENTRY_THREAD.get().incrementAndGet(); + } + } + + /** + * Sets the current thread as not managed by Sentry. + *

    + * The logs generated by Threads not managed by Sentry will send logs to Sentry. + */ + public static void stopManagingThread() { + try { + if (!isManagingThread()) { + //Start managing the thread only to send the warning + startManagingThread(); + logger.warn("Thread not yet managed by Sentry"); + } + } finally { + SENTRY_THREAD.get().decrementAndGet(); + } + } + + /** + * Checks whether the current thread is managed by Sentry or not. + * + * @return {@code true} if the thread is managed by Sentry, {@code false} otherwise. + */ + public static boolean isManagingThread() { + return SENTRY_THREAD.get().get() > 0; + } + + /** + * Returns sdk name+version string, used for HTTP User Agent, sentry_client, etc. + * + * @return Sentry sdk string + */ + public static String getSentryName() { + return SDK_NAME + "/" + SDK_VERSION; + } +} diff --git a/raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java b/sentry/src/main/java/io/sentry/event/Breadcrumb.java similarity index 99% rename from raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java rename to sentry/src/main/java/io/sentry/event/Breadcrumb.java index 62f771ad569..0105db2d580 100644 --- a/raven/src/main/java/com/getsentry/raven/event/Breadcrumb.java +++ b/sentry/src/main/java/io/sentry/event/Breadcrumb.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.event; +package io.sentry.event; import java.io.Serializable; import java.util.Date; diff --git a/raven/src/main/java/com/getsentry/raven/event/BreadcrumbBuilder.java b/sentry/src/main/java/io/sentry/event/BreadcrumbBuilder.java similarity index 98% rename from raven/src/main/java/com/getsentry/raven/event/BreadcrumbBuilder.java rename to sentry/src/main/java/io/sentry/event/BreadcrumbBuilder.java index cd929d2293c..6091ad7615e 100644 --- a/raven/src/main/java/com/getsentry/raven/event/BreadcrumbBuilder.java +++ b/sentry/src/main/java/io/sentry/event/BreadcrumbBuilder.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.event; +package io.sentry.event; import java.util.Date; import java.util.Map; diff --git a/raven/src/main/java/com/getsentry/raven/event/Breadcrumbs.java b/sentry/src/main/java/io/sentry/event/Breadcrumbs.java similarity index 74% rename from raven/src/main/java/com/getsentry/raven/event/Breadcrumbs.java rename to sentry/src/main/java/io/sentry/event/Breadcrumbs.java index 755801fc0d0..ae02d9dc414 100644 --- a/raven/src/main/java/com/getsentry/raven/event/Breadcrumbs.java +++ b/sentry/src/main/java/io/sentry/event/Breadcrumbs.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.event; +package io.sentry.event; -import com.getsentry.raven.Raven; +import io.sentry.Sentry; /** * Helpers for dealing with {@link Breadcrumb}s. @@ -20,7 +20,7 @@ private Breadcrumbs() { * @param breadcrumb Breadcrumb to record */ public static void record(Breadcrumb breadcrumb) { - Raven.getStoredInstance().getContext().recordBreadcrumb(breadcrumb); + Sentry.getStoredInstance().getContext().recordBreadcrumb(breadcrumb); } } diff --git a/raven/src/main/java/com/getsentry/raven/event/Event.java b/sentry/src/main/java/io/sentry/event/Event.java similarity index 99% rename from raven/src/main/java/com/getsentry/raven/event/Event.java rename to sentry/src/main/java/io/sentry/event/Event.java index 57ec748e06e..342ea344b58 100644 --- a/raven/src/main/java/com/getsentry/raven/event/Event.java +++ b/sentry/src/main/java/io/sentry/event/Event.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.event; +package io.sentry.event; -import com.getsentry.raven.event.interfaces.SentryInterface; +import io.sentry.event.interfaces.SentryInterface; import java.io.IOException; import java.io.ObjectInputStream; diff --git a/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java b/sentry/src/main/java/io/sentry/event/EventBuilder.java similarity index 98% rename from raven/src/main/java/com/getsentry/raven/event/EventBuilder.java rename to sentry/src/main/java/io/sentry/event/EventBuilder.java index bb0fea27854..77401139854 100644 --- a/raven/src/main/java/com/getsentry/raven/event/EventBuilder.java +++ b/sentry/src/main/java/io/sentry/event/EventBuilder.java @@ -1,7 +1,7 @@ -package com.getsentry.raven.event; +package io.sentry.event; -import com.getsentry.raven.environment.RavenEnvironment; -import com.getsentry.raven.event.interfaces.SentryInterface; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.interfaces.SentryInterface; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -86,10 +86,10 @@ private static void autoSetMissingValues(Event event) { // Ensure that an SDK is set if (event.getSdkName() == null) { - event.setSdkName(RavenEnvironment.SDK_NAME); + event.setSdkName(SentryEnvironment.SDK_NAME); } if (event.getSdkVersion() == null) { - event.setSdkVersion(RavenEnvironment.SDK_VERSION); + event.setSdkVersion(SentryEnvironment.SDK_VERSION); } // Ensure that a hostname is set diff --git a/raven/src/main/java/com/getsentry/raven/event/User.java b/sentry/src/main/java/io/sentry/event/User.java similarity index 96% rename from raven/src/main/java/com/getsentry/raven/event/User.java rename to sentry/src/main/java/io/sentry/event/User.java index 08962994144..3c99a9e88a2 100644 --- a/raven/src/main/java/com/getsentry/raven/event/User.java +++ b/sentry/src/main/java/io/sentry/event/User.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.event; +package io.sentry.event; /** diff --git a/raven/src/main/java/com/getsentry/raven/event/UserBuilder.java b/sentry/src/main/java/io/sentry/event/UserBuilder.java similarity index 97% rename from raven/src/main/java/com/getsentry/raven/event/UserBuilder.java rename to sentry/src/main/java/io/sentry/event/UserBuilder.java index 8b2dbdffd35..01177d4fedf 100644 --- a/raven/src/main/java/com/getsentry/raven/event/UserBuilder.java +++ b/sentry/src/main/java/io/sentry/event/UserBuilder.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.event; +package io.sentry.event; /** * Builder to assist with the creation of {@link User}s. diff --git a/raven/src/main/java/com/getsentry/raven/event/helper/BasicRemoteAddressResolver.java b/sentry/src/main/java/io/sentry/event/helper/BasicRemoteAddressResolver.java similarity index 92% rename from raven/src/main/java/com/getsentry/raven/event/helper/BasicRemoteAddressResolver.java rename to sentry/src/main/java/io/sentry/event/helper/BasicRemoteAddressResolver.java index 155337f81b6..f9ac01db54c 100644 --- a/raven/src/main/java/com/getsentry/raven/event/helper/BasicRemoteAddressResolver.java +++ b/sentry/src/main/java/io/sentry/event/helper/BasicRemoteAddressResolver.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.event.helper; +package io.sentry.event.helper; import javax.servlet.http.HttpServletRequest; diff --git a/raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java b/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java similarity index 65% rename from raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java rename to sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java index cd84f5b9de9..23fcbce5af1 100644 --- a/raven/src/main/java/com/getsentry/raven/event/helper/ContextBuilderHelper.java +++ b/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java @@ -1,11 +1,11 @@ -package com.getsentry.raven.event.helper; +package io.sentry.event.helper; -import com.getsentry.raven.Raven; -import com.getsentry.raven.context.Context; -import com.getsentry.raven.event.Breadcrumb; -import com.getsentry.raven.event.EventBuilder; -import com.getsentry.raven.event.User; -import com.getsentry.raven.event.interfaces.UserInterface; +import io.sentry.Sentry; +import io.sentry.context.Context; +import io.sentry.event.Breadcrumb; +import io.sentry.event.EventBuilder; +import io.sentry.event.User; +import io.sentry.event.interfaces.UserInterface; import java.util.ArrayList; import java.util.Iterator; @@ -13,28 +13,28 @@ /** * {@link EventBuilderHelper} that extracts and sends any data attached to the - * provided {@link Raven}'s {@link Context}. + * provided {@link Sentry}'s {@link Context}. */ public class ContextBuilderHelper implements EventBuilderHelper { /** - * Raven object where the Context comes from. + * Sentry object where the Context comes from. */ - private Raven raven; + private Sentry sentry; /** - * {@link EventBuilderHelper} that extracts context data from the provided {@link Raven} client. + * {@link EventBuilderHelper} that extracts context data from the provided {@link Sentry} client. * - * @param raven Raven client which holds Context to be used. + * @param sentry Sentry client which holds Context to be used. */ - public ContextBuilderHelper(Raven raven) { - this.raven = raven; + public ContextBuilderHelper(Sentry sentry) { + this.sentry = sentry; } @Override public void helpBuildingEvent(EventBuilder eventBuilder) { List breadcrumbs = new ArrayList<>(); - Context context = raven.getContext(); + Context context = sentry.getContext(); Iterator breadcrumbIterator = context.getBreadcrumbs(); while (breadcrumbIterator.hasNext()) { diff --git a/raven/src/main/java/com/getsentry/raven/event/helper/EventBuilderHelper.java b/sentry/src/main/java/io/sentry/event/helper/EventBuilderHelper.java similarity index 81% rename from raven/src/main/java/com/getsentry/raven/event/helper/EventBuilderHelper.java rename to sentry/src/main/java/io/sentry/event/helper/EventBuilderHelper.java index 49bcc8a95c0..3f893f90a95 100644 --- a/raven/src/main/java/com/getsentry/raven/event/helper/EventBuilderHelper.java +++ b/sentry/src/main/java/io/sentry/event/helper/EventBuilderHelper.java @@ -1,10 +1,10 @@ -package com.getsentry.raven.event.helper; +package io.sentry.event.helper; -import com.getsentry.raven.event.EventBuilder; +import io.sentry.event.EventBuilder; /** * Helper allowing to add extra information to the {@link EventBuilder} before creating the - * {@link com.getsentry.raven.event.Event} itself. + * {@link io.sentry.event.Event} itself. */ public interface EventBuilderHelper { /** diff --git a/raven/src/main/java/com/getsentry/raven/event/helper/ForwardedAddressResolver.java b/sentry/src/main/java/io/sentry/event/helper/ForwardedAddressResolver.java similarity index 93% rename from raven/src/main/java/com/getsentry/raven/event/helper/ForwardedAddressResolver.java rename to sentry/src/main/java/io/sentry/event/helper/ForwardedAddressResolver.java index 637fca57d93..fc1abcbc629 100644 --- a/raven/src/main/java/com/getsentry/raven/event/helper/ForwardedAddressResolver.java +++ b/sentry/src/main/java/io/sentry/event/helper/ForwardedAddressResolver.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.event.helper; +package io.sentry.event.helper; -import com.getsentry.raven.util.Util; +import io.sentry.util.Util; import javax.servlet.http.HttpServletRequest; import java.util.Arrays; diff --git a/raven/src/main/java/com/getsentry/raven/event/helper/HttpEventBuilderHelper.java b/sentry/src/main/java/io/sentry/event/helper/HttpEventBuilderHelper.java similarity index 85% rename from raven/src/main/java/com/getsentry/raven/event/helper/HttpEventBuilderHelper.java rename to sentry/src/main/java/io/sentry/event/helper/HttpEventBuilderHelper.java index 22dfd235692..47a96587102 100644 --- a/raven/src/main/java/com/getsentry/raven/event/helper/HttpEventBuilderHelper.java +++ b/sentry/src/main/java/io/sentry/event/helper/HttpEventBuilderHelper.java @@ -1,9 +1,9 @@ -package com.getsentry.raven.event.helper; +package io.sentry.event.helper; -import com.getsentry.raven.event.EventBuilder; -import com.getsentry.raven.event.interfaces.HttpInterface; -import com.getsentry.raven.event.interfaces.UserInterface; -import com.getsentry.raven.servlet.RavenServletRequestListener; +import io.sentry.event.EventBuilder; +import io.sentry.event.interfaces.HttpInterface; +import io.sentry.event.interfaces.UserInterface; +import io.sentry.servlet.SentryServletRequestListener; import javax.servlet.http.HttpServletRequest; @@ -35,7 +35,7 @@ public HttpEventBuilderHelper(RemoteAddressResolver remoteAddressResolver) { @Override public void helpBuildingEvent(EventBuilder eventBuilder) { - HttpServletRequest servletRequest = RavenServletRequestListener.getServletRequest(); + HttpServletRequest servletRequest = SentryServletRequestListener.getServletRequest(); if (servletRequest == null) { return; } diff --git a/raven/src/main/java/com/getsentry/raven/event/helper/RemoteAddressResolver.java b/sentry/src/main/java/io/sentry/event/helper/RemoteAddressResolver.java similarity index 79% rename from raven/src/main/java/com/getsentry/raven/event/helper/RemoteAddressResolver.java rename to sentry/src/main/java/io/sentry/event/helper/RemoteAddressResolver.java index 73e0d50a142..13c1c488231 100644 --- a/raven/src/main/java/com/getsentry/raven/event/helper/RemoteAddressResolver.java +++ b/sentry/src/main/java/io/sentry/event/helper/RemoteAddressResolver.java @@ -1,10 +1,10 @@ -package com.getsentry.raven.event.helper; +package io.sentry.event.helper; import javax.servlet.http.HttpServletRequest; /** * Interface that allows users to define how the REMOTE_ADDR - * is set on each {@link com.getsentry.raven.event.Event}. + * is set on each {@link io.sentry.event.Event}. */ public interface RemoteAddressResolver { diff --git a/raven/src/main/java/com/getsentry/raven/event/interfaces/ExceptionInterface.java b/sentry/src/main/java/io/sentry/event/interfaces/ExceptionInterface.java similarity index 97% rename from raven/src/main/java/com/getsentry/raven/event/interfaces/ExceptionInterface.java rename to sentry/src/main/java/io/sentry/event/interfaces/ExceptionInterface.java index 8b8815d0fb5..a88d61a6bb7 100644 --- a/raven/src/main/java/com/getsentry/raven/event/interfaces/ExceptionInterface.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/ExceptionInterface.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.event.interfaces; +package io.sentry.event.interfaces; import java.util.Deque; diff --git a/raven/src/main/java/com/getsentry/raven/event/interfaces/HttpInterface.java b/sentry/src/main/java/io/sentry/event/interfaces/HttpInterface.java similarity index 95% rename from raven/src/main/java/com/getsentry/raven/event/interfaces/HttpInterface.java rename to sentry/src/main/java/io/sentry/event/interfaces/HttpInterface.java index 8ef96aeb5da..a2957c17646 100644 --- a/raven/src/main/java/com/getsentry/raven/event/interfaces/HttpInterface.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/HttpInterface.java @@ -1,7 +1,7 @@ -package com.getsentry.raven.event.interfaces; +package io.sentry.event.interfaces; -import com.getsentry.raven.event.helper.BasicRemoteAddressResolver; -import com.getsentry.raven.event.helper.RemoteAddressResolver; +import io.sentry.event.helper.BasicRemoteAddressResolver; +import io.sentry.event.helper.RemoteAddressResolver; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; @@ -44,7 +44,7 @@ public HttpInterface(HttpServletRequest request) { } /** - * Creates an HTTP element for an {@link com.getsentry.raven.event.Event}. + * Creates an HTTP element for an {@link io.sentry.event.Event}. * * @param request Captured HTTP request to send to Sentry. * @param remoteAddressResolver RemoteAddressResolver @@ -54,7 +54,7 @@ public HttpInterface(HttpServletRequest request, RemoteAddressResolver remoteAdd } /** - * Creates an HTTP element for an {@link com.getsentry.raven.event.Event}. + * Creates an HTTP element for an {@link io.sentry.event.Event}. * * @param request Captured HTTP request to send to Sentry. * @param remoteAddressResolver RemoteAddressResolver diff --git a/raven/src/main/java/com/getsentry/raven/event/interfaces/MessageInterface.java b/sentry/src/main/java/io/sentry/event/interfaces/MessageInterface.java similarity index 86% rename from raven/src/main/java/com/getsentry/raven/event/interfaces/MessageInterface.java rename to sentry/src/main/java/io/sentry/event/interfaces/MessageInterface.java index bed4abb2260..aee19748a67 100644 --- a/raven/src/main/java/com/getsentry/raven/event/interfaces/MessageInterface.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/MessageInterface.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.event.interfaces; +package io.sentry.event.interfaces; import java.util.ArrayList; import java.util.Arrays; @@ -11,7 +11,7 @@ * by original message (rather than the formatted version). *

    * Sentry's ability to regroup event with the same messages is based on the content of the message, meaning that an - * {@link com.getsentry.raven.event.Event} with the message "User1 failed to provide an email address" + * {@link io.sentry.event.Event} with the message "User1 failed to provide an email address" * won't be grouped with an Event with the message "User2 failed to provide an email address". *

    * To allow this kind of grouping, sentry supports the message interface which will provide both the pattern of the @@ -21,7 +21,7 @@ * This way, Sentry will be able to put the two events in the same category. *

    * Note: Sentry won't attempt to format the message, this is why the formatted message should be set through - * {@link com.getsentry.raven.event.EventBuilder#withMessage(String)} in any case. + * {@link io.sentry.event.EventBuilder#withMessage(String)} in any case. */ public class MessageInterface implements SentryInterface { /** @@ -36,7 +36,7 @@ public class MessageInterface implements SentryInterface { * Creates a non parametrised message. *

    * While it's technically possible to create a non parametrised message with {@code MessageInterface}, it's - * recommended to use {@link com.getsentry.raven.event.EventBuilder#withMessage(String)} instead. + * recommended to use {@link io.sentry.event.EventBuilder#withMessage(String)} instead. * * @param message message to add to the event. */ @@ -45,7 +45,7 @@ public MessageInterface(String message) { } /** - * Creates a parametrised message for an {@link com.getsentry.raven.event.Event}. + * Creates a parametrised message for an {@link io.sentry.event.Event}. * * @param message original message. * @param params parameters of the message. @@ -55,7 +55,7 @@ public MessageInterface(String message, String... params) { } /** - * Creates a parametrised message for an {@link com.getsentry.raven.event.Event}. + * Creates a parametrised message for an {@link io.sentry.event.Event}. * * @param message original message. * @param parameters parameters of the message. @@ -65,7 +65,7 @@ public MessageInterface(String message, List parameters) { } /** - * Creates a parametrised message for an {@link com.getsentry.raven.event.Event}. + * Creates a parametrised message for an {@link io.sentry.event.Event}. * * @param message original message. * @param parameters parameters of the message. diff --git a/raven/src/main/java/com/getsentry/raven/event/interfaces/SentryException.java b/sentry/src/main/java/io/sentry/event/interfaces/SentryException.java similarity index 99% rename from raven/src/main/java/com/getsentry/raven/event/interfaces/SentryException.java rename to sentry/src/main/java/io/sentry/event/interfaces/SentryException.java index 0a4bdff8153..80626666866 100644 --- a/raven/src/main/java/com/getsentry/raven/event/interfaces/SentryException.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/SentryException.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.event.interfaces; +package io.sentry.event.interfaces; import java.io.Serializable; import java.util.ArrayDeque; diff --git a/raven/src/main/java/com/getsentry/raven/event/interfaces/SentryInterface.java b/sentry/src/main/java/io/sentry/event/interfaces/SentryInterface.java similarity index 87% rename from raven/src/main/java/com/getsentry/raven/event/interfaces/SentryInterface.java rename to sentry/src/main/java/io/sentry/event/interfaces/SentryInterface.java index 30b907354d1..519e14a58f7 100644 --- a/raven/src/main/java/com/getsentry/raven/event/interfaces/SentryInterface.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/SentryInterface.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.event.interfaces; +package io.sentry.event.interfaces; import java.io.Serializable; diff --git a/raven/src/main/java/com/getsentry/raven/event/interfaces/StackTraceInterface.java b/sentry/src/main/java/io/sentry/event/interfaces/StackTraceInterface.java similarity index 92% rename from raven/src/main/java/com/getsentry/raven/event/interfaces/StackTraceInterface.java rename to sentry/src/main/java/io/sentry/event/interfaces/StackTraceInterface.java index 5a1e23a44d6..37bca28a177 100644 --- a/raven/src/main/java/com/getsentry/raven/event/interfaces/StackTraceInterface.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/StackTraceInterface.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.event.interfaces; +package io.sentry.event.interfaces; import java.util.Arrays; @@ -14,7 +14,7 @@ public class StackTraceInterface implements SentryInterface { private final int framesCommonWithEnclosing; /** - * Creates a StackTrace for an {@link com.getsentry.raven.event.Event}. + * Creates a StackTrace for an {@link io.sentry.event.Event}. * * @param stackTrace StackTrace to provide to Sentry. */ @@ -23,7 +23,7 @@ public StackTraceInterface(StackTraceElement[] stackTrace) { } /** - * Creates a StackTrace for an {@link com.getsentry.raven.event.Event}. + * Creates a StackTrace for an {@link io.sentry.event.Event}. *

    * With the help of the enclosing StackTrace, figure out which frames are in common with the parent exception * to potentially hide them later in Sentry. diff --git a/raven/src/main/java/com/getsentry/raven/event/interfaces/UserInterface.java b/sentry/src/main/java/io/sentry/event/interfaces/UserInterface.java similarity index 97% rename from raven/src/main/java/com/getsentry/raven/event/interfaces/UserInterface.java rename to sentry/src/main/java/io/sentry/event/interfaces/UserInterface.java index 7674bdf4f00..e8240d788d3 100644 --- a/raven/src/main/java/com/getsentry/raven/event/interfaces/UserInterface.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/UserInterface.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.event.interfaces; +package io.sentry.event.interfaces; /** * The User interface for Sentry allows to send details about the User currently using the application. diff --git a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java b/sentry/src/main/java/io/sentry/jul/SentryHandler.java similarity index 83% rename from raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java rename to sentry/src/main/java/io/sentry/jul/SentryHandler.java index e8ba2b14451..7c7451a7c92 100644 --- a/raven/src/main/java/com/getsentry/raven/jul/SentryHandler.java +++ b/sentry/src/main/java/io/sentry/jul/SentryHandler.java @@ -1,16 +1,16 @@ -package com.getsentry.raven.jul; - -import com.getsentry.raven.Raven; -import com.getsentry.raven.RavenFactory; -import com.getsentry.raven.config.Lookup; -import com.getsentry.raven.dsn.Dsn; -import com.getsentry.raven.dsn.InvalidDsnException; -import com.getsentry.raven.environment.RavenEnvironment; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.EventBuilder; -import com.getsentry.raven.event.interfaces.ExceptionInterface; -import com.getsentry.raven.event.interfaces.MessageInterface; -import com.getsentry.raven.util.Util; +package io.sentry.jul; + +import io.sentry.Sentry; +import io.sentry.SentryFactory; +import io.sentry.config.Lookup; +import io.sentry.dsn.Dsn; +import io.sentry.dsn.InvalidDsnException; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; +import io.sentry.event.interfaces.ExceptionInterface; +import io.sentry.event.interfaces.MessageInterface; +import io.sentry.util.Util; import org.slf4j.MDC; import java.text.MessageFormat; @@ -34,13 +34,13 @@ public class SentryHandler extends Handler { /** * Name of the {@link Event#extra} property containing the Thread id. */ - public static final String THREAD_ID = "Raven-ThreadId"; + public static final String THREAD_ID = "Sentry-ThreadId"; /** - * Current instance of {@link Raven}. + * Current instance of {@link Sentry}. * - * @see #initRaven() + * @see #initSentry() */ - protected volatile Raven raven; + protected volatile Sentry sentry; /** * DSN property of the appender. *

    @@ -54,11 +54,11 @@ public class SentryHandler extends Handler { */ protected boolean printfStyle; /** - * Name of the {@link RavenFactory} being used. + * Name of the {@link SentryFactory} being used. *

    * Might be null in which case the factory should be defined automatically. */ - protected String ravenFactory; + protected String sentryFactory; /** * Identifies the version of the application. *

    @@ -97,17 +97,17 @@ public class SentryHandler extends Handler { */ public SentryHandler() { retrieveProperties(); - this.setFilter(new DropRavenFilter()); + this.setFilter(new DropSentryFilter()); } /** * Creates an instance of SentryHandler. * - * @param raven instance of Raven to use with this appender. + * @param sentry instance of Sentry to use with this appender. */ - public SentryHandler(Raven raven) { + public SentryHandler(Sentry sentry) { this(); - this.raven = raven; + this.sentry = sentry; } /** @@ -122,9 +122,9 @@ private void lazyInit() { if (!initialized) { try { - String ravenFactory = Lookup.lookup("ravenFactory"); - if (ravenFactory != null) { - setRavenFactory(ravenFactory); + String sentryFactory = Lookup.lookup("sentryFactory"); + if (sentryFactory != null) { + setSentryFactory(sentryFactory); } String release = Lookup.lookup("release"); @@ -158,8 +158,8 @@ private void lazyInit() { } } - if (raven == null) { - initRaven(); + if (sentry == null) { + initSentry(); } } @@ -167,7 +167,7 @@ private void lazyInit() { * Transforms a {@link Level} into an {@link Event.Level}. * * @param level original level as defined in JUL. - * @return log level used within raven. + * @return log level used within sentry. */ protected static Event.Level getLevel(Level level) { if (level.intValue() >= Level.SEVERE.intValue()) { @@ -209,9 +209,9 @@ protected void retrieveProperties() { if (dsnProperty != null) { setDsn(dsnProperty); } - String ravenFactoryProperty = manager.getProperty(className + ".ravenFactory"); - if (ravenFactoryProperty != null) { - setRavenFactory(ravenFactoryProperty); + String sentryFactoryProperty = manager.getProperty(className + ".sentryFactory"); + if (sentryFactoryProperty != null) { + setSentryFactory(sentryFactoryProperty); } String releaseProperty = manager.getProperty(className + ".release"); if (releaseProperty != null) { @@ -238,38 +238,38 @@ protected void retrieveProperties() { @Override public void publish(LogRecord record) { - // Do not log the event if the current thread is managed by raven - if (!isLoggable(record) || RavenEnvironment.isManagingThread()) { + // Do not log the event if the current thread is managed by sentry + if (!isLoggable(record) || SentryEnvironment.isManagingThread()) { return; } - RavenEnvironment.startManagingThread(); + SentryEnvironment.startManagingThread(); try { lazyInit(); Event event = buildEvent(record); - raven.sendEvent(event); + sentry.sendEvent(event); } catch (Exception e) { - reportError("An exception occurred while creating a new event in Raven", e, ErrorManager.WRITE_FAILURE); + reportError("An exception occurred while creating a new event in Sentry", e, ErrorManager.WRITE_FAILURE); } finally { - RavenEnvironment.stopManagingThread(); + SentryEnvironment.stopManagingThread(); } } /** - * Initialises the Raven instance. + * Initialises the Sentry instance. */ - protected synchronized void initRaven() { + protected synchronized void initSentry() { try { if (dsn == null) { dsn = Dsn.dsnLookup(); } - raven = RavenFactory.ravenInstance(new Dsn(dsn), ravenFactory); + sentry = SentryFactory.sentryInstance(new Dsn(dsn), sentryFactory); } catch (InvalidDsnException e) { - reportError("An exception occurred during the retrieval of the DSN for Raven", + reportError("An exception occurred during the retrieval of the DSN for Sentry", e, ErrorManager.OPEN_FAILURE); } catch (Exception e) { - reportError("An exception occurred during the creation of a Raven instance", e, ErrorManager.OPEN_FAILURE); + reportError("An exception occurred during the creation of a Sentry instance", e, ErrorManager.OPEN_FAILURE); } } @@ -281,7 +281,7 @@ protected synchronized void initRaven() { */ protected Event buildEvent(LogRecord record) { EventBuilder eventBuilder = new EventBuilder() - .withSdkName(RavenEnvironment.SDK_NAME + ":jul") + .withSdkName(SentryEnvironment.SDK_NAME + ":jul") .withLevel(getLevel(record.getLevel())) .withTimestamp(new Date(record.getMillis())) .withLogger(record.getLoggerName()); @@ -350,7 +350,7 @@ protected Event buildEvent(LogRecord record) { eventBuilder.withServerName(serverName.trim()); } - raven.runBuilderHelpers(eventBuilder); + sentry.runBuilderHelpers(eventBuilder); return eventBuilder.build(); } @@ -378,15 +378,15 @@ public void flush() { @Override public void close() throws SecurityException { - RavenEnvironment.startManagingThread(); + SentryEnvironment.startManagingThread(); try { - if (raven != null) { - raven.closeConnection(); + if (sentry != null) { + sentry.closeConnection(); } } catch (Exception e) { - reportError("An exception occurred while closing the Raven connection", e, ErrorManager.CLOSE_FAILURE); + reportError("An exception occurred while closing the Sentry connection", e, ErrorManager.CLOSE_FAILURE); } finally { - RavenEnvironment.stopManagingThread(); + SentryEnvironment.stopManagingThread(); } } @@ -398,8 +398,8 @@ public void setPrintfStyle(boolean printfStyle) { this.printfStyle = printfStyle; } - public void setRavenFactory(String ravenFactory) { - this.ravenFactory = ravenFactory; + public void setSentryFactory(String sentryFactory) { + this.sentryFactory = sentryFactory; } public void setRelease(String release) { @@ -427,11 +427,11 @@ public void setExtraTags(String extraTags) { this.extraTags = Util.parseExtraTags(extraTags); } - private class DropRavenFilter implements Filter { + private class DropSentryFilter implements Filter { @Override public boolean isLoggable(LogRecord record) { String loggerName = record.getLoggerName(); - return loggerName == null || !loggerName.startsWith("com.getsentry.raven"); + return loggerName == null || !loggerName.startsWith("io.sentry"); } } } diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/Marshaller.java b/sentry/src/main/java/io/sentry/marshaller/Marshaller.java similarity index 95% rename from raven/src/main/java/com/getsentry/raven/marshaller/Marshaller.java rename to sentry/src/main/java/io/sentry/marshaller/Marshaller.java index 41003f7af58..4a5448d1fa9 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/Marshaller.java +++ b/sentry/src/main/java/io/sentry/marshaller/Marshaller.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.marshaller; +package io.sentry.marshaller; -import com.getsentry.raven.event.Event; +import io.sentry.event.Event; import java.io.IOException; import java.io.OutputStream; diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/ExceptionInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/ExceptionInterfaceBinding.java similarity index 91% rename from raven/src/main/java/com/getsentry/raven/marshaller/json/ExceptionInterfaceBinding.java rename to sentry/src/main/java/io/sentry/marshaller/json/ExceptionInterfaceBinding.java index 4dc93e58572..37cd1dc8768 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/ExceptionInterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/ExceptionInterfaceBinding.java @@ -1,9 +1,9 @@ -package com.getsentry.raven.marshaller.json; +package io.sentry.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; -import com.getsentry.raven.event.interfaces.ExceptionInterface; -import com.getsentry.raven.event.interfaces.SentryException; -import com.getsentry.raven.event.interfaces.StackTraceInterface; +import io.sentry.event.interfaces.ExceptionInterface; +import io.sentry.event.interfaces.SentryException; +import io.sentry.event.interfaces.StackTraceInterface; import java.io.IOException; import java.util.Deque; diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/HttpInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/HttpInterfaceBinding.java similarity index 97% rename from raven/src/main/java/com/getsentry/raven/marshaller/json/HttpInterfaceBinding.java rename to sentry/src/main/java/io/sentry/marshaller/json/HttpInterfaceBinding.java index bd75fe1c150..9edf9bfdd25 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/HttpInterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/HttpInterfaceBinding.java @@ -1,8 +1,8 @@ -package com.getsentry.raven.marshaller.json; +package io.sentry.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; -import com.getsentry.raven.event.interfaces.HttpInterface; -import com.getsentry.raven.util.Util; +import io.sentry.event.interfaces.HttpInterface; +import io.sentry.util.Util; import java.io.IOException; import java.util.Collection; diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/InterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/InterfaceBinding.java similarity index 87% rename from raven/src/main/java/com/getsentry/raven/marshaller/json/InterfaceBinding.java rename to sentry/src/main/java/io/sentry/marshaller/json/InterfaceBinding.java index 043f73c0c73..ec5fe72aeb9 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/InterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/InterfaceBinding.java @@ -1,7 +1,7 @@ -package com.getsentry.raven.marshaller.json; +package io.sentry.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; -import com.getsentry.raven.event.interfaces.SentryInterface; +import io.sentry.event.interfaces.SentryInterface; import java.io.IOException; diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java b/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java similarity index 97% rename from raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java rename to sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java index ba79ff08f6a..5e8716cfc6f 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/JsonMarshaller.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java @@ -1,14 +1,14 @@ -package com.getsentry.raven.marshaller.json; +package io.sentry.marshaller.json; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; -import com.getsentry.raven.event.Breadcrumb; -import com.getsentry.raven.util.Base64; -import com.getsentry.raven.util.Base64OutputStream; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.interfaces.SentryInterface; -import com.getsentry.raven.marshaller.Marshaller; -import com.getsentry.raven.util.Util; +import io.sentry.event.Breadcrumb; +import io.sentry.util.Base64; +import io.sentry.util.Base64OutputStream; +import io.sentry.event.Event; +import io.sentry.event.interfaces.SentryInterface; +import io.sentry.marshaller.Marshaller; +import io.sentry.util.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -367,7 +367,7 @@ private String formatLevel(Event.Level level) { case ERROR: return "error"; default: - logger.error("The level '{}' isn't supported, this should NEVER happen, contact Raven developers", + logger.error("The level '{}' isn't supported, this should NEVER happen, contact Sentry developers", level.name()); return null; } diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/MessageInterfaceBinding.java similarity index 92% rename from raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java rename to sentry/src/main/java/io/sentry/marshaller/json/MessageInterfaceBinding.java index 6c24e5216ca..cb4e95a014f 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/MessageInterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/MessageInterfaceBinding.java @@ -1,8 +1,8 @@ -package com.getsentry.raven.marshaller.json; +package io.sentry.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; -import com.getsentry.raven.event.interfaces.MessageInterface; -import com.getsentry.raven.util.Util; +import io.sentry.event.interfaces.MessageInterface; +import io.sentry.util.Util; import java.io.IOException; diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/StackTraceInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java similarity index 96% rename from raven/src/main/java/com/getsentry/raven/marshaller/json/StackTraceInterfaceBinding.java rename to sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java index 4179fdaaaef..6d51bd63f9e 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/StackTraceInterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java @@ -1,7 +1,7 @@ -package com.getsentry.raven.marshaller.json; +package io.sentry.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; -import com.getsentry.raven.event.interfaces.StackTraceInterface; +import io.sentry.event.interfaces.StackTraceInterface; import java.io.IOException; import java.util.Collection; diff --git a/raven/src/main/java/com/getsentry/raven/marshaller/json/UserInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/UserInterfaceBinding.java similarity index 90% rename from raven/src/main/java/com/getsentry/raven/marshaller/json/UserInterfaceBinding.java rename to sentry/src/main/java/io/sentry/marshaller/json/UserInterfaceBinding.java index a637f769026..4748fc46074 100644 --- a/raven/src/main/java/com/getsentry/raven/marshaller/json/UserInterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/UserInterfaceBinding.java @@ -1,7 +1,7 @@ -package com.getsentry.raven.marshaller.json; +package io.sentry.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; -import com.getsentry.raven.event.interfaces.UserInterface; +import io.sentry.event.interfaces.UserInterface; import java.io.IOException; diff --git a/sentry/src/main/java/io/sentry/servlet/SentryServletContainerInitializer.java b/sentry/src/main/java/io/sentry/servlet/SentryServletContainerInitializer.java new file mode 100644 index 00000000000..f668d09ee1d --- /dev/null +++ b/sentry/src/main/java/io/sentry/servlet/SentryServletContainerInitializer.java @@ -0,0 +1,16 @@ +package io.sentry.servlet; + +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import java.util.Set; + +/** + * Servlet container initializer used to add the {@link SentryServletRequestListener} to the {@link ServletContext}. + */ +public class SentryServletContainerInitializer implements ServletContainerInitializer { + @Override + public void onStartup(Set> c, ServletContext ctx) throws ServletException { + ctx.addListener(SentryServletRequestListener.class); + } +} diff --git a/raven/src/main/java/com/getsentry/raven/servlet/RavenServletRequestListener.java b/sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java similarity index 73% rename from raven/src/main/java/com/getsentry/raven/servlet/RavenServletRequestListener.java rename to sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java index 4af257509c4..94f7a79861b 100644 --- a/raven/src/main/java/com/getsentry/raven/servlet/RavenServletRequestListener.java +++ b/sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.servlet; +package io.sentry.servlet; -import com.getsentry.raven.Raven; +import io.sentry.Sentry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,11 +11,11 @@ /** * Request listener in charge of capturing {@link HttpServletRequest} to allow - * {@link com.getsentry.raven.event.helper.HttpEventBuilderHelper} to provide details on the current HTTP session + * {@link io.sentry.event.helper.HttpEventBuilderHelper} to provide details on the current HTTP session * in the event sent to Sentry. */ -public class RavenServletRequestListener implements ServletRequestListener { - private static final Logger logger = LoggerFactory.getLogger(RavenServletRequestListener.class); +public class SentryServletRequestListener implements ServletRequestListener { + private static final Logger logger = LoggerFactory.getLogger(SentryServletRequestListener.class); private static final ThreadLocal THREAD_REQUEST = new ThreadLocal<>(); @@ -28,9 +28,9 @@ public void requestDestroyed(ServletRequestEvent servletRequestEvent) { THREAD_REQUEST.remove(); try { - Raven raven = Raven.getStoredInstance(); - if (raven != null) { - raven.getContext().clear(); + Sentry sentry = Sentry.getStoredInstance(); + if (sentry != null) { + sentry.getContext().clear(); } } catch (Exception e) { logger.error("Error clearing Context state.", e); diff --git a/raven/src/main/java/com/getsentry/raven/time/Clock.java b/sentry/src/main/java/io/sentry/time/Clock.java similarity index 92% rename from raven/src/main/java/com/getsentry/raven/time/Clock.java rename to sentry/src/main/java/io/sentry/time/Clock.java index f85fe9c44a0..fd9cc58cc7e 100644 --- a/raven/src/main/java/com/getsentry/raven/time/Clock.java +++ b/sentry/src/main/java/io/sentry/time/Clock.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.time; +package io.sentry.time; import java.util.Date; diff --git a/raven/src/main/java/com/getsentry/raven/time/FixedClock.java b/sentry/src/main/java/io/sentry/time/FixedClock.java similarity index 96% rename from raven/src/main/java/com/getsentry/raven/time/FixedClock.java rename to sentry/src/main/java/io/sentry/time/FixedClock.java index 54d846db343..9774acfcf24 100644 --- a/raven/src/main/java/com/getsentry/raven/time/FixedClock.java +++ b/sentry/src/main/java/io/sentry/time/FixedClock.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.time; +package io.sentry.time; import java.util.Date; import java.util.concurrent.TimeUnit; diff --git a/raven/src/main/java/com/getsentry/raven/time/SystemClock.java b/sentry/src/main/java/io/sentry/time/SystemClock.java similarity index 90% rename from raven/src/main/java/com/getsentry/raven/time/SystemClock.java rename to sentry/src/main/java/io/sentry/time/SystemClock.java index 9ac2e61e611..3de5b470deb 100644 --- a/raven/src/main/java/com/getsentry/raven/time/SystemClock.java +++ b/sentry/src/main/java/io/sentry/time/SystemClock.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.time; +package io.sentry.time; import java.util.Date; diff --git a/raven/src/main/java/com/getsentry/raven/util/Base64.java b/sentry/src/main/java/io/sentry/util/Base64.java similarity index 99% rename from raven/src/main/java/com/getsentry/raven/util/Base64.java rename to sentry/src/main/java/io/sentry/util/Base64.java index 093ec4bf897..44874327c3d 100644 --- a/raven/src/main/java/com/getsentry/raven/util/Base64.java +++ b/sentry/src/main/java/io/sentry/util/Base64.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.getsentry.raven.util; +package io.sentry.util; import java.io.UnsupportedEncodingException; @@ -688,4 +688,4 @@ public boolean process(byte[] input, int offset, int len, boolean finish) { } } private Base64() { } // don't instantiate -} \ No newline at end of file +} diff --git a/raven/src/main/java/com/getsentry/raven/util/Base64OutputStream.java b/sentry/src/main/java/io/sentry/util/Base64OutputStream.java similarity index 99% rename from raven/src/main/java/com/getsentry/raven/util/Base64OutputStream.java rename to sentry/src/main/java/io/sentry/util/Base64OutputStream.java index 217cfd559a9..95b34723614 100644 --- a/raven/src/main/java/com/getsentry/raven/util/Base64OutputStream.java +++ b/sentry/src/main/java/io/sentry/util/Base64OutputStream.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.getsentry.raven.util; +package io.sentry.util; import java.io.FilterOutputStream; import java.io.IOException; @@ -139,4 +139,4 @@ private byte[] embiggen(byte[] b, int len) { return b; } } -} \ No newline at end of file +} diff --git a/raven/src/main/java/com/getsentry/raven/util/CircularFifoQueue.java b/sentry/src/main/java/io/sentry/util/CircularFifoQueue.java similarity index 99% rename from raven/src/main/java/com/getsentry/raven/util/CircularFifoQueue.java rename to sentry/src/main/java/io/sentry/util/CircularFifoQueue.java index 88dfad8ab27..fe71f913977 100644 --- a/raven/src/main/java/com/getsentry/raven/util/CircularFifoQueue.java +++ b/sentry/src/main/java/io/sentry/util/CircularFifoQueue.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.getsentry.raven.util; +package io.sentry.util; import java.io.IOException; import java.io.ObjectInputStream; diff --git a/raven/src/main/java/com/getsentry/raven/util/Util.java b/sentry/src/main/java/io/sentry/util/Util.java similarity index 98% rename from raven/src/main/java/com/getsentry/raven/util/Util.java rename to sentry/src/main/java/io/sentry/util/Util.java index c964349b932..6e866af1139 100644 --- a/raven/src/main/java/com/getsentry/raven/util/Util.java +++ b/sentry/src/main/java/io/sentry/util/Util.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.util; +package io.sentry.util; import java.util.Arrays; import java.util.Collections; @@ -8,7 +8,7 @@ import java.util.Set; /** - * Raven static Utility class. + * Sentry static Utility class. */ public final class Util { diff --git a/sentry/src/main/resources/META-INF/services/io.sentry.SentryFactory b/sentry/src/main/resources/META-INF/services/io.sentry.SentryFactory new file mode 100644 index 00000000000..c2273663767 --- /dev/null +++ b/sentry/src/main/resources/META-INF/services/io.sentry.SentryFactory @@ -0,0 +1 @@ +io.sentry.DefaultSentryFactory diff --git a/sentry/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/sentry/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer new file mode 100644 index 00000000000..98c4a4fea2e --- /dev/null +++ b/sentry/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer @@ -0,0 +1 @@ +io.sentry.servlet.SentryServletContainerInitializer diff --git a/raven/src/main/resources/raven-build.properties b/sentry/src/main/resources/sentry-build.properties similarity index 100% rename from raven/src/main/resources/raven-build.properties rename to sentry/src/main/resources/sentry-build.properties diff --git a/raven/src/test/java/com/getsentry/raven/BaseIT.java b/sentry/src/test/java/io/sentry/BaseIT.java similarity index 87% rename from raven/src/test/java/com/getsentry/raven/BaseIT.java rename to sentry/src/test/java/io/sentry/BaseIT.java index 065578b17e0..2452cd223b0 100644 --- a/raven/src/test/java/com/getsentry/raven/BaseIT.java +++ b/sentry/src/test/java/io/sentry/BaseIT.java @@ -1,8 +1,8 @@ -package com.getsentry.raven; +package io.sentry; -import com.getsentry.raven.connection.HttpConnection; -import com.getsentry.raven.unmarshaller.JsonUnmarshaller; -import com.getsentry.raven.unmarshaller.event.UnmarshalledEvent; +import io.sentry.connection.HttpConnection; +import io.sentry.unmarshaller.JsonUnmarshaller; +import io.sentry.unmarshaller.event.UnmarshalledEvent; import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.github.tomakehurst.wiremock.matching.RegexPattern; import com.github.tomakehurst.wiremock.matching.StringValuePattern; @@ -26,7 +26,7 @@ public class BaseIT extends BaseTest { public static final String PROJECT1_ID = "1"; public static final String PROJECT1_STORE_URL = "/api/" + PROJECT1_ID + "/store/"; public static final String AUTH_HEADER = "X-Sentry-Auth"; - public static final StringValuePattern AUTH_HEADER_PATTERN = new RegexPattern("Sentry sentry_version=6,sentry_client=raven-java/[\\w\\-\\.]+,sentry_key=8292bf61d620417282e68a72ae03154a,sentry_secret=e3908e05ad874b24b7a168992bfa3577"); + public static final StringValuePattern AUTH_HEADER_PATTERN = new RegexPattern("Sentry sentry_version=6,sentry_client=sentry-java/[\\w\\-\\.]+,sentry_key=8292bf61d620417282e68a72ae03154a,sentry_secret=e3908e05ad874b24b7a168992bfa3577"); @Rule public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().port(8080)); diff --git a/raven/src/test/java/com/getsentry/raven/BaseTest.java b/sentry/src/test/java/io/sentry/BaseTest.java similarity index 97% rename from raven/src/test/java/com/getsentry/raven/BaseTest.java rename to sentry/src/test/java/io/sentry/BaseTest.java index 2816bb1d7d8..16c574ef9d6 100644 --- a/raven/src/test/java/com/getsentry/raven/BaseTest.java +++ b/sentry/src/test/java/io/sentry/BaseTest.java @@ -1,4 +1,4 @@ -package com.getsentry.raven; +package io.sentry; import java.util.concurrent.Callable; diff --git a/raven/src/test/java/com/getsentry/raven/ContextTest.java b/sentry/src/test/java/io/sentry/ContextTest.java similarity index 90% rename from raven/src/test/java/com/getsentry/raven/ContextTest.java rename to sentry/src/test/java/io/sentry/ContextTest.java index 029e132ee30..019f99f8589 100644 --- a/raven/src/test/java/com/getsentry/raven/ContextTest.java +++ b/sentry/src/test/java/io/sentry/ContextTest.java @@ -1,10 +1,10 @@ -package com.getsentry.raven; +package io.sentry; -import com.getsentry.raven.context.Context; -import com.getsentry.raven.event.Breadcrumb; -import com.getsentry.raven.event.BreadcrumbBuilder; -import com.getsentry.raven.event.User; -import com.getsentry.raven.event.UserBuilder; +import io.sentry.context.Context; +import io.sentry.event.Breadcrumb; +import io.sentry.event.BreadcrumbBuilder; +import io.sentry.event.User; +import io.sentry.event.UserBuilder; import org.testng.annotations.Test; import java.util.ArrayList; diff --git a/raven/src/test/java/com/getsentry/raven/DefaultRavenFactoryTest.java b/sentry/src/test/java/io/sentry/DefaultSentryFactoryTest.java similarity index 55% rename from raven/src/test/java/com/getsentry/raven/DefaultRavenFactoryTest.java rename to sentry/src/test/java/io/sentry/DefaultSentryFactoryTest.java index 8f67298da5d..1495ce500de 100644 --- a/raven/src/test/java/com/getsentry/raven/DefaultRavenFactoryTest.java +++ b/sentry/src/test/java/io/sentry/DefaultSentryFactoryTest.java @@ -1,4 +1,4 @@ -package com.getsentry.raven; +package io.sentry; import org.testng.annotations.Test; @@ -8,11 +8,11 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.instanceOf; -public class DefaultRavenFactoryTest { +public class DefaultSentryFactoryTest { @Test public void checkServiceLoaderProvidesFactory() throws Exception { - ServiceLoader ravenFactories = ServiceLoader.load(RavenFactory.class); + ServiceLoader sentryFactories = ServiceLoader.load(SentryFactory.class); - assertThat(ravenFactories, contains(instanceOf(DefaultRavenFactory.class))); + assertThat(sentryFactories, contains(instanceOf(DefaultSentryFactory.class))); } } diff --git a/raven/src/test/java/com/getsentry/raven/RavenFactoryTest.java b/sentry/src/test/java/io/sentry/SentryFactoryTest.java similarity index 52% rename from raven/src/test/java/com/getsentry/raven/RavenFactoryTest.java rename to sentry/src/test/java/io/sentry/SentryFactoryTest.java index 3058e4703c5..7eb73a31f7e 100644 --- a/raven/src/test/java/com/getsentry/raven/RavenFactoryTest.java +++ b/sentry/src/test/java/io/sentry/SentryFactoryTest.java @@ -1,7 +1,7 @@ -package com.getsentry.raven; +package io.sentry; import mockit.*; -import com.getsentry.raven.dsn.Dsn; +import io.sentry.dsn.Dsn; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -18,15 +18,15 @@ import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; -public class RavenFactoryTest { +public class SentryFactoryTest { @Tested - private RavenFactory ravenFactory = null; + private SentryFactory sentryFactory = null; @Injectable - private ServiceLoader mockServiceLoader = null; + private ServiceLoader mockServiceLoader = null; @BeforeMethod public void setUp() throws Exception { - setField(RavenFactory.class, "AUTO_REGISTERED_FACTORIES", mockServiceLoader); + setField(SentryFactory.class, "AUTO_REGISTERED_FACTORIES", mockServiceLoader); new NonStrictExpectations() {{ mockServiceLoader.iterator(); @@ -37,83 +37,83 @@ public void setUp() throws Exception { @AfterMethod public void tearDown() throws Exception { // Reset the registered factories - setField(RavenFactory.class, "MANUALLY_REGISTERED_FACTORIES", new HashSet<>()); - setField(RavenFactory.class, "AUTO_REGISTERED_FACTORIES", ServiceLoader.load(RavenFactory.class)); + setField(SentryFactory.class, "MANUALLY_REGISTERED_FACTORIES", new HashSet<>()); + setField(SentryFactory.class, "AUTO_REGISTERED_FACTORIES", ServiceLoader.load(SentryFactory.class)); } @Test - public void testGetFactoriesFromServiceLoader(@Injectable final Raven mockRaven, + public void testGetFactoriesFromServiceLoader(@Injectable final Sentry mockSentry, @Injectable final Dsn mockDsn) throws Exception { new NonStrictExpectations() {{ mockServiceLoader.iterator(); - result = new Delegate>() { + result = new Delegate>() { @SuppressWarnings("unused") - public Iterator iterator() { - return Collections.singletonList(ravenFactory).iterator(); + public Iterator iterator() { + return Collections.singletonList(sentryFactory).iterator(); } }; - ravenFactory.createRavenInstance(mockDsn); - result = mockRaven; + sentryFactory.createSentryInstance(mockDsn); + result = mockSentry; }}; - Raven raven = RavenFactory.ravenInstance(mockDsn); + Sentry sentry = SentryFactory.sentryInstance(mockDsn); - assertThat(raven, is(mockRaven)); + assertThat(sentry, is(mockSentry)); } @Test - public void testGetFactoriesManuallyAdded(@Injectable final Raven mockRaven, + public void testGetFactoriesManuallyAdded(@Injectable final Sentry mockSentry, @Injectable final Dsn mockDsn) throws Exception { - RavenFactory.registerFactory(ravenFactory); + SentryFactory.registerFactory(sentryFactory); new NonStrictExpectations() {{ - ravenFactory.createRavenInstance(mockDsn); - result = mockRaven; + sentryFactory.createSentryInstance(mockDsn); + result = mockSentry; }}; - Raven raven = RavenFactory.ravenInstance(mockDsn); + Sentry sentry = SentryFactory.sentryInstance(mockDsn); - assertThat(raven, is(mockRaven)); + assertThat(sentry, is(mockSentry)); } @Test - public void testRavenInstanceForFactoryNameSucceedsIfFactoryFound(@Injectable final Raven mockRaven, + public void testSentryInstanceForFactoryNameSucceedsIfFactoryFound(@Injectable final Sentry mockSentry, @Injectable final Dsn mockDsn) throws Exception { - String factoryName = ravenFactory.getClass().getName(); - RavenFactory.registerFactory(ravenFactory); + String factoryName = sentryFactory.getClass().getName(); + SentryFactory.registerFactory(sentryFactory); new NonStrictExpectations() {{ - ravenFactory.createRavenInstance(mockDsn); - result = mockRaven; + sentryFactory.createSentryInstance(mockDsn); + result = mockSentry; }}; - Raven raven = RavenFactory.ravenInstance(mockDsn, factoryName); + Sentry sentry = SentryFactory.sentryInstance(mockDsn, factoryName); - assertThat(raven, is(mockRaven)); + assertThat(sentry, is(mockSentry)); } @Test(expectedExceptions = IllegalStateException.class) - public void testRavenInstanceForFactoryNameFailsIfNoFactoryFound(@Injectable final Raven mockRaven, + public void testSentryInstanceForFactoryNameFailsIfNoFactoryFound(@Injectable final Sentry mockSentry, @Injectable final Dsn mockDsn) throws Exception { String factoryName = "invalidName"; - RavenFactory.registerFactory(ravenFactory); + SentryFactory.registerFactory(sentryFactory); new NonStrictExpectations() {{ - ravenFactory.createRavenInstance(mockDsn); - result = mockRaven; + sentryFactory.createSentryInstance(mockDsn); + result = mockSentry; }}; - RavenFactory.ravenInstance(mockDsn, factoryName); + SentryFactory.sentryInstance(mockDsn, factoryName); } @Test - public void testRavenInstantiationFailureCaught(@Injectable final Dsn mockDsn) throws Exception { - RavenFactory.registerFactory(ravenFactory); + public void testSentryInstantiationFailureCaught(@Injectable final Dsn mockDsn) throws Exception { + SentryFactory.registerFactory(sentryFactory); Exception exception = null; new NonStrictExpectations() {{ - ravenFactory.createRavenInstance(mockDsn); + sentryFactory.createSentryInstance(mockDsn); result = new RuntimeException(); }}; try { - RavenFactory.ravenInstance(mockDsn); + SentryFactory.sentryInstance(mockDsn); } catch (IllegalStateException e) { exception = e; } @@ -122,39 +122,39 @@ public void testRavenInstantiationFailureCaught(@Injectable final Dsn mockDsn) t } @Test - public void testAutoDetectDsnIfNotProvided(@Injectable final Raven mockRaven, + public void testAutoDetectDsnIfNotProvided(@Injectable final Sentry mockSentry, @SuppressWarnings("unused") @Mocked final Dsn mockDsn) throws Exception { final String dsn = "protocol://user:password@host:port/3"; - RavenFactory.registerFactory(ravenFactory); + SentryFactory.registerFactory(sentryFactory); new NonStrictExpectations() {{ Dsn.dsnLookup(); result = dsn; - ravenFactory.createRavenInstance((Dsn) any); - result = mockRaven; + sentryFactory.createSentryInstance((Dsn) any); + result = mockSentry; }}; - Raven raven = RavenFactory.ravenInstance(); + Sentry sentry = SentryFactory.sentryInstance(); - assertThat(raven, is(mockRaven)); + assertThat(sentry, is(mockSentry)); new Verifications() {{ new Dsn(dsn); }}; } @Test - public void testCreateDsnIfStringProvided(@Injectable final Raven mockRaven, + public void testCreateDsnIfStringProvided(@Injectable final Sentry mockSentry, @SuppressWarnings("unused") @Mocked final Dsn mockDsn) throws Exception { final String dsn = "protocol://user:password@host:port/2"; - RavenFactory.registerFactory(ravenFactory); + SentryFactory.registerFactory(sentryFactory); new NonStrictExpectations() {{ - ravenFactory.createRavenInstance((Dsn) any); - result = mockRaven; + sentryFactory.createSentryInstance((Dsn) any); + result = mockSentry; }}; - Raven raven = RavenFactory.ravenInstance(dsn); + Sentry sentry = SentryFactory.sentryInstance(dsn); - assertThat(raven, is(mockRaven)); + assertThat(sentry, is(mockSentry)); new Verifications() {{ new Dsn(dsn); }}; diff --git a/raven/src/test/java/com/getsentry/raven/RavenTest.java b/sentry/src/test/java/io/sentry/SentryTest.java similarity index 78% rename from raven/src/test/java/com/getsentry/raven/RavenTest.java rename to sentry/src/test/java/io/sentry/SentryTest.java index 42909e8eead..330baf086cb 100644 --- a/raven/src/test/java/com/getsentry/raven/RavenTest.java +++ b/sentry/src/test/java/io/sentry/SentryTest.java @@ -1,16 +1,16 @@ -package com.getsentry.raven; +package io.sentry; -import com.getsentry.raven.context.ContextManager; -import com.getsentry.raven.context.SingletonContextManager; +import io.sentry.context.ContextManager; +import io.sentry.context.SingletonContextManager; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; import mockit.Verifications; -import com.getsentry.raven.connection.Connection; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.EventBuilder; -import com.getsentry.raven.event.helper.EventBuilderHelper; -import com.getsentry.raven.event.interfaces.ExceptionInterface; +import io.sentry.connection.Connection; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; +import io.sentry.event.helper.EventBuilderHelper; +import io.sentry.event.interfaces.ExceptionInterface; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -19,9 +19,9 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -public class RavenTest { +public class SentryTest { @Tested - private Raven raven = null; + private Sentry sentry = null; @Injectable private Connection mockConnection = null; @Injectable @@ -38,7 +38,7 @@ public void setup() { @Test public void testSendEvent() throws Exception { - raven.sendEvent(mockEvent); + sentry.sendEvent(mockEvent); new Verifications() {{ mockConnection.send(mockEvent); @@ -48,9 +48,9 @@ public void testSendEvent() throws Exception { @Test public void testSendEventBuilder() throws Exception { final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; - raven.addBuilderHelper(mockEventBuilderHelper); + sentry.addBuilderHelper(mockEventBuilderHelper); - raven.sendEvent(new EventBuilder() + sentry.sendEvent(new EventBuilder() .withMessage(message) .withLevel(Event.Level.INFO)); @@ -70,7 +70,7 @@ public void testSendEventFailingIsCaught() throws Exception { result = new RuntimeException(); }}; - raven.sendEvent(mockEvent); + sentry.sendEvent(mockEvent); new Verifications() {{ mockConnection.send(mockEvent); @@ -80,9 +80,9 @@ public void testSendEventFailingIsCaught() throws Exception { @Test public void testSendMessage() throws Exception { final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; - raven.addBuilderHelper(mockEventBuilderHelper); + sentry.addBuilderHelper(mockEventBuilderHelper); - raven.sendMessage(message); + sentry.sendMessage(message); new Verifications() {{ Event event; @@ -97,9 +97,9 @@ public void testSendMessage() throws Exception { public void testSendException() throws Exception { final String message = "7b61ddb1-eb32-428d-bad9-a7d842605ba7"; final Exception exception = new Exception(message); - raven.addBuilderHelper(mockEventBuilderHelper); + sentry.addBuilderHelper(mockEventBuilderHelper); - raven.sendException(exception); + sentry.sendException(exception); new Verifications() {{ Event event; @@ -113,25 +113,25 @@ public void testSendException() throws Exception { @Test public void testAddRemoveBuilderHelpers(@Injectable final EventBuilderHelper mockBuilderHelper) throws Exception { - assertThat(raven.getBuilderHelpers(), not(contains(mockBuilderHelper))); + assertThat(sentry.getBuilderHelpers(), not(contains(mockBuilderHelper))); - raven.addBuilderHelper(mockBuilderHelper); - assertThat(raven.getBuilderHelpers(), contains(mockBuilderHelper)); - raven.removeBuilderHelper(mockBuilderHelper); - assertThat(raven.getBuilderHelpers(), not(contains(mockBuilderHelper))); + sentry.addBuilderHelper(mockBuilderHelper); + assertThat(sentry.getBuilderHelpers(), contains(mockBuilderHelper)); + sentry.removeBuilderHelper(mockBuilderHelper); + assertThat(sentry.getBuilderHelpers(), not(contains(mockBuilderHelper))); } @Test(expectedExceptions = UnsupportedOperationException.class) public void testCantModifyBuilderHelpersDirectly(@Injectable final EventBuilderHelper mockBuilderHelper) throws Exception { - raven.getBuilderHelpers().add(mockBuilderHelper); + sentry.getBuilderHelpers().add(mockBuilderHelper); } @Test public void testRunBuilderHelpers(@Injectable final EventBuilderHelper mockBuilderHelper, @Injectable final EventBuilder mockEventBuilder) throws Exception { - raven.addBuilderHelper(mockBuilderHelper); + sentry.addBuilderHelper(mockBuilderHelper); - raven.runBuilderHelpers(mockEventBuilder); + sentry.runBuilderHelpers(mockEventBuilder); new Verifications() {{ mockBuilderHelper.helpBuildingEvent(mockEventBuilder); @@ -140,7 +140,7 @@ public void testRunBuilderHelpers(@Injectable final EventBuilderHelper mockBuild @Test public void testCloseConnectionSuccessful() throws Exception { - raven.closeConnection(); + sentry.closeConnection(); new Verifications() {{ mockConnection.close(); @@ -154,7 +154,7 @@ public void testCloseConnectionFailed() throws Exception { result = new IOException(); }}; - raven.closeConnection(); + sentry.closeConnection(); } @Test @@ -162,7 +162,7 @@ public void testSendEventStatically() throws Exception { final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; Event event = new EventBuilder().withMessage(message).withLevel(Event.Level.ERROR).build(); - Raven.capture(event); + Sentry.capture(event); new Verifications() {{ Event event; @@ -176,9 +176,9 @@ public void testSendEventStatically() throws Exception { public void testSendEventBuilderStatically() throws Exception { final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; EventBuilder eventBuilder = new EventBuilder().withMessage(message).withLevel(Event.Level.ERROR); - raven.addBuilderHelper(mockEventBuilderHelper); + sentry.addBuilderHelper(mockEventBuilderHelper); - Raven.capture(eventBuilder); + Sentry.capture(eventBuilder); new Verifications() {{ Event event; @@ -192,9 +192,9 @@ public void testSendEventBuilderStatically() throws Exception { @Test public void testSendMessageStatically() throws Exception { final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; - raven.addBuilderHelper(mockEventBuilderHelper); + sentry.addBuilderHelper(mockEventBuilderHelper); - Raven.capture(message); + Sentry.capture(message); new Verifications() {{ Event event; @@ -209,9 +209,9 @@ public void testSendMessageStatically() throws Exception { public void testSendExceptionStatically() throws Exception { final String message = "7b61ddb1-eb32-428d-bad9-a7d842605ba7"; final Exception exception = new Exception(message); - raven.addBuilderHelper(mockEventBuilderHelper); + sentry.addBuilderHelper(mockEventBuilderHelper); - Raven.capture(exception); + Sentry.capture(exception); new Verifications() {{ Event event; diff --git a/raven/src/test/java/com/getsentry/raven/buffer/BufferTest.java b/sentry/src/test/java/io/sentry/buffer/BufferTest.java similarity index 91% rename from raven/src/test/java/com/getsentry/raven/buffer/BufferTest.java rename to sentry/src/test/java/io/sentry/buffer/BufferTest.java index 316b3f7d0d4..9a6b315af74 100644 --- a/raven/src/test/java/com/getsentry/raven/buffer/BufferTest.java +++ b/sentry/src/test/java/io/sentry/buffer/BufferTest.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.buffer; +package io.sentry.buffer; import java.io.File; diff --git a/raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java b/sentry/src/test/java/io/sentry/buffer/DiskBufferTest.java similarity index 91% rename from raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java rename to sentry/src/test/java/io/sentry/buffer/DiskBufferTest.java index 5c8b3854917..de2a703cdd3 100644 --- a/raven/src/test/java/com/getsentry/raven/buffer/DiskBufferTest.java +++ b/sentry/src/test/java/io/sentry/buffer/DiskBufferTest.java @@ -1,9 +1,9 @@ -package com.getsentry.raven.buffer; +package io.sentry.buffer; -import com.getsentry.raven.event.Breadcrumb; -import com.getsentry.raven.event.BreadcrumbBuilder; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.EventBuilder; +import io.sentry.event.Breadcrumb; +import io.sentry.event.BreadcrumbBuilder; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -19,7 +19,7 @@ public class DiskBufferTest extends BufferTest { - private static File BUFFER_DIR = new File("./raven-test-buffer-dir"); + private static File BUFFER_DIR = new File("./sentry-test-buffer-dir"); private int maxEvents = 2; private DiskBuffer buffer; diff --git a/raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java b/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java similarity index 97% rename from raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java rename to sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java index ed1cedd1f11..08056cbcd36 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/AbstractConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java @@ -1,8 +1,8 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; -import com.getsentry.raven.time.FixedClock; +import io.sentry.time.FixedClock; import mockit.*; -import com.getsentry.raven.event.Event; +import io.sentry.event.Event; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -39,7 +39,7 @@ public void testAuthHeader() throws Exception { String authHeader = abstractConnection.getAuthHeader(); assertThat(authHeader, is("Sentry sentry_version=6," - + "sentry_client=raven-java/test," + + "sentry_client=sentry-java/test," + "sentry_key=" + publicKey + "," + "sentry_secret=" + secretKey)); } diff --git a/raven/src/test/java/com/getsentry/raven/connection/AsyncConnectionTest.java b/sentry/src/test/java/io/sentry/connection/AsyncConnectionTest.java similarity index 90% rename from raven/src/test/java/com/getsentry/raven/connection/AsyncConnectionTest.java rename to sentry/src/test/java/io/sentry/connection/AsyncConnectionTest.java index 3bce0673e6b..64ba82518e0 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/AsyncConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/AsyncConnectionTest.java @@ -1,9 +1,9 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; import mockit.*; -import com.getsentry.raven.Raven; -import com.getsentry.raven.environment.RavenEnvironment; -import com.getsentry.raven.event.Event; +import io.sentry.Sentry; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Event; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -59,8 +59,8 @@ public void verifyShutdownHookNotAddedWhenNotGraceful() throws Exception { } @Test - public void verifyShutdownHookSetManagedByRavenAndCloseConnection( - @SuppressWarnings("unused") @Mocked({"startManagingThread", "stopManagingThread"}) Raven mockRaven) + public void verifyShutdownHookSetManagedBySentryAndCloseConnection( + @SuppressWarnings("unused") @Mocked({"startManagingThread", "stopManagingThread"}) Sentry mockSentry) throws Exception { // Ensure that the shutdown hooks for the unused @Tested instance are removed asyncConnection.close(); @@ -78,15 +78,15 @@ public void addShutdownHook(Thread hook) { new AsyncConnection(mockConnection, mockExecutorService, true, mockTimeout); new VerificationsInOrder() {{ - RavenEnvironment.startManagingThread(); + SentryEnvironment.startManagingThread(); mockConnection.close(); - RavenEnvironment.stopManagingThread(); + SentryEnvironment.stopManagingThread(); }}; } @Test public void ensureFailingShutdownHookStopsBeingManaged( - @SuppressWarnings("unused") @Mocked({"startManagingThread", "stopManagingThread"}) Raven mockRaven) + @SuppressWarnings("unused") @Mocked({"startManagingThread", "stopManagingThread"}) Sentry mockSentry) throws Exception { // Ensure that the shutdown hooks for the unused @Tested instance are removed asyncConnection.close(); @@ -106,7 +106,7 @@ public void addShutdownHook(Thread hook) { new AsyncConnection(mockConnection, mockExecutorService, true, mockTimeout); new Verifications() {{ - RavenEnvironment.stopManagingThread(); + SentryEnvironment.stopManagingThread(); }}; } diff --git a/raven/src/test/java/com/getsentry/raven/connection/BufferedConnectionTest.java b/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java similarity index 94% rename from raven/src/test/java/com/getsentry/raven/connection/BufferedConnectionTest.java rename to sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java index 11586caa7cf..ba5ee8c4d2c 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/BufferedConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java @@ -1,10 +1,10 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; -import com.getsentry.raven.BaseTest; -import com.getsentry.raven.buffer.Buffer; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.EventBuilder; -import com.getsentry.raven.time.FixedClock; +import io.sentry.BaseTest; +import io.sentry.buffer.Buffer; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; +import io.sentry.time.FixedClock; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; diff --git a/raven/src/test/java/com/getsentry/raven/connection/EventSendFailureCallbackTest.java b/sentry/src/test/java/io/sentry/connection/EventSendFailureCallbackTest.java similarity index 65% rename from raven/src/test/java/com/getsentry/raven/connection/EventSendFailureCallbackTest.java rename to sentry/src/test/java/io/sentry/connection/EventSendFailureCallbackTest.java index ad211ed991d..815629f043f 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/EventSendFailureCallbackTest.java +++ b/sentry/src/test/java/io/sentry/connection/EventSendFailureCallbackTest.java @@ -1,9 +1,9 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; -import com.getsentry.raven.DefaultRavenFactory; -import com.getsentry.raven.Raven; -import com.getsentry.raven.dsn.Dsn; -import com.getsentry.raven.event.Event; +import io.sentry.DefaultSentryFactory; +import io.sentry.Sentry; +import io.sentry.dsn.Dsn; +import io.sentry.event.Event; import org.testng.annotations.Test; import java.util.concurrent.atomic.AtomicBoolean; @@ -17,7 +17,7 @@ public class EventSendFailureCallbackTest { public void testSimpleCallback() { final AtomicBoolean flag = new AtomicBoolean(false); - DefaultRavenFactory factory = new DefaultRavenFactory() { + DefaultSentryFactory factory = new DefaultSentryFactory() { @Override protected Connection createConnection(Dsn dsn) { Connection connection = super.createConnection(dsn); @@ -33,16 +33,16 @@ public void onFailure(Event event, Exception exception) { } }; - String dsn = "https://foo:bar@localhost:1234/1?raven.async=false"; - Raven raven = factory.createRavenInstance(new Dsn(dsn)); - raven.sendMessage("Message that will fail because DSN points to garbage."); + String dsn = "https://foo:bar@localhost:1234/1?async=false"; + Sentry sentry = factory.createSentryInstance(new Dsn(dsn)); + sentry.sendMessage("Message that will fail because DSN points to garbage."); assertThat(flag.get(), is(true)); } @Test public void testExceptionInsideCallback() { - DefaultRavenFactory factory = new DefaultRavenFactory() { + DefaultSentryFactory factory = new DefaultSentryFactory() { @Override protected Connection createConnection(Dsn dsn) { Connection connection = super.createConnection(dsn); @@ -58,9 +58,9 @@ public void onFailure(Event event, Exception exception) { } }; - String dsn = "https://foo:bar@localhost:1234/1?raven.async=false"; - Raven raven = factory.createRavenInstance(new Dsn(dsn)); - raven.sendMessage("Message that will fail because DSN points to garbage."); + String dsn = "https://foo:bar@localhost:1234/1?async=false"; + Sentry sentry = factory.createSentryInstance(new Dsn(dsn)); + sentry.sendMessage("Message that will fail because DSN points to garbage."); } } diff --git a/raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java b/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java similarity index 95% rename from raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java rename to sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java index 51764077e26..b202cbbe2e0 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/HttpConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java @@ -1,9 +1,9 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; import mockit.*; -import com.getsentry.raven.environment.RavenEnvironment; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.marshaller.Marshaller; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Event; +import io.sentry.marshaller.Marshaller; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -123,7 +123,7 @@ public void testAuthHeaderSent(@Injectable final Event mockEvent) throws Excepti httpConnection.send(mockEvent); new Verifications() {{ - mockUrlConnection.setRequestProperty("User-Agent", RavenEnvironment.getRavenName()); + mockUrlConnection.setRequestProperty("User-Agent", SentryEnvironment.getSentryName()); mockUrlConnection.setRequestProperty("X-Sentry-Auth", httpConnection.getAuthHeader()); }}; } diff --git a/raven/src/test/java/com/getsentry/raven/connection/OutputStreamConnectionTest.java b/sentry/src/test/java/io/sentry/connection/OutputStreamConnectionTest.java similarity index 86% rename from raven/src/test/java/com/getsentry/raven/connection/OutputStreamConnectionTest.java rename to sentry/src/test/java/io/sentry/connection/OutputStreamConnectionTest.java index 2c55eac1fca..1d488f0b854 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/OutputStreamConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/OutputStreamConnectionTest.java @@ -1,9 +1,9 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; import mockit.*; -import com.getsentry.raven.environment.RavenEnvironment; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.marshaller.Marshaller; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Event; +import io.sentry.marshaller.Marshaller; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; diff --git a/raven/src/test/java/com/getsentry/raven/connection/RandomEventSamplerTest.java b/sentry/src/test/java/io/sentry/connection/RandomEventSamplerTest.java similarity index 89% rename from raven/src/test/java/com/getsentry/raven/connection/RandomEventSamplerTest.java rename to sentry/src/test/java/io/sentry/connection/RandomEventSamplerTest.java index b638043a60b..0ba5fba5334 100644 --- a/raven/src/test/java/com/getsentry/raven/connection/RandomEventSamplerTest.java +++ b/sentry/src/test/java/io/sentry/connection/RandomEventSamplerTest.java @@ -1,7 +1,7 @@ -package com.getsentry.raven.connection; +package io.sentry.connection; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.EventBuilder; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; diff --git a/raven/src/test/java/com/getsentry/raven/dsn/DsnTest.java b/sentry/src/test/java/io/sentry/dsn/DsnTest.java similarity index 98% rename from raven/src/test/java/com/getsentry/raven/dsn/DsnTest.java rename to sentry/src/test/java/io/sentry/dsn/DsnTest.java index ed1ffcd176a..4bbd95b36b0 100644 --- a/raven/src/test/java/com/getsentry/raven/dsn/DsnTest.java +++ b/sentry/src/test/java/io/sentry/dsn/DsnTest.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.dsn; +package io.sentry.dsn; -import com.getsentry.raven.config.JndiLookup; +import io.sentry.config.JndiLookup; import mockit.Expectations; import mockit.Mocked; import mockit.NonStrictExpectations; diff --git a/raven/src/test/java/com/getsentry/raven/dsn/InitialContextFactory.java b/sentry/src/test/java/io/sentry/dsn/InitialContextFactory.java similarity index 93% rename from raven/src/test/java/com/getsentry/raven/dsn/InitialContextFactory.java rename to sentry/src/test/java/io/sentry/dsn/InitialContextFactory.java index 282a1d482e9..e26468aeb91 100644 --- a/raven/src/test/java/com/getsentry/raven/dsn/InitialContextFactory.java +++ b/sentry/src/test/java/io/sentry/dsn/InitialContextFactory.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.dsn; +package io.sentry.dsn; import javax.naming.Context; import javax.naming.NamingException; diff --git a/sentry/src/test/java/io/sentry/environment/SentryEnvironmentTest.java b/sentry/src/test/java/io/sentry/environment/SentryEnvironmentTest.java new file mode 100644 index 00000000000..dbb117c5cb9 --- /dev/null +++ b/sentry/src/test/java/io/sentry/environment/SentryEnvironmentTest.java @@ -0,0 +1,62 @@ +package io.sentry.environment; + +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SentryEnvironmentTest { + @AfterMethod + public void tearDown() throws Exception { + SentryEnvironment.SENTRY_THREAD.remove(); + } + + @Test + public void testThreadNotManagedByDefault() throws Exception { + assertThat(SentryEnvironment.isManagingThread(), is(false)); + } + + @Test + public void testStartManagingThreadWorks() throws Exception { + SentryEnvironment.startManagingThread(); + + assertThat(SentryEnvironment.isManagingThread(), is(true)); + } + + @Test + public void testStartManagingAlreadyManagedThreadWorks() throws Exception { + SentryEnvironment.startManagingThread(); + + SentryEnvironment.startManagingThread(); + + assertThat(SentryEnvironment.isManagingThread(), is(true)); + } + + @Test + public void testStopManagingThreadWorks() throws Exception { + SentryEnvironment.startManagingThread(); + + SentryEnvironment.stopManagingThread(); + + assertThat(SentryEnvironment.isManagingThread(), is(false)); + } + + @Test + public void testStopManagingNonManagedThreadWorks() throws Exception { + SentryEnvironment.stopManagingThread(); + + assertThat(SentryEnvironment.isManagingThread(), is(false)); + } + + @Test + public void testThreadManagedTwiceNeedsToBeUnmanagedTwice() throws Exception { + SentryEnvironment.startManagingThread(); + SentryEnvironment.startManagingThread(); + + SentryEnvironment.stopManagingThread(); + assertThat(SentryEnvironment.isManagingThread(), is(true)); + SentryEnvironment.stopManagingThread(); + assertThat(SentryEnvironment.isManagingThread(), is(false)); + } +} diff --git a/raven/src/test/java/com/getsentry/raven/event/BreadcrumbTest.java b/sentry/src/test/java/io/sentry/event/BreadcrumbTest.java similarity index 78% rename from raven/src/test/java/com/getsentry/raven/event/BreadcrumbTest.java rename to sentry/src/test/java/io/sentry/event/BreadcrumbTest.java index dc720da18e9..04892fe7d14 100644 --- a/raven/src/test/java/com/getsentry/raven/event/BreadcrumbTest.java +++ b/sentry/src/test/java/io/sentry/event/BreadcrumbTest.java @@ -1,10 +1,10 @@ -package com.getsentry.raven.event; +package io.sentry.event; -import com.getsentry.raven.Raven; -import com.getsentry.raven.connection.Connection; -import com.getsentry.raven.context.ContextManager; -import com.getsentry.raven.context.SingletonContextManager; -import com.getsentry.raven.event.helper.ContextBuilderHelper; +import io.sentry.Sentry; +import io.sentry.connection.Connection; +import io.sentry.context.ContextManager; +import io.sentry.context.SingletonContextManager; +import io.sentry.event.helper.ContextBuilderHelper; import mockit.Injectable; import mockit.Tested; import mockit.Verifications; @@ -18,7 +18,7 @@ public class BreadcrumbTest { @Tested - private Raven raven = null; + private Sentry sentry = null; @Injectable private Connection mockConnection = null; @@ -32,7 +32,7 @@ public void setup() { @Test public void testBreadcrumbsViaContextRecording() { - raven.addBuilderHelper(new ContextBuilderHelper(raven)); + sentry.addBuilderHelper(new ContextBuilderHelper(sentry)); final Breadcrumb breadcrumb = new BreadcrumbBuilder() .setLevel(Breadcrumb.Level.INFO) @@ -40,9 +40,9 @@ public void testBreadcrumbsViaContextRecording() { .setCategory("step") .build(); - raven.getContext().recordBreadcrumb(breadcrumb); + sentry.getContext().recordBreadcrumb(breadcrumb); - raven.sendEvent(new EventBuilder() + sentry.sendEvent(new EventBuilder() .withMessage("Some random message") .withLevel(Event.Level.INFO)); @@ -55,7 +55,7 @@ public void testBreadcrumbsViaContextRecording() { @Test public void testBreadcrumbsViaEventBuilder() { - raven.addBuilderHelper(new ContextBuilderHelper(raven)); + sentry.addBuilderHelper(new ContextBuilderHelper(sentry)); final Breadcrumb breadcrumb = new BreadcrumbBuilder() .setLevel(Breadcrumb.Level.INFO) @@ -66,7 +66,7 @@ public void testBreadcrumbsViaEventBuilder() { ArrayList breadcrumbs = new ArrayList<>(); breadcrumbs.add(breadcrumb); - raven.sendEvent(new EventBuilder() + sentry.sendEvent(new EventBuilder() .withBreadcrumbs(breadcrumbs) .withMessage("Some random message") .withLevel(Event.Level.INFO)); @@ -80,7 +80,7 @@ public void testBreadcrumbsViaEventBuilder() { @Test public void testBreadcrumbsViaEvent() { - raven.addBuilderHelper(new ContextBuilderHelper(raven)); + sentry.addBuilderHelper(new ContextBuilderHelper(sentry)); final Breadcrumb breadcrumb = new BreadcrumbBuilder() .setLevel(Breadcrumb.Level.INFO) @@ -91,7 +91,7 @@ public void testBreadcrumbsViaEvent() { ArrayList breadcrumbs = new ArrayList<>(); breadcrumbs.add(breadcrumb); - raven.sendEvent(new EventBuilder() + sentry.sendEvent(new EventBuilder() .withBreadcrumbs(breadcrumbs) .withMessage("Some random message") .withLevel(Event.Level.INFO) @@ -103,4 +103,4 @@ public void testBreadcrumbsViaEvent() { assertThat(event.getBreadcrumbs().size(), equalTo(1)); }}; } -} \ No newline at end of file +} diff --git a/raven/src/test/java/com/getsentry/raven/event/EventBuilderHostnameCacheTest.java b/sentry/src/test/java/io/sentry/event/EventBuilderHostnameCacheTest.java similarity index 98% rename from raven/src/test/java/com/getsentry/raven/event/EventBuilderHostnameCacheTest.java rename to sentry/src/test/java/io/sentry/event/EventBuilderHostnameCacheTest.java index bd5776beb20..2f2ee4b54d9 100644 --- a/raven/src/test/java/com/getsentry/raven/event/EventBuilderHostnameCacheTest.java +++ b/sentry/src/test/java/io/sentry/event/EventBuilderHostnameCacheTest.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.event; +package io.sentry.event; import mockit.*; import org.testng.annotations.BeforeMethod; diff --git a/raven/src/test/java/com/getsentry/raven/event/EventBuilderTest.java b/sentry/src/test/java/io/sentry/event/EventBuilderTest.java similarity index 99% rename from raven/src/test/java/com/getsentry/raven/event/EventBuilderTest.java rename to sentry/src/test/java/io/sentry/event/EventBuilderTest.java index 12051209cf5..50a8e829076 100644 --- a/raven/src/test/java/com/getsentry/raven/event/EventBuilderTest.java +++ b/sentry/src/test/java/io/sentry/event/EventBuilderTest.java @@ -1,8 +1,8 @@ -package com.getsentry.raven.event; +package io.sentry.event; import mockit.Injectable; import mockit.NonStrictExpectations; -import com.getsentry.raven.event.interfaces.SentryInterface; +import io.sentry.event.interfaces.SentryInterface; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; diff --git a/raven/src/test/java/com/getsentry/raven/event/EventTest.java b/sentry/src/test/java/io/sentry/event/EventTest.java similarity index 98% rename from raven/src/test/java/com/getsentry/raven/event/EventTest.java rename to sentry/src/test/java/io/sentry/event/EventTest.java index a885998bb5a..1bb1552f7ec 100644 --- a/raven/src/test/java/com/getsentry/raven/event/EventTest.java +++ b/sentry/src/test/java/io/sentry/event/EventTest.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.event; +package io.sentry.event; import mockit.Injectable; import mockit.NonStrictExpectations; diff --git a/raven/src/test/java/com/getsentry/raven/event/UserTest.java b/sentry/src/test/java/io/sentry/event/UserTest.java similarity index 73% rename from raven/src/test/java/com/getsentry/raven/event/UserTest.java rename to sentry/src/test/java/io/sentry/event/UserTest.java index cebeac78c85..c3a2521d66f 100644 --- a/raven/src/test/java/com/getsentry/raven/event/UserTest.java +++ b/sentry/src/test/java/io/sentry/event/UserTest.java @@ -1,11 +1,11 @@ -package com.getsentry.raven.event; - -import com.getsentry.raven.Raven; -import com.getsentry.raven.connection.Connection; -import com.getsentry.raven.context.ContextManager; -import com.getsentry.raven.context.SingletonContextManager; -import com.getsentry.raven.event.helper.ContextBuilderHelper; -import com.getsentry.raven.event.interfaces.UserInterface; +package io.sentry.event; + +import io.sentry.Sentry; +import io.sentry.connection.Connection; +import io.sentry.context.ContextManager; +import io.sentry.context.SingletonContextManager; +import io.sentry.event.helper.ContextBuilderHelper; +import io.sentry.event.interfaces.UserInterface; import mockit.Injectable; import mockit.Tested; import mockit.Verifications; @@ -17,7 +17,7 @@ public class UserTest { @Tested - private Raven raven = null; + private Sentry sentry = null; @Injectable private Connection mockConnection = null; @Injectable @@ -30,16 +30,16 @@ public void setup() { @Test public void testUserPropagation() { - raven.addBuilderHelper(new ContextBuilderHelper(raven)); + sentry.addBuilderHelper(new ContextBuilderHelper(sentry)); final User user = new UserBuilder() .setEmail("test@example.com") .setId("1234") .setIpAddress("192.168.0.1") .setUsername("testUser_123").build(); - raven.getContext().setUser(user); + sentry.getContext().setUser(user); - raven.sendEvent(new EventBuilder() + sentry.sendEvent(new EventBuilder() .withMessage("Some random message") .withLevel(Event.Level.INFO)); diff --git a/raven/src/test/java/com/getsentry/raven/event/helper/HttpEventBuilderHelperTest.java b/sentry/src/test/java/io/sentry/event/helper/HttpEventBuilderHelperTest.java similarity index 77% rename from raven/src/test/java/com/getsentry/raven/event/helper/HttpEventBuilderHelperTest.java rename to sentry/src/test/java/io/sentry/event/helper/HttpEventBuilderHelperTest.java index 17f029eaa09..c253cce70de 100644 --- a/raven/src/test/java/com/getsentry/raven/event/helper/HttpEventBuilderHelperTest.java +++ b/sentry/src/test/java/io/sentry/event/helper/HttpEventBuilderHelperTest.java @@ -1,11 +1,11 @@ -package com.getsentry.raven.event.helper; +package io.sentry.event.helper; import mockit.*; -import com.getsentry.raven.event.EventBuilder; -import com.getsentry.raven.event.interfaces.HttpInterface; -import com.getsentry.raven.event.interfaces.SentryInterface; -import com.getsentry.raven.event.interfaces.UserInterface; -import com.getsentry.raven.servlet.RavenServletRequestListener; +import io.sentry.event.EventBuilder; +import io.sentry.event.interfaces.HttpInterface; +import io.sentry.event.interfaces.SentryInterface; +import io.sentry.event.interfaces.UserInterface; +import io.sentry.servlet.SentryServletRequestListener; import org.testng.annotations.Test; import javax.servlet.http.HttpServletRequest; @@ -22,8 +22,8 @@ public class HttpEventBuilderHelperTest { @Test public void testNoRequest() throws Exception { - new NonStrictExpectations(RavenServletRequestListener.class) {{ - RavenServletRequestListener.getServletRequest(); + new NonStrictExpectations(SentryServletRequestListener.class) {{ + SentryServletRequestListener.getServletRequest(); result = null; }}; httpEventBuilderHelper.helpBuildingEvent(mockEventBuilder); @@ -39,8 +39,8 @@ public void testNoRequest() throws Exception { @Test public void testWithRequest(@Injectable final HttpServletRequest mockHttpServletRequest) throws Exception { - new NonStrictExpectations(RavenServletRequestListener.class) {{ - RavenServletRequestListener.getServletRequest(); + new NonStrictExpectations(SentryServletRequestListener.class) {{ + SentryServletRequestListener.getServletRequest(); result = mockHttpServletRequest; }}; @@ -59,8 +59,8 @@ public void testWithUserPrincipal(@Injectable final HttpServletRequest mockHttpS @Injectable final Principal mockUserPrincipal, @Injectable("93ad24e4-cad1-4214-af8e-2e48e76b02de") final String mockUsername) throws Exception { - new NonStrictExpectations(RavenServletRequestListener.class) {{ - RavenServletRequestListener.getServletRequest(); + new NonStrictExpectations(SentryServletRequestListener.class) {{ + SentryServletRequestListener.getServletRequest(); result = mockHttpServletRequest; mockHttpServletRequest.getUserPrincipal(); result = mockUserPrincipal; @@ -80,8 +80,8 @@ public void testWithUserPrincipal(@Injectable final HttpServletRequest mockHttpS public void testWithIpAddress(@Injectable final HttpServletRequest mockHttpServletRequest, @Injectable("d90e92cc-1929-4f9e-a44c-3062a8b00c70") final String mockIpAddress) throws Exception { - new NonStrictExpectations(RavenServletRequestListener.class) {{ - RavenServletRequestListener.getServletRequest(); + new NonStrictExpectations(SentryServletRequestListener.class) {{ + SentryServletRequestListener.getServletRequest(); result = mockHttpServletRequest; mockHttpServletRequest.getRemoteAddr(); result = mockIpAddress; diff --git a/raven/src/test/java/com/getsentry/raven/event/interfaces/HttpInterfaceTest.java b/sentry/src/test/java/io/sentry/event/interfaces/HttpInterfaceTest.java similarity index 99% rename from raven/src/test/java/com/getsentry/raven/event/interfaces/HttpInterfaceTest.java rename to sentry/src/test/java/io/sentry/event/interfaces/HttpInterfaceTest.java index 140f79f1dab..a156efb6b7e 100644 --- a/raven/src/test/java/com/getsentry/raven/event/interfaces/HttpInterfaceTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/HttpInterfaceTest.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.event.interfaces; +package io.sentry.event.interfaces; import mockit.Injectable; import mockit.NonStrictExpectations; diff --git a/raven/src/test/java/com/getsentry/raven/event/interfaces/MessageInterfaceTest.java b/sentry/src/test/java/io/sentry/event/interfaces/MessageInterfaceTest.java similarity index 97% rename from raven/src/test/java/com/getsentry/raven/event/interfaces/MessageInterfaceTest.java rename to sentry/src/test/java/io/sentry/event/interfaces/MessageInterfaceTest.java index a6381c6f298..610bbcc2e2a 100644 --- a/raven/src/test/java/com/getsentry/raven/event/interfaces/MessageInterfaceTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/MessageInterfaceTest.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.event.interfaces; +package io.sentry.event.interfaces; import org.testng.annotations.Test; diff --git a/raven/src/test/java/com/getsentry/raven/event/interfaces/SentryExceptionTest.java b/sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java similarity index 96% rename from raven/src/test/java/com/getsentry/raven/event/interfaces/SentryExceptionTest.java rename to sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java index 088029fbf50..82b366731ef 100644 --- a/raven/src/test/java/com/getsentry/raven/event/interfaces/SentryExceptionTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.event.interfaces; +package io.sentry.event.interfaces; import mockit.Delegate; import mockit.Injectable; diff --git a/raven/src/test/java/com/getsentry/raven/event/interfaces/StackTraceInterfaceTest.java b/sentry/src/test/java/io/sentry/event/interfaces/StackTraceInterfaceTest.java similarity index 93% rename from raven/src/test/java/com/getsentry/raven/event/interfaces/StackTraceInterfaceTest.java rename to sentry/src/test/java/io/sentry/event/interfaces/StackTraceInterfaceTest.java index 194b2c501b3..f1a153b5f49 100644 --- a/raven/src/test/java/com/getsentry/raven/event/interfaces/StackTraceInterfaceTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/StackTraceInterfaceTest.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.event.interfaces; +package io.sentry.event.interfaces; import org.testng.annotations.Test; diff --git a/raven/src/test/java/com/getsentry/raven/event/interfaces/UserInterfaceTest.java b/sentry/src/test/java/io/sentry/event/interfaces/UserInterfaceTest.java similarity index 95% rename from raven/src/test/java/com/getsentry/raven/event/interfaces/UserInterfaceTest.java rename to sentry/src/test/java/io/sentry/event/interfaces/UserInterfaceTest.java index 0cc343f743e..1b3a1af4079 100644 --- a/raven/src/test/java/com/getsentry/raven/event/interfaces/UserInterfaceTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/UserInterfaceTest.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.event.interfaces; +package io.sentry.event.interfaces; import org.testng.annotations.Test; diff --git a/raven/src/test/java/com/getsentry/raven/helper/RemoteAddressResolverTest.java b/sentry/src/test/java/io/sentry/helper/RemoteAddressResolverTest.java similarity index 90% rename from raven/src/test/java/com/getsentry/raven/helper/RemoteAddressResolverTest.java rename to sentry/src/test/java/io/sentry/helper/RemoteAddressResolverTest.java index 6d66370e853..f2071b67d6f 100644 --- a/raven/src/test/java/com/getsentry/raven/helper/RemoteAddressResolverTest.java +++ b/sentry/src/test/java/io/sentry/helper/RemoteAddressResolverTest.java @@ -1,8 +1,8 @@ -package com.getsentry.raven.helper; +package io.sentry.helper; -import com.getsentry.raven.dsn.Dsn; -import com.getsentry.raven.event.helper.BasicRemoteAddressResolver; -import com.getsentry.raven.event.helper.ForwardedAddressResolver; +import io.sentry.dsn.Dsn; +import io.sentry.event.helper.BasicRemoteAddressResolver; +import io.sentry.event.helper.ForwardedAddressResolver; import mockit.Expectations; import mockit.Mocked; import org.testng.annotations.Test; diff --git a/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerEventBuildingTest.java b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java similarity index 86% rename from raven/src/test/java/com/getsentry/raven/jul/SentryHandlerEventBuildingTest.java rename to sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java index c840c1c9d9d..f6932a24f40 100644 --- a/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerEventBuildingTest.java +++ b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java @@ -1,16 +1,16 @@ -package com.getsentry.raven.jul; +package io.sentry.jul; -import com.getsentry.raven.environment.RavenEnvironment; +import io.sentry.environment.SentryEnvironment; import mockit.Injectable; import mockit.Tested; import mockit.Verifications; -import com.getsentry.raven.Raven; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.EventBuilder; -import com.getsentry.raven.event.interfaces.ExceptionInterface; -import com.getsentry.raven.event.interfaces.MessageInterface; -import com.getsentry.raven.event.interfaces.SentryException; -import com.getsentry.raven.event.interfaces.SentryInterface; +import io.sentry.Sentry; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; +import io.sentry.event.interfaces.ExceptionInterface; +import io.sentry.event.interfaces.MessageInterface; +import io.sentry.event.interfaces.SentryException; +import io.sentry.event.interfaces.SentryInterface; import org.hamcrest.Matchers; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -30,7 +30,7 @@ public class SentryHandlerEventBuildingTest { @Injectable private ErrorManager errorManager = null; @Injectable - private Raven mockRaven = null; + private Sentry mockSentry = null; private void assertNoErrorsInErrorManager() throws Exception { new Verifications() {{ @@ -51,8 +51,8 @@ public void testSimpleMessageLogging() throws Exception { new Verifications() {{ Event event; - mockRaven.runBuilderHelpers((EventBuilder) any); - mockRaven.sendEvent(event = withCapture()); + mockSentry.runBuilderHelpers((EventBuilder) any); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getMessage(), is(message)); Map sentryInterfaces = event.getSentryInterfaces(); assertThat(sentryInterfaces, hasKey(MessageInterface.MESSAGE_INTERFACE)); @@ -61,7 +61,7 @@ public void testSimpleMessageLogging() throws Exception { assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryHandler.THREAD_ID, (int) threadId)); assertThat(event.getTimestamp(), is(date)); - assertThat(event.getSdkName(), is(RavenEnvironment.SDK_NAME + ":jul")); + assertThat(event.getSdkName(), is(SentryEnvironment.SDK_NAME + ":jul")); }}; assertNoErrorsInErrorManager(); } @@ -84,7 +84,7 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getLevel(), is(expectedLevel)); }}; assertNoErrorsInErrorManager(); @@ -98,7 +98,7 @@ public void testExceptionLogging() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); final SentryException sentryException = exceptionInterface.getExceptions().getFirst(); @@ -119,7 +119,7 @@ public void testCulpritWithSource() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is("a.b")); }}; assertNoErrorsInErrorManager(); @@ -133,7 +133,7 @@ public void testCulpritWithoutSource() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is(loggerName)); }}; assertNoErrorsInErrorManager(); @@ -169,7 +169,7 @@ public void testReleaseAddedToEvent() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getRelease(), is(release)); }}; assertNoErrorsInErrorManager(); @@ -184,7 +184,7 @@ public void testEnvironmentAddedToEvent() throws Exception { new Verifications() {{ Event event; - mockRaven.sendEvent(event = withCapture()); + mockSentry.sendEvent(event = withCapture()); assertThat(event.getEnvironment(), is(environment)); }}; assertNoErrorsInErrorManager(); diff --git a/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerIT.java b/sentry/src/test/java/io/sentry/jul/SentryHandlerIT.java similarity index 77% rename from raven/src/test/java/com/getsentry/raven/jul/SentryHandlerIT.java rename to sentry/src/test/java/io/sentry/jul/SentryHandlerIT.java index 96e9ef5a11f..872e68c5b7d 100644 --- a/raven/src/test/java/com/getsentry/raven/jul/SentryHandlerIT.java +++ b/sentry/src/test/java/io/sentry/jul/SentryHandlerIT.java @@ -1,6 +1,6 @@ -package com.getsentry.raven.jul; +package io.sentry.jul; -import com.getsentry.raven.BaseIT; +import io.sentry.BaseIT; import org.junit.Before; import org.junit.Test; @@ -11,11 +11,11 @@ public class SentryHandlerIT extends BaseIT { /* - We filter out loggers that start with `com.getsentry.raven`, so we deliberately + We filter out loggers that start with `io.sentry`, so we deliberately use a custom logger name here. */ private static final Logger logger = Logger.getLogger("jul.SentryHandlerIT"); - private static final Logger ravenLogger = Logger.getLogger(SentryHandlerIT.class.getName()); + private static final Logger sentryLogger = Logger.getLogger(SentryHandlerIT.class.getName()); @Before public void setup() { @@ -46,11 +46,11 @@ public void testChainedExceptions() throws Exception { } @Test - public void testNoRavenLogging() throws Exception { + public void testNoSentryLogging() throws Exception { verifyProject1PostRequestCount(0); verifyStoredEventCount(0); - ravenLogger.log(Level.SEVERE, "This is a test"); + sentryLogger.log(Level.SEVERE, "This is a test"); verifyProject1PostRequestCount(0); verifyStoredEventCount(0); diff --git a/raven/src/test/java/com/getsentry/raven/marshaller/UncloseableOutputStreamTest.java b/sentry/src/test/java/io/sentry/marshaller/UncloseableOutputStreamTest.java similarity index 97% rename from raven/src/test/java/com/getsentry/raven/marshaller/UncloseableOutputStreamTest.java rename to sentry/src/test/java/io/sentry/marshaller/UncloseableOutputStreamTest.java index e8c50f93597..96305983d0f 100644 --- a/raven/src/test/java/com/getsentry/raven/marshaller/UncloseableOutputStreamTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/UncloseableOutputStreamTest.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.marshaller; +package io.sentry.marshaller; import mockit.Injectable; import mockit.Tested; @@ -67,4 +67,4 @@ public void testClose() throws Exception { times = 0; }}; } -} \ No newline at end of file +} diff --git a/raven/src/test/java/com/getsentry/raven/marshaller/json/ExceptionInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/ExceptionInterfaceBindingTest.java similarity index 90% rename from raven/src/test/java/com/getsentry/raven/marshaller/json/ExceptionInterfaceBindingTest.java rename to sentry/src/test/java/io/sentry/marshaller/json/ExceptionInterfaceBindingTest.java index 880fb2c3805..31ae3de6874 100644 --- a/raven/src/test/java/com/getsentry/raven/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/ExceptionInterfaceBindingTest.java @@ -1,13 +1,13 @@ -package com.getsentry.raven.marshaller.json; +package io.sentry.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; import mockit.Delegate; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; -import com.getsentry.raven.event.interfaces.ExceptionInterface; -import com.getsentry.raven.event.interfaces.SentryException; -import com.getsentry.raven.event.interfaces.StackTraceInterface; +import io.sentry.event.interfaces.ExceptionInterface; +import io.sentry.event.interfaces.SentryException; +import io.sentry.event.interfaces.StackTraceInterface; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -15,7 +15,7 @@ import java.util.Deque; import static mockit.Deencapsulation.setField; -import static com.getsentry.raven.marshaller.json.JsonComparisonUtil.*; +import static io.sentry.marshaller.json.JsonComparisonUtil.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -59,7 +59,7 @@ public Deque getExceptions() { interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockExceptionInterface); - assertThat(jsonGeneratorParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/Exception1.json"))); + assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/Exception1.json"))); } @Test @@ -79,7 +79,7 @@ public Deque getExceptions() { interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockExceptionInterface); - assertThat(jsonGeneratorParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/Exception2.json"))); + assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/Exception2.json"))); } @Test @@ -101,7 +101,7 @@ public Deque getExceptions() { interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockExceptionInterface); - assertThat(jsonGeneratorParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/Exception3.json"))); + assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/Exception3.json"))); } } diff --git a/raven/src/test/java/com/getsentry/raven/marshaller/json/HttpInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/HttpInterfaceBindingTest.java similarity index 89% rename from raven/src/test/java/com/getsentry/raven/marshaller/json/HttpInterfaceBindingTest.java rename to sentry/src/test/java/io/sentry/marshaller/json/HttpInterfaceBindingTest.java index ca4a1ebdeaa..44ff91eadde 100644 --- a/raven/src/test/java/com/getsentry/raven/marshaller/json/HttpInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/HttpInterfaceBindingTest.java @@ -1,7 +1,7 @@ -package com.getsentry.raven.marshaller.json; +package io.sentry.marshaller.json; import com.fasterxml.jackson.databind.JsonNode; -import com.getsentry.raven.event.interfaces.HttpInterface; +import io.sentry.event.interfaces.HttpInterface; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; @@ -12,7 +12,7 @@ import java.util.HashMap; import java.util.Map; -import static com.getsentry.raven.marshaller.json.JsonComparisonUtil.*; +import static io.sentry.marshaller.json.JsonComparisonUtil.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -62,6 +62,6 @@ public void testHeaders() throws Exception { interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockMessageInterface); JsonNode value = jsonGeneratorParser.value(); - assertThat(value, is(jsonResource("/com/getsentry/raven/marshaller/json/Http1.json"))); + assertThat(value, is(jsonResource("/io/sentry/marshaller/json/Http1.json"))); } } diff --git a/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonComparisonUtil.java b/sentry/src/test/java/io/sentry/marshaller/json/JsonComparisonUtil.java similarity index 98% rename from raven/src/test/java/com/getsentry/raven/marshaller/json/JsonComparisonUtil.java rename to sentry/src/test/java/io/sentry/marshaller/json/JsonComparisonUtil.java index b641133a9dd..ac774d83d03 100644 --- a/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonComparisonUtil.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/JsonComparisonUtil.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.marshaller.json; +package io.sentry.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonNode; diff --git a/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java b/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java similarity index 81% rename from raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java rename to sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java index c60aa68ab47..6afee732636 100644 --- a/raven/src/test/java/com/getsentry/raven/marshaller/json/JsonMarshallerTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java @@ -1,11 +1,11 @@ -package com.getsentry.raven.marshaller.json; +package io.sentry.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; -import com.getsentry.raven.event.Breadcrumb; -import com.getsentry.raven.event.BreadcrumbBuilder; +import io.sentry.event.Breadcrumb; +import io.sentry.event.BreadcrumbBuilder; import mockit.*; -import com.getsentry.raven.event.Event; -import com.getsentry.raven.event.interfaces.SentryInterface; +import io.sentry.event.Event; +import io.sentry.event.interfaces.SentryInterface; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -14,7 +14,7 @@ import java.io.IOException; import java.util.*; -import static com.getsentry.raven.marshaller.json.JsonComparisonUtil.*; +import static io.sentry.marshaller.json.JsonComparisonUtil.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -52,7 +52,7 @@ public void testEventIdWrittenProperly(@Injectable final UUID mockUuid) throws E jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEventId.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testEventId.json"))); } @Test @@ -65,7 +65,7 @@ public void testEventMessageWrittenProperly(@Injectable("message") final String jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testMessage.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testMessage.json"))); } @Test @@ -81,17 +81,17 @@ public void testEventTimestampWrittenProperly(@Injectable final Date mockTimesta jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTimestamp.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testTimestamp.json"))); } @DataProvider(name = "levelProvider") public Object[][] levelProvider() { return new Object[][]{ - {Event.Level.DEBUG, "/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json"}, - {Event.Level.INFO, "/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json"}, - {Event.Level.WARNING, "/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json"}, - {Event.Level.ERROR, "/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelError.json"}, - {Event.Level.FATAL, "/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json"}, + {Event.Level.DEBUG, "/io/sentry/marshaller/json/jsonmarshallertest/testLevelDebug.json"}, + {Event.Level.INFO, "/io/sentry/marshaller/json/jsonmarshallertest/testLevelInfo.json"}, + {Event.Level.WARNING, "/io/sentry/marshaller/json/jsonmarshallertest/testLevelWarning.json"}, + {Event.Level.ERROR, "/io/sentry/marshaller/json/jsonmarshallertest/testLevelError.json"}, + {Event.Level.FATAL, "/io/sentry/marshaller/json/jsonmarshallertest/testLevelFatal.json"}, }; } @@ -118,7 +118,7 @@ public void testEventLoggerWrittenProperly(@Injectable("logger") final String mo jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLogger.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testLogger.json"))); } @Test @@ -131,7 +131,7 @@ public void testEventPlaftormWrittenProperly(@Injectable("platform") final Strin jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testPlatform.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testPlatform.json"))); } @Test @@ -147,7 +147,7 @@ public void testEventPlaftormWrittenProperly(@Injectable("sdkName") final String jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testSdk.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json"))); } @Test @@ -160,7 +160,7 @@ public void testEventCulpritWrittenProperly(@Injectable("culprit") final String jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testCulprit.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testCulprit.json"))); } @Test @@ -174,7 +174,7 @@ public void testEventTagsWrittenProperly(@Injectable("tagName") final String moc jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTags.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testTags.json"))); } @Test @@ -188,7 +188,7 @@ public void testFingerPrintWrittenProperly(@Injectable("fingerprint1") final Str jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testFingerprint.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testFingerprint.json"))); } @@ -202,7 +202,7 @@ public void testEventServerNameWrittenProperly(@Injectable("serverName") final S jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testServerName.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testServerName.json"))); } @Test @@ -215,7 +215,7 @@ public void testEventReleaseWrittenProperly(@Injectable("release") final String jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testRelease.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testRelease.json"))); } @Test @@ -228,7 +228,7 @@ public void testEventEnvironmentWrittenProperly(@Injectable("environment") final jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEnvironment.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testEnvironment.json"))); } @Test @@ -259,7 +259,7 @@ public void testEventBreadcrumbsWrittenProperly() throws Exception { jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testBreadcrumbs.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json"))); } @Test @@ -289,7 +289,7 @@ public void testEventContextsWrittenProperly() throws Exception { jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testContexts.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testContexts.json"))); } @Test @@ -302,23 +302,23 @@ public void testEventChecksumWrittenProperly(@Injectable("1234567890abcdef") fin jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testChecksum.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testChecksum.json"))); } @DataProvider(name = "extraProvider") public Object[][] extraProvider() { return new Object[][]{ - {"key", "string", "/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraString.json"}, - {"key", 1, "/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json"}, - {"key", true, "/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json"}, - {"key", null, "/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNull.json"}, - {"key", new Object[]{"string", 1, null, true}, "/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraArray.json"}, - {"key", new Object[]{new Object[]{"string", 1, null, true}}, "/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json"}, - {"key", Arrays.asList(true, null, 1, "string"), "/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json"}, - {"key", Collections.singletonMap("key", "value"), "/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraMap.json"}, - {"key", Collections.singletonMap(true, "value"), "/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json"}, - {"key", Collections.singletonMap(null, "value"), "/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json"}, - {"key", Collections.singletonMap("key", Arrays.asList("string", 1, true, null)), "/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json"} + {"key", "string", "/io/sentry/marshaller/json/jsonmarshallertest/testExtraString.json"}, + {"key", 1, "/io/sentry/marshaller/json/jsonmarshallertest/testExtraNumber.json"}, + {"key", true, "/io/sentry/marshaller/json/jsonmarshallertest/testExtraBoolean.json"}, + {"key", null, "/io/sentry/marshaller/json/jsonmarshallertest/testExtraNull.json"}, + {"key", new Object[]{"string", 1, null, true}, "/io/sentry/marshaller/json/jsonmarshallertest/testExtraArray.json"}, + {"key", new Object[]{new Object[]{"string", 1, null, true}}, "/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json"}, + {"key", Arrays.asList(true, null, 1, "string"), "/io/sentry/marshaller/json/jsonmarshallertest/testExtraIterable.json"}, + {"key", Collections.singletonMap("key", "value"), "/io/sentry/marshaller/json/jsonmarshallertest/testExtraMap.json"}, + {"key", Collections.singletonMap(true, "value"), "/io/sentry/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json"}, + {"key", Collections.singletonMap(null, "value"), "/io/sentry/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json"}, + {"key", Collections.singletonMap("key", Arrays.asList("string", 1, true, null)), "/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json"} }; } @@ -348,7 +348,7 @@ public void testEventExtraWrittenProperly(@Injectable("key") final String mockEx jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testExtraCustomValue.json"))); } @Test @@ -374,7 +374,7 @@ public void writeInterface(JsonGenerator generator, SentryInterface sentryInterf new Verifications() {{ mockInterfaceBinding.writeInterface((JsonGenerator) any, mockSentryInterface); }}; - assertThat(jsonOutputStreamParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json"))); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testInterfaceBinding.json"))); } @Test diff --git a/raven/src/test/java/com/getsentry/raven/marshaller/json/MessageInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/MessageInterfaceBindingTest.java similarity index 83% rename from raven/src/test/java/com/getsentry/raven/marshaller/json/MessageInterfaceBindingTest.java rename to sentry/src/test/java/io/sentry/marshaller/json/MessageInterfaceBindingTest.java index ff7984881b1..b7cdbbb6946 100644 --- a/raven/src/test/java/com/getsentry/raven/marshaller/json/MessageInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/MessageInterfaceBindingTest.java @@ -1,15 +1,15 @@ -package com.getsentry.raven.marshaller.json; +package io.sentry.marshaller.json; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; -import com.getsentry.raven.event.interfaces.MessageInterface; +import io.sentry.event.interfaces.MessageInterface; import org.testng.annotations.Test; import java.util.Arrays; import java.util.List; -import static com.getsentry.raven.marshaller.json.JsonComparisonUtil.*; +import static io.sentry.marshaller.json.JsonComparisonUtil.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -34,6 +34,6 @@ public void testSimpleMessage() throws Exception { interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockMessageInterface); - assertThat(jsonGeneratorParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/Message1.json"))); + assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/Message1.json"))); } } diff --git a/raven/src/test/java/com/getsentry/raven/marshaller/json/StackTraceInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java similarity index 88% rename from raven/src/test/java/com/getsentry/raven/marshaller/json/StackTraceInterfaceBindingTest.java rename to sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java index c6939e70ccc..02befa35314 100644 --- a/raven/src/test/java/com/getsentry/raven/marshaller/json/StackTraceInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java @@ -1,12 +1,12 @@ -package com.getsentry.raven.marshaller.json; +package io.sentry.marshaller.json; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; -import com.getsentry.raven.event.interfaces.StackTraceInterface; +import io.sentry.event.interfaces.StackTraceInterface; import org.testng.annotations.Test; -import static com.getsentry.raven.marshaller.json.JsonComparisonUtil.*; +import static io.sentry.marshaller.json.JsonComparisonUtil.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -30,7 +30,7 @@ public void testSingleStackFrame() throws Exception { interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockStackTraceInterface); - assertThat(jsonGeneratorParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/StackTrace1.json"))); + assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/StackTrace1.json"))); } @Test @@ -47,7 +47,7 @@ public void testFramesCommonWithEnclosing() throws Exception { interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockStackTraceInterface); - assertThat(jsonGeneratorParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/StackTrace2.json"))); + assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/StackTrace2.json"))); } @Test @@ -64,6 +64,6 @@ public void testFramesCommonWithEnclosingDisabled() throws Exception { interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockStackTraceInterface); - assertThat(jsonGeneratorParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/StackTrace3.json"))); + assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/StackTrace3.json"))); } } diff --git a/raven/src/test/java/com/getsentry/raven/marshaller/json/UserInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/UserInterfaceBindingTest.java similarity index 76% rename from raven/src/test/java/com/getsentry/raven/marshaller/json/UserInterfaceBindingTest.java rename to sentry/src/test/java/io/sentry/marshaller/json/UserInterfaceBindingTest.java index 1ea7a7e4da1..e60150064e4 100644 --- a/raven/src/test/java/com/getsentry/raven/marshaller/json/UserInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/UserInterfaceBindingTest.java @@ -1,14 +1,14 @@ -package com.getsentry.raven.marshaller.json; +package io.sentry.marshaller.json; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; -import com.getsentry.raven.event.interfaces.UserInterface; -import com.getsentry.raven.marshaller.json.JsonComparisonUtil.JsonGeneratorParser; +import io.sentry.event.interfaces.UserInterface; +import io.sentry.marshaller.json.JsonComparisonUtil.JsonGeneratorParser; import org.testng.annotations.Test; -import static com.getsentry.raven.marshaller.json.JsonComparisonUtil.jsonResource; -import static com.getsentry.raven.marshaller.json.JsonComparisonUtil.newJsonGenerator; +import static io.sentry.marshaller.json.JsonComparisonUtil.jsonResource; +import static io.sentry.marshaller.json.JsonComparisonUtil.newJsonGenerator; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -38,6 +38,6 @@ public void testSimpleMessage() throws Exception { userInterfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockUserInterface); - assertThat(jsonGeneratorParser.value(), is(jsonResource("/com/getsentry/raven/marshaller/json/User1.json"))); + assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/User1.json"))); } } diff --git a/raven/src/test/java/com/getsentry/raven/servlet/RavenServletContainerInitializerTest.java b/sentry/src/test/java/io/sentry/servlet/SentryServletContainerInitializerTest.java similarity index 64% rename from raven/src/test/java/com/getsentry/raven/servlet/RavenServletContainerInitializerTest.java rename to sentry/src/test/java/io/sentry/servlet/SentryServletContainerInitializerTest.java index 1aa99f6350b..87f7035964c 100644 --- a/raven/src/test/java/com/getsentry/raven/servlet/RavenServletContainerInitializerTest.java +++ b/sentry/src/test/java/io/sentry/servlet/SentryServletContainerInitializerTest.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.servlet; +package io.sentry.servlet; import mockit.Injectable; import mockit.Tested; @@ -13,22 +13,22 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.instanceOf; -public class RavenServletContainerInitializerTest { +public class SentryServletContainerInitializerTest { @Tested - private RavenServletContainerInitializer ravenServletContainerInitializer = null; + private SentryServletContainerInitializer sentryServletContainerInitializer = null; @Test public void testInitializerInjectedViaServiceLoader() throws Exception { ServiceLoader serviceLoader = ServiceLoader.load(ServletContainerInitializer.class); - assertThat(serviceLoader, contains(instanceOf(RavenServletContainerInitializer.class))); + assertThat(serviceLoader, contains(instanceOf(SentryServletContainerInitializer.class))); } @Test public void testFilterAddedToServletContext(@Injectable final ServletContext mockServletContext) throws Exception { - ravenServletContainerInitializer.onStartup(null, mockServletContext); + sentryServletContainerInitializer.onStartup(null, mockServletContext); new Verifications() {{ - mockServletContext.addListener(RavenServletRequestListener.class); + mockServletContext.addListener(SentryServletRequestListener.class); }}; } } diff --git a/raven/src/test/java/com/getsentry/raven/servlet/RavenServletRequestListenerTest.java b/sentry/src/test/java/io/sentry/servlet/SentryServletRequestListenerTest.java similarity index 65% rename from raven/src/test/java/com/getsentry/raven/servlet/RavenServletRequestListenerTest.java rename to sentry/src/test/java/io/sentry/servlet/SentryServletRequestListenerTest.java index e857115c208..b5e290ea6e8 100644 --- a/raven/src/test/java/com/getsentry/raven/servlet/RavenServletRequestListenerTest.java +++ b/sentry/src/test/java/io/sentry/servlet/SentryServletRequestListenerTest.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.servlet; +package io.sentry.servlet; import mockit.Injectable; import mockit.NonStrictExpectations; @@ -15,21 +15,21 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; -public class RavenServletRequestListenerTest { +public class SentryServletRequestListenerTest { @Tested - private RavenServletRequestListener ravenServletRequestListener = null; + private SentryServletRequestListener sentryServletRequestListener = null; @Injectable private ServletRequestEvent mockServletRequestEvent = null; @AfterMethod public void tearDown() throws Exception { // Reset the threadLocal value - ((ThreadLocal) getField(RavenServletRequestListener.class, "THREAD_REQUEST")).remove(); + ((ThreadLocal) getField(SentryServletRequestListener.class, "THREAD_REQUEST")).remove(); } @Test public void requestListenerEmptyByDefault() throws Exception { - assertThat(RavenServletRequestListener.getServletRequest(), is(nullValue())); + assertThat(SentryServletRequestListener.getServletRequest(), is(nullValue())); } @Test @@ -40,9 +40,9 @@ public void requestListenerContainsTheCurrentRequest(@Injectable final HttpServl result = mockHttpServletRequest; }}; - ravenServletRequestListener.requestInitialized(mockServletRequestEvent); + sentryServletRequestListener.requestInitialized(mockServletRequestEvent); - assertThat(RavenServletRequestListener.getServletRequest(), is(mockHttpServletRequest)); + assertThat(SentryServletRequestListener.getServletRequest(), is(mockHttpServletRequest)); } @Test @@ -53,9 +53,9 @@ public void requestListenerDoesntWorkWithNonHttpRequests(@Injectable final Servl result = mockServletRequest; }}; - ravenServletRequestListener.requestInitialized(mockServletRequestEvent); + sentryServletRequestListener.requestInitialized(mockServletRequestEvent); - assertThat(RavenServletRequestListener.getServletRequest(), is(nullValue())); + assertThat(SentryServletRequestListener.getServletRequest(), is(nullValue())); } @Test @@ -65,11 +65,11 @@ public void requestListenerDestroyRemovesTheCurrentRequest(@Injectable final Htt mockServletRequestEvent.getServletRequest(); result = mockHttpServletRequest; }}; - ravenServletRequestListener.requestInitialized(mockServletRequestEvent); + sentryServletRequestListener.requestInitialized(mockServletRequestEvent); - ravenServletRequestListener.requestDestroyed(mockServletRequestEvent); + sentryServletRequestListener.requestDestroyed(mockServletRequestEvent); - assertThat(RavenServletRequestListener.getServletRequest(), is(nullValue())); + assertThat(SentryServletRequestListener.getServletRequest(), is(nullValue())); } @Test @@ -83,10 +83,10 @@ public void requestListenerSpecificToLocalThread(@Injectable final HttpServletRe new Thread() { @Override public void run() { - ravenServletRequestListener.requestInitialized(mockServletRequestEvent); + sentryServletRequestListener.requestInitialized(mockServletRequestEvent); } }.start(); - assertThat(RavenServletRequestListener.getServletRequest(), is(nullValue())); + assertThat(SentryServletRequestListener.getServletRequest(), is(nullValue())); } -} \ No newline at end of file +} diff --git a/raven/src/test/java/com/getsentry/raven/time/ClockTest.java b/sentry/src/test/java/io/sentry/time/ClockTest.java similarity index 95% rename from raven/src/test/java/com/getsentry/raven/time/ClockTest.java rename to sentry/src/test/java/io/sentry/time/ClockTest.java index bf3b6775e03..eb538d48ec1 100644 --- a/raven/src/test/java/com/getsentry/raven/time/ClockTest.java +++ b/sentry/src/test/java/io/sentry/time/ClockTest.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.time; +package io.sentry.time; import org.testng.annotations.Test; diff --git a/raven/src/test/java/com/getsentry/raven/unmarshaller/Base64.java b/sentry/src/test/java/io/sentry/unmarshaller/Base64.java similarity index 99% rename from raven/src/test/java/com/getsentry/raven/unmarshaller/Base64.java rename to sentry/src/test/java/io/sentry/unmarshaller/Base64.java index 190d1c67f0c..4854987eb9e 100644 --- a/raven/src/test/java/com/getsentry/raven/unmarshaller/Base64.java +++ b/sentry/src/test/java/io/sentry/unmarshaller/Base64.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.getsentry.raven.unmarshaller; +package io.sentry.unmarshaller; import java.io.UnsupportedEncodingException; @@ -682,4 +682,4 @@ public boolean process(byte[] input, int offset, int len, boolean finish) { } } private Base64() { } // don't instantiate -} \ No newline at end of file +} diff --git a/raven/src/test/java/com/getsentry/raven/unmarshaller/Base64InputStream.java b/sentry/src/test/java/io/sentry/unmarshaller/Base64InputStream.java similarity index 99% rename from raven/src/test/java/com/getsentry/raven/unmarshaller/Base64InputStream.java rename to sentry/src/test/java/io/sentry/unmarshaller/Base64InputStream.java index d56f8af9b82..97d2737902f 100644 --- a/raven/src/test/java/com/getsentry/raven/unmarshaller/Base64InputStream.java +++ b/sentry/src/test/java/io/sentry/unmarshaller/Base64InputStream.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.getsentry.raven.unmarshaller; +package io.sentry.unmarshaller; import java.io.FilterInputStream; import java.io.IOException; @@ -137,4 +137,4 @@ private void refill() throws IOException { outputEnd = coder.op; outputStart = 0; } -} \ No newline at end of file +} diff --git a/raven/src/test/java/com/getsentry/raven/unmarshaller/JsonDecoder.java b/sentry/src/test/java/io/sentry/unmarshaller/JsonDecoder.java similarity index 99% rename from raven/src/test/java/com/getsentry/raven/unmarshaller/JsonDecoder.java rename to sentry/src/test/java/io/sentry/unmarshaller/JsonDecoder.java index fb56d744034..d012f92fb99 100644 --- a/raven/src/test/java/com/getsentry/raven/unmarshaller/JsonDecoder.java +++ b/sentry/src/test/java/io/sentry/unmarshaller/JsonDecoder.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.unmarshaller; +package io.sentry.unmarshaller; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; @@ -131,4 +131,4 @@ public boolean markSupported() { return original.markSupported(); } } -} \ No newline at end of file +} diff --git a/raven/src/test/java/com/getsentry/raven/unmarshaller/JsonUnmarshaller.java b/sentry/src/test/java/io/sentry/unmarshaller/JsonUnmarshaller.java similarity index 88% rename from raven/src/test/java/com/getsentry/raven/unmarshaller/JsonUnmarshaller.java rename to sentry/src/test/java/io/sentry/unmarshaller/JsonUnmarshaller.java index 9f5034eaede..5ee6b194371 100644 --- a/raven/src/test/java/com/getsentry/raven/unmarshaller/JsonUnmarshaller.java +++ b/sentry/src/test/java/io/sentry/unmarshaller/JsonUnmarshaller.java @@ -1,7 +1,7 @@ -package com.getsentry.raven.unmarshaller; +package io.sentry.unmarshaller; import com.fasterxml.jackson.databind.ObjectMapper; -import com.getsentry.raven.unmarshaller.event.UnmarshalledEvent; +import io.sentry.unmarshaller.event.UnmarshalledEvent; import java.io.IOException; import java.io.InputStream; @@ -26,4 +26,4 @@ public UnmarshalledEvent unmarshal(InputStream source) { return event; } -} \ No newline at end of file +} diff --git a/raven/src/test/java/com/getsentry/raven/unmarshaller/Unmarshaller.java b/sentry/src/test/java/io/sentry/unmarshaller/Unmarshaller.java similarity index 51% rename from raven/src/test/java/com/getsentry/raven/unmarshaller/Unmarshaller.java rename to sentry/src/test/java/io/sentry/unmarshaller/Unmarshaller.java index 304f638317b..98829d210ea 100644 --- a/raven/src/test/java/com/getsentry/raven/unmarshaller/Unmarshaller.java +++ b/sentry/src/test/java/io/sentry/unmarshaller/Unmarshaller.java @@ -1,9 +1,9 @@ -package com.getsentry.raven.unmarshaller; +package io.sentry.unmarshaller; -import com.getsentry.raven.unmarshaller.event.UnmarshalledEvent; +import io.sentry.unmarshaller.event.UnmarshalledEvent; import java.io.InputStream; public interface Unmarshaller { UnmarshalledEvent unmarshal(InputStream source); -} \ No newline at end of file +} diff --git a/raven/src/test/java/com/getsentry/raven/unmarshaller/event/UnmarshalledEvent.java b/sentry/src/test/java/io/sentry/unmarshaller/event/UnmarshalledEvent.java similarity index 88% rename from raven/src/test/java/com/getsentry/raven/unmarshaller/event/UnmarshalledEvent.java rename to sentry/src/test/java/io/sentry/unmarshaller/event/UnmarshalledEvent.java index d78dde387aa..eb37e51e951 100644 --- a/raven/src/test/java/com/getsentry/raven/unmarshaller/event/UnmarshalledEvent.java +++ b/sentry/src/test/java/io/sentry/unmarshaller/event/UnmarshalledEvent.java @@ -1,10 +1,10 @@ -package com.getsentry.raven.unmarshaller.event; +package io.sentry.unmarshaller.event; import com.fasterxml.jackson.annotation.JsonProperty; -import com.getsentry.raven.event.Breadcrumb; -import com.getsentry.raven.unmarshaller.event.interfaces.ExceptionInterface; -import com.getsentry.raven.unmarshaller.event.interfaces.MessageInterface; -import com.getsentry.raven.unmarshaller.event.interfaces.StackTraceInterface; +import io.sentry.event.Breadcrumb; +import io.sentry.unmarshaller.event.interfaces.ExceptionInterface; +import io.sentry.unmarshaller.event.interfaces.MessageInterface; +import io.sentry.unmarshaller.event.interfaces.StackTraceInterface; import java.util.Date; import java.util.List; @@ -69,4 +69,4 @@ public void setExceptionInterfacesLong(List exceptionInterfa public void setStackTraceInterfaceLong(StackTraceInterface stackTraceInterface) { this.stackTraceInterface = stackTraceInterface; } -} \ No newline at end of file +} diff --git a/raven/src/test/java/com/getsentry/raven/unmarshaller/event/interfaces/ExceptionInterface.java b/sentry/src/test/java/io/sentry/unmarshaller/event/interfaces/ExceptionInterface.java similarity index 85% rename from raven/src/test/java/com/getsentry/raven/unmarshaller/event/interfaces/ExceptionInterface.java rename to sentry/src/test/java/io/sentry/unmarshaller/event/interfaces/ExceptionInterface.java index 9bb9a6568c7..43f69726c9b 100644 --- a/raven/src/test/java/com/getsentry/raven/unmarshaller/event/interfaces/ExceptionInterface.java +++ b/sentry/src/test/java/io/sentry/unmarshaller/event/interfaces/ExceptionInterface.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.unmarshaller.event.interfaces; +package io.sentry.unmarshaller.event.interfaces; import com.fasterxml.jackson.annotation.JsonProperty; @@ -11,4 +11,4 @@ public class ExceptionInterface { private String module; @JsonProperty(value = "stacktrace") private StackTraceInterface stackTraceInterface; -} \ No newline at end of file +} diff --git a/raven/src/test/java/com/getsentry/raven/unmarshaller/event/interfaces/MessageInterface.java b/sentry/src/test/java/io/sentry/unmarshaller/event/interfaces/MessageInterface.java similarity index 84% rename from raven/src/test/java/com/getsentry/raven/unmarshaller/event/interfaces/MessageInterface.java rename to sentry/src/test/java/io/sentry/unmarshaller/event/interfaces/MessageInterface.java index 9f4a28c5d0f..2d9a8126fe1 100644 --- a/raven/src/test/java/com/getsentry/raven/unmarshaller/event/interfaces/MessageInterface.java +++ b/sentry/src/test/java/io/sentry/unmarshaller/event/interfaces/MessageInterface.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.unmarshaller.event.interfaces; +package io.sentry.unmarshaller.event.interfaces; import com.fasterxml.jackson.annotation.JsonProperty; @@ -11,4 +11,4 @@ public class MessageInterface { private List params; @JsonProperty(value = "formatted") private String formatted; -} \ No newline at end of file +} diff --git a/raven/src/test/java/com/getsentry/raven/unmarshaller/event/interfaces/StackTraceInterface.java b/sentry/src/test/java/io/sentry/unmarshaller/event/interfaces/StackTraceInterface.java similarity index 94% rename from raven/src/test/java/com/getsentry/raven/unmarshaller/event/interfaces/StackTraceInterface.java rename to sentry/src/test/java/io/sentry/unmarshaller/event/interfaces/StackTraceInterface.java index a710ba8692d..c925a89aa0d 100644 --- a/raven/src/test/java/com/getsentry/raven/unmarshaller/event/interfaces/StackTraceInterface.java +++ b/sentry/src/test/java/io/sentry/unmarshaller/event/interfaces/StackTraceInterface.java @@ -1,4 +1,4 @@ -package com.getsentry.raven.unmarshaller.event.interfaces; +package io.sentry.unmarshaller.event.interfaces; import com.fasterxml.jackson.annotation.JsonProperty; @@ -32,4 +32,4 @@ public static class StackFrame { @JsonProperty(value = "vars") private Object vars; } -} \ No newline at end of file +} diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/Exception1.json b/sentry/src/test/resources/io/sentry/marshaller/json/Exception1.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/Exception1.json rename to sentry/src/test/resources/io/sentry/marshaller/json/Exception1.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/Exception2.json b/sentry/src/test/resources/io/sentry/marshaller/json/Exception2.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/Exception2.json rename to sentry/src/test/resources/io/sentry/marshaller/json/Exception2.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/Exception3.json b/sentry/src/test/resources/io/sentry/marshaller/json/Exception3.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/Exception3.json rename to sentry/src/test/resources/io/sentry/marshaller/json/Exception3.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/Http1.json b/sentry/src/test/resources/io/sentry/marshaller/json/Http1.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/Http1.json rename to sentry/src/test/resources/io/sentry/marshaller/json/Http1.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/Message1.json b/sentry/src/test/resources/io/sentry/marshaller/json/Message1.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/Message1.json rename to sentry/src/test/resources/io/sentry/marshaller/json/Message1.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/StackTrace1.json b/sentry/src/test/resources/io/sentry/marshaller/json/StackTrace1.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/StackTrace1.json rename to sentry/src/test/resources/io/sentry/marshaller/json/StackTrace1.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/StackTrace2.json b/sentry/src/test/resources/io/sentry/marshaller/json/StackTrace2.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/StackTrace2.json rename to sentry/src/test/resources/io/sentry/marshaller/json/StackTrace2.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/StackTrace3.json b/sentry/src/test/resources/io/sentry/marshaller/json/StackTrace3.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/StackTrace3.json rename to sentry/src/test/resources/io/sentry/marshaller/json/StackTrace3.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/User1.json b/sentry/src/test/resources/io/sentry/marshaller/json/User1.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/User1.json rename to sentry/src/test/resources/io/sentry/marshaller/json/User1.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testBreadcrumbs.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testBreadcrumbs.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testChecksum.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testChecksum.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testChecksum.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testChecksum.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testContexts.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testContexts.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testContexts.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testContexts.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testCulprit.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCulprit.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testCulprit.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCulprit.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEnvironment.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEnvironment.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEnvironment.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEnvironment.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEventId.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEventId.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testEventId.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEventId.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraArray.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraArray.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraArray.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraArray.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraBoolean.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraBoolean.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraBoolean.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraCustomValue.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraCustomValue.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraCustomValue.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraIterable.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraIterable.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraIterable.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraMap.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraMap.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraMap.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraMap.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNull.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNull.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNull.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNull.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNumber.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraNumber.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNumber.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraString.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraString.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testExtraString.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraString.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testFingerprint.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testFingerprint.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testFingerprint.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testFingerprint.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testInterfaceBinding.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testInterfaceBinding.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testInterfaceBinding.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelDebug.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelDebug.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelDebug.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelError.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelError.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelError.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelError.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelFatal.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelFatal.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelFatal.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelInfo.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelInfo.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelInfo.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelWarning.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLevelWarning.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelWarning.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLogger.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLogger.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testLogger.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLogger.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testMessage.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testMessage.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testMessage.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testMessage.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testPlatform.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testPlatform.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testPlatform.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testPlatform.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testRelease.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testRelease.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testRelease.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testRelease.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testSdk.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testSdk.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testServerName.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testServerName.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testServerName.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testServerName.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTags.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTags.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTags.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTags.json diff --git a/raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTimestamp.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTimestamp.json similarity index 100% rename from raven/src/test/resources/com/getsentry/raven/marshaller/json/jsonmarshallertest/testTimestamp.json rename to sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTimestamp.json diff --git a/sentry/src/test/resources/jndi.properties b/sentry/src/test/resources/jndi.properties new file mode 100644 index 00000000000..5edb4d7d547 --- /dev/null +++ b/sentry/src/test/resources/jndi.properties @@ -0,0 +1 @@ +java.naming.factory.initial=io.sentry.dsn.InitialContextFactory diff --git a/sentry/src/test/resources/logging-integration.properties b/sentry/src/test/resources/logging-integration.properties new file mode 100644 index 00000000000..29e5eda5213 --- /dev/null +++ b/sentry/src/test/resources/logging-integration.properties @@ -0,0 +1,13 @@ + +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +java.util.logging.ConsoleHandler.level=INFO +java.util.logging.SimpleFormatter.format=[SENTRY] [%4$.4s] %3$s - %5$s%n + +io.sentry.jul.SentryHandler.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?async=false +# Set Sentry to WARNING level, as we recommend this as the lowest users go in their own configuration +io.sentry.jul.SentryHandler.level=WARNING + +handlers=java.util.logging.ConsoleHandler +.level=INFO + +jul.SentryHandlerIT.handlers=java.util.logging.ConsoleHandler, io.sentry.jul.SentryHandler diff --git a/raven/src/test/resources/logging-test.properties b/sentry/src/test/resources/logging-test.properties similarity index 70% rename from raven/src/test/resources/logging-test.properties rename to sentry/src/test/resources/logging-test.properties index fc4bb62f3e2..f7fd6978698 100644 --- a/raven/src/test/resources/logging-test.properties +++ b/sentry/src/test/resources/logging-test.properties @@ -3,4 +3,4 @@ handlers=java.util.logging.ConsoleHandler java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter java.util.logging.ConsoleHandler.level=ALL -java.util.logging.SimpleFormatter.format=[RAVEN] [%4$.4s] %3$s - %5$s%n +java.util.logging.SimpleFormatter.format=[SENTRY] [%4$.4s] %3$s - %5$s%n diff --git a/raven/src/test/resources/raven-build.properties b/sentry/src/test/resources/sentry-build.properties similarity index 100% rename from raven/src/test/resources/raven-build.properties rename to sentry/src/test/resources/sentry-build.properties diff --git a/src/checkstyle/suppressions.xml b/src/checkstyle/suppressions.xml index 93097875bb7..4c3960263dd 100644 --- a/src/checkstyle/suppressions.xml +++ b/src/checkstyle/suppressions.xml @@ -4,6 +4,6 @@ "http://www.puppycrawl.com/dtds/suppressions_1_1.dtd"> - - + + diff --git a/src/site/site.xml b/src/site/site.xml deleted file mode 100644 index 7dde67dce06..00000000000 --- a/src/site/site.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - org.apache.maven.skins - maven-fluido-skin - 1.3.0 - - - - - pre-release - true - - ${github.repo} - right - red - - - - - - - - - -

    - - - - From 2931f203695a0ef4ee78192ea2816ad33e7d08e8 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 19 Apr 2017 19:42:46 -0500 Subject: [PATCH 1577/2152] Add SentryStackTraceElement class and use it in place of Java's StackTraceElement so we can better support React Native. --- .../SentryAppenderEventBuildingTest.java | 6 +- .../SentryAppenderEventBuildingTest.java | 10 +- .../SentryAppenderEventBuildingTest.java | 10 +- .../interfaces/SentryStackTraceElement.java | 134 ++++++++++++++++++ .../event/interfaces/StackTraceInterface.java | 16 ++- .../json/StackTraceInterfaceBinding.java | 41 ++++-- .../jul/SentryHandlerEventBuildingTest.java | 8 +- .../java/io/sentry/jul/SentryHandlerIT.java | 2 - .../json/StackTraceInterfaceBindingTest.java | 31 +++- .../event/interfaces/StackTraceInterface.java | 2 + .../marshaller/json/SentryStackTrace.json | 14 ++ 11 files changed, 230 insertions(+), 44 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java create mode 100644 sentry/src/test/resources/io/sentry/marshaller/json/SentryStackTrace.json diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java index 9bd84d1f970..3fba3e34e24 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java @@ -1,6 +1,7 @@ package io.sentry.log4j; import io.sentry.environment.SentryEnvironment; +import io.sentry.event.interfaces.SentryStackTraceElement; import mockit.*; import io.sentry.Sentry; import io.sentry.event.Event; @@ -112,7 +113,8 @@ public void testExceptionLogging() throws Exception { .get(ExceptionInterface.EXCEPTION_INTERFACE); SentryException sentryException = exceptionInterface.getExceptions().getFirst(); assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); - assertThat(sentryException.getStackTraceInterface().getStackTrace(), is(exception.getStackTrace())); + assertThat(sentryException.getStackTraceInterface().getStackTrace(), + is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace()))); }}; assertNoErrorsInErrorHandler(); } @@ -176,7 +178,7 @@ public void testSourceUsedAsStacktrace(@Injectable final LocationInfo locationIn .get(StackTraceInterface.STACKTRACE_INTERFACE); assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); assertThat(stackTraceInterface.getStackTrace()[0], - is(new StackTraceElement(className, methodName, fileName, line))); + is(new SentryStackTraceElement(className, methodName, fileName, line, null, null, null))); }}; assertNoErrorsInErrorHandler(); } diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java index 43a47293c9e..9347546f1a6 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java @@ -1,16 +1,13 @@ package io.sentry.log4j2; import io.sentry.environment.SentryEnvironment; +import io.sentry.event.interfaces.*; import mockit.Injectable; import mockit.Tested; import mockit.Verifications; import io.sentry.Sentry; import io.sentry.event.Event; import io.sentry.event.EventBuilder; -import io.sentry.event.interfaces.ExceptionInterface; -import io.sentry.event.interfaces.MessageInterface; -import io.sentry.event.interfaces.SentryException; -import io.sentry.event.interfaces.StackTraceInterface; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.ThreadContext; @@ -107,7 +104,8 @@ public void testExceptionLogging() throws Exception { .get(ExceptionInterface.EXCEPTION_INTERFACE); SentryException sentryException = exceptionInterface.getExceptions().getFirst(); assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); - assertThat(sentryException.getStackTraceInterface().getStackTrace(), is(exception.getStackTrace())); + assertThat(sentryException.getStackTraceInterface().getStackTrace(), + is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace()))); }}; assertNoErrorsInErrorHandler(); } @@ -198,7 +196,7 @@ public void testSourceUsedAsStacktrace() throws Exception { StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() .get(StackTraceInterface.STACKTRACE_INTERFACE); assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); - assertThat(stackTraceInterface.getStackTrace()[0], is(location)); + assertThat(stackTraceInterface.getStackTrace()[0], is(SentryStackTraceElement.fromStackTraceElement(location))); }}; assertNoErrorsInErrorHandler(); } diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java index f0049e25579..28c2126e5ae 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java @@ -6,6 +6,7 @@ import ch.qos.logback.core.Context; import ch.qos.logback.core.status.OnConsoleStatusListener; import io.sentry.environment.SentryEnvironment; +import io.sentry.event.interfaces.*; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; @@ -13,10 +14,6 @@ import io.sentry.Sentry; import io.sentry.event.Event; import io.sentry.event.EventBuilder; -import io.sentry.event.interfaces.ExceptionInterface; -import io.sentry.event.interfaces.MessageInterface; -import io.sentry.event.interfaces.SentryException; -import io.sentry.event.interfaces.StackTraceInterface; import org.hamcrest.Matchers; import org.slf4j.MarkerFactory; import org.testng.annotations.BeforeMethod; @@ -121,7 +118,8 @@ public void testExceptionLogging() throws Exception { .get(ExceptionInterface.EXCEPTION_INTERFACE); SentryException sentryException = exceptionInterface.getExceptions().getFirst(); assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); - assertThat(sentryException.getStackTraceInterface().getStackTrace(), is(exception.getStackTrace())); + assertThat(sentryException.getStackTraceInterface().getStackTrace(), + is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace()))); }}; assertNoErrorsInStatusManager(); } @@ -235,7 +233,7 @@ public void testSourceUsedAsStacktrace() throws Exception { mockSentry.sendEvent(event = withCapture()); StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() .get(StackTraceInterface.STACKTRACE_INTERFACE); - assertThat(stackTraceInterface.getStackTrace(), is(location)); + assertThat(stackTraceInterface.getStackTrace(), is(SentryStackTraceElement.fromStackTraceElements(location))); }}; assertNoErrorsInStatusManager(); } diff --git a/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java b/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java new file mode 100644 index 00000000000..fe00b903943 --- /dev/null +++ b/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java @@ -0,0 +1,134 @@ +package io.sentry.event.interfaces; + +import java.util.Objects; + +/** + * Richer StackTraceElement class. + */ +public class SentryStackTraceElement { + private final String module; + private final String function; + private final String fileName; + private final int lineno; + private final Integer colno; + private final String absPath; + private final String platform; + + /** + * Construct a SentryStackTraceElement. + * + * @param module Module (class) name. + * @param function Function (method) name. + * @param fileName Filename. + * @param lineno Line number. + * @param colno Column number. + * @param absPath Absolute path. + * @param platform Platform name. + */ + public SentryStackTraceElement(String module, String function, String fileName, int lineno, + Integer colno, String absPath, String platform) { + this.module = module; + this.function = function; + this.fileName = fileName; + this.lineno = lineno; + this.colno = colno; + this.absPath = absPath; + this.platform = platform; + } + + public String getModule() { + return module; + } + + public String getFunction() { + return function; + } + + public String getFileName() { + return fileName; + } + + public int getLineno() { + return lineno; + } + + public Integer getColno() { + return colno; + } + + public String getAbsPath() { + return absPath; + } + + public String getPlatform() { + return platform; + } + + /** + * Convert an array of {@link StackTraceElement}s to {@link SentryStackTraceElement}s. + * + * @param stackTraceElements Array of {@link StackTraceElement}s to convert. + * @return Array of {@link SentryStackTraceElement}s. + */ + public static SentryStackTraceElement[] fromStackTraceElements(StackTraceElement[] stackTraceElements) { + SentryStackTraceElement[] sentryStackTraceElements = new SentryStackTraceElement[stackTraceElements.length]; + for (int i = 0; i < stackTraceElements.length; i++) { + sentryStackTraceElements[i] = fromStackTraceElement(stackTraceElements[i]); + } + return sentryStackTraceElements; + } + + /** + * Convert a single {@link StackTraceElement} to a {@link SentryStackTraceElement}. + * + * @param stackTraceElement {@link StackTraceElement} to convert. + * @return {@link SentryStackTraceElement} + */ + public static SentryStackTraceElement fromStackTraceElement(StackTraceElement stackTraceElement) { + return new SentryStackTraceElement( + stackTraceElement.getClassName(), + stackTraceElement.getMethodName(), + stackTraceElement.getFileName(), + stackTraceElement.getLineNumber(), + null, + null, + null + ); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SentryStackTraceElement that = (SentryStackTraceElement) o; + return lineno == that.lineno + && Objects.equals(module, that.module) + && Objects.equals(function, that.function) + && Objects.equals(fileName, that.fileName) + && Objects.equals(colno, that.colno) + && Objects.equals(absPath, that.absPath) + && Objects.equals(platform, that.platform); + } + + @Override + public int hashCode() { + return Objects.hash(module, function, fileName, lineno, colno, absPath, platform); + } + + @Override + public String toString() { + return "SentryStackTraceElement{" + + "module='" + module + '\'' + + ", function='" + function + '\'' + + ", fileName='" + fileName + '\'' + + ", lineno=" + lineno + + ", colno=" + colno + + ", absPath='" + absPath + '\'' + + ", platform='" + platform + '\'' + + '}'; + } +} diff --git a/sentry/src/main/java/io/sentry/event/interfaces/StackTraceInterface.java b/sentry/src/main/java/io/sentry/event/interfaces/StackTraceInterface.java index 37bca28a177..84f92adcb6a 100644 --- a/sentry/src/main/java/io/sentry/event/interfaces/StackTraceInterface.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/StackTraceInterface.java @@ -10,7 +10,7 @@ public class StackTraceInterface implements SentryInterface { * Name of the Sentry interface allowing to send a StackTrace. */ public static final String STACKTRACE_INTERFACE = "sentry.interfaces.Stacktrace"; - private final StackTraceElement[] stackTrace; + private final SentryStackTraceElement[] stackTrace; private final int framesCommonWithEnclosing; /** @@ -33,7 +33,7 @@ public StackTraceInterface(StackTraceElement[] stackTrace) { * are in common. */ public StackTraceInterface(StackTraceElement[] stackTrace, StackTraceElement[] enclosingStackTrace) { - this.stackTrace = Arrays.copyOf(stackTrace, stackTrace.length); + this.stackTrace = SentryStackTraceElement.fromStackTraceElements(stackTrace); int m = stackTrace.length - 1; int n = enclosingStackTrace.length - 1; @@ -44,12 +44,22 @@ public StackTraceInterface(StackTraceElement[] stackTrace, StackTraceElement[] e framesCommonWithEnclosing = stackTrace.length - 1 - m; } + /** + * Creates a StackTrace for an {@link io.sentry.event.Event}. + * + * @param stackTrace StackTrace to provide to Sentry. + */ + public StackTraceInterface(SentryStackTraceElement[] stackTrace) { + this.stackTrace = Arrays.copyOf(stackTrace, stackTrace.length);; + this.framesCommonWithEnclosing = 0; + } + @Override public String getInterfaceName() { return STACKTRACE_INTERFACE; } - public StackTraceElement[] getStackTrace() { + public SentryStackTraceElement[] getStackTrace() { return Arrays.copyOf(stackTrace, stackTrace.length); } diff --git a/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java index 6d51bd63f9e..96b799a3e13 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java @@ -1,6 +1,7 @@ package io.sentry.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; +import io.sentry.event.interfaces.SentryStackTraceElement; import io.sentry.event.interfaces.StackTraceInterface; import java.io.IOException; @@ -16,12 +17,14 @@ public class StackTraceInterfaceBinding implements InterfaceBinding inAppFrames = Collections.emptyList(); private boolean removeCommonFramesWithEnclosing = true; @@ -30,22 +33,36 @@ public class StackTraceInterfaceBinding implements InterfaceBinding= 0; i--) { - writeFrame(generator, stackTrace[i], commonWithEnclosing-- > 0); + for (int i = sentryStackTrace.length - 1; i >= 0; i--) { + writeFrame(generator, sentryStackTrace[i], commonWithEnclosing-- > 0); } - generator.writeEndArray(); generator.writeEndObject(); } diff --git a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java index f6932a24f40..546c85b2d58 100644 --- a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java +++ b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java @@ -1,16 +1,13 @@ package io.sentry.jul; import io.sentry.environment.SentryEnvironment; +import io.sentry.event.interfaces.*; import mockit.Injectable; import mockit.Tested; import mockit.Verifications; import io.sentry.Sentry; import io.sentry.event.Event; import io.sentry.event.EventBuilder; -import io.sentry.event.interfaces.ExceptionInterface; -import io.sentry.event.interfaces.MessageInterface; -import io.sentry.event.interfaces.SentryException; -import io.sentry.event.interfaces.SentryInterface; import org.hamcrest.Matchers; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -103,7 +100,8 @@ public void testExceptionLogging() throws Exception { .get(ExceptionInterface.EXCEPTION_INTERFACE); final SentryException sentryException = exceptionInterface.getExceptions().getFirst(); assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); - assertThat(sentryException.getStackTraceInterface().getStackTrace(), is(exception.getStackTrace())); + assertThat(sentryException.getStackTraceInterface().getStackTrace(), + is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace()))); }}; assertNoErrorsInErrorManager(); } diff --git a/sentry/src/test/java/io/sentry/jul/SentryHandlerIT.java b/sentry/src/test/java/io/sentry/jul/SentryHandlerIT.java index 872e68c5b7d..318bf976648 100644 --- a/sentry/src/test/java/io/sentry/jul/SentryHandlerIT.java +++ b/sentry/src/test/java/io/sentry/jul/SentryHandlerIT.java @@ -7,8 +7,6 @@ import java.util.logging.Level; import java.util.logging.Logger; -import static com.github.tomakehurst.wiremock.client.WireMock.*; - public class SentryHandlerIT extends BaseIT { /* We filter out loggers that start with `io.sentry`, so we deliberately diff --git a/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java index 02befa35314..b87565643b4 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java @@ -1,5 +1,6 @@ package io.sentry.marshaller.json; +import io.sentry.event.interfaces.SentryStackTraceElement; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; @@ -16,16 +17,32 @@ public class StackTraceInterfaceBindingTest { @Injectable private StackTraceInterface mockStackTraceInterface = null; + @Test + public void testSingleSentryStackFrame() throws Exception { + final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); + final SentryStackTraceElement sentryStackTraceElement = new SentryStackTraceElement( + "", "throwError", "index.js", 100, 10, + "http://localhost","javascript"); + new NonStrictExpectations() {{ + mockStackTraceInterface.getStackTrace(); + result = new SentryStackTraceElement[]{sentryStackTraceElement}; + }}; + interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockStackTraceInterface); + + assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/SentryStackTrace.json"))); + } + @Test public void testSingleStackFrame() throws Exception { final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final String methodName = "0cce55c9-478f-4386-8ede-4b6f000da3e6"; final String className = "31b26f01-9b97-442b-9f36-8a317f94ad76"; final int lineNumber = 1; - final StackTraceElement stackTraceElement = new StackTraceElement(className, methodName, "File.java", lineNumber); + final SentryStackTraceElement stackTraceElement = new SentryStackTraceElement(className, methodName, + "File.java", lineNumber, null, null, null); new NonStrictExpectations() {{ mockStackTraceInterface.getStackTrace(); - result = new StackTraceElement[]{stackTraceElement}; + result = new SentryStackTraceElement[]{stackTraceElement}; }}; interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockStackTraceInterface); @@ -36,10 +53,11 @@ public void testSingleStackFrame() throws Exception { @Test public void testFramesCommonWithEnclosing() throws Exception { final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); - final StackTraceElement stackTraceElement = new StackTraceElement("", "", "File.java", 0); + final SentryStackTraceElement stackTraceElement = new SentryStackTraceElement("", "", + "File.java", 0, null, null, null); new NonStrictExpectations() {{ mockStackTraceInterface.getStackTrace(); - result = new StackTraceElement[]{stackTraceElement, stackTraceElement}; + result = new SentryStackTraceElement[]{stackTraceElement, stackTraceElement}; mockStackTraceInterface.getFramesCommonWithEnclosing(); result = 1; }}; @@ -53,10 +71,11 @@ public void testFramesCommonWithEnclosing() throws Exception { @Test public void testFramesCommonWithEnclosingDisabled() throws Exception { final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); - final StackTraceElement stackTraceElement = new StackTraceElement("", "", "File.java", 0); + final SentryStackTraceElement stackTraceElement = new SentryStackTraceElement("", "", + "File.java", 0, null, null, null); new NonStrictExpectations() {{ mockStackTraceInterface.getStackTrace(); - result = new StackTraceElement[]{stackTraceElement, stackTraceElement}; + result = new SentryStackTraceElement[]{stackTraceElement, stackTraceElement}; mockStackTraceInterface.getFramesCommonWithEnclosing(); result = 1; }}; diff --git a/sentry/src/test/java/io/sentry/unmarshaller/event/interfaces/StackTraceInterface.java b/sentry/src/test/java/io/sentry/unmarshaller/event/interfaces/StackTraceInterface.java index c925a89aa0d..dfd3a79b9fc 100644 --- a/sentry/src/test/java/io/sentry/unmarshaller/event/interfaces/StackTraceInterface.java +++ b/sentry/src/test/java/io/sentry/unmarshaller/event/interfaces/StackTraceInterface.java @@ -31,5 +31,7 @@ public static class StackFrame { private boolean inApp; @JsonProperty(value = "vars") private Object vars; + @JsonProperty(value = "platform") + private Object platform; } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/SentryStackTrace.json b/sentry/src/test/resources/io/sentry/marshaller/json/SentryStackTrace.json new file mode 100644 index 00000000000..0ccbee8ca6b --- /dev/null +++ b/sentry/src/test/resources/io/sentry/marshaller/json/SentryStackTrace.json @@ -0,0 +1,14 @@ +{ + "frames": [ + { + "filename": "index.js", + "module": "", + "function": "throwError", + "lineno": 100, + "colno": 10, + "platform": "javascript", + "abs_path": "http://localhost", + "in_app": false + } + ] +} \ No newline at end of file From a8280d4dbfba8bd765da323b123bf6b3963bf86c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 20 Apr 2017 12:43:38 -0500 Subject: [PATCH 1578/2152] Add 'dist' field to Event. (#379) --- CHANGES | 1 + .../helper/AndroidEventBuilderHelper.java | 3 +- .../java/io/sentry/log4j/SentryAppender.java | 18 ++++++++ .../java/io/sentry/log4j2/SentryAppender.java | 41 +++++++++++++++---- .../io/sentry/logback/SentryAppender.java | 18 ++++++++ .../src/main/java/io/sentry/event/Event.java | 12 ++++++ .../java/io/sentry/event/EventBuilder.java | 11 +++++ .../java/io/sentry/jul/SentryHandler.java | 22 ++++++++++ .../marshaller/json/JsonMarshaller.java | 5 +++ .../marshaller/json/JsonMarshallerTest.java | 24 +++++++++-- .../unmarshaller/event/UnmarshalledEvent.java | 2 + .../jsonmarshallertest/testBreadcrumbs.json | 1 + .../json/jsonmarshallertest/testChecksum.json | 1 + .../json/jsonmarshallertest/testContexts.json | 1 + .../json/jsonmarshallertest/testCulprit.json | 1 + .../json/jsonmarshallertest/testDist.json | 20 +++++++++ .../jsonmarshallertest/testEnvironment.json | 1 + .../json/jsonmarshallertest/testEventId.json | 1 + .../jsonmarshallertest/testExtraArray.json | 1 + .../jsonmarshallertest/testExtraBoolean.json | 1 + .../testExtraCustomValue.json | 1 + .../jsonmarshallertest/testExtraIterable.json | 1 + .../json/jsonmarshallertest/testExtraMap.json | 1 + .../jsonmarshallertest/testExtraNull.json | 1 + .../testExtraNullKeyMap.json | 1 + .../jsonmarshallertest/testExtraNumber.json | 1 + .../testExtraObjectKeyMap.json | 1 + .../testExtraRecursiveArray.json | 1 + .../testExtraRecursiveMap.json | 1 + .../jsonmarshallertest/testExtraString.json | 1 + .../jsonmarshallertest/testFingerprint.json | 1 + .../testInterfaceBinding.json | 1 + .../jsonmarshallertest/testLevelDebug.json | 1 + .../jsonmarshallertest/testLevelError.json | 1 + .../jsonmarshallertest/testLevelFatal.json | 1 + .../jsonmarshallertest/testLevelInfo.json | 1 + .../jsonmarshallertest/testLevelWarning.json | 1 + .../json/jsonmarshallertest/testLogger.json | 1 + .../json/jsonmarshallertest/testMessage.json | 1 + .../json/jsonmarshallertest/testPlatform.json | 1 + .../json/jsonmarshallertest/testRelease.json | 1 + .../json/jsonmarshallertest/testSdk.json | 1 + .../jsonmarshallertest/testServerName.json | 1 + .../json/jsonmarshallertest/testTags.json | 1 + .../jsonmarshallertest/testTimestamp.json | 1 + 45 files changed, 196 insertions(+), 14 deletions(-) create mode 100644 sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testDist.json diff --git a/CHANGES b/CHANGES index 26062fc1586..0e020e0a519 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ Version 1.0.0 - Rename from ``raven-java`` to ``sentry-java``. - Move from ``com.getsentry.raven`` package to ``io.sentry``. +- Add ``dist`` field to Event. Version 8.0.2 ------------- diff --git a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java index 78a2f07d370..e72489b29f5 100644 --- a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java +++ b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java @@ -60,7 +60,8 @@ public void helpBuildingEvent(EventBuilder eventBuilder) { eventBuilder.withSdkName(SentryEnvironment.SDK_NAME + ":android"); PackageInfo packageInfo = getPackageInfo(ctx); if (packageInfo != null) { - eventBuilder.withRelease(packageInfo.versionName); + eventBuilder.withRelease(packageInfo.packageName + "-" + packageInfo.versionName); + eventBuilder.withDist(Integer.toString(packageInfo.versionCode)); } String androidId = Settings.Secure.getString(ctx.getContentResolver(), Settings.Secure.ANDROID_ID); diff --git a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java index a725c8d62ef..0b2c8fa9086 100644 --- a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java +++ b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java @@ -60,6 +60,12 @@ public class SentryAppender extends AppenderSkeleton { * Might be null in which case the release information will not be sent with the event. */ protected String release; + /** + * Identifies the distribution of the application. + *

    + * Might be null in which case the release distribution will not be sent with the event. + */ + protected String dist; /** * Identifies the environment the application is running in. *

    @@ -127,6 +133,11 @@ private void lazyInit() { setRelease(release); } + String dist = Lookup.lookup("dist"); + if (dist != null) { + setDist(dist); + } + String environment = Lookup.lookup("environment"); if (environment != null) { setEnvironment(environment); @@ -259,6 +270,9 @@ protected Event buildEvent(LoggingEvent loggingEvent) { if (!Util.isNullOrEmpty(release)) { eventBuilder.withRelease(release.trim()); + if (!Util.isNullOrEmpty(dist)) { + eventBuilder.withDist(dist.trim()); + } } if (!Util.isNullOrEmpty(environment)) { @@ -324,6 +338,10 @@ public void setRelease(String release) { this.release = release; } + public void setDist(String dist) { + this.dist = dist; + } + public void setEnvironment(String environment) { this.environment = environment; } diff --git a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java index 27e3d0e4da2..92b674e1b05 100644 --- a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -77,6 +77,12 @@ public class SentryAppender extends AbstractAppender { * Might be null in which case the release information will not be sent with the event. */ protected String release; + /** + * Identifies the distribution of the application. + *

    + * Might be null in which case the release distribution will not be sent with the event. + */ + protected String dist; /** * Identifies the environment the application is running in. *

    @@ -138,15 +144,16 @@ protected SentryAppender(String name, Filter filter) { /** * Create a Sentry Appender. * - * @param name The name of the Appender. - * @param dsn Data Source Name to access the Sentry server. - * @param sentryFactory Name of the factory to use to build the {@link Sentry} instance. - * @param release Release to be sent to Sentry. - * @param environment Environment to be sent to Sentry. - * @param serverName serverName to be sent to Sentry. - * @param tags Tags to add to each event. - * @param extraTags Tags to search through the Thread Context Map. - * @param filter The filter, if any, to use. + * @param name The name of the Appender. + * @param dsn Data Source Name to access the Sentry server. + * @param sentryFactory Name of the factory to use to build the {@link SentryClient} instance. + * @param release Release to be sent to Sentry. + * @param dist Dist to be sent to Sentry. + * @param environment Environment to be sent to Sentry. + * @param serverName serverName to be sent to Sentry. + * @param tags Tags to add to each event. + * @param extraTags Tags to search through the Thread Context Map. + * @param filter The filter, if any, to use. * @return The SentryAppender. */ @PluginFactory @@ -155,6 +162,7 @@ public static SentryAppender createAppender(@PluginAttribute("name") final Strin @PluginAttribute("dsn") final String dsn, @PluginAttribute("sentryFactory") final String sentryFactory, @PluginAttribute("release") final String release, + @PluginAttribute("dist") final String dist, @PluginAttribute("environment") final String environment, @PluginAttribute("serverName") final String serverName, @PluginAttribute("tags") final String tags, @@ -171,6 +179,9 @@ public static SentryAppender createAppender(@PluginAttribute("name") final Strin if (release != null) { sentryAppender.setRelease(release); } + if (dist != null) { + sentryAppender.setDist(dist); + } if (environment != null) { sentryAppender.setEnvironment(environment); } @@ -208,6 +219,11 @@ private void lazyInit() { setRelease(release); } + String dist = Lookup.lookup("dist"); + if (dist != null) { + setDist(dist); + } + String environment = Lookup.lookup("environment"); if (environment != null) { setEnvironment(environment); @@ -341,6 +357,9 @@ protected Event buildEvent(LogEvent event) { if (!Util.isNullOrEmpty(release)) { eventBuilder.withRelease(release.trim()); + if (!Util.isNullOrEmpty(dist)) { + eventBuilder.withDist(dist.trim()); + } } if (!Util.isNullOrEmpty(environment)) { @@ -408,6 +427,10 @@ public void setRelease(String release) { this.release = release; } + public void setDist(String dist) { + this.dist = dist; + } + public void setEnvironment(String environment) { this.environment = environment; } diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index b90a4b3e000..b71129a396e 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -68,6 +68,12 @@ public class SentryAppender extends AppenderBase { * Might be null in which case the release information will not be sent with the event. */ protected String release; + /** + * Identifies the distribution of the application. + *

    + * Might be null in which case the release distribution will not be sent with the event. + */ + protected String dist; /** * Identifies the environment the application is running in. *

    @@ -139,6 +145,11 @@ private void lazyInit() { setRelease(release); } + String dist = Lookup.lookup("dist"); + if (dist != null) { + setDist(dist); + } + String environment = Lookup.lookup("environment"); if (environment != null) { setEnvironment(environment); @@ -275,6 +286,9 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { if (!Util.isNullOrEmpty(release)) { eventBuilder.withRelease(release.trim()); + if (!Util.isNullOrEmpty(dist)) { + eventBuilder.withDist(dist.trim()); + } } if (!Util.isNullOrEmpty(environment)) { @@ -433,6 +447,10 @@ public void setRelease(String release) { this.release = release; } + public void setDist(String dist) { + this.dist = dist; + } + public void setEnvironment(String environment) { this.environment = environment; } diff --git a/sentry/src/main/java/io/sentry/event/Event.java b/sentry/src/main/java/io/sentry/event/Event.java index 342ea344b58..4ed0d0d6297 100644 --- a/sentry/src/main/java/io/sentry/event/Event.java +++ b/sentry/src/main/java/io/sentry/event/Event.java @@ -84,6 +84,10 @@ public class Event implements Serializable { * Identifies the version of the application. */ private String release; + /** + * Identifies the distribution of the application. + */ + private String dist; /** * Identifies the environment the application is running in. */ @@ -237,6 +241,14 @@ void setRelease(String release) { this.release = release; } + public String getDist() { + return dist; + } + + public void setDist(String dist) { + this.dist = dist; + } + public String getEnvironment() { return environment; } diff --git a/sentry/src/main/java/io/sentry/event/EventBuilder.java b/sentry/src/main/java/io/sentry/event/EventBuilder.java index 77401139854..f7a7bdd76ec 100644 --- a/sentry/src/main/java/io/sentry/event/EventBuilder.java +++ b/sentry/src/main/java/io/sentry/event/EventBuilder.java @@ -168,6 +168,17 @@ public EventBuilder withRelease(String release) { return this; } + /** + * Sets application distribution version in the event. + * + * @param dist application distribution version in the event. + * @return the current {@code EventBuilder} for chained calls. + */ + public EventBuilder withDist(String dist) { + event.setDist(dist); + return this; + } + /** * Sets application environment in the event. * diff --git a/sentry/src/main/java/io/sentry/jul/SentryHandler.java b/sentry/src/main/java/io/sentry/jul/SentryHandler.java index 7c7451a7c92..42bd6b2b9b9 100644 --- a/sentry/src/main/java/io/sentry/jul/SentryHandler.java +++ b/sentry/src/main/java/io/sentry/jul/SentryHandler.java @@ -65,6 +65,12 @@ public class SentryHandler extends Handler { * Might be null in which case the release information will not be sent with the event. */ protected String release; + /** + * Identifies the distribution of the application. + *

    + * Might be null in which case the release distribution will not be sent with the event. + */ + protected String dist; /** * Identifies the environment the application is running in. *

    @@ -132,6 +138,11 @@ private void lazyInit() { setRelease(release); } + String dist = Lookup.lookup("dist"); + if (dist != null) { + setDist(dist); + } + String environment = Lookup.lookup("environment"); if (environment != null) { setEnvironment(environment); @@ -217,6 +228,10 @@ protected void retrieveProperties() { if (releaseProperty != null) { setRelease(releaseProperty); } + String distProperty = manager.getProperty(className + ".dist"); + if (distProperty != null) { + setDist(distProperty); + } String environmentProperty = manager.getProperty(className + ".environment"); if (environmentProperty != null) { setEnvironment(environmentProperty); @@ -340,6 +355,9 @@ protected Event buildEvent(LogRecord record) { if (!Util.isNullOrEmpty(release)) { eventBuilder.withRelease(release.trim()); + if (!Util.isNullOrEmpty(dist)) { + eventBuilder.withDist(dist.trim()); + } } if (!Util.isNullOrEmpty(environment)) { @@ -406,6 +424,10 @@ public void setRelease(String release) { this.release = release; } + public void setDist(String dist) { + this.dist = dist; + } + public void setEnvironment(String environment) { this.environment = environment; } diff --git a/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java b/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java index 5e8716cfc6f..7778bfc7428 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java @@ -78,6 +78,10 @@ public class JsonMarshaller implements Marshaller { * Identifies the the version of the application. */ public static final String RELEASE = "release"; + /** + * Identifies the the distribution of the application. + */ + public static final String DIST = "dist"; /** * Identifies the environment the application is running in. */ @@ -180,6 +184,7 @@ private void writeContent(JsonGenerator generator, Event event) throws IOExcepti writeContexts(generator, event.getContexts()); generator.writeStringField(SERVER_NAME, event.getServerName()); generator.writeStringField(RELEASE, event.getRelease()); + generator.writeStringField(DIST, event.getDist()); generator.writeStringField(ENVIRONMENT, event.getEnvironment()); writeExtras(generator, event.getExtra()); writeCollection(generator, FINGERPRINT, event.getFingerprint()); diff --git a/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java b/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java index 6afee732636..781a019bae1 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java @@ -218,6 +218,22 @@ public void testEventReleaseWrittenProperly(@Injectable("release") final String assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testRelease.json"))); } + @Test + public void testEventDistWrittenProperly(@Injectable("release") final String mockRelease, + @Injectable("dist") final String mockDist) throws Exception { + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); + new NonStrictExpectations() {{ + mockEvent.getRelease(); + result = mockRelease; + mockEvent.getDist(); + result = mockDist; + }}; + + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); + + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testDist.json"))); + } + @Test public void testEventEnvironmentWrittenProperly(@Injectable("environment") final String mockEnvironment) throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); @@ -385,10 +401,10 @@ public void testCompressedDataIsWorking() throws Exception { jsonMarshaller.marshall(mockEvent, outputStream); assertThat(new String(outputStream.toByteArray(), "UTF-8"), is("" - + "eJyFj8EKwyAMht8l5w7saczn2L1Imzlp1JLYMih994V1ym6T/+AX/z+JO" - + "+CGqQxhAgvmz4EOIoo4j2DTStRBCVooLi6a7m9XczG96m6M/UgDpP2p2i" - + "l7j1xpIVcemWPlcaWFQ6ko0wx2h+RiG7chS8jpxEPHOy/q0Zsg6+Pwa2Y" - + "kdNIQ0xY4p6i/baVXYXfmxyeOs6zfXY43jPBZ0g==" + + "eJyFj8EKwzAIht/FcwfpaSzPsXsJrctCTVI0LYPSd5+sS9ht4iGf+f3VHXDDVIYw" + + "gQXzJ6CDiCLOI9i0EnVQghaKi4t297eruZhe826M/aQ2kPpTlVP2HrnSQq48MsfK" + + "40oLh1JRphnsDsnFNm5DlpDTiYeOd15Uoy9B1s/hV8xI6KThFKRZY9oC5xT18lZ6" + + "FXan1/jEcZb1u9fxBs7LXes=" )); } } diff --git a/sentry/src/test/java/io/sentry/unmarshaller/event/UnmarshalledEvent.java b/sentry/src/test/java/io/sentry/unmarshaller/event/UnmarshalledEvent.java index eb37e51e951..bc8341695b5 100644 --- a/sentry/src/test/java/io/sentry/unmarshaller/event/UnmarshalledEvent.java +++ b/sentry/src/test/java/io/sentry/unmarshaller/event/UnmarshalledEvent.java @@ -35,6 +35,8 @@ public class UnmarshalledEvent { private String serverName; @JsonProperty(value = "release") private String release; + @JsonProperty(value = "dist") + private String dist; @JsonProperty(value = "environment") private String environment; @JsonProperty(value = "modules") diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json index 907572b7874..b2be143dd43 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": {}, "checksum": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testChecksum.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testChecksum.json index 0f539d28757..81cf201a0ac 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testChecksum.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testChecksum.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": {}, "checksum": "1234567890abcdef", diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testContexts.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testContexts.json index 4094bf88507..1b39db03086 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testContexts.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testContexts.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": {}, "checksum": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCulprit.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCulprit.json index c87935fc74f..2c91e2bfeb9 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCulprit.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCulprit.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": {}, "checksum": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testDist.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testDist.json new file mode 100644 index 00000000000..1bff5bd5974 --- /dev/null +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testDist.json @@ -0,0 +1,20 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "release": "release", + "dist": "dist", + "environment": null, + "extra": {}, + "checksum": null, + "sdk": { + "name": null, + "version": null + } +} diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEnvironment.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEnvironment.json index 1384c8b466d..f8551e45e2d 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEnvironment.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEnvironment.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": "environment", "extra": {}, "checksum": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEventId.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEventId.json index 8b82c8cde91..fb2257cb609 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEventId.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEventId.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": {}, "checksum": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraArray.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraArray.json index e24df5371bc..d63ce5b4305 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraArray.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraArray.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": { "key": [ diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraBoolean.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraBoolean.json index 0cda8102b77..77b30ac06f3 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraBoolean.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraBoolean.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": { "key": true diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraCustomValue.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraCustomValue.json index 8d6ce33adbe..18dce9ca406 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraCustomValue.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraCustomValue.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": { "key": "test" diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraIterable.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraIterable.json index b4dbfeb734f..ae8f4cf3892 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraIterable.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraIterable.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": { "key": [ diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraMap.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraMap.json index 94a5c005e9a..64938136d07 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraMap.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraMap.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": { "key": { diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNull.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNull.json index f3781b86c77..f1f89f41e70 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNull.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNull.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": { "key": null diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json index 3f2c420d408..83e1ade91a5 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": { "key": { diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNumber.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNumber.json index a401012a901..805ffd0f76a 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNumber.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNumber.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": { "key": 1 diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json index 8fa35ba341e..3690d2af0ff 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": { "key": { diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json index 71afb16f4e8..8198a588750 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": { "key": [ diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json index 46472bd6d1f..aead22802d9 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": { "key": { diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraString.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraString.json index b10994f3224..aaac5137a9b 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraString.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraString.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": { "key": "string" diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testFingerprint.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testFingerprint.json index 7b0b1088112..d61bfed0479 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testFingerprint.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testFingerprint.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": {}, "fingerprint": [ diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testInterfaceBinding.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testInterfaceBinding.json index 2446742d4dc..2ec7285b472 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testInterfaceBinding.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testInterfaceBinding.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": {}, "checksum": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelDebug.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelDebug.json index 7e9f7514e52..218e5eea8b3 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelDebug.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelDebug.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": {}, "checksum": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelError.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelError.json index 1ba7f9751ad..db444c29792 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelError.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelError.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": {}, "checksum": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelFatal.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelFatal.json index 9c8f894b166..6e4bda95bab 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelFatal.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelFatal.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": {}, "checksum": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelInfo.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelInfo.json index 986886bca6b..9969e36f342 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelInfo.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelInfo.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": {}, "checksum": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelWarning.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelWarning.json index 2af16e2d25b..7a29641c8d6 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelWarning.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelWarning.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": {}, "checksum": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLogger.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLogger.json index 1f9cabdaabb..ee2d8fb2273 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLogger.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLogger.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": {}, "checksum": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testMessage.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testMessage.json index 44d559372c1..72e57587d3f 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testMessage.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testMessage.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": {}, "checksum": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testPlatform.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testPlatform.json index 978829a5522..7a84d9cc2f4 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testPlatform.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testPlatform.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": {}, "checksum": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testRelease.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testRelease.json index ca43ee78933..e21a6f10da9 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testRelease.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testRelease.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": "release", + "dist": null, "environment": null, "extra": {}, "checksum": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json index 3d292f6db55..55b02f1e80b 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": {}, "checksum": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testServerName.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testServerName.json index 25bf5705f0f..1dbf076ddd5 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testServerName.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testServerName.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": "serverName", "release": null, + "dist": null, "environment": null, "extra": {}, "checksum": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTags.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTags.json index 47787fc1413..28d6eeb7f5c 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTags.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTags.json @@ -11,6 +11,7 @@ }, "server_name": null, "release": null, + "dist": null, "environment": null, "extra": {}, "checksum": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTimestamp.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTimestamp.json index 9a954e27b28..49533e30bd8 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTimestamp.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTimestamp.json @@ -9,6 +9,7 @@ "tags": {}, "server_name": null, "release": null, + "dist": null, "extra": {}, "environment": null, "checksum": null, From 72cef705da7c49833385a741ed95f0a3f36db732 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Sat, 22 Apr 2017 11:30:59 -0500 Subject: [PATCH 1579/2152] =?UTF-8?q?Rename=20Sentry=20to=20SentryClient,?= =?UTF-8?q?=20rename=20android.Sentry=20to=20SentryAndroid=E2=80=A6=20(#37?= =?UTF-8?q?8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename the ``Sentry`` class to ``SentryClient``. - Rename the ``SentryFactory`` class to ``SentryClientFactory``, and renamed all subclasses to now end in ``ClientFactory``. - Remove ``Breadcrumbs`` class. - Add new ``Sentry`` class for static client access and usage. - Rename ``android.Sentry`` to ``android.AndroidSentry`` to make it unambiguous when used in code. - Rename ``sentryFactory`` option to ``factory`` so that the environment variable configuration is now ``SENTRY_FACTORY`` and the Java System Property is ``sentry.factory``. --- CHANGES | 10 +- docs/config.rst | 27 +- docs/context.rst | 13 +- docs/modules/android.rst | 2 +- docs/modules/appengine.rst | 4 +- docs/modules/log4j.rst | 12 +- docs/modules/log4j2.rst | 8 +- docs/modules/logback.rst | 8 +- docs/modules/{raven.rst => sentry.rst} | 8 +- docs/usage.rst | 33 ++- ...y.java => AndroidSentryClientFactory.java} | 18 +- .../{Sentry.java => SentryAndroid.java} | 116 +++----- .../SentryUncaughtExceptionHandler.java | 3 +- .../java/io/sentry/android/AndroidTest.java | 5 +- .../{SentryIT.java => SentryAndroidIT.java} | 2 +- .../io/sentry/android/SentryITActivity.java | 11 +- ...java => AppEngineSentryClientFactory.java} | 19 +- .../services/io.sentry.SentryClientFactory | 1 + .../META-INF/services/io.sentry.SentryFactory | 1 - ... => AppEngineSentryClientFactoryTest.java} | 28 +- .../java/io/sentry/log4j/SentryAppender.java | 46 ++-- .../sentry/log4j/SentryAppenderCloseTest.java | 28 +- .../sentry/log4j/SentryAppenderDsnTest.java | 18 +- .../SentryAppenderEventBuildingTest.java | 32 +-- .../log4j/SentryAppenderFailuresTest.java | 20 +- .../java/io/sentry/log4j2/SentryAppender.java | 58 ++-- .../log4j2/SentryAppenderCloseTest.java | 28 +- .../sentry/log4j2/SentryAppenderDsnTest.java | 18 +- .../SentryAppenderEventBuildingTest.java | 34 +-- .../log4j2/SentryAppenderFailuresTest.java | 20 +- .../io/sentry/logback/SentryAppender.java | 51 ++-- .../logback/SentryAppenderCloseTest.java | 28 +- .../sentry/logback/SentryAppenderDsnTest.java | 18 +- .../SentryAppenderEventBuildingTest.java | 54 ++-- .../SentryAppenderEventLevelFilterTest.java | 10 +- .../logback/SentryAppenderFailuresTest.java | 24 +- ...y.java => DefaultSentryClientFactory.java} | 16 +- sentry/src/main/java/io/sentry/Sentry.java | 249 +++++------------- .../src/main/java/io/sentry/SentryClient.java | 182 +++++++++++++ ...yFactory.java => SentryClientFactory.java} | 85 +++--- .../io/sentry/connection/AsyncConnection.java | 4 +- .../io/sentry/connection/HttpConnection.java | 2 +- .../java/io/sentry/event/Breadcrumbs.java | 26 -- .../src/main/java/io/sentry/event/Event.java | 2 +- .../event/helper/ContextBuilderHelper.java | 16 +- .../java/io/sentry/jul/SentryHandler.java | 51 ++-- .../servlet/SentryServletRequestListener.java | 7 +- .../services/io.sentry.SentryClientFactory | 1 + .../META-INF/services/io.sentry.SentryFactory | 1 - ...va => DefaultSentryClientFactoryTest.java} | 6 +- ...Test.java => SentryClientFactoryTest.java} | 96 ++++--- .../test/java/io/sentry/SentryClientTest.java | 159 +++++++++++ .../src/test/java/io/sentry/SentryTest.java | 146 +--------- .../connection/AsyncConnectionTest.java | 6 +- .../EventSendFailureCallbackTest.java | 16 +- .../java/io/sentry/event/BreadcrumbTest.java | 18 +- .../test/java/io/sentry/event/UserTest.java | 10 +- .../jul/SentryHandlerEventBuildingTest.java | 20 +- 58 files changed, 972 insertions(+), 963 deletions(-) rename docs/modules/{raven.rst => sentry.rst} (95%) rename sentry-android/src/main/java/io/sentry/android/{AndroidSentryFactory.java => AndroidSentryClientFactory.java} (69%) rename sentry-android/src/main/java/io/sentry/android/{Sentry.java => SentryAndroid.java} (55%) rename sentry-android/src/test/java/io/sentry/android/{SentryIT.java => SentryAndroidIT.java} (94%) rename sentry-appengine/src/main/java/io/sentry/appengine/{AppEngineSentryFactory.java => AppEngineSentryClientFactory.java} (76%) create mode 100644 sentry-appengine/src/main/resources/META-INF/services/io.sentry.SentryClientFactory delete mode 100644 sentry-appengine/src/main/resources/META-INF/services/io.sentry.SentryFactory rename sentry-appengine/src/test/java/io/sentry/appengine/{AppEngineSentryFactoryTest.java => AppEngineSentryClientFactoryTest.java} (64%) rename sentry/src/main/java/io/sentry/{DefaultSentryFactory.java => DefaultSentryClientFactory.java} (98%) create mode 100644 sentry/src/main/java/io/sentry/SentryClient.java rename sentry/src/main/java/io/sentry/{SentryFactory.java => SentryClientFactory.java} (58%) delete mode 100644 sentry/src/main/java/io/sentry/event/Breadcrumbs.java create mode 100644 sentry/src/main/resources/META-INF/services/io.sentry.SentryClientFactory delete mode 100644 sentry/src/main/resources/META-INF/services/io.sentry.SentryFactory rename sentry/src/test/java/io/sentry/{DefaultSentryFactoryTest.java => DefaultSentryClientFactoryTest.java} (68%) rename sentry/src/test/java/io/sentry/{SentryFactoryTest.java => SentryClientFactoryTest.java} (51%) create mode 100644 sentry/src/test/java/io/sentry/SentryClientTest.java diff --git a/CHANGES b/CHANGES index 0e020e0a519..2242d06ff1e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,8 +1,16 @@ Version 1.0.0 ------------- -- Rename from ``raven-java`` to ``sentry-java``. - Move from ``com.getsentry.raven`` package to ``io.sentry``. +- Rename from ``raven-java`` to ``sentry-java``. +- Rename the ``Sentry`` class to ``SentryClient``. +- Rename the ``SentryFactory`` class to ``SentryClientFactory``, and renamed all subclasses + to now end in ``ClientFactory``. +- Remove ``Breadcrumbs`` class. +- Add new ``Sentry`` class for static client access and usage. +- Rename ``android.Sentry`` to ``android.AndroidSentry`` to make it unambiguous when used in code. +- Rename ``sentryFactory`` option to ``factory`` so that the environment variable configuration is + now ``SENTRY_FACTORY`` and the Java System Property is ``sentry.factory``. - Add ``dist`` field to Event. Version 8.0.2 diff --git a/docs/config.rst b/docs/config.rst index 249f42a2079..d9797c7424a 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -135,7 +135,7 @@ could be deployed and undeployed regularly. To avoid this behaviour, it is possible to disable the graceful shutdown. This might lead to some log entries being lost if the log application -doesn't shut down the Sentry instance nicely. +doesn't shut down the ``SentryClient`` instance nicely. The option to do so is ``sentry.async.gracefulshutdown``: @@ -351,11 +351,11 @@ It's possible to manually set the timeout length with ``sentry.timeout`` http://public:private@host:port/1?timeout=10000 -Custom SentryFactory --------------------- +Custom SentryClientFactory +-------------------------- At times, you may require custom functionality that is not included in ``sentry-java`` -already. The most common way to do this is to create your own ``SentryFactory`` instance +already. The most common way to do this is to create your own ``SentryClientFactory`` instance as seen in the example below. Note that you'll also need to register it with Sentry and possibly configure your integration to use it, as shown below. @@ -364,11 +364,11 @@ Implementation .. sourcecode:: java - public class MySentryFactory extends DefaultSentryFactory { + public class MySentryClientFactory extends DefaultSentryClientFactory { @Override - public Sentry createSentryInstance(Dsn dsn) { - Sentry sentry = new Sentry(createConnection(dsn)); + public SentryClient createSentryClient(Dsn dsn) { + SentryClient sentry = new SentryClient(createConnection(dsn)); /* Create and use the ForwardedAddressResolver, which will use the @@ -391,14 +391,15 @@ Java ServiceLoader Provider (Recommended) ````````````````````````````````````````` You'll need to add a ``ServiceLoader`` provider file to your project at -``src/main/resources/META-INF/services/io.sentry.SentryFactory`` that contains -the name of your class so that it will be considered as a candidate ``SentryFactory``. For an example, see -`how we configure the DefaultSentryFactory itself `_. +``src/main/resources/META-INF/services/io.sentry.SentryClientFactory`` that contains +the name of your class so that it will be considered as a candidate ``SentryClientFactory``. For an example, see +`how we configure the DefaultSentryClientFactory itself +`_. Manual Registration ``````````````````` -You can also manually register your ``SentryFactory`` instance. If you are using +You can also manually register your ``SentryClientFactory`` instance. If you are using an integration that builds its own Sentry client, such as a logging integration, this should be done early in your application lifecycle so that your factory is available the first time you attempt to send an event to the Sentry server. @@ -407,7 +408,7 @@ you attempt to send an event to the Sentry server. class MyApp { public static void main(String[] args) { - SentryFactory.registerFactory(new MySentryFactory()); + SentryClientFactory.registerFactory(new MySentryClientFactory()); // ... your app code ... } } @@ -416,4 +417,4 @@ Configuration ~~~~~~~~~~~~~ Finally, see the documentation for the integration you use to find out how to -configure it to use your custom ``SentryFactory``. +configure it to use your custom ``SentryClientFactory``. diff --git a/docs/context.rst b/docs/context.rst index 85f68b1cc21..daee057efb9 100644 --- a/docs/context.rst +++ b/docs/context.rst @@ -18,7 +18,8 @@ Sentry defaults to the ``SingletonContextManager`` on Android, which maintains a context instance for all threads for the lifetime of the application. To override the ``ContextManager`` you will need to override the ``getContextManager`` -method in the ``DefaultSentryFactory``. A simpler API will likely be provided in the future. +method in the ``DefaultSentryClientFactory``. A simpler API will likely be provided in +the future. Using Breadcrumbs ----------------- @@ -27,7 +28,7 @@ Breadcrumbs can be used to describe actions that occurred in your application le up to an event being sent. For example, whether external API requests were made, or whether a user clicked on something in an Android application. -Once a Sentry instance has been initialized, either via a logging framework or manually, +Once a ``SentryClient`` instance has been initialized, either via a logging framework or manually, you can begin recording breadcrumbs. By default the last 100 breadcrumbs for a given context instance will be stored and sent with future events. @@ -41,17 +42,17 @@ context instance will be stored and sent with future events. public void example() { // Record a breadcrumb without having to look up the context instance manually - Breadcrumbs.record( + Sentry.record( new BreadcrumbBuilder().setMessage("User did something specific again!").build() ); // ... or retrieve and manipulate the context instance manually - // Retrieve the stored Sentry instance - Sentry sentry = Sentry.getStoredInstance(); + // Retrieve the stored SentryClient instance + SentryClient sentryClient = getStoredClient(); // Get the current context instance - Context context = sentry.getContext(); + Context context = sentryClient.getContext(); // Set the current User in the context context.setUser( diff --git a/docs/modules/android.rst b/docs/modules/android.rst index ed3f3e8b81f..1c234a33773 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -81,7 +81,7 @@ Now you can use ``Sentry`` to capture events anywhere in your application: Sentry.capture("Error message"); // Set a breadcrumb that will be sent with the next event(s) - Breadcrumbs.record( + Sentry.record( new BreadcrumbBuilder().setMessage("User made an action").build() ); diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index f7ab50283b7..491c009b3c1 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -38,9 +38,9 @@ For other dependency managers see the `central Maven repository - - + + @@ -280,7 +280,7 @@ In Practice void logWithBreadcrumbs() { // Record a breadcrumb that will be sent with the next event(s), // by default the last 100 breadcrumbs are kept. - Breadcrumbs.record( + Sentry.record( new BreadcrumbBuilder().setMessage("User made an action").build() ); diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index d8c5a31f901..926c534feaa 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -99,7 +99,7 @@ Environment variable Java System Property Example value ``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application ``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in ``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) -``SENTRY_SENTRYFACTORY`` ``sentry.sentryfactory`` ``com.foo.SentryFactory`` Optional, select the sentryFactory class +``SENTRY_FACTORY`` ``sentry.factory`` ``com.foo.SentryClientFactory`` Optional, select the SentryClientFactory class ``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags ``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC ======================== ======================== =============================== =========== @@ -135,8 +135,8 @@ Example configuration in the ``log4j.properties`` file: server1 - - com.foo.SentryFactory + + com.foo.SentryClientFactory tag1:value1,tag2:value2 @@ -207,7 +207,7 @@ In Practice void logWithBreadcrumbs() { // Record a breadcrumb that will be sent with the next event(s), // by default the last 100 breadcrumbs are kept. - Breadcrumbs.record( + Sentry.record( new BreadcrumbBuilder().setMessage("User made an action").build() ); diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 64588c6f61a..4f81b516d98 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -103,7 +103,7 @@ Environment variable Java System Property Example value ``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application ``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in ``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) -``SENTRY_SENTRYFACTORY`` ``sentry.sentryfactory`` ``com.foo.SentryFactory`` Optional, select the sentryFactory class +``SENTRY_FACTORY`` ``sentry.factory`` ``com.foo.SentryClientFactory`` Optional, select the SentryClientFactory class ``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags ``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC ======================== ======================== =============================== =========== @@ -145,8 +145,8 @@ Example configuration in the ``logback.xml`` file: server1 - - com.foo.SentryFactory + + com.foo.SentryClientFactory tag1:value1,tag2:value2 @@ -214,7 +214,7 @@ In Practice void logWithBreadcrumbs() { // Record a breadcrumb that will be sent with the next event(s), // by default the last 100 breadcrumbs are kept. - Breadcrumbs.record( + Sentry.record( new BreadcrumbBuilder().setMessage("User made an action").build() ); diff --git a/docs/modules/raven.rst b/docs/modules/sentry.rst similarity index 95% rename from docs/modules/raven.rst rename to docs/modules/sentry.rst index c3e64906608..a49f0c793c0 100644 --- a/docs/modules/raven.rst +++ b/docs/modules/sentry.rst @@ -94,7 +94,7 @@ Environment variable Java System Property Example value ``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application ``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in ``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) -``SENTRY_SENTRYFACTORY`` ``sentry.sentryfactory`` ``com.foo.SentryFactory`` Optional, select the sentryFactory class +``SENTRY_FACTORY`` ``sentry.factory`` ``com.foo.SentryClientFactory`` Optional, select the SentryClientFactory class ``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags ``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC ======================== ======================== =============================== =========== @@ -134,8 +134,8 @@ Example configuration in the ``logging.properties`` file: # Optional, override the server name (rather than looking it up dynamically) io.sentry.jul.SentryHandler.serverName=server1 - # Optional, select the sentryFactory class - io.sentry.jul.SentryHandler.sentryFactory=com.foo.SentryFactory + # Optional, select the SentryClientFactorclass + io.sentry.jul.SentryHandler.factory=com.foo.SentryClientFactory # Optional, provide tag names to be extracted from MDC io.sentry.jul.SentryHandler.extraTags=foo,bar,baz @@ -159,7 +159,7 @@ In Practice void logWithBreadcrumbs() { // Record a breadcrumb that will be sent with the next event(s), // by default the last 100 breadcrumbs are kept. - Breadcrumbs.record( + Sentry.record( new BreadcrumbBuilder().setMessage("User made an action").build() ); diff --git a/docs/usage.rst b/docs/usage.rst index bd8048ddf41..205843549ce 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -41,23 +41,20 @@ of the send methods it provides. .. sourcecode:: java import io.sentry.Sentry; - import io.sentry.SentryFactory; + import io.sentry.SentryClientFactory; public class MyClass { - private static Sentry sentry; + private static SentryClient sentry; public static void main(String... args) { // Creation of the client with a specific DSN String dsn = args[0]; - sentry = SentryFactory.sentryInstance(dsn); - - // Or, if you don't provide a DSN, - sentry = SentryFactory.sentryInstance(); + sentry = SentryClientFactory.sentryClient(dsn); // It is also possible to use the DSN detection system, which // will check the environment variable "SENTRY_DSN" and the Java // System Property "sentry.dsn". - sentry = SentryFactory.sentryInstance(); + sentry = SentryClientFactory.sentryClient(); } void logSimpleMessage() { @@ -68,7 +65,7 @@ of the send methods it provides. void logWithBreadcrumbs() { // Record a breadcrumb that will be sent with the next event(s), // by default the last 100 breadcrumbs are kept. - Breadcrumbs.record( + Sentry.record( new BreadcrumbBuilder().setMessage("User made an action").build() ); @@ -99,7 +96,7 @@ For more complex messages, you'll need to build an ``Event`` with the .. sourcecode:: java import io.sentry.Sentry; - import io.sentry.SentryFactory; + import io.sentry.SentryClientFactory; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.interfaces.ExceptionInterface; @@ -111,15 +108,15 @@ For more complex messages, you'll need to build an ``Event`` with the public static void main(String... args) { // Creation of the client with a specific DSN String dsn = args[0]; - sentry = SentryFactory.sentryInstance(dsn); + sentry = SentryClientFactory.sentryClient(dsn); // It is also possible to use the DSN detection system, which // will check the environment variable "SENTRY_DSN" and the Java // System Property "sentry.dsn". - sentry = SentryFactory.sentryInstance(); + sentry = SentryClientFactory.sentryClient(); - // Advanced: specify the sentryFactory used - sentry = SentryFactory.sentryInstance(new Dsn(dsn), "io.sentry.DefaultSentryFactory"); + // Advanced: specify the sentryClientFactory used + sentry = SentryClientFactory.sentryClient(new Dsn(dsn), "io.sentry.DefaultSentryClientFactory"); } void logSimpleMessage() { @@ -159,16 +156,16 @@ be used easily from anywhere in your application. .. sourcecode:: java import io.sentry.Sentry; - import io.sentry.SentryFactory; + import io.sentry.SentryClientFactory; public class MyClass { public static void main(String... args) { - // Create a Sentry instance - SentryFactory.sentryInstance(); + // Create a SentryClient instance + SentryClientFactory.sentryClient(); } public somewhereElse() { - // Use the Sentry instance statically. Note that we are + // Use the stored SentryClient instance statically. Note that we are // using the Class (and a static method) here Sentry.capture("Error message"); @@ -184,5 +181,5 @@ be used easily from anywhere in your application. } -Note that a Sentry instance *must* be created before you can use the ``Sentry.capture`` +Note that a SentryClient instance *must* be created before you can use the ``Sentry.capture`` method, otherwise a ``NullPointerException`` (with an explanation) will be thrown. diff --git a/sentry-android/src/main/java/io/sentry/android/AndroidSentryFactory.java b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java similarity index 69% rename from sentry-android/src/main/java/io/sentry/android/AndroidSentryFactory.java rename to sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java index 190563474b0..276ff42f5ae 100644 --- a/sentry-android/src/main/java/io/sentry/android/AndroidSentryFactory.java +++ b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java @@ -13,15 +13,15 @@ import java.io.File; /** - * SentryFactory that handles Android-specific construction, like taking advantage + * SentryClientFactory that handles Android-specific construction, like taking advantage * of the Android Context instance. */ -public class AndroidSentryFactory extends DefaultSentryFactory { +public class AndroidSentryClientFactory extends DefaultSentryClientFactory { /** * Logger tag. */ - public static final String TAG = AndroidSentryFactory.class.getName(); + public static final String TAG = AndroidSentryClientFactory.class.getName(); /** * Default Buffer directory name. */ @@ -30,21 +30,21 @@ public class AndroidSentryFactory extends DefaultSentryFactory { private Context ctx; /** - * Construct an AndroidSentryFactory using the specified Android Context. + * Construct an AndroidSentryClientFactory using the specified Android Context. * * @param ctx Android Context. */ - public AndroidSentryFactory(Context ctx) { + public AndroidSentryClientFactory(Context ctx) { this.ctx = ctx; Log.d(TAG, "Construction of Android Sentry."); } @Override - public io.sentry.Sentry createSentryInstance(Dsn dsn) { - io.sentry.Sentry sentryInstance = super.createSentryInstance(dsn); - sentryInstance.addBuilderHelper(new AndroidEventBuilderHelper(ctx)); - return sentryInstance; + public SentryClient createSentryClient(Dsn dsn) { + SentryClient sentryClient = super.createSentryClient(dsn); + sentryClient.addBuilderHelper(new AndroidEventBuilderHelper(ctx)); + return sentryClient; } @Override diff --git a/sentry-android/src/main/java/io/sentry/android/Sentry.java b/sentry-android/src/main/java/io/sentry/android/SentryAndroid.java similarity index 55% rename from sentry-android/src/main/java/io/sentry/android/Sentry.java rename to sentry-android/src/main/java/io/sentry/android/SentryAndroid.java index 2abe7675fcf..0ee6db4226b 100644 --- a/sentry-android/src/main/java/io/sentry/android/Sentry.java +++ b/sentry-android/src/main/java/io/sentry/android/SentryAndroid.java @@ -6,29 +6,26 @@ import android.content.pm.PackageManager; import android.text.TextUtils; import android.util.Log; -import io.sentry.DefaultSentryFactory; -import io.sentry.SentryFactory; +import io.sentry.DefaultSentryClientFactory; +import io.sentry.SentryClient; +import io.sentry.SentryClientFactory; import io.sentry.dsn.Dsn; -import io.sentry.event.Event; -import io.sentry.event.EventBuilder; /** * Android specific class to interface with Sentry. Supplements the default Java classes * with Android specific state and features. */ -public final class Sentry { +public final class SentryAndroid { /** * Logger tag. */ - public static final String TAG = Sentry.class.getName(); - - private static volatile io.sentry.Sentry sentry; + public static final String TAG = SentryAndroid.class.getName(); /** * Hide constructor. */ - private Sentry() { + private SentryAndroid() { } @@ -36,18 +33,20 @@ private Sentry() { * Initialize Sentry using a DSN set in the AndroidManifest. * * @param ctx Android application ctx + * @return SentryClient */ - public static void init(Context ctx) { - init(ctx, getDefaultSentryFactory(ctx)); + public static SentryClient init(Context ctx) { + return init(ctx, getDefaultSentryClientFactory(ctx)); } /** * Initialize Sentry using a DSN set in the AndroidManifest. * * @param ctx Android application ctx - * @param sentryFactory the SentryFactory to be used to generate the Sentry instance + * @param sentryClientFactory the SentryClientFactory to be used to generate the {@link SentryClient} instance + * @return SentryClient */ - public static void init(Context ctx, AndroidSentryFactory sentryFactory) { + public static SentryClient init(Context ctx, AndroidSentryClientFactory sentryClientFactory) { ctx = ctx.getApplicationContext(); String dsn = ""; @@ -66,7 +65,7 @@ public static void init(Context ctx, AndroidSentryFactory sentryFactory) { + "the constructor or AndroidManifest."); } - init(ctx, dsn, sentryFactory); + return init(ctx, dsn, sentryClientFactory); } /** @@ -74,9 +73,10 @@ public static void init(Context ctx, AndroidSentryFactory sentryFactory) { * * @param ctx Android application ctx * @param dsn Sentry DSN string + * @return SentryClient */ - public static void init(Context ctx, String dsn) { - init(ctx, new Dsn(dsn), getDefaultSentryFactory(ctx)); + public static SentryClient init(Context ctx, String dsn) { + return init(ctx, new Dsn(dsn), getDefaultSentryClientFactory(ctx)); } /** @@ -84,10 +84,11 @@ public static void init(Context ctx, String dsn) { * * @param ctx Android application ctx * @param dsn Sentry DSN string - * @param sentryFactory the SentryFactory to be used to generate the Sentry instance + * @param sentryClientFactory the SentryClientFactory to be used to generate the SentryClient + * @return SentryClient */ - public static void init(Context ctx, String dsn, AndroidSentryFactory sentryFactory) { - init(ctx, new Dsn(dsn), sentryFactory); + public static SentryClient init(Context ctx, String dsn, AndroidSentryClientFactory sentryClientFactory) { + return init(ctx, new Dsn(dsn), sentryClientFactory); } /** @@ -95,9 +96,10 @@ public static void init(Context ctx, String dsn, AndroidSentryFactory sentryFact * * @param ctx Android application ctx * @param dsn Sentry DSN object + * @return SentryClient */ - public static void init(Context ctx, Dsn dsn) { - init(ctx, dsn, getDefaultSentryFactory(ctx)); + public static SentryClient init(Context ctx, Dsn dsn) { + return init(ctx, dsn, getDefaultSentryClientFactory(ctx)); } /** @@ -106,15 +108,10 @@ public static void init(Context ctx, Dsn dsn) { * * @param ctx Android application ctx * @param dsn Sentry DSN object - * @param sentryFactory the SentryFactory to be used to generate the Sentry instance + * @param sentryClientFactory the SentryClientFactory to be used to generate the {@link SentryClient} instance + * @return SentryClient */ - public static void init(Context ctx, Dsn dsn, AndroidSentryFactory sentryFactory) { - if (sentry != null) { - Log.e(TAG, "Initializing Sentry multiple times."); - // cleanup existing connections - sentry.closeConnection(); - } - + public static SentryClient init(Context ctx, Dsn dsn, AndroidSentryClientFactory sentryClientFactory) { // Ensure we have the application context Context context = ctx.getApplicationContext(); @@ -131,19 +128,21 @@ public static void init(Context ctx, Dsn dsn, AndroidSentryFactory sentryFactory + " Sentry Android, but received: " + protocol); } - if ("false".equalsIgnoreCase(dsn.getOptions().get(DefaultSentryFactory.ASYNC_OPTION))) { + if ("false".equalsIgnoreCase(dsn.getOptions().get(DefaultSentryClientFactory.ASYNC_OPTION))) { throw new IllegalArgumentException("Sentry Android cannot use synchronous connections, remove '" - + DefaultSentryFactory.ASYNC_OPTION + "=false' from your DSN."); + + DefaultSentryClientFactory.ASYNC_OPTION + "=false' from your DSN."); } - SentryFactory.registerFactory(sentryFactory); - sentry = SentryFactory.sentryInstance(dsn); + SentryClientFactory.registerFactory(sentryClientFactory); + // SentryClient will store the instance statically on the Sentry utility class. + SentryClient sentryClient = SentryClientFactory.sentryClient(dsn); setupUncaughtExceptionHandler(); + return sentryClient; } - private static AndroidSentryFactory getDefaultSentryFactory(Context ctx) { - return new AndroidSentryFactory(ctx); + private static AndroidSentryClientFactory getDefaultSentryClientFactory(Context ctx) { + return new AndroidSentryClientFactory(ctx); } /** @@ -164,51 +163,4 @@ private static void setupUncaughtExceptionHandler() { } } - /** - * Send an Event using the statically stored Sentry instance. - * - * @param event Event to send to the Sentry server - */ - public static void capture(Event event) { - sentry.sendEvent(event); - } - - /** - * Sends an exception (or throwable) to the Sentry server using the statically stored Sentry instance. - *

    - * The exception will be logged at the {@link Event.Level#ERROR} level. - * - * @param throwable exception to send to Sentry. - */ - public static void capture(Throwable throwable) { - sentry.sendException(throwable); - } - - /** - * Sends a message to the Sentry server using the statically stored Sentry instance. - *

    - * The message will be logged at the {@link Event.Level#INFO} level. - * - * @param message message to send to Sentry. - */ - public static void capture(String message) { - sentry.sendMessage(message); - } - - /** - * Builds and sends an {@link Event} to the Sentry server using the statically stored Sentry instance. - * - * @param eventBuilder {@link EventBuilder} to send to Sentry. - */ - public static void capture(EventBuilder eventBuilder) { - sentry.sendEvent(eventBuilder); - } - - /** - * Clear statically stored Sentry instance. Useful for tests. - */ - public static void clearStoredSentry() { - sentry = null; - } - } diff --git a/sentry-android/src/main/java/io/sentry/android/SentryUncaughtExceptionHandler.java b/sentry-android/src/main/java/io/sentry/android/SentryUncaughtExceptionHandler.java index aee5a0e86c3..12d0986fd57 100644 --- a/sentry-android/src/main/java/io/sentry/android/SentryUncaughtExceptionHandler.java +++ b/sentry-android/src/main/java/io/sentry/android/SentryUncaughtExceptionHandler.java @@ -1,6 +1,7 @@ package io.sentry.android; import android.util.Log; +import io.sentry.Sentry; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.interfaces.ExceptionInterface; @@ -48,7 +49,7 @@ public void uncaughtException(Thread thread, Throwable thrown) { .withSentryInterface(new ExceptionInterface(thrown)); try { - io.sentry.Sentry.capture(eventBuilder); + Sentry.capture(eventBuilder); } catch (Exception e) { Log.e(TAG, "Error sending excepting to Sentry.", e); } diff --git a/sentry-android/src/test/java/io/sentry/android/AndroidTest.java b/sentry-android/src/test/java/io/sentry/android/AndroidTest.java index 786e940ac07..93683888a2c 100644 --- a/sentry-android/src/test/java/io/sentry/android/AndroidTest.java +++ b/sentry-android/src/test/java/io/sentry/android/AndroidTest.java @@ -1,11 +1,10 @@ package io.sentry.android; import io.sentry.BaseIT; +import io.sentry.Sentry; import org.junit.After; import org.junit.Before; -import static com.github.tomakehurst.wiremock.client.WireMock.*; - public class AndroidTest extends BaseIT { @Before @@ -15,7 +14,7 @@ public void setup() { @After public void tearDown() throws Exception { - Sentry.clearStoredSentry(); + Sentry.setStoredClient(null); } } diff --git a/sentry-android/src/test/java/io/sentry/android/SentryIT.java b/sentry-android/src/test/java/io/sentry/android/SentryAndroidIT.java similarity index 94% rename from sentry-android/src/test/java/io/sentry/android/SentryIT.java rename to sentry-android/src/test/java/io/sentry/android/SentryAndroidIT.java index 8edb35d7b1a..26367e72326 100644 --- a/sentry-android/src/test/java/io/sentry/android/SentryIT.java +++ b/sentry-android/src/test/java/io/sentry/android/SentryAndroidIT.java @@ -9,7 +9,7 @@ import java.util.concurrent.Callable; @RunWith(RobolectricTestRunner.class) -public class SentryIT extends AndroidTest { +public class SentryAndroidIT extends AndroidTest { @Test public void test() throws Exception { diff --git a/sentry-android/src/test/java/io/sentry/android/SentryITActivity.java b/sentry-android/src/test/java/io/sentry/android/SentryITActivity.java index e54d170b646..ecb99391ebc 100644 --- a/sentry-android/src/test/java/io/sentry/android/SentryITActivity.java +++ b/sentry-android/src/test/java/io/sentry/android/SentryITActivity.java @@ -3,6 +3,7 @@ import android.app.Activity; import android.content.Context; import android.os.Bundle; +import io.sentry.Sentry; import java.util.concurrent.atomic.AtomicBoolean; @@ -10,13 +11,13 @@ public class SentryITActivity extends Activity { private AtomicBoolean customFactoryUsed = new AtomicBoolean(false); - class CustomAndroidSentryFactory extends AndroidSentryFactory { + class CustomAndroidSentryClientFactory extends AndroidSentryClientFactory { /** - * Construct an AndroidSentryFactory using the specified Android Context. + * Construct an AndroidSentryClientFactory using the specified Android Context. * * @param ctx Android Context. */ - public CustomAndroidSentryFactory(Context ctx) { + public CustomAndroidSentryClientFactory(Context ctx) { super(ctx); customFactoryUsed.set(true); } @@ -25,10 +26,10 @@ public CustomAndroidSentryFactory(Context ctx) { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Sentry.init( + SentryAndroid.init( this.getApplicationContext(), "http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1", - new CustomAndroidSentryFactory(getApplicationContext())); + new CustomAndroidSentryClientFactory(getApplicationContext())); } public void sendEvent() { diff --git a/sentry-appengine/src/main/java/io/sentry/appengine/AppEngineSentryFactory.java b/sentry-appengine/src/main/java/io/sentry/appengine/AppEngineSentryClientFactory.java similarity index 76% rename from sentry-appengine/src/main/java/io/sentry/appengine/AppEngineSentryFactory.java rename to sentry-appengine/src/main/java/io/sentry/appengine/AppEngineSentryClientFactory.java index cd548e641ac..8f9de122a8a 100644 --- a/sentry-appengine/src/main/java/io/sentry/appengine/AppEngineSentryFactory.java +++ b/sentry-appengine/src/main/java/io/sentry/appengine/AppEngineSentryClientFactory.java @@ -1,17 +1,17 @@ package io.sentry.appengine; import com.google.appengine.api.utils.SystemProperty; -import io.sentry.DefaultSentryFactory; -import io.sentry.Sentry; +import io.sentry.DefaultSentryClientFactory; +import io.sentry.SentryClient; import io.sentry.appengine.connection.AppEngineAsyncConnection; import io.sentry.appengine.event.helper.AppEngineEventBuilderHelper; import io.sentry.connection.Connection; import io.sentry.dsn.Dsn; /** - * SentryFactory dedicated to create async connections within Google App Engine. + * SentryClientFactory dedicated to create async connections within Google App Engine. */ -public class AppEngineSentryFactory extends DefaultSentryFactory { +public class AppEngineSentryClientFactory extends DefaultSentryClientFactory { /** * Option for the queue name used in Google App Engine of threads assigned for the connection. */ @@ -30,10 +30,10 @@ public class AppEngineSentryFactory extends DefaultSentryFactory { public static final String CONNECTION_IDENTIFIER = "sentry.async.gae.connectionid"; @Override - public Sentry createSentryInstance(Dsn dsn) { - Sentry sentryInstance = super.createSentryInstance(dsn); - sentryInstance.addBuilderHelper(new AppEngineEventBuilderHelper()); - return sentryInstance; + public SentryClient createSentryClient(Dsn dsn) { + SentryClient sentryClientInstance = super.createSentryClient(dsn); + sentryClientInstance.addBuilderHelper(new AppEngineEventBuilderHelper()); + return sentryClientInstance; } /** @@ -50,7 +50,8 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { if (dsn.getOptions().containsKey(CONNECTION_IDENTIFIER)) { connectionIdentifier = dsn.getOptions().get(CONNECTION_IDENTIFIER); } else { - connectionIdentifier = AppEngineSentryFactory.class.getCanonicalName() + dsn + SystemProperty.version.get(); + connectionIdentifier = AppEngineSentryClientFactory.class.getCanonicalName() + + dsn + SystemProperty.version.get(); } AppEngineAsyncConnection asyncConnection = new AppEngineAsyncConnection(connectionIdentifier, connection); diff --git a/sentry-appengine/src/main/resources/META-INF/services/io.sentry.SentryClientFactory b/sentry-appengine/src/main/resources/META-INF/services/io.sentry.SentryClientFactory new file mode 100644 index 00000000000..462523b696e --- /dev/null +++ b/sentry-appengine/src/main/resources/META-INF/services/io.sentry.SentryClientFactory @@ -0,0 +1 @@ +io.sentry.appengine.AppEngineSentryClientFactory diff --git a/sentry-appengine/src/main/resources/META-INF/services/io.sentry.SentryFactory b/sentry-appengine/src/main/resources/META-INF/services/io.sentry.SentryFactory deleted file mode 100644 index 2384481459b..00000000000 --- a/sentry-appengine/src/main/resources/META-INF/services/io.sentry.SentryFactory +++ /dev/null @@ -1 +0,0 @@ -io.sentry.appengine.AppEngineSentryFactory diff --git a/sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryFactoryTest.java b/sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryClientFactoryTest.java similarity index 64% rename from sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryFactoryTest.java rename to sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryClientFactoryTest.java index e149a62eddf..bf097478f1d 100644 --- a/sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryFactoryTest.java +++ b/sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryClientFactoryTest.java @@ -1,7 +1,7 @@ package io.sentry.appengine; import mockit.*; -import io.sentry.SentryFactory; +import io.sentry.SentryClientFactory; import io.sentry.appengine.connection.AppEngineAsyncConnection; import io.sentry.connection.Connection; import io.sentry.dsn.Dsn; @@ -15,9 +15,9 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -public class AppEngineSentryFactoryTest { +public class AppEngineSentryClientFactoryTest { @Tested - private AppEngineSentryFactory appEngineSentryFactory; + private AppEngineSentryClientFactory appEngineSentryClientFactory; @Injectable private Connection mockConnection; @Injectable @@ -25,14 +25,14 @@ public class AppEngineSentryFactoryTest { @Test public void checkServiceLoaderProvidesFactory() throws Exception { - ServiceLoader sentryFactories = ServiceLoader.load(SentryFactory.class); + ServiceLoader sentryFactories = ServiceLoader.load(SentryClientFactory.class); - assertThat(sentryFactories, Matchers.hasItem(instanceOf(AppEngineSentryFactory.class))); + assertThat(sentryFactories, Matchers.hasItem(instanceOf(AppEngineSentryClientFactory.class))); } @Test - public void asyncConnectionCreatedByAppEngineSentryFactoryIsForAppEngine() throws Exception { - Connection connection = appEngineSentryFactory.createAsyncConnection(mockDsn, mockConnection); + public void asyncConnectionCreatedByAppEngineSentryClientFactoryIsForAppEngine() throws Exception { + Connection connection = appEngineSentryClientFactory.createAsyncConnection(mockDsn, mockConnection); assertThat(connection, is(instanceOf(AppEngineAsyncConnection.class))); } @@ -45,10 +45,10 @@ public void asyncConnectionWithoutConnectionIdGeneratesDefaultId() throws Except result = dnsString; }}; - appEngineSentryFactory.createAsyncConnection(mockDsn, mockConnection); + appEngineSentryClientFactory.createAsyncConnection(mockDsn, mockConnection); new Verifications() {{ - String connectionId = AppEngineSentryFactory.class.getCanonicalName() + dnsString; + String connectionId = AppEngineSentryClientFactory.class.getCanonicalName() + dnsString; new AppEngineAsyncConnection(connectionId, mockConnection); }}; } @@ -58,10 +58,10 @@ public void asyncConnectionWithConnectionIdUsesId( @Injectable("543afd41-379d-41cb-8c99-8ce73e83a0cc") final String connectionId) throws Exception { new NonStrictExpectations() {{ mockDsn.getOptions(); - result = Collections.singletonMap(AppEngineSentryFactory.CONNECTION_IDENTIFIER, connectionId); + result = Collections.singletonMap(AppEngineSentryClientFactory.CONNECTION_IDENTIFIER, connectionId); }}; - appEngineSentryFactory.createAsyncConnection(mockDsn, mockConnection); + appEngineSentryClientFactory.createAsyncConnection(mockDsn, mockConnection); new Verifications() {{ new AppEngineAsyncConnection(connectionId, mockConnection); @@ -71,7 +71,7 @@ public void asyncConnectionWithConnectionIdUsesId( @Test public void asyncConnectionWithoutQueueNameKeepsDefaultQueue( @Mocked final AppEngineAsyncConnection mockAppEngineAsyncConnection) throws Exception { - appEngineSentryFactory.createAsyncConnection(mockDsn, mockConnection); + appEngineSentryClientFactory.createAsyncConnection(mockDsn, mockConnection); new Verifications() {{ mockAppEngineAsyncConnection.setQueue(anyString); @@ -85,10 +85,10 @@ public void asyncConnectionWithQueueNameSetsQueue( @Injectable("queueName") final String mockQueueName) throws Exception { new NonStrictExpectations() {{ mockDsn.getOptions(); - result = Collections.singletonMap(AppEngineSentryFactory.QUEUE_NAME, mockQueueName); + result = Collections.singletonMap(AppEngineSentryClientFactory.QUEUE_NAME, mockQueueName); }}; - appEngineSentryFactory.createAsyncConnection(mockDsn, mockConnection); + appEngineSentryClientFactory.createAsyncConnection(mockDsn, mockConnection); new Verifications() {{ mockAppEngineAsyncConnection.setQueue(mockQueueName); diff --git a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java index 0b2c8fa9086..bac4a63c343 100644 --- a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java +++ b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java @@ -1,7 +1,7 @@ package io.sentry.log4j; -import io.sentry.Sentry; -import io.sentry.SentryFactory; +import io.sentry.SentryClient; +import io.sentry.SentryClientFactory; import io.sentry.config.Lookup; import io.sentry.dsn.Dsn; import io.sentry.dsn.InvalidDsnException; @@ -37,11 +37,11 @@ public class SentryAppender extends AppenderSkeleton { */ public static final String THREAD_NAME = "Sentry-Threadname"; /** - * Current instance of {@link Sentry}. + * Current instance of {@link SentryClient}. * * @see #initSentry() */ - protected volatile Sentry sentry; + protected volatile SentryClient sentryClient; /** * DSN property of the appender. *

    @@ -49,11 +49,11 @@ public class SentryAppender extends AppenderSkeleton { */ protected String dsn; /** - * Name of the {@link SentryFactory} being used. + * Name of the {@link SentryClientFactory} being used. *

    * Might be null in which case the factory should be defined automatically. */ - protected String sentryFactory; + protected String sentryClientFactory; /** * Identifies the version of the application. *

    @@ -105,11 +105,11 @@ public SentryAppender() { /** * Creates an instance of SentryAppender. * - * @param sentry instance of Sentry to use with this appender. + * @param sentryClient instance of Sentry to use with this appender. */ - public SentryAppender(Sentry sentry) { + public SentryAppender(SentryClient sentryClient) { this(); - this.sentry = sentry; + this.sentryClient = sentryClient; } /** @@ -123,9 +123,9 @@ private void lazyInit() { synchronized (this) { if (!initialized) { try { - String sentryFactory = Lookup.lookup("sentryFactory"); - if (sentryFactory != null) { - setSentryFactory(sentryFactory); + String sentryClientFactory = Lookup.lookup("factory"); + if (sentryClientFactory != null) { + setFactory(sentryClientFactory); } String release = Lookup.lookup("release"); @@ -164,7 +164,7 @@ private void lazyInit() { } } - if (sentry == null) { + if (sentryClient == null) { initSentry(); } } @@ -211,7 +211,7 @@ public void activateOptions() { } /** - * Initialises the Sentry instance. + * Initialises the {@link SentryClient} instance. */ protected synchronized void initSentry() { try { @@ -219,13 +219,13 @@ protected synchronized void initSentry() { dsn = Dsn.dsnLookup(); } - sentry = SentryFactory.sentryInstance(new Dsn(dsn), sentryFactory); + sentryClient = SentryClientFactory.sentryClient(new Dsn(dsn), sentryClientFactory); } catch (InvalidDsnException e) { getErrorHandler().error("An exception occurred during the retrieval of the DSN for Sentry", e, ErrorCode.ADDRESS_PARSE_FAILURE); } catch (Exception e) { - getErrorHandler().error("An exception occurred during the creation of a Sentry instance", e, - ErrorCode.FILE_OPEN_FAILURE); + getErrorHandler().error("An exception occurred during the creation of a " + + "SentryClient instance", e, ErrorCode.FILE_OPEN_FAILURE); } } @@ -240,7 +240,7 @@ protected void append(LoggingEvent loggingEvent) { try { lazyInit(); Event event = buildEvent(loggingEvent); - sentry.sendEvent(event); + sentryClient.sendEvent(event); } catch (Exception e) { getErrorHandler().error("An exception occurred while creating a new event in Sentry", e, ErrorCode.WRITE_FAILURE); @@ -322,12 +322,12 @@ protected Event buildEvent(LoggingEvent loggingEvent) { eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); } - sentry.runBuilderHelpers(eventBuilder); + sentryClient.runBuilderHelpers(eventBuilder); return eventBuilder.build(); } - public void setSentryFactory(String sentryFactory) { - this.sentryFactory = sentryFactory; + public void setFactory(String factory) { + this.sentryClientFactory = factory; } public void setDsn(String dsn) { @@ -376,8 +376,8 @@ public void close() { return; } this.closed = true; - if (sentry != null) { - sentry.closeConnection(); + if (sentryClient != null) { + sentryClient.closeConnection(); } } catch (Exception e) { getErrorHandler().error("An exception occurred while closing the Sentry connection", e, diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java index a7915638c75..2b1258f48fa 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java @@ -1,8 +1,8 @@ package io.sentry.log4j; import mockit.*; -import io.sentry.Sentry; -import io.sentry.SentryFactory; +import io.sentry.SentryClient; +import io.sentry.SentryClientFactory; import io.sentry.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -13,10 +13,10 @@ public class SentryAppenderCloseTest { private MockUpErrorHandler mockUpErrorHandler; @Injectable - private Sentry mockSentry = null; + private SentryClient mockSentryClient = null; @SuppressWarnings("unused") - @Mocked("sentryInstance") - private SentryFactory mockSentryFactory = null; + @Mocked("sentryClient") + private SentryClientFactory mockSentryClientFactory = null; @SuppressWarnings("unused") @Mocked("dsnLookup") private Dsn mockDsn = null; @@ -32,35 +32,35 @@ private void assertNoErrorsInErrorHandler() throws Exception { @Test public void testConnectionClosedWhenAppenderClosed() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockSentry); + final SentryAppender sentryAppender = new SentryAppender(mockSentryClient); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.activateOptions(); sentryAppender.close(); new Verifications() {{ - mockSentry.closeConnection(); + mockSentryClient.closeConnection(); }}; assertNoErrorsInErrorHandler(); } @Test - public void testClosedIfSentryInstanceNotProvided() throws Exception { + public void testClosedIfSentryClientNotProvided() throws Exception { final String dsnUri = "protocol://public:private@host/1"; final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); new Expectations() {{ Dsn.dsnLookup(); result = dsnUri; - SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockSentry; + SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); + result = mockSentryClient; }}; sentryAppender.activateOptions(); sentryAppender.close(); new Verifications() {{ - mockSentry.closeConnection(); + mockSentryClient.closeConnection(); }}; assertNoErrorsInErrorHandler(); } @@ -71,7 +71,7 @@ public void testCloseDoNotFailIfInitFailed() throws Exception { final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); new NonStrictExpectations() {{ - SentryFactory.sentryInstance((Dsn) any, anyString); + SentryClientFactory.sentryClient((Dsn) any, anyString); result = new UnsupportedOperationException(); }}; sentryAppender.activateOptions(); @@ -94,7 +94,7 @@ public void testCloseDoNotFailIfNoInit() @Test public void testCloseDoNotFailWhenMultipleCalls() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockSentry); + final SentryAppender sentryAppender = new SentryAppender(mockSentryClient); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.activateOptions(); @@ -102,7 +102,7 @@ public void testCloseDoNotFailWhenMultipleCalls() throws Exception { sentryAppender.close(); new Verifications() {{ - mockSentry.closeConnection(); + mockSentryClient.closeConnection(); times = 1; }}; assertNoErrorsInErrorHandler(); diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderDsnTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderDsnTest.java index 02aaf8f3a5b..dfa817bd35c 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderDsnTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderDsnTest.java @@ -4,8 +4,8 @@ import mockit.Injectable; import mockit.Mocked; import mockit.Tested; -import io.sentry.Sentry; -import io.sentry.SentryFactory; +import io.sentry.SentryClient; +import io.sentry.SentryClientFactory; import io.sentry.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -18,10 +18,10 @@ public class SentryAppenderDsnTest { private SentryAppender sentryAppender = null; private MockUpErrorHandler mockUpErrorHandler; @Injectable - private Sentry mockSentry = null; + private SentryClient mockSentryClient = null; @SuppressWarnings("unused") - @Mocked("sentryInstance") - private SentryFactory mockSentryFactory = null; + @Mocked("sentryClient") + private SentryClientFactory mockSentryClientFactory = null; @SuppressWarnings("unused") @Mocked("dsnLookup") private Dsn mockDsn = null; @@ -43,8 +43,8 @@ public void testDsnDetected() throws Exception { new Expectations() {{ Dsn.dsnLookup(); result = dsnUri; - SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockSentry; + SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); + result = mockSentryClient; }}; sentryAppender.initSentry(); @@ -57,8 +57,8 @@ public void testDsnProvided() throws Exception { final String dsnUri = "protocol://public:private@host/2"; sentryAppender.setDsn(dsnUri); new Expectations() {{ - SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockSentry; + SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); + result = mockSentryClient; }}; sentryAppender.initSentry(); diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java index 3fba3e34e24..8df1aba4fee 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java @@ -1,9 +1,9 @@ package io.sentry.log4j; +import io.sentry.SentryClient; import io.sentry.environment.SentryEnvironment; import io.sentry.event.interfaces.SentryStackTraceElement; import mockit.*; -import io.sentry.Sentry; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.interfaces.ExceptionInterface; @@ -32,14 +32,14 @@ public class SentryAppenderEventBuildingTest { private SentryAppender sentryAppender = null; private MockUpErrorHandler mockUpErrorHandler; @Injectable - private Sentry mockSentry = null; + private SentryClient mockSentryClient = null; @Injectable private Logger mockLogger = null; private String mockExtraTag = "a8e0ad33-3c11-4899-b8c7-c99926c6d7b8"; @BeforeMethod public void setUp() throws Exception { - sentryAppender = new SentryAppender(mockSentry); + sentryAppender = new SentryAppender(mockSentryClient); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.setExtraTags(mockExtraTag); @@ -66,8 +66,8 @@ public void testSimpleMessageLogging() throws Exception { new Verifications() {{ Event event; - mockSentry.runBuilderHelpers((EventBuilder) any); - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.runBuilderHelpers((EventBuilder) any); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getMessage(), is(message)); assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); @@ -94,7 +94,7 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getLevel(), is(expectedLevel)); }}; assertNoErrorsInErrorHandler(); @@ -108,7 +108,7 @@ public void testExceptionLogging() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); SentryException sentryException = exceptionInterface.getExceptions().getFirst(); @@ -129,7 +129,7 @@ public void testMdcAddedToExtra() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); }}; assertNoErrorsInErrorHandler(); @@ -144,7 +144,7 @@ public void testNdcAddedToExtra() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.LOG4J_NDC, ndcEntries)); }}; assertNoErrorsInErrorHandler(); @@ -173,7 +173,7 @@ public void testSourceUsedAsStacktrace(@Injectable final LocationInfo locationIn new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() .get(StackTraceInterface.STACKTRACE_INTERFACE); assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); @@ -206,7 +206,7 @@ public void testCulpritWithSource(@Injectable final LocationInfo locationInfo) t new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is("a.b(c:42)")); }}; assertNoErrorsInErrorHandler(); @@ -224,7 +224,7 @@ public void testCulpritWithoutSource() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is(loggerName)); }}; assertNoErrorsInErrorHandler(); @@ -240,7 +240,7 @@ public void testExtraTagObtainedFromMdc() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getTags().entrySet(), hasSize(1)); assertThat(event.getTags(), hasEntry(mockExtraTag, "ac84f38a-3889-41ed-9519-201402688abb")); assertThat(event.getExtra(), not(hasKey(mockExtraTag))); @@ -261,7 +261,7 @@ public void testExtraTagObtainedFromMdcConvertedToString(@Injectable final Objec new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getTags().entrySet(), hasSize(1)); assertThat(event.getTags(), hasEntry(mockExtraTag, "3c8981b4-01ad-47ec-8a3a-77a0bbcb42e2")); assertThat(event.getExtra(), not(hasKey(mockExtraTag))); @@ -278,7 +278,7 @@ public void testReleaseAddedToEvent() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getRelease(), is(release)); }}; assertNoErrorsInErrorHandler(); @@ -293,7 +293,7 @@ public void testEnvironmentAddedToEvent() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getEnvironment(), is(environment)); }}; assertNoErrorsInErrorHandler(); diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java index f8269b3cec6..482c65c6c3a 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java @@ -1,8 +1,8 @@ package io.sentry.log4j; +import io.sentry.SentryClient; import mockit.*; -import io.sentry.Sentry; -import io.sentry.SentryFactory; +import io.sentry.SentryClientFactory; import io.sentry.dsn.Dsn; import io.sentry.environment.SentryEnvironment; import io.sentry.event.Event; @@ -20,16 +20,16 @@ public class SentryAppenderFailuresTest { private SentryAppender sentryAppender = null; private MockUpErrorHandler mockUpErrorHandler; @Injectable - private Sentry mockSentry = null; + private SentryClient mockSentryClient = null; @Injectable private Logger mockLogger = null; @SuppressWarnings("unused") - @Mocked("sentryInstance") - private SentryFactory mockSentryFactory = null; + @Mocked("sentryClient") + private SentryClientFactory mockSentryClientFactory = null; @BeforeMethod public void setUp() throws Exception { - sentryAppender = new SentryAppender(mockSentry); + sentryAppender = new SentryAppender(mockSentryClient); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.activateOptions(); @@ -38,7 +38,7 @@ public void setUp() throws Exception { @Test public void testSentryFailureDoesNotPropagate() throws Exception { new NonStrictExpectations() {{ - mockSentry.sendEvent((Event) any); + mockSentryClient.sendEvent((Event) any); result = new UnsupportedOperationException(); }}; @@ -48,9 +48,9 @@ public void testSentryFailureDoesNotPropagate() throws Exception { } @Test - public void testSentryFactoryFailureDoesNotPropagate() throws Exception { + public void testSentryClientFactoryFailureDoesNotPropagate() throws Exception { new NonStrictExpectations() {{ - SentryFactory.sentryInstance((Dsn) any, anyString); + SentryClientFactory.sentryClient((Dsn) any, anyString); result = new UnsupportedOperationException(); }}; SentryAppender sentryAppender = new SentryAppender(); @@ -69,7 +69,7 @@ public void testAppendFailIfCurrentThreadSpawnedBySentry() throws Exception { sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); new Verifications() {{ - mockSentry.sendEvent((Event) any); + mockSentryClient.sendEvent((Event) any); times = 0; }}; assertThat(mockUpErrorHandler.getErrorCount(), is(0)); diff --git a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java index 92b674e1b05..12a641496b9 100644 --- a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -1,7 +1,7 @@ package io.sentry.log4j2; -import io.sentry.Sentry; -import io.sentry.SentryFactory; +import io.sentry.SentryClient; +import io.sentry.SentryClientFactory; import io.sentry.config.Lookup; import io.sentry.dsn.Dsn; import io.sentry.dsn.InvalidDsnException; @@ -54,11 +54,11 @@ public class SentryAppender extends AbstractAppender { */ public static final String THREAD_NAME = "Sentry-Threadname"; /** - * Current instance of {@link Sentry}. + * Current instance of {@link SentryClient}. * * @see #initSentry() */ - protected volatile Sentry sentry; + protected volatile SentryClient sentryClient; /** * DSN property of the appender. *

    @@ -66,11 +66,11 @@ public class SentryAppender extends AbstractAppender { */ protected String dsn; /** - * Name of the {@link SentryFactory} being used. + * Name of the {@link SentryClientFactory} being used. *

    * Might be null in which case the factory should be defined automatically. */ - protected String sentryFactory; + protected String sentryClientFactory; /** * Identifies the version of the application. *

    @@ -123,11 +123,11 @@ public SentryAppender() { /** * Creates an instance of SentryAppender. * - * @param sentry instance of Sentry to use with this appender. + * @param sentryClient instance of Sentry to use with this appender. */ - public SentryAppender(Sentry sentry) { + public SentryAppender(SentryClient sentryClient) { this(); - this.sentry = sentry; + this.sentryClient = sentryClient; } /** @@ -146,7 +146,7 @@ protected SentryAppender(String name, Filter filter) { * * @param name The name of the Appender. * @param dsn Data Source Name to access the Sentry server. - * @param sentryFactory Name of the factory to use to build the {@link SentryClient} instance. + * @param factory Name of the factory to use to build the {@link SentryClient} instance. * @param release Release to be sent to Sentry. * @param dist Dist to be sent to Sentry. * @param environment Environment to be sent to Sentry. @@ -160,7 +160,7 @@ protected SentryAppender(String name, Filter filter) { @SuppressWarnings("checkstyle:parameternumber") public static SentryAppender createAppender(@PluginAttribute("name") final String name, @PluginAttribute("dsn") final String dsn, - @PluginAttribute("sentryFactory") final String sentryFactory, + @PluginAttribute("factory") final String factory, @PluginAttribute("release") final String release, @PluginAttribute("dist") final String dist, @PluginAttribute("environment") final String environment, @@ -194,7 +194,7 @@ public static SentryAppender createAppender(@PluginAttribute("name") final Strin if (extraTags != null) { sentryAppender.setExtraTags(extraTags); } - sentryAppender.setSentryFactory(sentryFactory); + sentryAppender.setFactory(factory); return sentryAppender; } @@ -209,9 +209,9 @@ private void lazyInit() { synchronized (this) { if (!initialized) { try { - String sentryFactory = Lookup.lookup("sentryFactory"); - if (sentryFactory != null) { - setSentryFactory(sentryFactory); + String sentryClientFactory = Lookup.lookup("factory"); + if (sentryClientFactory != null) { + setFactory(sentryClientFactory); } String release = Lookup.lookup("release"); @@ -250,7 +250,7 @@ private void lazyInit() { } } - if (sentry == null) { + if (sentryClient == null) { initSentry(); } } @@ -291,14 +291,6 @@ protected static List formatMessageParameters(Object[] parameters) { return stringParameters; } - /** - * {@inheritDoc} - *

    - * The sentry instance is set in this method instead of {@link #start()} in order to avoid substitute loggers - * being generated during the instantiation of {@link Sentry}.
    - * - * @param logEvent The LogEvent. - */ @Override public void append(LogEvent logEvent) { // Do not log the event if the current thread is managed by sentry @@ -310,7 +302,7 @@ public void append(LogEvent logEvent) { try { lazyInit(); Event event = buildEvent(logEvent); - sentry.sendEvent(event); + sentryClient.sendEvent(event); } catch (Exception e) { error("An exception occurred while creating a new event in Sentry", logEvent, e); } finally { @@ -319,7 +311,7 @@ public void append(LogEvent logEvent) { } /** - * Initialises the Sentry instance. + * Initialises the {@link SentryClient} instance. */ protected synchronized void initSentry() { try { @@ -327,11 +319,11 @@ protected synchronized void initSentry() { dsn = Dsn.dsnLookup(); } - sentry = SentryFactory.sentryInstance(new Dsn(dsn), sentryFactory); + sentryClient = SentryClientFactory.sentryClient(new Dsn(dsn), sentryClientFactory); } catch (InvalidDsnException e) { error("An exception occurred during the retrieval of the DSN for Sentry", e); } catch (Exception e) { - error("An exception occurred during the creation of a Sentry instance", e); + error("An exception occurred during the creation of a SentryClient instance", e); } } @@ -411,7 +403,7 @@ protected Event buildEvent(LogEvent event) { eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); } - sentry.runBuilderHelpers(eventBuilder); + sentryClient.runBuilderHelpers(eventBuilder); return eventBuilder.build(); } @@ -419,8 +411,8 @@ public void setDsn(String dsn) { this.dsn = dsn; } - public void setSentryFactory(String sentryFactory) { - this.sentryFactory = sentryFactory; + public void setFactory(String factory) { + this.sentryClientFactory = factory; } public void setRelease(String release) { @@ -466,8 +458,8 @@ public void stop() { return; } super.stop(); - if (sentry != null) { - sentry.closeConnection(); + if (sentryClient != null) { + sentryClient.closeConnection(); } } catch (Exception e) { error("An exception occurred while closing the Sentry connection", e); diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java index a6b0dd760ac..65f21a37b61 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java @@ -1,8 +1,8 @@ package io.sentry.log4j2; +import io.sentry.SentryClient; import mockit.*; -import io.sentry.Sentry; -import io.sentry.SentryFactory; +import io.sentry.SentryClientFactory; import io.sentry.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -13,10 +13,10 @@ public class SentryAppenderCloseTest { private MockUpErrorHandler mockUpErrorHandler; @Injectable - private Sentry mockSentry = null; + private SentryClient mockSentryClient = null; @SuppressWarnings("unused") - @Mocked("sentryInstance") - private SentryFactory mockSentryFactory = null; + @Mocked("sentryClient") + private SentryClientFactory mockSentryClientFactory = null; @SuppressWarnings("unused") @Mocked("dsnLookup") private Dsn mockDsn = null; @@ -32,28 +32,28 @@ private void assertNoErrorsInErrorHandler() throws Exception { @Test public void testConnectionClosedWhenAppenderStopped() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockSentry); + final SentryAppender sentryAppender = new SentryAppender(mockSentryClient); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.start(); sentryAppender.stop(); new Verifications() {{ - mockSentry.closeConnection(); + mockSentryClient.closeConnection(); }}; assertNoErrorsInErrorHandler(); } @Test - public void testStopIfSentryInstanceNotProvided() throws Exception { + public void testStopIfSentryClientNotProvided() throws Exception { final String dsnUri = "protocol://public:private@host/1"; final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); new Expectations() {{ Dsn.dsnLookup(); result = dsnUri; - SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockSentry; + SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); + result = mockSentryClient; }}; sentryAppender.start(); sentryAppender.append(null); @@ -61,7 +61,7 @@ public void testStopIfSentryInstanceNotProvided() throws Exception { sentryAppender.stop(); new Verifications() {{ - mockSentry.closeConnection(); + mockSentryClient.closeConnection(); }}; //One error, because of the null event. assertThat(mockUpErrorHandler.getErrorCount(), is(1)); @@ -73,7 +73,7 @@ public void testStopDoNotFailIfInitFailed() throws Exception { final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); new NonStrictExpectations() {{ - SentryFactory.sentryInstance((Dsn) any, anyString); + SentryClientFactory.sentryClient((Dsn) any, anyString); result = new UnsupportedOperationException(); }}; sentryAppender.start(); @@ -98,7 +98,7 @@ public void testStopDoNotFailIfNoInit() @Test public void testStopDoNotFailWhenMultipleCalls() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockSentry); + final SentryAppender sentryAppender = new SentryAppender(mockSentryClient); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.start(); @@ -106,7 +106,7 @@ public void testStopDoNotFailWhenMultipleCalls() throws Exception { sentryAppender.stop(); new Verifications() {{ - mockSentry.closeConnection(); + mockSentryClient.closeConnection(); times = 1; }}; assertNoErrorsInErrorHandler(); diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderDsnTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderDsnTest.java index 00ebdc45632..11a588ad355 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderDsnTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderDsnTest.java @@ -4,8 +4,8 @@ import mockit.Injectable; import mockit.Mocked; import mockit.Tested; -import io.sentry.Sentry; -import io.sentry.SentryFactory; +import io.sentry.SentryClient; +import io.sentry.SentryClientFactory; import io.sentry.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -18,10 +18,10 @@ public class SentryAppenderDsnTest { private SentryAppender sentryAppender = null; private MockUpErrorHandler mockUpErrorHandler = new MockUpErrorHandler(); @Injectable - private Sentry mockSentry = null; + private SentryClient mockSentryClient = null; @SuppressWarnings("unused") - @Mocked("sentryInstance") - private SentryFactory mockSentryFactory = null; + @Mocked("sentryClient") + private SentryClientFactory mockSentryClientFactory = null; @SuppressWarnings("unused") @Mocked("dsnLookup") private Dsn mockDsn = null; @@ -42,8 +42,8 @@ public void testDsnDetected() throws Exception { new Expectations() {{ Dsn.dsnLookup(); result = dsnUri; - SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockSentry; + SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); + result = mockSentryClient; }}; sentryAppender.initSentry(); @@ -56,8 +56,8 @@ public void testDsnProvided() throws Exception { final String dsnUri = "protocol://public:private@host/2"; sentryAppender.setDsn(dsnUri); new Expectations() {{ - SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockSentry; + SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); + result = mockSentryClient; }}; sentryAppender.initSentry(); diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java index 9347546f1a6..e8beae91cfe 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java @@ -1,11 +1,11 @@ package io.sentry.log4j2; +import io.sentry.SentryClient; import io.sentry.environment.SentryEnvironment; import io.sentry.event.interfaces.*; import mockit.Injectable; import mockit.Tested; import mockit.Verifications; -import io.sentry.Sentry; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import org.apache.logging.log4j.Level; @@ -30,12 +30,12 @@ public class SentryAppenderEventBuildingTest { private SentryAppender sentryAppender = null; private MockUpErrorHandler mockUpErrorHandler; @Injectable - private Sentry mockSentry = null; + private SentryClient mockSentryClient = null; private String mockExtraTag = "d421627f-7a25-4d43-8210-140dfe73ff10"; @BeforeMethod public void setUp() throws Exception { - sentryAppender = new SentryAppender(mockSentry); + sentryAppender = new SentryAppender(mockSentryClient); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.setExtraTags(mockExtraTag); @@ -57,8 +57,8 @@ public void testSimpleMessageLogging() throws Exception { new Verifications() {{ Event event; - mockSentry.runBuilderHelpers((EventBuilder) any); - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.runBuilderHelpers((EventBuilder) any); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getMessage(), is(message)); assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); @@ -85,7 +85,7 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getLevel(), is(expectedLevel)); }}; assertNoErrorsInErrorHandler(); @@ -99,7 +99,7 @@ public void testExceptionLogging() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); SentryException sentryException = exceptionInterface.getExceptions().getFirst(); @@ -120,7 +120,7 @@ public void testLogParametrisedMessage() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); MessageInterface messageInterface = (MessageInterface) event.getSentryInterfaces() .get(MessageInterface.MESSAGE_INTERFACE); @@ -141,7 +141,7 @@ public void testMarkerAddedToTag() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getTags(), Matchers.hasEntry(SentryAppender.LOG4J_MARKER, markerName)); }}; assertNoErrorsInErrorHandler(); @@ -157,7 +157,7 @@ public void testMdcAddedToExtra() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); }}; assertNoErrorsInErrorHandler(); @@ -176,7 +176,7 @@ public void testNdcAddedToExtra() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat((List) event.getExtra().get(SentryAppender.LOG4J_NDC), equalTo(contextStack.asList())); }}; assertNoErrorsInErrorHandler(); @@ -192,7 +192,7 @@ public void testSourceUsedAsStacktrace() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() .get(StackTraceInterface.STACKTRACE_INTERFACE); assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); @@ -210,7 +210,7 @@ public void testCulpritWithSource() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is("a.b(c:42)")); }}; assertNoErrorsInErrorHandler(); @@ -224,7 +224,7 @@ public void testCulpritWithoutSource() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is(loggerName)); }}; assertNoErrorsInErrorHandler(); @@ -240,7 +240,7 @@ public void testExtraTagObtainedFromMdc() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getTags().entrySet(), hasSize(1)); assertThat(event.getTags(), hasEntry(mockExtraTag, "565940d2-f4a4-42f6-9496-42e3c7c85c43")); assertThat(event.getExtra(), not(hasKey(mockExtraTag))); @@ -258,7 +258,7 @@ public void testReleaseAddedToEvent() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getRelease(), is(release)); }}; assertNoErrorsInErrorHandler(); @@ -273,7 +273,7 @@ public void testEnvironmentAddedToEvent() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getEnvironment(), is(environment)); }}; assertNoErrorsInErrorHandler(); diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java index 3d36b31e3ff..ce66a703c41 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java @@ -4,8 +4,8 @@ import mockit.Mocked; import mockit.NonStrictExpectations; import mockit.Verifications; -import io.sentry.Sentry; -import io.sentry.SentryFactory; +import io.sentry.SentryClient; +import io.sentry.SentryClientFactory; import io.sentry.dsn.Dsn; import io.sentry.environment.SentryEnvironment; import io.sentry.event.Event; @@ -22,14 +22,14 @@ public class SentryAppenderFailuresTest { private SentryAppender sentryAppender; private MockUpErrorHandler mockUpErrorHandler; @Injectable - private Sentry mockSentry = null; + private SentryClient mockSentryClient = null; @SuppressWarnings("unused") - @Mocked("sentryInstance") - private SentryFactory mockSentryFactory = null; + @Mocked("sentryClient") + private SentryClientFactory mockSentryClientFactory = null; @BeforeMethod public void setUp() throws Exception { - sentryAppender = new SentryAppender(mockSentry); + sentryAppender = new SentryAppender(mockSentryClient); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); } @@ -37,7 +37,7 @@ public void setUp() throws Exception { @Test public void testSentryFailureDoesNotPropagate() throws Exception { new NonStrictExpectations() {{ - mockSentry.sendEvent((Event) any); + mockSentryClient.sendEvent((Event) any); result = new UnsupportedOperationException(); }}; @@ -47,9 +47,9 @@ public void testSentryFailureDoesNotPropagate() throws Exception { } @Test - public void testSentryFactoryFailureDoesNotPropagate() throws Exception { + public void testSentryClientFactoryFailureDoesNotPropagate() throws Exception { new NonStrictExpectations() {{ - SentryFactory.sentryInstance((Dsn) any, anyString); + SentryClientFactory.sentryClient((Dsn) any, anyString); result = new UnsupportedOperationException(); }}; SentryAppender sentryAppender = new SentryAppender(); @@ -68,7 +68,7 @@ public void testAppendFailIfCurrentThreadSpawnedBySentry() throws Exception { sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); new Verifications() {{ - mockSentry.sendEvent((Event) any); + mockSentryClient.sendEvent((Event) any); times = 0; }}; assertThat(mockUpErrorHandler.getErrorCount(), is(0)); diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index b71129a396e..0c407b22d96 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -7,8 +7,8 @@ import ch.qos.logback.core.AppenderBase; import ch.qos.logback.core.filter.Filter; import ch.qos.logback.core.spi.FilterReply; -import io.sentry.Sentry; -import io.sentry.SentryFactory; +import io.sentry.SentryClient; +import io.sentry.SentryClientFactory; import io.sentry.config.Lookup; import io.sentry.dsn.Dsn; import io.sentry.dsn.InvalidDsnException; @@ -45,11 +45,11 @@ public class SentryAppender extends AppenderBase { */ public static final String THREAD_NAME = "Sentry-Threadname"; /** - * Current instance of {@link Sentry}. + * Current instance of {@link SentryClient}. * * @see #initSentry() */ - protected volatile Sentry sentry; + protected volatile SentryClient sentryClient; /** * DSN property of the appender. *

    @@ -57,11 +57,11 @@ public class SentryAppender extends AppenderBase { */ protected String dsn; /** - * Name of the {@link SentryFactory} being used. + * Name of the {@link SentryClientFactory} being used. *

    * Might be null in which case the factory should be defined automatically. */ - protected String sentryFactory; + protected String sentryClientFactory; /** * Identifies the version of the application. *

    @@ -117,11 +117,11 @@ public SentryAppender() { /** * Creates an instance of SentryAppender. * - * @param sentry instance of Sentry to use with this appender. + * @param sentryClient instance of Sentry to use with this appender. */ - public SentryAppender(Sentry sentry) { + public SentryAppender(SentryClient sentryClient) { this(); - this.sentry = sentry; + this.sentryClient = sentryClient; } /** @@ -135,9 +135,9 @@ private void lazyInit() { synchronized (this) { if (!initialized) { try { - String sentryFactory = Lookup.lookup("sentryFactory"); - if (sentryFactory != null) { - setSentryFactory(sentryFactory); + String sentryClientFactory = Lookup.lookup("factory"); + if (sentryClientFactory != null) { + setFactory(sentryClientFactory); } String release = Lookup.lookup("release"); @@ -176,7 +176,7 @@ private void lazyInit() { } } - if (sentry == null) { + if (sentryClient == null) { initSentry(); } } @@ -217,13 +217,6 @@ protected static Event.Level formatLevel(Level level) { } } - /** - * {@inheritDoc} - *

    - * The sentry instance is started in this method instead of {@link #start()} in order to avoid substitute loggers - * being generated during the instantiation of {@link Sentry}.
    - * More on www.slf4j.org/codes.html#substituteLogger - */ @Override protected void append(ILoggingEvent iLoggingEvent) { // Do not log the event if the current thread is managed by sentry @@ -239,7 +232,7 @@ protected void append(ILoggingEvent iLoggingEvent) { lazyInit(); Event event = buildEvent(iLoggingEvent); - sentry.sendEvent(event); + sentryClient.sendEvent(event); } catch (Exception e) { addError("An exception occurred while creating a new event in Sentry", e); } finally { @@ -248,7 +241,7 @@ protected void append(ILoggingEvent iLoggingEvent) { } /** - * Initialises the Sentry instance. + * Initialises the {@link SentryClient} instance. */ protected synchronized void initSentry() { try { @@ -256,11 +249,11 @@ protected synchronized void initSentry() { dsn = Dsn.dsnLookup(); } - sentry = SentryFactory.sentryInstance(new Dsn(dsn), sentryFactory); + sentryClient = SentryClientFactory.sentryClient(new Dsn(dsn), sentryClientFactory); } catch (InvalidDsnException e) { addError("An exception occurred during the retrieval of the DSN for Sentry", e); } catch (Exception e) { - addError("An exception occurred during the creation of a Sentry instance", e); + addError("An exception occurred during the creation of a SentryClient instance", e); } } @@ -334,7 +327,7 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); } - sentry.runBuilderHelpers(eventBuilder); + sentryClient.runBuilderHelpers(eventBuilder); return eventBuilder.build(); } @@ -439,8 +432,8 @@ public void setDsn(String dsn) { this.dsn = dsn; } - public void setSentryFactory(String sentryFactory) { - this.sentryFactory = sentryFactory; + public void setFactory(String factory) { + this.sentryClientFactory = factory; } public void setRelease(String release) { @@ -489,8 +482,8 @@ public void stop() { return; } super.stop(); - if (sentry != null) { - sentry.closeConnection(); + if (sentryClient != null) { + sentryClient.closeConnection(); } } catch (Exception e) { addError("An exception occurred while closing the Sentry connection", e); diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java index 2563c72aa5f..878ed0fceec 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java @@ -4,8 +4,8 @@ import ch.qos.logback.core.Context; import ch.qos.logback.core.status.OnConsoleStatusListener; import mockit.*; -import io.sentry.Sentry; -import io.sentry.SentryFactory; +import io.sentry.SentryClient; +import io.sentry.SentryClientFactory; import io.sentry.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -15,12 +15,12 @@ public class SentryAppenderCloseTest { @Injectable - private Sentry mockSentry = null; + private SentryClient mockSentryClient = null; @Injectable private Context mockContext = null; @SuppressWarnings("unused") - @Mocked("sentryInstance") - private SentryFactory mockSentryFactory = null; + @Mocked("sentryClient") + private SentryClientFactory mockSentryClientFactory = null; @SuppressWarnings("unused") @Mocked("dsnLookup") private Dsn mockDsn = null; @@ -45,28 +45,28 @@ private void assertNoErrorsInStatusManager() throws Exception { @Test public void testConnectionClosedWhenAppenderStopped() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockSentry); + final SentryAppender sentryAppender = new SentryAppender(mockSentryClient); sentryAppender.setContext(mockContext); sentryAppender.start(); sentryAppender.stop(); new Verifications() {{ - mockSentry.closeConnection(); + mockSentryClient.closeConnection(); }}; assertNoErrorsInStatusManager(); } @Test - public void testStopIfSentryInstanceNotProvided() throws Exception { + public void testStopIfSentryClientNotProvided() throws Exception { final String dsnUri = "protocol://public:private@host/1"; final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setContext(mockContext); new Expectations() {{ Dsn.dsnLookup(); result = dsnUri; - SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockSentry; + SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); + result = mockSentryClient; }}; sentryAppender.start(); sentryAppender.append(null); @@ -74,7 +74,7 @@ public void testStopIfSentryInstanceNotProvided() throws Exception { sentryAppender.stop(); new Verifications() {{ - mockSentry.closeConnection(); + mockSentryClient.closeConnection(); }}; //One error, because of the null event. assertThat(mockContext.getStatusManager().getCount(), is(1)); @@ -86,7 +86,7 @@ public void testStopDoNotFailIfInitFailed() throws Exception { final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setContext(mockContext); new NonStrictExpectations() {{ - SentryFactory.sentryInstance((Dsn) any, anyString); + SentryClientFactory.sentryClient((Dsn) any, anyString); result = new UnsupportedOperationException(); }}; sentryAppender.start(); @@ -111,7 +111,7 @@ public void testStopDoNotFailIfNoInit() @Test public void testStopDoNotFailWhenMultipleCalls() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockSentry); + final SentryAppender sentryAppender = new SentryAppender(mockSentryClient); sentryAppender.setContext(mockContext); sentryAppender.start(); @@ -119,7 +119,7 @@ public void testStopDoNotFailWhenMultipleCalls() throws Exception { sentryAppender.stop(); new Verifications() {{ - mockSentry.closeConnection(); + mockSentryClient.closeConnection(); times = 1; }}; assertNoErrorsInStatusManager(); diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderDsnTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderDsnTest.java index 52f7df9ae5c..0d6b915c774 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderDsnTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderDsnTest.java @@ -4,8 +4,8 @@ import ch.qos.logback.core.Context; import ch.qos.logback.core.status.OnConsoleStatusListener; import mockit.*; -import io.sentry.Sentry; -import io.sentry.SentryFactory; +import io.sentry.SentryClient; +import io.sentry.SentryClientFactory; import io.sentry.dsn.Dsn; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -17,12 +17,12 @@ public class SentryAppenderDsnTest { @Tested private SentryAppender sentryAppender = null; @Injectable - private Sentry mockSentry = null; + private SentryClient mockSentryClient = null; @Injectable private Context mockContext = null; @SuppressWarnings("unused") - @Mocked("sentryInstance") - private SentryFactory mockSentryFactory = null; + @Mocked("sentryClient") + private SentryClientFactory mockSentryClientFactory = null; @SuppressWarnings("unused") @Mocked("dsnLookup") private Dsn mockDsn = null; @@ -54,8 +54,8 @@ public void testDsnDetected() throws Exception { new Expectations() {{ Dsn.dsnLookup(); result = dsnUri; - SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockSentry; + SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); + result = mockSentryClient; }}; sentryAppender.initSentry(); @@ -68,8 +68,8 @@ public void testDsnProvided() throws Exception { final String dsnUri = "protocol://public:private@host/2"; sentryAppender.setDsn(dsnUri); new Expectations() {{ - SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); - result = mockSentry; + SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); + result = mockSentryClient; }}; sentryAppender.initSentry(); diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java index 28c2126e5ae..e97ba2c2f27 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java @@ -5,13 +5,13 @@ import ch.qos.logback.core.BasicStatusManager; import ch.qos.logback.core.Context; import ch.qos.logback.core.status.OnConsoleStatusListener; +import io.sentry.SentryClient; import io.sentry.environment.SentryEnvironment; import io.sentry.event.interfaces.*; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; import mockit.Verifications; -import io.sentry.Sentry; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import org.hamcrest.Matchers; @@ -29,7 +29,7 @@ public class SentryAppenderEventBuildingTest { @Tested private SentryAppender sentryAppender = null; @Injectable - private Sentry mockSentry = null; + private SentryClient mockSentryClient = null; @Injectable private Context mockContext = null; private String mockExtraTag = "60f42409-c029-447d-816a-fb2722913c93"; @@ -38,7 +38,7 @@ public class SentryAppenderEventBuildingTest { @BeforeMethod public void setUp() throws Exception { new MockUpStatusPrinter(); - sentryAppender = new SentryAppender(mockSentry); + sentryAppender = new SentryAppender(mockSentryClient); sentryAppender.setContext(mockContext); sentryAppender.setExtraTags(mockExtraTag); sentryAppender.setMinLevel(mockMinLevel); @@ -71,8 +71,8 @@ public void testSimpleMessageLogging() throws Exception { new Verifications() {{ Event event; - mockSentry.runBuilderHelpers((EventBuilder) any); - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.runBuilderHelpers((EventBuilder) any); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getMessage(), is(message)); assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); @@ -98,7 +98,7 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getLevel(), is(expectedLevel)); }}; assertNoErrorsInStatusManager(); @@ -112,8 +112,8 @@ public void testExceptionLogging() throws Exception { new Verifications() {{ Event event; - mockSentry.runBuilderHelpers((EventBuilder) any); - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.runBuilderHelpers((EventBuilder) any); + mockSentryClient.sendEvent(event = withCapture()); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); SentryException sentryException = exceptionInterface.getExceptions().getFirst(); @@ -134,8 +134,8 @@ public void testLogParametrisedMessage() throws Exception { new Verifications() {{ Event event; - mockSentry.runBuilderHelpers((EventBuilder) any); - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.runBuilderHelpers((EventBuilder) any); + mockSentryClient.sendEvent(event = withCapture()); MessageInterface messageInterface = (MessageInterface) event.getSentryInterfaces() .get(MessageInterface.MESSAGE_INTERFACE); @@ -157,8 +157,8 @@ public void testMarkerAddedToTag() throws Exception { new Verifications() {{ Event event; - mockSentry.runBuilderHelpers((EventBuilder) any); - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.runBuilderHelpers((EventBuilder) any); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getTags(), Matchers.hasEntry(SentryAppender.LOGBACK_MARKER, markerName)); }}; assertNoErrorsInStatusManager(); @@ -174,8 +174,8 @@ public void testMdcAddedToExtra() throws Exception { new Verifications() {{ Event event; - mockSentry.runBuilderHelpers((EventBuilder) any); - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.runBuilderHelpers((EventBuilder) any); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); }}; assertNoErrorsInStatusManager(); @@ -191,8 +191,8 @@ public void testContextPropertiesAddedToExtra() throws Exception { new Verifications() {{ Event event; - mockSentry.runBuilderHelpers((EventBuilder) any); - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.runBuilderHelpers((EventBuilder) any); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); }}; assertNoErrorsInStatusManager(); @@ -211,8 +211,8 @@ public void testMdcTakesPrecedenceOverContextProperties() throws Exception { new Verifications() {{ Event event; - mockSentry.runBuilderHelpers((EventBuilder) any); - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.runBuilderHelpers((EventBuilder) any); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getExtra(), Matchers.hasEntry(mdcKey, mdcValue)); }}; assertNoErrorsInStatusManager(); @@ -229,8 +229,8 @@ public void testSourceUsedAsStacktrace() throws Exception { new Verifications() {{ Event event; - mockSentry.runBuilderHelpers((EventBuilder) any); - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.runBuilderHelpers((EventBuilder) any); + mockSentryClient.sendEvent(event = withCapture()); StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() .get(StackTraceInterface.STACKTRACE_INTERFACE); assertThat(stackTraceInterface.getStackTrace(), is(SentryStackTraceElement.fromStackTraceElements(location))); @@ -248,8 +248,8 @@ public void testCulpritWithSource() throws Exception { new Verifications() {{ Event event; - mockSentry.runBuilderHelpers((EventBuilder) any); - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.runBuilderHelpers((EventBuilder) any); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is("a.b(c:42)")); }}; assertNoErrorsInStatusManager(); @@ -263,8 +263,8 @@ public void testCulpritWithoutSource() throws Exception { new Verifications() {{ Event event; - mockSentry.runBuilderHelpers((EventBuilder) any); - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.runBuilderHelpers((EventBuilder) any); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is(loggerName)); }}; assertNoErrorsInStatusManager(); @@ -281,7 +281,7 @@ public void testExtraTagObtainedFromMdc() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getTags().entrySet(), hasSize(1)); assertThat(event.getTags(), hasEntry(mockExtraTag, "47008f35-50c8-4e40-94ca-c8c1a3ddb729")); assertThat(event.getExtra(), not(hasKey(mockExtraTag))); @@ -299,7 +299,7 @@ public void testReleaseAddedToEvent() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getRelease(), is(release)); }}; assertNoErrorsInStatusManager(); @@ -314,7 +314,7 @@ public void testEnvironmentAddedToEvent() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getEnvironment(), is(environment)); }}; assertNoErrorsInStatusManager(); diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java index b12cc59e8fe..5b7abf0b36b 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java @@ -5,7 +5,7 @@ import mockit.Injectable; import mockit.Tested; import mockit.Verifications; -import io.sentry.Sentry; +import io.sentry.SentryClient; import io.sentry.event.Event; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; @@ -18,14 +18,14 @@ public class SentryAppenderEventLevelFilterTest { @Tested private SentryAppender sentryAppender = null; @Injectable - private Sentry mockSentry = null; + private SentryClient mockSentryClient = null; @Injectable private Context mockContext = null; @BeforeMethod public void setUp() throws Exception { new MockUpStatusPrinter(); - sentryAppender = new SentryAppender(mockSentry); + sentryAppender = new SentryAppender(mockSentryClient); sentryAppender.setContext(mockContext); } @@ -53,7 +53,7 @@ public void testLevelFilter(final String minLevel, final Integer expectedEvents) sentryAppender.append(new MockUpLoggingEvent(null, null, Level.ERROR, null, null, null).getMockInstance()); new Verifications() {{ - mockSentry.sendEvent((Event) any); + mockSentryClient.sendEvent((Event) any); minTimes = expectedEvents; maxTimes = expectedEvents; }}; @@ -68,7 +68,7 @@ public void testDefaultLevelFilter() throws Exception { sentryAppender.append(new MockUpLoggingEvent(null, null, Level.ERROR, null, null, null).getMockInstance()); new Verifications() {{ - mockSentry.sendEvent((Event) any); + mockSentryClient.sendEvent((Event) any); minTimes = 5; maxTimes = 5; }}; diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java index 8ffd76da07b..e537e96fb41 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java @@ -4,9 +4,9 @@ import ch.qos.logback.core.BasicStatusManager; import ch.qos.logback.core.Context; import ch.qos.logback.core.status.OnConsoleStatusListener; +import io.sentry.SentryClient; import mockit.*; -import io.sentry.Sentry; -import io.sentry.SentryFactory; +import io.sentry.SentryClientFactory; import io.sentry.dsn.Dsn; import io.sentry.environment.SentryEnvironment; import io.sentry.event.Event; @@ -18,12 +18,12 @@ public class SentryAppenderFailuresTest { @Injectable - private Sentry mockSentry = null; + private SentryClient mockSentryClient = null; @Injectable private Context mockContext = null; @SuppressWarnings("unused") - @Mocked("sentryInstance") - private SentryFactory mockSentryFactory; + @Mocked("sentryClient") + private SentryClientFactory mockSentryClientFactory; @BeforeMethod public void setUp() throws Exception { @@ -41,11 +41,11 @@ public void setUp() throws Exception { @Test public void testSentryFailureDoesNotPropagate() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockSentry); + final SentryAppender sentryAppender = new SentryAppender(mockSentryClient); sentryAppender.setContext(mockContext); sentryAppender.setMinLevel("ALL"); new NonStrictExpectations() {{ - mockSentry.sendEvent((Event) any); + mockSentryClient.sendEvent((Event) any); result = new UnsupportedOperationException(); }}; sentryAppender.start(); @@ -53,19 +53,19 @@ public void testSentryFailureDoesNotPropagate() throws Exception { sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null).getMockInstance()); new Verifications() {{ - mockSentry.sendEvent((Event) any); + mockSentryClient.sendEvent((Event) any); }}; assertThat(mockContext.getStatusManager().getCount(), is(1)); } @Test - public void testSentryFactoryFailureDoesNotPropagate() throws Exception { + public void testSentryClientFactoryFailureDoesNotPropagate() throws Exception { final String dsnUri = "proto://private:public@host/1"; final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setContext(mockContext); sentryAppender.setDsn(dsnUri); new Expectations() {{ - SentryFactory.sentryInstance(withEqual(new Dsn(dsnUri)), anyString); + SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); result = new UnsupportedOperationException(); }}; sentryAppender.start(); @@ -79,14 +79,14 @@ public void testSentryFactoryFailureDoesNotPropagate() throws Exception { public void testAppendFailIfCurrentThreadSpawnedBySentry() throws Exception { SentryEnvironment.startManagingThread(); try { - final SentryAppender sentryAppender = new SentryAppender(mockSentry); + final SentryAppender sentryAppender = new SentryAppender(mockSentryClient); sentryAppender.setContext(mockContext); sentryAppender.start(); sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null).getMockInstance()); new Verifications() {{ - mockSentry.sendEvent((Event) any); + mockSentryClient.sendEvent((Event) any); times = 0; }}; assertThat(mockContext.getStatusManager().getCount(), is(0)); diff --git a/sentry/src/main/java/io/sentry/DefaultSentryFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java similarity index 98% rename from sentry/src/main/java/io/sentry/DefaultSentryFactory.java rename to sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index c9b0a39e885..1ed79067acd 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -25,11 +25,11 @@ import java.util.concurrent.atomic.AtomicInteger; /** - * Default implementation of {@link SentryFactory}. + * Default implementation of {@link SentryClientFactory}. *

    * In most cases this is the implementation to use or extend for additional features. */ -public class DefaultSentryFactory extends SentryFactory { +public class DefaultSentryClientFactory extends SentryClientFactory { //TODO: Add support for tags set by default /** * Protocol setting to disable security checks over an SSL connection. @@ -181,7 +181,7 @@ public class DefaultSentryFactory extends SentryFactory { */ public static final int HTTP_PROXY_PORT_DEFAULT = 80; - private static final Logger logger = LoggerFactory.getLogger(DefaultSentryFactory.class); + private static final Logger logger = LoggerFactory.getLogger(DefaultSentryClientFactory.class); private static final String FALSE = Boolean.FALSE.toString(); private static final Map REJECT_EXECUTION_HANDLERS = new HashMap<>(); @@ -192,20 +192,20 @@ public class DefaultSentryFactory extends SentryFactory { } @Override - public Sentry createSentryInstance(Dsn dsn) { - Sentry sentry = new Sentry(createConnection(dsn), getContextManager(dsn)); + public SentryClient createSentryClient(Dsn dsn) { + SentryClient sentryClient = new SentryClient(createConnection(dsn), getContextManager(dsn)); try { // `ServletRequestListener` was added in the Servlet 2.4 API, and // is used as part of the `HttpEventBuilderHelper`, see: // https://tomcat.apache.org/tomcat-5.5-doc/servletapi/ Class.forName("javax.servlet.ServletRequestListener", false, this.getClass().getClassLoader()); - sentry.addBuilderHelper(new HttpEventBuilderHelper()); + sentryClient.addBuilderHelper(new HttpEventBuilderHelper()); } catch (ClassNotFoundException e) { logger.debug("The current environment doesn't provide access to servlets," + " or provides an unsupported version."); } - sentry.addBuilderHelper(new ContextBuilderHelper(sentry)); - return sentry; + sentryClient.addBuilderHelper(new ContextBuilderHelper(sentryClient)); + return sentryClient; } /** diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 70c3862e533..61c36bde9cc 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -1,250 +1,121 @@ package io.sentry; -import io.sentry.connection.Connection; -import io.sentry.connection.LockedDownException; -import io.sentry.context.Context; -import io.sentry.context.ContextManager; -import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Breadcrumb; import io.sentry.event.Event; import io.sentry.event.EventBuilder; -import io.sentry.event.helper.EventBuilderHelper; -import io.sentry.event.interfaces.ExceptionInterface; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.Collections; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import io.sentry.event.User; /** - * Sentry is a client for Sentry allowing to send an {@link Event} that will be processed and sent to a Sentry server. - *

    - * It is recommended to create an instance of Sentry through - * {@link SentryFactory#createSentryInstance(io.sentry.dsn.Dsn)}, this will use the best factory available to - * create a sensible instance of Sentry. + * Sentry provides easy access to a statically stored {@link SentryClient} instance. */ -public class Sentry { - private static final Logger logger = LoggerFactory.getLogger(Sentry.class); - // CHECKSTYLE.OFF: ConstantName - private static final Logger lockdownLogger = LoggerFactory.getLogger(Sentry.class.getName() + ".lockdown"); - // CHECKSTYLE.ON: ConstantName - /** - * The most recently constructed Sentry instance, used by static helper methods like {@link Sentry#capture(Event)}. - */ - private static volatile Sentry stored = null; - /** - * The underlying {@link Connection} to use for sending events to Sentry. - */ - private volatile Connection connection; +public final class Sentry { /** - * Set of {@link EventBuilderHelper}s. Note that we wrap a {@link ConcurrentHashMap} because there - * isn't a concurrent set in the standard library. + * The most recently constructed {@link SentryClient} instance, used by static helper + * methods like {@link Sentry#capture(Event)}. */ - private final Set builderHelpers = - Collections.newSetFromMap(new ConcurrentHashMap()); - /** - * The {@link ContextManager} to use for locating and storing data that is context specific, - * such as {@link io.sentry.event.Breadcrumb}s. - */ - private final ContextManager contextManager; + private static volatile SentryClient storedClient = null; + /** - * Constructs a Sentry instance using the provided connection. - * - * Note that the most recently constructed instance is stored statically so it can be used with - * the static helper methods. - * - * @param connection Underlying {@link Connection} instance to use for sending events - * @param contextManager {@link ContextManager} instance to use for storing contextual data + * Hide constructor. */ - public Sentry(Connection connection, ContextManager contextManager) { - this.connection = connection; - this.contextManager = contextManager; - stored = this; + private Sentry() { + } /** - * Runs the {@link EventBuilderHelper} against the {@link EventBuilder} to obtain additional information with a - * MDC-like system. + * Returns the last statically stored {@link SentryClient} instance or null if one has + * never been stored. * - * @param eventBuilder event builder containing a not yet finished event. + * @return statically stored {@link SentryClient} instance */ - public void runBuilderHelpers(EventBuilder eventBuilder) { - for (EventBuilderHelper builderHelper : builderHelpers) { - builderHelper.helpBuildingEvent(eventBuilder); - } + public static SentryClient getStoredClient() { + return storedClient; } - /** - * Sends a built {@link Event} to the Sentry server. - * - * @param event event to send to Sentry. - */ - public void sendEvent(Event event) { - try { - connection.send(event); - } catch (LockedDownException e) { - lockdownLogger.warn("The connection to Sentry is currently locked down.", e); - } catch (Exception e) { - logger.error("An exception occurred while sending the event to Sentry.", e); - } finally { - getContext().setLastEventId(event.getId()); - } + public static void setStoredClient(SentryClient storedClient) { + Sentry.storedClient = storedClient; } - /** - * Builds and sends an {@link Event} to the Sentry server. - * - * @param eventBuilder {@link EventBuilder} to send to Sentry. - */ - public void sendEvent(EventBuilder eventBuilder) { - runBuilderHelpers(eventBuilder); - Event event = eventBuilder.build(); - sendEvent(event); + private static void verifyStoredClient() { + if (storedClient == null) { + throw new NullPointerException("No stored SentryClient instance is available to use." + + " You must construct a SentryClient instance before using the static Sentry methods."); + } } /** - * Sends a message to the Sentry server. - *

    - * The message will be logged at the {@link Event.Level#INFO} level. + * Send an Event using the statically stored {@link SentryClient} instance. * - * @param message message to send to Sentry. + * @param event Event to send to the Sentry server */ - public void sendMessage(String message) { - EventBuilder eventBuilder = new EventBuilder().withMessage(message) - .withLevel(Event.Level.INFO); - runBuilderHelpers(eventBuilder); - Event event = eventBuilder.build(); - sendEvent(event); + public static void capture(Event event) { + verifyStoredClient(); + getStoredClient().sendEvent(event); } /** - * Sends an exception (or throwable) to the Sentry server. + * Sends an exception (or throwable) to the Sentry server using the statically stored + * {@link SentryClient} instance. *

    * The exception will be logged at the {@link Event.Level#ERROR} level. * * @param throwable exception to send to Sentry. */ - public void sendException(Throwable throwable) { - EventBuilder eventBuilder = new EventBuilder().withMessage(throwable.getMessage()) - .withLevel(Event.Level.ERROR) - .withSentryInterface(new ExceptionInterface(throwable)); - runBuilderHelpers(eventBuilder); - Event event = eventBuilder.build(); - sendEvent(event); - } - - /** - * Removes a builder helper. - * - * @param builderHelper builder helper to remove. - */ - public void removeBuilderHelper(EventBuilderHelper builderHelper) { - logger.debug("Removing '{}' from the list of builder helpers.", builderHelper); - builderHelpers.remove(builderHelper); + public static void capture(Throwable throwable) { + verifyStoredClient(); + getStoredClient().sendException(throwable); } /** - * Adds a builder helper. + * Sends a message to the Sentry server using the statically stored {@link SentryClient} instance. + *

    + * The message will be logged at the {@link Event.Level#INFO} level. * - * @param builderHelper builder helper to add. - */ - public void addBuilderHelper(EventBuilderHelper builderHelper) { - logger.debug("Adding '{}' to the list of builder helpers.", builderHelper); - builderHelpers.add(builderHelper); - } - - public Set getBuilderHelpers() { - return Collections.unmodifiableSet(builderHelpers); - } - - /** - * Closes the connection for the Sentry instance. + * @param message message to send to Sentry. */ - public void closeConnection() { - try { - connection.close(); - } catch (IOException e) { - throw new RuntimeException("Couldn't close the Sentry connection", e); - } - } - - public Context getContext() { - return contextManager.getContext(); - } - - @Override - public String toString() { - return "Sentry{" - + "name=" + SentryEnvironment.getSentryName() - + ", connection=" + connection - + ", contextManager=" + contextManager - + '}'; + public static void capture(String message) { + verifyStoredClient(); + getStoredClient().sendMessage(message); } - // -------------------------------------------------------- - // Static helper methods follow - // -------------------------------------------------------- - /** - * Returns the last statically stored Sentry instance or null if one has - * never been stored. + * Builds and sends an {@link Event} to the Sentry server using the statically stored + * {@link SentryClient} instance. * - * @return statically stored {@link Sentry} instance + * @param eventBuilder {@link EventBuilder} to send to Sentry. */ - public static Sentry getStoredInstance() { - return stored; - } - - private static void verifyStoredInstance() { - if (stored == null) { - throw new NullPointerException("No stored Sentry instance is available to use." - + " You must construct a Sentry instance before using the static Sentry methods."); - } + public static void capture(EventBuilder eventBuilder) { + verifyStoredClient(); + getStoredClient().sendEvent(eventBuilder); } /** - * Send an Event using the statically stored Sentry instance. + * Record a {@link Breadcrumb}. * - * @param event Event to send to the Sentry server + * @param breadcrumb Breadcrumb to record */ - public static void capture(Event event) { - verifyStoredInstance(); - getStoredInstance().sendEvent(event); + public static void record(Breadcrumb breadcrumb) { + verifyStoredClient(); + getStoredClient().getContext().recordBreadcrumb(breadcrumb); } /** - * Sends an exception (or throwable) to the Sentry server using the statically stored Sentry instance. - *

    - * The exception will be logged at the {@link Event.Level#ERROR} level. + * Set the {@link User} in the current context. * - * @param throwable exception to send to Sentry. + * @param user User to store. */ - public static void capture(Throwable throwable) { - verifyStoredInstance(); - getStoredInstance().sendException(throwable); + public static void setUser(User user) { + verifyStoredClient(); + getStoredClient().getContext().setUser(user); } /** - * Sends a message to the Sentry server using the statically stored Sentry instance. - *

    - * The message will be logged at the {@link Event.Level#INFO} level. - * - * @param message message to send to Sentry. + * Clears the current context. */ - public static void capture(String message) { - verifyStoredInstance(); - getStoredInstance().sendMessage(message); + public static void clearContext() { + verifyStoredClient(); + getStoredClient().getContext().clear(); } - /** - * Builds and sends an {@link Event} to the Sentry server using the statically stored Sentry instance. - * - * @param eventBuilder {@link EventBuilder} to send to Sentry. - */ - public static void capture(EventBuilder eventBuilder) { - verifyStoredInstance(); - getStoredInstance().sendEvent(eventBuilder); - } } diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java new file mode 100644 index 00000000000..a3f6a21fbd8 --- /dev/null +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -0,0 +1,182 @@ +package io.sentry; + +import io.sentry.connection.Connection; +import io.sentry.connection.LockedDownException; +import io.sentry.context.Context; +import io.sentry.context.ContextManager; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; +import io.sentry.event.helper.EventBuilderHelper; +import io.sentry.event.interfaces.ExceptionInterface; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Sentry client, for sending {@link Event}s to a Sentry server. + *

    + * It is recommended that you create an instance of Sentry through + * {@link SentryClientFactory#createSentryClient(io.sentry.dsn.Dsn)}, which will use the best factory available. + */ +public class SentryClient { + private static final Logger logger = LoggerFactory.getLogger(SentryClient.class); + // CHECKSTYLE.OFF: ConstantName + private static final Logger lockdownLogger = LoggerFactory.getLogger(SentryClient.class.getName() + ".lockdown"); + // CHECKSTYLE.ON: ConstantName + + /** + * The underlying {@link Connection} to use for sending events to Sentry. + */ + private volatile Connection connection; + /** + * Set of {@link EventBuilderHelper}s. Note that we wrap a {@link ConcurrentHashMap} because there + * isn't a concurrent set in the standard library. + */ + private final Set builderHelpers = + Collections.newSetFromMap(new ConcurrentHashMap()); + /** + * The {@link ContextManager} to use for locating and storing data that is context specific, + * such as {@link io.sentry.event.Breadcrumb}s. + */ + private final ContextManager contextManager; + + /** + * Constructs a {@link SentryClient} instance using the provided connection. + * + * Note that the most recently constructed instance is stored statically so it can be used with + * the static helper methods. + * + * @param connection Underlying {@link Connection} instance to use for sending events + * @param contextManager {@link ContextManager} instance to use for storing contextual data + */ + public SentryClient(Connection connection, ContextManager contextManager) { + this.connection = connection; + this.contextManager = contextManager; + + Sentry.setStoredClient(this); + } + + /** + * Runs the {@link EventBuilderHelper} against the {@link EventBuilder} to obtain additional information with a + * MDC-like system. + * + * @param eventBuilder event builder containing a not yet finished event. + */ + public void runBuilderHelpers(EventBuilder eventBuilder) { + for (EventBuilderHelper builderHelper : builderHelpers) { + builderHelper.helpBuildingEvent(eventBuilder); + } + } + + /** + * Sends a built {@link Event} to the Sentry server. + * + * @param event event to send to Sentry. + */ + public void sendEvent(Event event) { + try { + connection.send(event); + } catch (LockedDownException e) { + lockdownLogger.warn("The connection to Sentry is currently locked down.", e); + } catch (Exception e) { + logger.error("An exception occurred while sending the event to Sentry.", e); + } finally { + getContext().setLastEventId(event.getId()); + } + } + + /** + * Builds and sends an {@link Event} to the Sentry server. + * + * @param eventBuilder {@link EventBuilder} to send to Sentry. + */ + public void sendEvent(EventBuilder eventBuilder) { + runBuilderHelpers(eventBuilder); + Event event = eventBuilder.build(); + sendEvent(event); + } + + /** + * Sends a message to the Sentry server. + *

    + * The message will be logged at the {@link Event.Level#INFO} level. + * + * @param message message to send to Sentry. + */ + public void sendMessage(String message) { + EventBuilder eventBuilder = new EventBuilder().withMessage(message) + .withLevel(Event.Level.INFO); + runBuilderHelpers(eventBuilder); + Event event = eventBuilder.build(); + sendEvent(event); + } + + /** + * Sends an exception (or throwable) to the Sentry server. + *

    + * The exception will be logged at the {@link Event.Level#ERROR} level. + * + * @param throwable exception to send to Sentry. + */ + public void sendException(Throwable throwable) { + EventBuilder eventBuilder = new EventBuilder().withMessage(throwable.getMessage()) + .withLevel(Event.Level.ERROR) + .withSentryInterface(new ExceptionInterface(throwable)); + runBuilderHelpers(eventBuilder); + Event event = eventBuilder.build(); + sendEvent(event); + } + + /** + * Removes a builder helper. + * + * @param builderHelper builder helper to remove. + */ + public void removeBuilderHelper(EventBuilderHelper builderHelper) { + logger.debug("Removing '{}' from the list of builder helpers.", builderHelper); + builderHelpers.remove(builderHelper); + } + + /** + * Adds a builder helper. + * + * @param builderHelper builder helper to add. + */ + public void addBuilderHelper(EventBuilderHelper builderHelper) { + logger.debug("Adding '{}' to the list of builder helpers.", builderHelper); + builderHelpers.add(builderHelper); + } + + public Set getBuilderHelpers() { + return Collections.unmodifiableSet(builderHelpers); + } + + /** + * Closes the connection for the {@link SentryClient} instance. + */ + public void closeConnection() { + try { + connection.close(); + } catch (IOException e) { + throw new RuntimeException("Couldn't close the Sentry connection", e); + } + } + + public Context getContext() { + return contextManager.getContext(); + } + + @Override + public String toString() { + return "Sentry{" + + "name=" + SentryEnvironment.getSentryName() + + ", connection=" + connection + + ", contextManager=" + contextManager + + '}'; + } +} diff --git a/sentry/src/main/java/io/sentry/SentryFactory.java b/sentry/src/main/java/io/sentry/SentryClientFactory.java similarity index 58% rename from sentry/src/main/java/io/sentry/SentryFactory.java rename to sentry/src/main/java/io/sentry/SentryClientFactory.java index dff773524e0..5f0cc26f7c5 100644 --- a/sentry/src/main/java/io/sentry/SentryFactory.java +++ b/sentry/src/main/java/io/sentry/SentryClientFactory.java @@ -12,33 +12,33 @@ import java.util.Set; /** - * Factory in charge of creating {@link Sentry} instances. + * Factory in charge of creating {@link SentryClient} instances. *

    * The factories register themselves through the {@link ServiceLoader} system. */ -public abstract class SentryFactory { - private static final ServiceLoader AUTO_REGISTERED_FACTORIES = - ServiceLoader.load(SentryFactory.class, SentryFactory.class.getClassLoader()); - private static final Set MANUALLY_REGISTERED_FACTORIES = new HashSet<>(); - private static final Logger logger = LoggerFactory.getLogger(SentryFactory.class); +public abstract class SentryClientFactory { + private static final ServiceLoader AUTO_REGISTERED_FACTORIES = + ServiceLoader.load(SentryClientFactory.class, SentryClientFactory.class.getClassLoader()); + private static final Set MANUALLY_REGISTERED_FACTORIES = new HashSet<>(); + private static final Logger logger = LoggerFactory.getLogger(SentryClientFactory.class); /** - * Manually adds a SentryFactory to the system. + * Manually adds a SentryClientFactory to the system. *

    * Usually SentryFactories are automatically detected with the {@link ServiceLoader} system, but some systems * such as Android do not provide a fully working ServiceLoader.
    * If the factory isn't detected automatically, it's possible to add it through this method. * - * @param sentryFactory sentryFactory to support. + * @param sentryClientFactory sentryClientFactory to support. */ - public static void registerFactory(SentryFactory sentryFactory) { - MANUALLY_REGISTERED_FACTORIES.add(sentryFactory); + public static void registerFactory(SentryClientFactory sentryClientFactory) { + MANUALLY_REGISTERED_FACTORIES.add(sentryClientFactory); } - private static Iterable getRegisteredFactories() { - List sentryFactories = new LinkedList<>(); + private static Iterable getRegisteredFactories() { + List sentryFactories = new LinkedList<>(); sentryFactories.addAll(MANUALLY_REGISTERED_FACTORIES); - for (SentryFactory autoRegisteredFactory : AUTO_REGISTERED_FACTORIES) { + for (SentryClientFactory autoRegisteredFactory : AUTO_REGISTERED_FACTORIES) { sentryFactories.add(autoRegisteredFactory); } return sentryFactories; @@ -49,8 +49,8 @@ private static Iterable getRegisteredFactories() { * * @return an instance of Sentry. */ - public static Sentry sentryInstance() { - return sentryInstance(Dsn.dsnLookup()); + public static SentryClient sentryClient() { + return sentryClient(Dsn.dsnLookup()); } /** @@ -59,8 +59,8 @@ public static Sentry sentryInstance() { * @param dsn Data Source Name of the Sentry server. * @return an instance of Sentry. */ - public static Sentry sentryInstance(String dsn) { - return sentryInstance(new Dsn(dsn)); + public static SentryClient sentryClient(String dsn) { + return sentryClient(new Dsn(dsn)); } /** @@ -69,20 +69,20 @@ public static Sentry sentryInstance(String dsn) { * @param dsn Data Source Name of the Sentry server. * @return an instance of Sentry. */ - public static Sentry sentryInstance(Dsn dsn) { - return sentryInstance(dsn, null); + public static SentryClient sentryClient(Dsn dsn) { + return sentryClient(dsn, null); } /** * Creates an instance of Sentry using the provided DSN and the specified factory. * * @param dsn Data Source Name of the Sentry server. - * @param sentryFactoryName name of the SentryFactory to use to generate an instance of Sentry. + * @param sentryClientFactoryName name of the SentryClientFactory to use to generate an instance of Sentry. * @return an instance of Sentry. * @throws IllegalStateException when no instance of Sentry has been created. */ - public static Sentry sentryInstance(Dsn dsn, String sentryFactoryName) { - logger.debug("Attempting to find a working SentryFactory"); + public static SentryClient sentryClient(Dsn dsn, String sentryClientFactoryName) { + logger.debug("Attempting to find a working SentryClientFactory"); // Loop through registered factories, keeping track of which classes we skip, which we try to instantiate, // and the last exception thrown. @@ -90,47 +90,48 @@ public static Sentry sentryInstance(Dsn dsn, String sentryFactoryName) { ArrayList triedFactories = new ArrayList<>(); RuntimeException lastExc = null; - for (SentryFactory sentryFactory : getRegisteredFactories()) { - String name = sentryFactory.getClass().getName(); - if (sentryFactoryName != null && !sentryFactoryName.equals(name)) { + for (SentryClientFactory sentryClientFactory : getRegisteredFactories()) { + String name = sentryClientFactory.getClass().getName(); + if (sentryClientFactoryName != null && !sentryClientFactoryName.equals(name)) { skippedFactories.add(name); continue; } - logger.debug("Attempting to use '{}' as a SentryFactory.", sentryFactory); + logger.debug("Attempting to use '{}' as a SentryClientFactory.", sentryClientFactory); triedFactories.add(name); try { - Sentry sentryInstance = sentryFactory.createSentryInstance(dsn); - logger.debug("The SentryFactory '{}' created an instance of Sentry.", sentryFactory); - return sentryInstance; + SentryClient sentryClientInstance = sentryClientFactory.createSentryClient(dsn); + logger.debug("The SentryClientFactory '{}' created an instance of Sentry.", sentryClientFactory); + return sentryClientInstance; } catch (RuntimeException e) { lastExc = e; - logger.debug("The SentryFactory '{}' couldn't create an instance of Sentry.", sentryFactory, e); + logger.debug("The SentryClientFactory '{}' couldn't create an instance of Sentry.", + sentryClientFactory, e); } } - if (sentryFactoryName != null && triedFactories.isEmpty()) { + if (sentryClientFactoryName != null && triedFactories.isEmpty()) { try { // see if the provided class exists on the classpath at all - Class.forName(sentryFactoryName); + Class.forName(sentryClientFactoryName); logger.error( - "The SentryFactory class '{}' was found on your classpath but was not " + "The SentryClientFactory class '{}' was found on your classpath but was not " + "registered with Sentry, see: " - + "https://github.com/getsentry/sentry-java/#custom-sentryfactory", sentryFactoryName); + + "https://github.com/getsentry/sentry-java/#custom-sentryClientFactory", sentryClientFactoryName); } catch (ClassNotFoundException e) { - logger.error("The SentryFactory class name '{}' was specified but " - + "the class was not found on your classpath.", sentryFactoryName); + logger.error("The SentryClientFactory class name '{}' was specified but " + + "the class was not found on your classpath.", sentryClientFactoryName); } } // Throw an IllegalStateException that attempts to be helpful. StringBuilder sb = new StringBuilder(); - sb.append("Couldn't create a sentry instance for: '"); + sb.append("Couldn't create a SentryClient instance for: '"); sb.append(dsn); sb.append('\''); - if (sentryFactoryName != null) { - sb.append("; sentryFactoryName: "); - sb.append(sentryFactoryName); + if (sentryClientFactoryName != null) { + sb.append("; sentryClientFactoryName: "); + sb.append(sentryClientFactoryName); if (skippedFactories.isEmpty()) { sb.append("; no skipped factories"); @@ -169,11 +170,11 @@ public static Sentry sentryInstance(Dsn dsn, String sentryFactoryName) { * @return an instance of Sentry. * @throws RuntimeException when an instance couldn't be created. */ - public abstract Sentry createSentryInstance(Dsn dsn); + public abstract SentryClient createSentryClient(Dsn dsn); @Override public String toString() { - return "SentryFactory{" + return "SentryClientFactory{" + "name='" + this.getClass().getName() + '\'' + '}'; } diff --git a/sentry/src/main/java/io/sentry/connection/AsyncConnection.java b/sentry/src/main/java/io/sentry/connection/AsyncConnection.java index 4373b7fd5d5..e92d2d742b2 100644 --- a/sentry/src/main/java/io/sentry/connection/AsyncConnection.java +++ b/sentry/src/main/java/io/sentry/connection/AsyncConnection.java @@ -1,6 +1,6 @@ package io.sentry.connection; -import io.sentry.Sentry; +import io.sentry.SentryClient; import io.sentry.environment.SentryEnvironment; import io.sentry.event.Event; import org.slf4j.Logger; @@ -21,7 +21,7 @@ public class AsyncConnection implements Connection { private static final Logger logger = LoggerFactory.getLogger(AsyncConnection.class); // CHECKSTYLE.OFF: ConstantName - private static final Logger lockdownLogger = LoggerFactory.getLogger(Sentry.class.getName() + ".lockdown"); + private static final Logger lockdownLogger = LoggerFactory.getLogger(SentryClient.class.getName() + ".lockdown"); // CHECKSTYLE.ON: ConstantName /** * Timeout of the {@link #executorService}, in milliseconds. diff --git a/sentry/src/main/java/io/sentry/connection/HttpConnection.java b/sentry/src/main/java/io/sentry/connection/HttpConnection.java index d0535fb3fdc..8b19e9218ec 100644 --- a/sentry/src/main/java/io/sentry/connection/HttpConnection.java +++ b/sentry/src/main/java/io/sentry/connection/HttpConnection.java @@ -92,7 +92,7 @@ public HttpConnection(URL sentryUrl, String publicKey, String secretKey, Proxy p /** * Automatically determines the URL to the HTTP API of Sentry. * - * @param sentryUri URI of the Sentry instance. + * @param sentryUri URI of the Sentry server. * @param projectId unique identifier of the current project. * @return an URL to the HTTP API of Sentry. */ diff --git a/sentry/src/main/java/io/sentry/event/Breadcrumbs.java b/sentry/src/main/java/io/sentry/event/Breadcrumbs.java deleted file mode 100644 index ae02d9dc414..00000000000 --- a/sentry/src/main/java/io/sentry/event/Breadcrumbs.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.sentry.event; - -import io.sentry.Sentry; - -/** - * Helpers for dealing with {@link Breadcrumb}s. - */ -public final class Breadcrumbs { - - /** - * Private constructor because this is a utility class. - */ - private Breadcrumbs() { - - } - - /** - * Record a {@link Breadcrumb} into all of this thread's active contexts. - * - * @param breadcrumb Breadcrumb to record - */ - public static void record(Breadcrumb breadcrumb) { - Sentry.getStoredInstance().getContext().recordBreadcrumb(breadcrumb); - } - -} diff --git a/sentry/src/main/java/io/sentry/event/Event.java b/sentry/src/main/java/io/sentry/event/Event.java index 4ed0d0d6297..cfcc4f8baaa 100644 --- a/sentry/src/main/java/io/sentry/event/Event.java +++ b/sentry/src/main/java/io/sentry/event/Event.java @@ -14,7 +14,7 @@ import java.util.UUID; /** - * Plain Old Java Object describing an event that will be sent to a Sentry instance. + * Plain Old Java Object describing an event that will be sent to a Sentry server. *

    * For security purposes, an event should be created from an {@link EventBuilder} only, and be completely immutable * once it has been fully generated. diff --git a/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java b/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java index 23fcbce5af1..feb9684fe3c 100644 --- a/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java +++ b/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java @@ -1,6 +1,6 @@ package io.sentry.event.helper; -import io.sentry.Sentry; +import io.sentry.SentryClient; import io.sentry.context.Context; import io.sentry.event.Breadcrumb; import io.sentry.event.EventBuilder; @@ -13,28 +13,28 @@ /** * {@link EventBuilderHelper} that extracts and sends any data attached to the - * provided {@link Sentry}'s {@link Context}. + * provided {@link SentryClient}'s {@link Context}. */ public class ContextBuilderHelper implements EventBuilderHelper { /** * Sentry object where the Context comes from. */ - private Sentry sentry; + private SentryClient sentryClient; /** - * {@link EventBuilderHelper} that extracts context data from the provided {@link Sentry} client. + * {@link EventBuilderHelper} that extracts context data from the provided {@link SentryClient} client. * - * @param sentry Sentry client which holds Context to be used. + * @param sentryClient Sentry client which holds Context to be used. */ - public ContextBuilderHelper(Sentry sentry) { - this.sentry = sentry; + public ContextBuilderHelper(SentryClient sentryClient) { + this.sentryClient = sentryClient; } @Override public void helpBuildingEvent(EventBuilder eventBuilder) { List breadcrumbs = new ArrayList<>(); - Context context = sentry.getContext(); + Context context = sentryClient.getContext(); Iterator breadcrumbIterator = context.getBreadcrumbs(); while (breadcrumbIterator.hasNext()) { diff --git a/sentry/src/main/java/io/sentry/jul/SentryHandler.java b/sentry/src/main/java/io/sentry/jul/SentryHandler.java index 42bd6b2b9b9..646680fa6ac 100644 --- a/sentry/src/main/java/io/sentry/jul/SentryHandler.java +++ b/sentry/src/main/java/io/sentry/jul/SentryHandler.java @@ -1,7 +1,7 @@ package io.sentry.jul; -import io.sentry.Sentry; -import io.sentry.SentryFactory; +import io.sentry.SentryClient; +import io.sentry.SentryClientFactory; import io.sentry.config.Lookup; import io.sentry.dsn.Dsn; import io.sentry.dsn.InvalidDsnException; @@ -36,11 +36,11 @@ public class SentryHandler extends Handler { */ public static final String THREAD_ID = "Sentry-ThreadId"; /** - * Current instance of {@link Sentry}. + * Current instance of {@link SentryClient}. * * @see #initSentry() */ - protected volatile Sentry sentry; + protected volatile SentryClient sentryClient; /** * DSN property of the appender. *

    @@ -54,11 +54,11 @@ public class SentryHandler extends Handler { */ protected boolean printfStyle; /** - * Name of the {@link SentryFactory} being used. + * Name of the {@link SentryClientFactory} being used. *

    * Might be null in which case the factory should be defined automatically. */ - protected String sentryFactory; + protected String sentryClientFactory; /** * Identifies the version of the application. *

    @@ -109,11 +109,11 @@ public SentryHandler() { /** * Creates an instance of SentryHandler. * - * @param sentry instance of Sentry to use with this appender. + * @param sentryClient instance of Sentry to use with this appender. */ - public SentryHandler(Sentry sentry) { + public SentryHandler(SentryClient sentryClient) { this(); - this.sentry = sentry; + this.sentryClient = sentryClient; } /** @@ -128,9 +128,9 @@ private void lazyInit() { if (!initialized) { try { - String sentryFactory = Lookup.lookup("sentryFactory"); - if (sentryFactory != null) { - setSentryFactory(sentryFactory); + String sentryClientFactory = Lookup.lookup("factory"); + if (sentryClientFactory != null) { + setFactory(sentryClientFactory); } String release = Lookup.lookup("release"); @@ -169,7 +169,7 @@ private void lazyInit() { } } - if (sentry == null) { + if (sentryClient == null) { initSentry(); } } @@ -220,9 +220,9 @@ protected void retrieveProperties() { if (dsnProperty != null) { setDsn(dsnProperty); } - String sentryFactoryProperty = manager.getProperty(className + ".sentryFactory"); - if (sentryFactoryProperty != null) { - setSentryFactory(sentryFactoryProperty); + String sentryClientFactoryProperty = manager.getProperty(className + ".factory"); + if (sentryClientFactoryProperty != null) { + setFactory(sentryClientFactoryProperty); } String releaseProperty = manager.getProperty(className + ".release"); if (releaseProperty != null) { @@ -262,7 +262,7 @@ public void publish(LogRecord record) { try { lazyInit(); Event event = buildEvent(record); - sentry.sendEvent(event); + sentryClient.sendEvent(event); } catch (Exception e) { reportError("An exception occurred while creating a new event in Sentry", e, ErrorManager.WRITE_FAILURE); } finally { @@ -271,7 +271,7 @@ public void publish(LogRecord record) { } /** - * Initialises the Sentry instance. + * Initialises the {@link SentryClient} instance. */ protected synchronized void initSentry() { try { @@ -279,12 +279,13 @@ protected synchronized void initSentry() { dsn = Dsn.dsnLookup(); } - sentry = SentryFactory.sentryInstance(new Dsn(dsn), sentryFactory); + sentryClient = SentryClientFactory.sentryClient(new Dsn(dsn), sentryClientFactory); } catch (InvalidDsnException e) { reportError("An exception occurred during the retrieval of the DSN for Sentry", e, ErrorManager.OPEN_FAILURE); } catch (Exception e) { - reportError("An exception occurred during the creation of a Sentry instance", e, ErrorManager.OPEN_FAILURE); + reportError("An exception occurred during the creation of a SentryClient instance", + e, ErrorManager.OPEN_FAILURE); } } @@ -368,7 +369,7 @@ protected Event buildEvent(LogRecord record) { eventBuilder.withServerName(serverName.trim()); } - sentry.runBuilderHelpers(eventBuilder); + sentryClient.runBuilderHelpers(eventBuilder); return eventBuilder.build(); } @@ -398,8 +399,8 @@ public void flush() { public void close() throws SecurityException { SentryEnvironment.startManagingThread(); try { - if (sentry != null) { - sentry.closeConnection(); + if (sentryClient != null) { + sentryClient.closeConnection(); } } catch (Exception e) { reportError("An exception occurred while closing the Sentry connection", e, ErrorManager.CLOSE_FAILURE); @@ -416,8 +417,8 @@ public void setPrintfStyle(boolean printfStyle) { this.printfStyle = printfStyle; } - public void setSentryFactory(String sentryFactory) { - this.sentryFactory = sentryFactory; + public void setFactory(String factory) { + this.sentryClientFactory = factory; } public void setRelease(String release) { diff --git a/sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java b/sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java index 94f7a79861b..b017641b2a6 100644 --- a/sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java +++ b/sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java @@ -1,6 +1,7 @@ package io.sentry.servlet; import io.sentry.Sentry; +import io.sentry.SentryClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,9 +29,9 @@ public void requestDestroyed(ServletRequestEvent servletRequestEvent) { THREAD_REQUEST.remove(); try { - Sentry sentry = Sentry.getStoredInstance(); - if (sentry != null) { - sentry.getContext().clear(); + SentryClient sentryClient = Sentry.getStoredClient(); + if (sentryClient != null) { + sentryClient.getContext().clear(); } } catch (Exception e) { logger.error("Error clearing Context state.", e); diff --git a/sentry/src/main/resources/META-INF/services/io.sentry.SentryClientFactory b/sentry/src/main/resources/META-INF/services/io.sentry.SentryClientFactory new file mode 100644 index 00000000000..3e935271875 --- /dev/null +++ b/sentry/src/main/resources/META-INF/services/io.sentry.SentryClientFactory @@ -0,0 +1 @@ +io.sentry.DefaultSentryClientFactory diff --git a/sentry/src/main/resources/META-INF/services/io.sentry.SentryFactory b/sentry/src/main/resources/META-INF/services/io.sentry.SentryFactory deleted file mode 100644 index c2273663767..00000000000 --- a/sentry/src/main/resources/META-INF/services/io.sentry.SentryFactory +++ /dev/null @@ -1 +0,0 @@ -io.sentry.DefaultSentryFactory diff --git a/sentry/src/test/java/io/sentry/DefaultSentryFactoryTest.java b/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java similarity index 68% rename from sentry/src/test/java/io/sentry/DefaultSentryFactoryTest.java rename to sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java index 1495ce500de..9e7eb5b2623 100644 --- a/sentry/src/test/java/io/sentry/DefaultSentryFactoryTest.java +++ b/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java @@ -8,11 +8,11 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.instanceOf; -public class DefaultSentryFactoryTest { +public class DefaultSentryClientFactoryTest { @Test public void checkServiceLoaderProvidesFactory() throws Exception { - ServiceLoader sentryFactories = ServiceLoader.load(SentryFactory.class); + ServiceLoader sentryFactories = ServiceLoader.load(SentryClientFactory.class); - assertThat(sentryFactories, contains(instanceOf(DefaultSentryFactory.class))); + assertThat(sentryFactories, contains(instanceOf(DefaultSentryClientFactory.class))); } } diff --git a/sentry/src/test/java/io/sentry/SentryFactoryTest.java b/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java similarity index 51% rename from sentry/src/test/java/io/sentry/SentryFactoryTest.java rename to sentry/src/test/java/io/sentry/SentryClientFactoryTest.java index 7eb73a31f7e..9a8de1944f1 100644 --- a/sentry/src/test/java/io/sentry/SentryFactoryTest.java +++ b/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java @@ -6,11 +6,9 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; -import java.util.List; import java.util.ServiceLoader; import static mockit.Deencapsulation.setField; @@ -18,15 +16,15 @@ import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; -public class SentryFactoryTest { +public class SentryClientFactoryTest { @Tested - private SentryFactory sentryFactory = null; + private SentryClientFactory sentryClientFactory = null; @Injectable - private ServiceLoader mockServiceLoader = null; + private ServiceLoader mockServiceLoader = null; @BeforeMethod public void setUp() throws Exception { - setField(SentryFactory.class, "AUTO_REGISTERED_FACTORIES", mockServiceLoader); + setField(SentryClientFactory.class, "AUTO_REGISTERED_FACTORIES", mockServiceLoader); new NonStrictExpectations() {{ mockServiceLoader.iterator(); @@ -37,83 +35,83 @@ public void setUp() throws Exception { @AfterMethod public void tearDown() throws Exception { // Reset the registered factories - setField(SentryFactory.class, "MANUALLY_REGISTERED_FACTORIES", new HashSet<>()); - setField(SentryFactory.class, "AUTO_REGISTERED_FACTORIES", ServiceLoader.load(SentryFactory.class)); + setField(SentryClientFactory.class, "MANUALLY_REGISTERED_FACTORIES", new HashSet<>()); + setField(SentryClientFactory.class, "AUTO_REGISTERED_FACTORIES", ServiceLoader.load(SentryClientFactory.class)); } @Test - public void testGetFactoriesFromServiceLoader(@Injectable final Sentry mockSentry, + public void testGetFactoriesFromServiceLoader(@Injectable final SentryClient mockSentryClient, @Injectable final Dsn mockDsn) throws Exception { new NonStrictExpectations() {{ mockServiceLoader.iterator(); - result = new Delegate>() { + result = new Delegate>() { @SuppressWarnings("unused") - public Iterator iterator() { - return Collections.singletonList(sentryFactory).iterator(); + public Iterator iterator() { + return Collections.singletonList(sentryClientFactory).iterator(); } }; - sentryFactory.createSentryInstance(mockDsn); - result = mockSentry; + sentryClientFactory.createSentryClient(mockDsn); + result = mockSentryClient; }}; - Sentry sentry = SentryFactory.sentryInstance(mockDsn); + SentryClient sentryClient = SentryClientFactory.sentryClient(mockDsn); - assertThat(sentry, is(mockSentry)); + assertThat(sentryClient, is(mockSentryClient)); } @Test - public void testGetFactoriesManuallyAdded(@Injectable final Sentry mockSentry, + public void testGetFactoriesManuallyAdded(@Injectable final SentryClient mockSentryClient, @Injectable final Dsn mockDsn) throws Exception { - SentryFactory.registerFactory(sentryFactory); + SentryClientFactory.registerFactory(sentryClientFactory); new NonStrictExpectations() {{ - sentryFactory.createSentryInstance(mockDsn); - result = mockSentry; + sentryClientFactory.createSentryClient(mockDsn); + result = mockSentryClient; }}; - Sentry sentry = SentryFactory.sentryInstance(mockDsn); + SentryClient sentryClient = SentryClientFactory.sentryClient(mockDsn); - assertThat(sentry, is(mockSentry)); + assertThat(sentryClient, is(mockSentryClient)); } @Test - public void testSentryInstanceForFactoryNameSucceedsIfFactoryFound(@Injectable final Sentry mockSentry, + public void testSentryClientForFactoryNameSucceedsIfFactoryFound(@Injectable final SentryClient mockSentryClient, @Injectable final Dsn mockDsn) throws Exception { - String factoryName = sentryFactory.getClass().getName(); - SentryFactory.registerFactory(sentryFactory); + String factoryName = sentryClientFactory.getClass().getName(); + SentryClientFactory.registerFactory(sentryClientFactory); new NonStrictExpectations() {{ - sentryFactory.createSentryInstance(mockDsn); - result = mockSentry; + sentryClientFactory.createSentryClient(mockDsn); + result = mockSentryClient; }}; - Sentry sentry = SentryFactory.sentryInstance(mockDsn, factoryName); + SentryClient sentryClient = SentryClientFactory.sentryClient(mockDsn, factoryName); - assertThat(sentry, is(mockSentry)); + assertThat(sentryClient, is(mockSentryClient)); } @Test(expectedExceptions = IllegalStateException.class) - public void testSentryInstanceForFactoryNameFailsIfNoFactoryFound(@Injectable final Sentry mockSentry, + public void testSentryClientForFactoryNameFailsIfNoFactoryFound(@Injectable final SentryClient mockSentryClient, @Injectable final Dsn mockDsn) throws Exception { String factoryName = "invalidName"; - SentryFactory.registerFactory(sentryFactory); + SentryClientFactory.registerFactory(sentryClientFactory); new NonStrictExpectations() {{ - sentryFactory.createSentryInstance(mockDsn); - result = mockSentry; + sentryClientFactory.createSentryClient(mockDsn); + result = mockSentryClient; }}; - SentryFactory.sentryInstance(mockDsn, factoryName); + SentryClientFactory.sentryClient(mockDsn, factoryName); } @Test public void testSentryInstantiationFailureCaught(@Injectable final Dsn mockDsn) throws Exception { - SentryFactory.registerFactory(sentryFactory); + SentryClientFactory.registerFactory(sentryClientFactory); Exception exception = null; new NonStrictExpectations() {{ - sentryFactory.createSentryInstance(mockDsn); + sentryClientFactory.createSentryClient(mockDsn); result = new RuntimeException(); }}; try { - SentryFactory.sentryInstance(mockDsn); + SentryClientFactory.sentryClient(mockDsn); } catch (IllegalStateException e) { exception = e; } @@ -122,39 +120,39 @@ public void testSentryInstantiationFailureCaught(@Injectable final Dsn mockDsn) } @Test - public void testAutoDetectDsnIfNotProvided(@Injectable final Sentry mockSentry, + public void testAutoDetectDsnIfNotProvided(@Injectable final SentryClient mockSentryClient, @SuppressWarnings("unused") @Mocked final Dsn mockDsn) throws Exception { final String dsn = "protocol://user:password@host:port/3"; - SentryFactory.registerFactory(sentryFactory); + SentryClientFactory.registerFactory(sentryClientFactory); new NonStrictExpectations() {{ Dsn.dsnLookup(); result = dsn; - sentryFactory.createSentryInstance((Dsn) any); - result = mockSentry; + sentryClientFactory.createSentryClient((Dsn) any); + result = mockSentryClient; }}; - Sentry sentry = SentryFactory.sentryInstance(); + SentryClient sentryClient = SentryClientFactory.sentryClient(); - assertThat(sentry, is(mockSentry)); + assertThat(sentryClient, is(mockSentryClient)); new Verifications() {{ new Dsn(dsn); }}; } @Test - public void testCreateDsnIfStringProvided(@Injectable final Sentry mockSentry, + public void testCreateDsnIfStringProvided(@Injectable final SentryClient mockSentryClient, @SuppressWarnings("unused") @Mocked final Dsn mockDsn) throws Exception { final String dsn = "protocol://user:password@host:port/2"; - SentryFactory.registerFactory(sentryFactory); + SentryClientFactory.registerFactory(sentryClientFactory); new NonStrictExpectations() {{ - sentryFactory.createSentryInstance((Dsn) any); - result = mockSentry; + sentryClientFactory.createSentryClient((Dsn) any); + result = mockSentryClient; }}; - Sentry sentry = SentryFactory.sentryInstance(dsn); + SentryClient sentryClient = SentryClientFactory.sentryClient(dsn); - assertThat(sentry, is(mockSentry)); + assertThat(sentryClient, is(mockSentryClient)); new Verifications() {{ new Dsn(dsn); }}; diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.java b/sentry/src/test/java/io/sentry/SentryClientTest.java new file mode 100644 index 00000000000..510a756fe64 --- /dev/null +++ b/sentry/src/test/java/io/sentry/SentryClientTest.java @@ -0,0 +1,159 @@ +package io.sentry; + +import io.sentry.context.ContextManager; +import io.sentry.context.SingletonContextManager; +import mockit.Injectable; +import mockit.NonStrictExpectations; +import mockit.Tested; +import mockit.Verifications; +import io.sentry.connection.Connection; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; +import io.sentry.event.helper.EventBuilderHelper; +import io.sentry.event.interfaces.ExceptionInterface; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.IOException; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class SentryClientTest { + @Tested + private SentryClient sentryClient = null; + @Injectable + private Connection mockConnection = null; + @Injectable + private ContextManager contextManager = new SingletonContextManager(); + @Injectable + private Event mockEvent = null; + @Injectable + private EventBuilderHelper mockEventBuilderHelper = null; + + @BeforeMethod + public void setup() { + contextManager.getContext().clear(); + } + + @Test + public void testSendEvent() throws Exception { + sentryClient.sendEvent(mockEvent); + + new Verifications() {{ + mockConnection.send(mockEvent); + }}; + } + + @Test + public void testSendEventBuilder() throws Exception { + final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; + sentryClient.addBuilderHelper(mockEventBuilderHelper); + + sentryClient.sendEvent(new EventBuilder() + .withMessage(message) + .withLevel(Event.Level.INFO)); + + new Verifications() {{ + Event event; + mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); + mockConnection.send(event = withCapture()); + assertThat(event.getLevel(), equalTo(Event.Level.INFO)); + assertThat(event.getMessage(), equalTo(message)); + }}; + } + + @Test + public void testSendEventFailingIsCaught() throws Exception { + new NonStrictExpectations() {{ + mockConnection.send((Event) any); + result = new RuntimeException(); + }}; + + sentryClient.sendEvent(mockEvent); + + new Verifications() {{ + mockConnection.send(mockEvent); + }}; + } + + @Test + public void testSendMessage() throws Exception { + final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; + sentryClient.addBuilderHelper(mockEventBuilderHelper); + + sentryClient.sendMessage(message); + + new Verifications() {{ + Event event; + mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); + mockConnection.send(event = withCapture()); + assertThat(event.getLevel(), equalTo(Event.Level.INFO)); + assertThat(event.getMessage(), equalTo(message)); + }}; + } + + @Test + public void testSendException() throws Exception { + final String message = "7b61ddb1-eb32-428d-bad9-a7d842605ba7"; + final Exception exception = new Exception(message); + sentryClient.addBuilderHelper(mockEventBuilderHelper); + + sentryClient.sendException(exception); + + new Verifications() {{ + Event event; + mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); + mockConnection.send(event = withCapture()); + assertThat(event.getLevel(), equalTo(Event.Level.ERROR)); + assertThat(event.getMessage(), equalTo(message)); + assertThat(event.getSentryInterfaces(), hasKey(ExceptionInterface.EXCEPTION_INTERFACE)); + }}; + } + + @Test + public void testAddRemoveBuilderHelpers(@Injectable final EventBuilderHelper mockBuilderHelper) throws Exception { + assertThat(sentryClient.getBuilderHelpers(), not(contains(mockBuilderHelper))); + + sentryClient.addBuilderHelper(mockBuilderHelper); + assertThat(sentryClient.getBuilderHelpers(), contains(mockBuilderHelper)); + sentryClient.removeBuilderHelper(mockBuilderHelper); + assertThat(sentryClient.getBuilderHelpers(), not(contains(mockBuilderHelper))); + } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testCantModifyBuilderHelpersDirectly(@Injectable final EventBuilderHelper mockBuilderHelper) throws Exception { + sentryClient.getBuilderHelpers().add(mockBuilderHelper); + } + + @Test + public void testRunBuilderHelpers(@Injectable final EventBuilderHelper mockBuilderHelper, + @Injectable final EventBuilder mockEventBuilder) throws Exception { + sentryClient.addBuilderHelper(mockBuilderHelper); + + sentryClient.runBuilderHelpers(mockEventBuilder); + + new Verifications() {{ + mockBuilderHelper.helpBuildingEvent(mockEventBuilder); + }}; + } + + @Test + public void testCloseConnectionSuccessful() throws Exception { + sentryClient.closeConnection(); + + new Verifications() {{ + mockConnection.close(); + }}; + } + + @Test(expectedExceptions = RuntimeException.class) + public void testCloseConnectionFailed() throws Exception { + new NonStrictExpectations() {{ + mockConnection.close(); + result = new IOException(); + }}; + + sentryClient.closeConnection(); + } +} diff --git a/sentry/src/test/java/io/sentry/SentryTest.java b/sentry/src/test/java/io/sentry/SentryTest.java index 330baf086cb..3b40c4a7922 100644 --- a/sentry/src/test/java/io/sentry/SentryTest.java +++ b/sentry/src/test/java/io/sentry/SentryTest.java @@ -1,34 +1,30 @@ package io.sentry; +import io.sentry.connection.Connection; import io.sentry.context.ContextManager; import io.sentry.context.SingletonContextManager; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Tested; -import mockit.Verifications; -import io.sentry.connection.Connection; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.helper.EventBuilderHelper; import io.sentry.event.interfaces.ExceptionInterface; +import mockit.Injectable; +import mockit.Tested; +import mockit.Verifications; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import java.io.IOException; - import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasKey; public class SentryTest { @Tested - private Sentry sentry = null; + private SentryClient sentryClient = null; @Injectable private Connection mockConnection = null; @Injectable private ContextManager contextManager = new SingletonContextManager(); @Injectable - private Event mockEvent = null; - @Injectable private EventBuilderHelper mockEventBuilderHelper = null; @BeforeMethod @@ -36,127 +32,6 @@ public void setup() { contextManager.getContext().clear(); } - @Test - public void testSendEvent() throws Exception { - sentry.sendEvent(mockEvent); - - new Verifications() {{ - mockConnection.send(mockEvent); - }}; - } - - @Test - public void testSendEventBuilder() throws Exception { - final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; - sentry.addBuilderHelper(mockEventBuilderHelper); - - sentry.sendEvent(new EventBuilder() - .withMessage(message) - .withLevel(Event.Level.INFO)); - - new Verifications() {{ - Event event; - mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); - mockConnection.send(event = withCapture()); - assertThat(event.getLevel(), equalTo(Event.Level.INFO)); - assertThat(event.getMessage(), equalTo(message)); - }}; - } - - @Test - public void testSendEventFailingIsCaught() throws Exception { - new NonStrictExpectations() {{ - mockConnection.send((Event) any); - result = new RuntimeException(); - }}; - - sentry.sendEvent(mockEvent); - - new Verifications() {{ - mockConnection.send(mockEvent); - }}; - } - - @Test - public void testSendMessage() throws Exception { - final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; - sentry.addBuilderHelper(mockEventBuilderHelper); - - sentry.sendMessage(message); - - new Verifications() {{ - Event event; - mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); - mockConnection.send(event = withCapture()); - assertThat(event.getLevel(), equalTo(Event.Level.INFO)); - assertThat(event.getMessage(), equalTo(message)); - }}; - } - - @Test - public void testSendException() throws Exception { - final String message = "7b61ddb1-eb32-428d-bad9-a7d842605ba7"; - final Exception exception = new Exception(message); - sentry.addBuilderHelper(mockEventBuilderHelper); - - sentry.sendException(exception); - - new Verifications() {{ - Event event; - mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); - mockConnection.send(event = withCapture()); - assertThat(event.getLevel(), equalTo(Event.Level.ERROR)); - assertThat(event.getMessage(), equalTo(message)); - assertThat(event.getSentryInterfaces(), hasKey(ExceptionInterface.EXCEPTION_INTERFACE)); - }}; - } - - @Test - public void testAddRemoveBuilderHelpers(@Injectable final EventBuilderHelper mockBuilderHelper) throws Exception { - assertThat(sentry.getBuilderHelpers(), not(contains(mockBuilderHelper))); - - sentry.addBuilderHelper(mockBuilderHelper); - assertThat(sentry.getBuilderHelpers(), contains(mockBuilderHelper)); - sentry.removeBuilderHelper(mockBuilderHelper); - assertThat(sentry.getBuilderHelpers(), not(contains(mockBuilderHelper))); - } - - @Test(expectedExceptions = UnsupportedOperationException.class) - public void testCantModifyBuilderHelpersDirectly(@Injectable final EventBuilderHelper mockBuilderHelper) throws Exception { - sentry.getBuilderHelpers().add(mockBuilderHelper); - } - - @Test - public void testRunBuilderHelpers(@Injectable final EventBuilderHelper mockBuilderHelper, - @Injectable final EventBuilder mockEventBuilder) throws Exception { - sentry.addBuilderHelper(mockBuilderHelper); - - sentry.runBuilderHelpers(mockEventBuilder); - - new Verifications() {{ - mockBuilderHelper.helpBuildingEvent(mockEventBuilder); - }}; - } - - @Test - public void testCloseConnectionSuccessful() throws Exception { - sentry.closeConnection(); - - new Verifications() {{ - mockConnection.close(); - }}; - } - - @Test(expectedExceptions = RuntimeException.class) - public void testCloseConnectionFailed() throws Exception { - new NonStrictExpectations() {{ - mockConnection.close(); - result = new IOException(); - }}; - - sentry.closeConnection(); - } - @Test public void testSendEventStatically() throws Exception { final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; @@ -176,7 +51,7 @@ public void testSendEventStatically() throws Exception { public void testSendEventBuilderStatically() throws Exception { final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; EventBuilder eventBuilder = new EventBuilder().withMessage(message).withLevel(Event.Level.ERROR); - sentry.addBuilderHelper(mockEventBuilderHelper); + sentryClient.addBuilderHelper(mockEventBuilderHelper); Sentry.capture(eventBuilder); @@ -192,7 +67,7 @@ public void testSendEventBuilderStatically() throws Exception { @Test public void testSendMessageStatically() throws Exception { final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; - sentry.addBuilderHelper(mockEventBuilderHelper); + sentryClient.addBuilderHelper(mockEventBuilderHelper); Sentry.capture(message); @@ -209,7 +84,7 @@ public void testSendMessageStatically() throws Exception { public void testSendExceptionStatically() throws Exception { final String message = "7b61ddb1-eb32-428d-bad9-a7d842605ba7"; final Exception exception = new Exception(message); - sentry.addBuilderHelper(mockEventBuilderHelper); + sentryClient.addBuilderHelper(mockEventBuilderHelper); Sentry.capture(exception); @@ -222,4 +97,5 @@ public void testSendExceptionStatically() throws Exception { assertThat(event.getSentryInterfaces(), hasKey(ExceptionInterface.EXCEPTION_INTERFACE)); }}; } + } diff --git a/sentry/src/test/java/io/sentry/connection/AsyncConnectionTest.java b/sentry/src/test/java/io/sentry/connection/AsyncConnectionTest.java index 64ba82518e0..fc2601b8e81 100644 --- a/sentry/src/test/java/io/sentry/connection/AsyncConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/AsyncConnectionTest.java @@ -1,7 +1,7 @@ package io.sentry.connection; +import io.sentry.SentryClient; import mockit.*; -import io.sentry.Sentry; import io.sentry.environment.SentryEnvironment; import io.sentry.event.Event; import org.testng.annotations.BeforeMethod; @@ -60,7 +60,7 @@ public void verifyShutdownHookNotAddedWhenNotGraceful() throws Exception { @Test public void verifyShutdownHookSetManagedBySentryAndCloseConnection( - @SuppressWarnings("unused") @Mocked({"startManagingThread", "stopManagingThread"}) Sentry mockSentry) + @SuppressWarnings("unused") @Mocked({"startManagingThread", "stopManagingThread"}) SentryClient mockSentryClient) throws Exception { // Ensure that the shutdown hooks for the unused @Tested instance are removed asyncConnection.close(); @@ -86,7 +86,7 @@ public void addShutdownHook(Thread hook) { @Test public void ensureFailingShutdownHookStopsBeingManaged( - @SuppressWarnings("unused") @Mocked({"startManagingThread", "stopManagingThread"}) Sentry mockSentry) + @SuppressWarnings("unused") @Mocked({"startManagingThread", "stopManagingThread"}) SentryClient mockSentryClient) throws Exception { // Ensure that the shutdown hooks for the unused @Tested instance are removed asyncConnection.close(); diff --git a/sentry/src/test/java/io/sentry/connection/EventSendFailureCallbackTest.java b/sentry/src/test/java/io/sentry/connection/EventSendFailureCallbackTest.java index 815629f043f..0a27a57508b 100644 --- a/sentry/src/test/java/io/sentry/connection/EventSendFailureCallbackTest.java +++ b/sentry/src/test/java/io/sentry/connection/EventSendFailureCallbackTest.java @@ -1,7 +1,7 @@ package io.sentry.connection; -import io.sentry.DefaultSentryFactory; -import io.sentry.Sentry; +import io.sentry.DefaultSentryClientFactory; +import io.sentry.SentryClient; import io.sentry.dsn.Dsn; import io.sentry.event.Event; import org.testng.annotations.Test; @@ -17,7 +17,7 @@ public class EventSendFailureCallbackTest { public void testSimpleCallback() { final AtomicBoolean flag = new AtomicBoolean(false); - DefaultSentryFactory factory = new DefaultSentryFactory() { + DefaultSentryClientFactory factory = new DefaultSentryClientFactory() { @Override protected Connection createConnection(Dsn dsn) { Connection connection = super.createConnection(dsn); @@ -34,15 +34,15 @@ public void onFailure(Event event, Exception exception) { }; String dsn = "https://foo:bar@localhost:1234/1?async=false"; - Sentry sentry = factory.createSentryInstance(new Dsn(dsn)); - sentry.sendMessage("Message that will fail because DSN points to garbage."); + SentryClient sentryClient = factory.createSentryClient(new Dsn(dsn)); + sentryClient.sendMessage("Message that will fail because DSN points to garbage."); assertThat(flag.get(), is(true)); } @Test public void testExceptionInsideCallback() { - DefaultSentryFactory factory = new DefaultSentryFactory() { + DefaultSentryClientFactory factory = new DefaultSentryClientFactory() { @Override protected Connection createConnection(Dsn dsn) { Connection connection = super.createConnection(dsn); @@ -59,8 +59,8 @@ public void onFailure(Event event, Exception exception) { }; String dsn = "https://foo:bar@localhost:1234/1?async=false"; - Sentry sentry = factory.createSentryInstance(new Dsn(dsn)); - sentry.sendMessage("Message that will fail because DSN points to garbage."); + SentryClient sentryClient = factory.createSentryClient(new Dsn(dsn)); + sentryClient.sendMessage("Message that will fail because DSN points to garbage."); } } diff --git a/sentry/src/test/java/io/sentry/event/BreadcrumbTest.java b/sentry/src/test/java/io/sentry/event/BreadcrumbTest.java index 04892fe7d14..89b21a346e1 100644 --- a/sentry/src/test/java/io/sentry/event/BreadcrumbTest.java +++ b/sentry/src/test/java/io/sentry/event/BreadcrumbTest.java @@ -1,6 +1,6 @@ package io.sentry.event; -import io.sentry.Sentry; +import io.sentry.SentryClient; import io.sentry.connection.Connection; import io.sentry.context.ContextManager; import io.sentry.context.SingletonContextManager; @@ -18,7 +18,7 @@ public class BreadcrumbTest { @Tested - private Sentry sentry = null; + private SentryClient sentryClient = null; @Injectable private Connection mockConnection = null; @@ -32,7 +32,7 @@ public void setup() { @Test public void testBreadcrumbsViaContextRecording() { - sentry.addBuilderHelper(new ContextBuilderHelper(sentry)); + sentryClient.addBuilderHelper(new ContextBuilderHelper(sentryClient)); final Breadcrumb breadcrumb = new BreadcrumbBuilder() .setLevel(Breadcrumb.Level.INFO) @@ -40,9 +40,9 @@ public void testBreadcrumbsViaContextRecording() { .setCategory("step") .build(); - sentry.getContext().recordBreadcrumb(breadcrumb); + sentryClient.getContext().recordBreadcrumb(breadcrumb); - sentry.sendEvent(new EventBuilder() + sentryClient.sendEvent(new EventBuilder() .withMessage("Some random message") .withLevel(Event.Level.INFO)); @@ -55,7 +55,7 @@ public void testBreadcrumbsViaContextRecording() { @Test public void testBreadcrumbsViaEventBuilder() { - sentry.addBuilderHelper(new ContextBuilderHelper(sentry)); + sentryClient.addBuilderHelper(new ContextBuilderHelper(sentryClient)); final Breadcrumb breadcrumb = new BreadcrumbBuilder() .setLevel(Breadcrumb.Level.INFO) @@ -66,7 +66,7 @@ public void testBreadcrumbsViaEventBuilder() { ArrayList breadcrumbs = new ArrayList<>(); breadcrumbs.add(breadcrumb); - sentry.sendEvent(new EventBuilder() + sentryClient.sendEvent(new EventBuilder() .withBreadcrumbs(breadcrumbs) .withMessage("Some random message") .withLevel(Event.Level.INFO)); @@ -80,7 +80,7 @@ public void testBreadcrumbsViaEventBuilder() { @Test public void testBreadcrumbsViaEvent() { - sentry.addBuilderHelper(new ContextBuilderHelper(sentry)); + sentryClient.addBuilderHelper(new ContextBuilderHelper(sentryClient)); final Breadcrumb breadcrumb = new BreadcrumbBuilder() .setLevel(Breadcrumb.Level.INFO) @@ -91,7 +91,7 @@ public void testBreadcrumbsViaEvent() { ArrayList breadcrumbs = new ArrayList<>(); breadcrumbs.add(breadcrumb); - sentry.sendEvent(new EventBuilder() + sentryClient.sendEvent(new EventBuilder() .withBreadcrumbs(breadcrumbs) .withMessage("Some random message") .withLevel(Event.Level.INFO) diff --git a/sentry/src/test/java/io/sentry/event/UserTest.java b/sentry/src/test/java/io/sentry/event/UserTest.java index c3a2521d66f..f0ab5afd134 100644 --- a/sentry/src/test/java/io/sentry/event/UserTest.java +++ b/sentry/src/test/java/io/sentry/event/UserTest.java @@ -1,6 +1,6 @@ package io.sentry.event; -import io.sentry.Sentry; +import io.sentry.SentryClient; import io.sentry.connection.Connection; import io.sentry.context.ContextManager; import io.sentry.context.SingletonContextManager; @@ -17,7 +17,7 @@ public class UserTest { @Tested - private Sentry sentry = null; + private SentryClient sentryClient = null; @Injectable private Connection mockConnection = null; @Injectable @@ -30,16 +30,16 @@ public void setup() { @Test public void testUserPropagation() { - sentry.addBuilderHelper(new ContextBuilderHelper(sentry)); + sentryClient.addBuilderHelper(new ContextBuilderHelper(sentryClient)); final User user = new UserBuilder() .setEmail("test@example.com") .setId("1234") .setIpAddress("192.168.0.1") .setUsername("testUser_123").build(); - sentry.getContext().setUser(user); + sentryClient.getContext().setUser(user); - sentry.sendEvent(new EventBuilder() + sentryClient.sendEvent(new EventBuilder() .withMessage("Some random message") .withLevel(Event.Level.INFO)); diff --git a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java index 546c85b2d58..717748f93c5 100644 --- a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java +++ b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java @@ -1,11 +1,11 @@ package io.sentry.jul; +import io.sentry.SentryClient; import io.sentry.environment.SentryEnvironment; import io.sentry.event.interfaces.*; import mockit.Injectable; import mockit.Tested; import mockit.Verifications; -import io.sentry.Sentry; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import org.hamcrest.Matchers; @@ -27,7 +27,7 @@ public class SentryHandlerEventBuildingTest { @Injectable private ErrorManager errorManager = null; @Injectable - private Sentry mockSentry = null; + private SentryClient mockSentryClient = null; private void assertNoErrorsInErrorManager() throws Exception { new Verifications() {{ @@ -48,8 +48,8 @@ public void testSimpleMessageLogging() throws Exception { new Verifications() {{ Event event; - mockSentry.runBuilderHelpers((EventBuilder) any); - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.runBuilderHelpers((EventBuilder) any); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getMessage(), is(message)); Map sentryInterfaces = event.getSentryInterfaces(); assertThat(sentryInterfaces, hasKey(MessageInterface.MESSAGE_INTERFACE)); @@ -81,7 +81,7 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getLevel(), is(expectedLevel)); }}; assertNoErrorsInErrorManager(); @@ -95,7 +95,7 @@ public void testExceptionLogging() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); final SentryException sentryException = exceptionInterface.getExceptions().getFirst(); @@ -117,7 +117,7 @@ public void testCulpritWithSource() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is("a.b")); }}; assertNoErrorsInErrorManager(); @@ -131,7 +131,7 @@ public void testCulpritWithoutSource() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getCulprit(), is(loggerName)); }}; assertNoErrorsInErrorManager(); @@ -167,7 +167,7 @@ public void testReleaseAddedToEvent() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getRelease(), is(release)); }}; assertNoErrorsInErrorManager(); @@ -182,7 +182,7 @@ public void testEnvironmentAddedToEvent() throws Exception { new Verifications() {{ Event event; - mockSentry.sendEvent(event = withCapture()); + mockSentryClient.sendEvent(event = withCapture()); assertThat(event.getEnvironment(), is(environment)); }}; assertNoErrorsInErrorManager(); From 41d72f05e1396ca3b673971412a8f0e42b73b669 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 27 Apr 2017 08:24:33 -0500 Subject: [PATCH 1580/2152] Drop base64 encoding. (#382) --- CHANGES | 1 + docs/config.rst | 3 +- pom.xml | 3 - .../io/sentry/connection/HttpConnection.java | 9 + .../java/io/sentry/marshaller/Marshaller.java | 21 +- .../marshaller/json/JsonMarshaller.java | 30 +- .../src/main/java/io/sentry/util/Base64.java | 691 ------------------ .../io/sentry/util/Base64OutputStream.java | 142 ---- sentry/src/test/java/io/sentry/BaseIT.java | 3 + .../marshaller/json/JsonMarshallerTest.java | 25 +- .../java/io/sentry/unmarshaller/Base64.java | 685 ----------------- .../unmarshaller/Base64InputStream.java | 140 ---- .../io/sentry/unmarshaller/JsonDecoder.java | 24 +- .../jsonmarshallertest/testCompression.json | 20 + src/checkstyle/suppressions.xml | 1 - 15 files changed, 102 insertions(+), 1696 deletions(-) delete mode 100644 sentry/src/main/java/io/sentry/util/Base64.java delete mode 100644 sentry/src/main/java/io/sentry/util/Base64OutputStream.java delete mode 100644 sentry/src/test/java/io/sentry/unmarshaller/Base64.java delete mode 100644 sentry/src/test/java/io/sentry/unmarshaller/Base64InputStream.java create mode 100644 sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCompression.json diff --git a/CHANGES b/CHANGES index 2242d06ff1e..0805cafd287 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,7 @@ Version 1.0.0 - Rename ``sentryFactory`` option to ``factory`` so that the environment variable configuration is now ``SENTRY_FACTORY`` and the Java System Property is ``sentry.factory``. - Add ``dist`` field to Event. +- Compressed JSON payloads are no longer base64 encoded. Version 8.0.2 ------------- diff --git a/docs/config.rst b/docs/config.rst index d9797c7424a..55ba0d7df06 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -312,8 +312,7 @@ To enable a similar behaviour in Sentry use the ``sentry.stacktrace.hidecommon`` Compression ~~~~~~~~~~~ -By default the content sent to Sentry is compressed and encoded in base64 before -being sent. +By default the content sent to Sentry is compressed before being sent. However, compressing and encoding the data adds a small CPU and memory hit which might not be useful if the connection to Sentry is fast and reliable. diff --git a/pom.xml b/pom.xml index a5181e5a5a1..85dae501014 100644 --- a/pom.xml +++ b/pom.xml @@ -375,9 +375,6 @@ org.apache.maven.plugins maven-javadoc-plugin 2.10.4 - - io.sentry.util.base64* - org.apache.maven.plugins diff --git a/sentry/src/main/java/io/sentry/connection/HttpConnection.java b/sentry/src/main/java/io/sentry/connection/HttpConnection.java index 8b19e9218ec..83540b855de 100644 --- a/sentry/src/main/java/io/sentry/connection/HttpConnection.java +++ b/sentry/src/main/java/io/sentry/connection/HttpConnection.java @@ -127,6 +127,15 @@ protected HttpURLConnection getConnection() { connection.setConnectTimeout(timeout); connection.setRequestProperty(USER_AGENT, SentryEnvironment.getSentryName()); connection.setRequestProperty(SENTRY_AUTH, getAuthHeader()); + + if (marshaller.getContentType() != null) { + connection.setRequestProperty("Content-Type", marshaller.getContentType()); + } + + if (marshaller.getContentEncoding() != null) { + connection.setRequestProperty("Content-Encoding", marshaller.getContentEncoding()); + } + return connection; } catch (IOException e) { throw new IllegalStateException("Couldn't set up a connection to the Sentry server.", e); diff --git a/sentry/src/main/java/io/sentry/marshaller/Marshaller.java b/sentry/src/main/java/io/sentry/marshaller/Marshaller.java index 4a5448d1fa9..1592ee86fe5 100644 --- a/sentry/src/main/java/io/sentry/marshaller/Marshaller.java +++ b/sentry/src/main/java/io/sentry/marshaller/Marshaller.java @@ -15,10 +15,25 @@ public interface Marshaller { * The marshaller should not close the given stream, use {@link UncloseableOutputStream} to prevent automatic calls * to {@link OutputStream#close()}. * - * @param event event to serialise. - * @param destination destination stream. + * @param event event to serialise. + * @param destination destination stream. + * @throws IOException on write error */ - void marshall(Event event, OutputStream destination); + void marshall(Event event, OutputStream destination) throws IOException; + + /** + * Returns the HTTP Content-Type, if applicable, or null. + * + * @return the HTTP Content-Type, if applicable, or null. + */ + String getContentType(); + + /** + * Returns the HTTP Content-Encoding, if applicable, or null. + * + * @return the HTTP Content-Encoding, if applicable, or null. + */ + String getContentEncoding(); /** * OutputStream delegating every call except for {@link #close()} to an other OutputStream. diff --git a/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java b/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java index 7778bfc7428..5dccc1b9076 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java @@ -3,8 +3,6 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import io.sentry.event.Breadcrumb; -import io.sentry.util.Base64; -import io.sentry.util.Base64OutputStream; import io.sentry.event.Event; import io.sentry.event.interfaces.SentryInterface; import io.sentry.marshaller.Marshaller; @@ -17,13 +15,12 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; -import java.util.zip.DeflaterOutputStream; +import java.util.zip.GZIPOutputStream; /** * Event marshaller using JSON to send the data. *

    - * The content can also be compressed with {@link DeflaterOutputStream} in which case the binary result is encoded - * in base 64. + * The content can also be compressed with {@link GZIPOutputStream}. */ public class JsonMarshaller implements Marshaller { /** @@ -147,12 +144,12 @@ public JsonMarshaller(int maxMessageLength) { } @Override - public void marshall(Event event, OutputStream destination) { + public void marshall(Event event, OutputStream destination) throws IOException { // Prevent the stream from being closed automatically destination = new UncloseableOutputStream(destination); if (compression) { - destination = new DeflaterOutputStream(new Base64OutputStream(destination, Base64.NO_WRAP)); + destination = new GZIPOutputStream(destination); } try (JsonGenerator generator = jsonFactory.createGenerator(destination)) { @@ -168,6 +165,19 @@ public void marshall(Event event, OutputStream destination) { } } + @Override + public String getContentType() { + return "application/json"; + } + + @Override + public String getContentEncoding() { + if (isCompressed()) { + return "gzip"; + } + return null; + } + private void writeContent(JsonGenerator generator, Event event) throws IOException { generator.writeStartObject(); @@ -392,11 +402,15 @@ public void addInterfaceBinding(Class2045 and 3548. - */ -public class Base64 { - /** - * Default values for encoder/decoder flags. - */ - public static final int DEFAULT = 0; - /** - * Encoder flag bit to omit the padding '=' characters at the end - * of the output (if any). - */ - public static final int NO_PADDING = 1; - /** - * Encoder flag bit to omit all line terminators (i.e., the output - * will be on one long line). - */ - public static final int NO_WRAP = 2; - /** - * Encoder flag bit to indicate lines should be terminated with a - * CRLF pair instead of just an LF. Has no effect if {@code - * NO_WRAP} is specified as well. - */ - public static final int CRLF = 4; - /** - * Encoder/decoder flag bit to indicate using the "URL and - * filename safe" variant of Base64 (see RFC 3548 section 4) where - * {@code -} and {@code _} are used in place of {@code +} and - * {@code /}. - */ - public static final int URL_SAFE = 8; - /** - * Flag to pass to {@link Base64OutputStream} to indicate that it - * should not close the output stream it is wrapping when it - * itself is closed. - */ - public static final int NO_CLOSE = 16; - // -------------------------------------------------------- - // shared code - // -------------------------------------------------------- - /* package */ static abstract class Coder { - public byte[] output; - public int op; - /** - * Encode/decode another block of input data. this.output is - * provided by the caller, and must be big enough to hold all - * the coded data. On exit, this.opwill be set to the length - * of the coded data. - * - * @param finish true if this is the final call to process for - * this object. Will finalize the coder state and - * include any final bytes in the output. - * - * @return true if the input so far is good; false if some - * error has been detected in the input stream.. - */ - public abstract boolean process(byte[] input, int offset, int len, boolean finish); - /** - * @return the maximum number of bytes a call to process() - * could produce for the given number of input bytes. This may - * be an overestimate. - */ - public abstract int maxOutputSize(int len); - } - // -------------------------------------------------------- - // decoding - // -------------------------------------------------------- - /** - * Decode the Base64-encoded data in input and return the data in - * a new byte array. - * - *

    The padding '=' characters at the end are considered optional, but - * if any are present, there must be the correct number of them. - * - * @param str the input String to decode, which is converted to - * bytes using the default charset - * @param flags controls certain features of the decoded output. - * Pass {@code DEFAULT} to decode standard Base64. - * - * @throws IllegalArgumentException if the input contains - * incorrect padding - * - * @return byte[] - */ - public static byte[] decode(String str, int flags) { - return decode(str.getBytes(), flags); - } - /** - * Decode the Base64-encoded data in input and return the data in - * a new byte array. - * - *

    The padding '=' characters at the end are considered optional, but - * if any are present, there must be the correct number of them. - * - * @param input the input array to decode - * @param flags controls certain features of the decoded output. - * Pass {@code DEFAULT} to decode standard Base64. - * - * @throws IllegalArgumentException if the input contains - * incorrect padding - * - * @return byte[] - */ - public static byte[] decode(byte[] input, int flags) { - return decode(input, 0, input.length, flags); - } - /** - * Decode the Base64-encoded data in input and return the data in - * a new byte array. - * - *

    The padding '=' characters at the end are considered optional, but - * if any are present, there must be the correct number of them. - * - * @param input the data to decode - * @param offset the position within the input array at which to start - * @param len the number of bytes of input to decode - * @param flags controls certain features of the decoded output. - * Pass {@code DEFAULT} to decode standard Base64. - * - * @throws IllegalArgumentException if the input contains - * incorrect padding - * - * @return byte[] - */ - public static byte[] decode(byte[] input, int offset, int len, int flags) { - // Allocate space for the most data the input could represent. - // (It could contain less if it contains whitespace, etc.) - Decoder decoder = new Decoder(flags, new byte[len*3/4]); - if (!decoder.process(input, offset, len, true)) { - throw new IllegalArgumentException("bad base-64"); - } - // Maybe we got lucky and allocated exactly enough output space. - if (decoder.op == decoder.output.length) { - return decoder.output; - } - // Need to shorten the array, so allocate a new one of the - // right size and copy. - byte[] temp = new byte[decoder.op]; - System.arraycopy(decoder.output, 0, temp, 0, decoder.op); - return temp; - } - /* package */ static class Decoder extends Coder { - /** - * Lookup table for turning bytes into their position in the - * Base64 alphabet. - */ - private static final int DECODE[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - }; - /** - * Decode lookup table for the "web safe" variant (RFC 3548 - * sec. 4) where - and _ replace + and /. - */ - private static final int DECODE_WEBSAFE[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - }; - /** Non-data values in the DECODE arrays. */ - private static final int SKIP = -1; - private static final int EQUALS = -2; - /** - * States 0-3 are reading through the next input tuple. - * State 4 is having read one '=' and expecting exactly - * one more. - * State 5 is expecting no more data or padding characters - * in the input. - * State 6 is the error state; an error has been detected - * in the input and no future input can "fix" it. - */ - private int state; // state number (0 to 6) - private int value; - final private int[] alphabet; - public Decoder(int flags, byte[] output) { - this.output = output; - alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE; - state = 0; - value = 0; - } - /** - * @return an overestimate for the number of bytes {@code - * len} bytes could decode to. - */ - public int maxOutputSize(int len) { - return len * 3/4 + 10; - } - /** - * Decode another block of input data. - * - * @return true if the state machine is still healthy. false if - * bad base-64 data has been detected in the input stream. - */ - public boolean process(byte[] input, int offset, int len, boolean finish) { - if (this.state == 6) { - return false; - } - int p = offset; - len += offset; - // Using local variables makes the decoder about 12% - // faster than if we manipulate the member variables in - // the loop. (Even alphabet makes a measurable - // difference, which is somewhat surprising to me since - // the member variable is final.) - int state = this.state; - int value = this.value; - int op = 0; - final byte[] output = this.output; - final int[] alphabet = this.alphabet; - while (p < len) { - // Try the fast path: we're starting a new tuple and the - // next four bytes of the input stream are all data - // bytes. This corresponds to going through states - // 0-1-2-3-0. We expect to use this method for most of - // the data. - // - // If any of the next four bytes of input are non-data - // (whitespace, etc.), value will end up negative. (All - // the non-data values in decode are small negative - // numbers, so shifting any of them up and or'ing them - // together will result in a value with its top bit set.) - // - // You can remove this whole block and the output should - // be the same, just slower. - if (state == 0) { - while (p+4 <= len && - (value = ((alphabet[input[p] & 0xff] << 18) | - (alphabet[input[p+1] & 0xff] << 12) | - (alphabet[input[p+2] & 0xff] << 6) | - (alphabet[input[p+3] & 0xff]))) >= 0) { - output[op+2] = (byte) value; - output[op+1] = (byte) (value >> 8); - output[op] = (byte) (value >> 16); - op += 3; - p += 4; - } - if (p >= len) { - break; - } - } - // The fast path isn't available -- either we've read a - // partial tuple, or the next four input bytes aren't all - // data, or whatever. Fall back to the slower state - // machine implementation. - int d = alphabet[input[p++] & 0xff]; - switch (state) { - case 0: - if (d >= 0) { - value = d; - ++state; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - case 1: - if (d >= 0) { - value = (value << 6) | d; - ++state; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - case 2: - if (d >= 0) { - value = (value << 6) | d; - ++state; - } else if (d == EQUALS) { - // Emit the last (partial) output tuple; - // expect exactly one more padding character. - output[op++] = (byte) (value >> 4); - state = 4; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - case 3: - if (d >= 0) { - // Emit the output triple and return to state 0. - value = (value << 6) | d; - output[op+2] = (byte) value; - output[op+1] = (byte) (value >> 8); - output[op] = (byte) (value >> 16); - op += 3; - state = 0; - } else if (d == EQUALS) { - // Emit the last (partial) output tuple; - // expect no further data or padding characters. - output[op+1] = (byte) (value >> 2); - output[op] = (byte) (value >> 10); - op += 2; - state = 5; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - case 4: - if (d == EQUALS) { - ++state; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - case 5: - if (d != SKIP) { - this.state = 6; - return false; - } - break; - } - } - if (!finish) { - // We're out of input, but a future call could provide - // more. - this.state = state; - this.value = value; - this.op = op; - return true; - } - // Done reading input. Now figure out where we are left in - // the state machine and finish up. - switch (state) { - case 0: - // Output length is a multiple of three. Fine. - break; - case 1: - // Read one extra input byte, which isn't enough to - // make another output byte. Illegal. - this.state = 6; - return false; - case 2: - // Read two extra input bytes, enough to emit 1 more - // output byte. Fine. - output[op++] = (byte) (value >> 4); - break; - case 3: - // Read three extra input bytes, enough to emit 2 more - // output bytes. Fine. - output[op++] = (byte) (value >> 10); - output[op++] = (byte) (value >> 2); - break; - case 4: - // Read one padding '=' when we expected 2. Illegal. - this.state = 6; - return false; - case 5: - // Read all the padding '='s we expected and no more. - // Fine. - break; - } - this.state = state; - this.op = op; - return true; - } - } - // -------------------------------------------------------- - // encoding - // -------------------------------------------------------- - /** - * Base64-encode the given data and return a newly allocated - * String with the result. - * - * @param input the data to encode - * @param flags controls certain features of the encoded output. - * Passing {@code DEFAULT} results in output that - * adheres to RFC 2045. - * - * @return String - */ - public static String encodeToString(byte[] input, int flags) { - try { - return new String(encode(input, flags), "US-ASCII"); - } catch (UnsupportedEncodingException e) { - // US-ASCII is guaranteed to be available. - throw new AssertionError(e); - } - } - /** - * Base64-encode the given data and return a newly allocated - * String with the result. - * - * @param input the data to encode - * @param offset the position within the input array at which to - * start - * @param len the number of bytes of input to encode - * @param flags controls certain features of the encoded output. - * Passing {@code DEFAULT} results in output that - * adheres to RFC 2045. - * - * @return String - */ - public static String encodeToString(byte[] input, int offset, int len, int flags) { - try { - return new String(encode(input, offset, len, flags), "US-ASCII"); - } catch (UnsupportedEncodingException e) { - // US-ASCII is guaranteed to be available. - throw new AssertionError(e); - } - } - /** - * Base64-encode the given data and return a newly allocated - * byte[] with the result. - * - * @param input the data to encode - * @param flags controls certain features of the encoded output. - * Passing {@code DEFAULT} results in output that - * adheres to RFC 2045. - * - * @return byte[] - */ - public static byte[] encode(byte[] input, int flags) { - return encode(input, 0, input.length, flags); - } - /** - * Base64-encode the given data and return a newly allocated - * byte[] with the result. - * - * @param input the data to encode - * @param offset the position within the input array at which to - * start - * @param len the number of bytes of input to encode - * @param flags controls certain features of the encoded output. - * Passing {@code DEFAULT} results in output that - * adheres to RFC 2045. - * - * @return byte[] - */ - public static byte[] encode(byte[] input, int offset, int len, int flags) { - Encoder encoder = new Encoder(flags, null); - // Compute the exact length of the array we will produce. - int output_len = len / 3 * 4; - // Account for the tail of the data and the padding bytes, if any. - if (encoder.do_padding) { - if (len % 3 > 0) { - output_len += 4; - } - } else { - switch (len % 3) { - case 0: break; - case 1: output_len += 2; break; - case 2: output_len += 3; break; - } - } - // Account for the newlines, if any. - if (encoder.do_newline && len > 0) { - output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) * - (encoder.do_cr ? 2 : 1); - } - encoder.output = new byte[output_len]; - encoder.process(input, offset, len, true); - assert encoder.op == output_len; - return encoder.output; - } - /* package */ static class Encoder extends Coder { - /** - * Emit a new line every this many output tuples. Corresponds to - * a 76-character line length (the maximum allowable according to - * RFC 2045). - */ - public static final int LINE_GROUPS = 19; - /** - * Lookup table for turning Base64 alphabet positions (6 bits) - * into output bytes. - */ - private static final byte ENCODE[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', - }; - /** - * Lookup table for turning Base64 alphabet positions (6 bits) - * into output bytes. - */ - private static final byte ENCODE_WEBSAFE[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', - }; - final private byte[] tail; - /* package */ int tailLen; - private int count; - final public boolean do_padding; - final public boolean do_newline; - final public boolean do_cr; - final private byte[] alphabet; - public Encoder(int flags, byte[] output) { - this.output = output; - do_padding = (flags & NO_PADDING) == 0; - do_newline = (flags & NO_WRAP) == 0; - do_cr = (flags & CRLF) != 0; - alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE; - tail = new byte[2]; - tailLen = 0; - count = do_newline ? LINE_GROUPS : -1; - } - /** - * @return an overestimate for the number of bytes {@code - * len} bytes could encode to. - */ - public int maxOutputSize(int len) { - return len * 8/5 + 10; - } - public boolean process(byte[] input, int offset, int len, boolean finish) { - // Using local variables makes the encoder about 9% faster. - final byte[] alphabet = this.alphabet; - final byte[] output = this.output; - int op = 0; - int count = this.count; - int p = offset; - len += offset; - int v = -1; - // First we need to concatenate the tail of the previous call - // with any input bytes available now and see if we can empty - // the tail. - switch (tailLen) { - case 0: - // There was no tail. - break; - case 1: - if (p+2 <= len) { - // A 1-byte tail with at least 2 bytes of - // input available now. - v = ((tail[0] & 0xff) << 16) | - ((input[p++] & 0xff) << 8) | - (input[p++] & 0xff); - tailLen = 0; - }; - break; - case 2: - if (p+1 <= len) { - // A 2-byte tail with at least 1 byte of input. - v = ((tail[0] & 0xff) << 16) | - ((tail[1] & 0xff) << 8) | - (input[p++] & 0xff); - tailLen = 0; - } - break; - } - if (v != -1) { - output[op++] = alphabet[(v >> 18) & 0x3f]; - output[op++] = alphabet[(v >> 12) & 0x3f]; - output[op++] = alphabet[(v >> 6) & 0x3f]; - output[op++] = alphabet[v & 0x3f]; - if (--count == 0) { - if (do_cr) { - output[op++] = '\r'; - } - output[op++] = '\n'; - count = LINE_GROUPS; - } - } - // At this point either there is no tail, or there are fewer - // than 3 bytes of input available. - // The main loop, turning 3 input bytes into 4 output bytes on - // each iteration. - while (p+3 <= len) { - v = ((input[p] & 0xff) << 16) | - ((input[p+1] & 0xff) << 8) | - (input[p+2] & 0xff); - output[op] = alphabet[(v >> 18) & 0x3f]; - output[op+1] = alphabet[(v >> 12) & 0x3f]; - output[op+2] = alphabet[(v >> 6) & 0x3f]; - output[op+3] = alphabet[v & 0x3f]; - p += 3; - op += 4; - if (--count == 0) { - if (do_cr) { - output[op++] = '\r'; - } - output[op++] = '\n'; - count = LINE_GROUPS; - } - } - if (finish) { - // Finish up the tail of the input. Note that we need to - // consume any bytes in tail before any bytes - // remaining in input; there should be at most two bytes - // total. - if (p-tailLen == len-1) { - int t = 0; - v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4; - tailLen -= t; - output[op++] = alphabet[(v >> 6) & 0x3f]; - output[op++] = alphabet[v & 0x3f]; - if (do_padding) { - output[op++] = '='; - output[op++] = '='; - } - if (do_newline) { - if (do_cr) { - output[op++] = '\r'; - } - output[op++] = '\n'; - } - } else if (p-tailLen == len-2) { - int t = 0; - v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) | - (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2); - tailLen -= t; - output[op++] = alphabet[(v >> 12) & 0x3f]; - output[op++] = alphabet[(v >> 6) & 0x3f]; - output[op++] = alphabet[v & 0x3f]; - if (do_padding) { - output[op++] = '='; - } - if (do_newline) { - if (do_cr) { - output[op++] = '\r'; - } - output[op++] = '\n'; - } - } else if (do_newline && op > 0 && count != LINE_GROUPS) { - if (do_cr) { - output[op++] = '\r'; - } - output[op++] = '\n'; - } - assert tailLen == 0; - assert p == len; - } else { - // Save the leftovers in tail to be consumed on the next - // call to encodeInternal. - if (p == len-1) { - tail[tailLen++] = input[p]; - } else if (p == len-2) { - tail[tailLen++] = input[p]; - tail[tailLen++] = input[p+1]; - } - } - this.op = op; - this.count = count; - return true; - } - } - private Base64() { } // don't instantiate -} diff --git a/sentry/src/main/java/io/sentry/util/Base64OutputStream.java b/sentry/src/main/java/io/sentry/util/Base64OutputStream.java deleted file mode 100644 index 95b34723614..00000000000 --- a/sentry/src/main/java/io/sentry/util/Base64OutputStream.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.sentry.util; - -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** - * An OutputStream that does Base64 encoding on the data written to - * it, writing the resulting data to another OutputStream. - */ -public class Base64OutputStream extends FilterOutputStream { - private final Base64.Coder coder; - private final int flags; - private byte[] buffer = null; - private int bpos = 0; - private static byte[] EMPTY = new byte[0]; - /** - * Performs Base64 encoding on the data written to the stream, - * writing the encoded data to another OutputStream. - * - * @param out the OutputStream to write the encoded data to - * @param flags bit flags for controlling the encoder; see the - * constants in {@link Base64} - */ - public Base64OutputStream(OutputStream out, int flags) { - this(out, flags, true); - } - /** - * Performs Base64 encoding or decoding on the data written to the - * stream, writing the encoded/decoded data to another - * OutputStream. - * - * @param out the OutputStream to write the encoded data to - * @param flags bit flags for controlling the encoder; see the - * constants in {@link Base64} - * @param encode true to encode, false to decode - */ - public Base64OutputStream(OutputStream out, int flags, boolean encode) { - super(out); - this.flags = flags; - if (encode) { - coder = new Base64.Encoder(flags, null); - } else { - coder = new Base64.Decoder(flags, null); - } - } - public void write(int b) throws IOException { - // To avoid invoking the encoder/decoder routines for single - // bytes, we buffer up calls to write(int) in an internal - // byte array to transform them into writes of decently-sized - // arrays. - if (buffer == null) { - buffer = new byte[1024]; - } - if (bpos >= buffer.length) { - // internal buffer full; write it out. - internalWrite(buffer, 0, bpos, false); - bpos = 0; - } - buffer[bpos++] = (byte) b; - } - /** - * Flush any buffered data from calls to write(int). Needed - * before doing a write(byte[], int, int) or a close(). - */ - private void flushBuffer() throws IOException { - if (bpos > 0) { - internalWrite(buffer, 0, bpos, false); - bpos = 0; - } - } - public void write(byte[] b, int off, int len) throws IOException { - if (len <= 0) { - return; - } - flushBuffer(); - internalWrite(b, off, len, false); - } - public void close() throws IOException { - IOException thrown = null; - try { - flushBuffer(); - internalWrite(EMPTY, 0, 0, true); - } catch (IOException e) { - thrown = e; - } - try { - if ((flags & Base64.NO_CLOSE) == 0) { - out.close(); - } else { - out.flush(); - } - } catch (IOException e) { - if (thrown != null) { - thrown = e; - } - } - if (thrown != null) { - throw thrown; - } - } - /** - * Write the given bytes to the encoder/decoder. - * - * @param finish true if this is the last batch of input, to cause - * encoder/decoder state to be finalized. - */ - private void internalWrite(byte[] b, int off, int len, boolean finish) throws IOException { - coder.output = embiggen(coder.output, coder.maxOutputSize(len)); - if (!coder.process(b, off, len, finish)) { - throw new IOException("bad base-64"); - } - out.write(coder.output, 0, coder.op); - } - /** - * If b.length is at least len, return b. Otherwise return a new - * byte array of length len. - */ - private byte[] embiggen(byte[] b, int len) { - if (b == null || b.length < len) { - return new byte[len]; - } else { - return b; - } - } -} diff --git a/sentry/src/test/java/io/sentry/BaseIT.java b/sentry/src/test/java/io/sentry/BaseIT.java index 2452cd223b0..e25dd0b4e0f 100644 --- a/sentry/src/test/java/io/sentry/BaseIT.java +++ b/sentry/src/test/java/io/sentry/BaseIT.java @@ -1,5 +1,6 @@ package io.sentry; +import com.github.tomakehurst.wiremock.matching.EqualToPattern; import io.sentry.connection.HttpConnection; import io.sentry.unmarshaller.JsonUnmarshaller; import io.sentry.unmarshaller.event.UnmarshalledEvent; @@ -35,6 +36,8 @@ public void stub200ForProject1Store() { wireMockRule.stubFor( post(urlEqualTo(PROJECT1_STORE_URL)) .withHeader(AUTH_HEADER, AUTH_HEADER_PATTERN) + .withHeader("Content-Type", new EqualToPattern("application/json")) + .withHeader("Content-Encoding", new EqualToPattern("gzip")) .willReturn(aResponse().withStatus(200))); } diff --git a/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java b/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java index 781a019bae1..6e38a194743 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java @@ -1,6 +1,8 @@ package io.sentry.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import io.sentry.event.Breadcrumb; import io.sentry.event.BreadcrumbBuilder; import mockit.*; @@ -10,9 +12,14 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.*; +import java.util.zip.DataFormatException; +import java.util.zip.GZIPInputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; import static io.sentry.marshaller.json.JsonComparisonUtil.*; import static org.hamcrest.MatcherAssert.assertThat; @@ -400,11 +407,17 @@ public void testCompressedDataIsWorking() throws Exception { jsonMarshaller.marshall(mockEvent, outputStream); - assertThat(new String(outputStream.toByteArray(), "UTF-8"), is("" - + "eJyFj8EKwzAIht/FcwfpaSzPsXsJrctCTVI0LYPSd5+sS9ht4iGf+f3VHXDDVIYw" - + "gQXzJ6CDiCLOI9i0EnVQghaKi4t297eruZhe826M/aQ2kPpTlVP2HrnSQq48MsfK" - + "40oLh1JRphnsDsnFNm5DlpDTiYeOd15Uoy9B1s/hV8xI6KThFKRZY9oC5xT18lZ6" - + "FXan1/jEcZb1u9fxBs7LXes=" - )); + JsonNode json = deserialize(decompress(outputStream.toByteArray())); + assertThat(json, is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testCompression.json"))); + } + + private String decompress(byte[] compressedData) throws Exception { + GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData)); + Scanner s = new Scanner(gzipInputStream).useDelimiter("\\A"); + return s.hasNext() ? s.next() : ""; + } + + private JsonNode deserialize(String jsonString) throws Exception { + return new ObjectMapper().readTree(jsonString); } } diff --git a/sentry/src/test/java/io/sentry/unmarshaller/Base64.java b/sentry/src/test/java/io/sentry/unmarshaller/Base64.java deleted file mode 100644 index 4854987eb9e..00000000000 --- a/sentry/src/test/java/io/sentry/unmarshaller/Base64.java +++ /dev/null @@ -1,685 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.sentry.unmarshaller; - -import java.io.UnsupportedEncodingException; - -/** - * Utilities for encoding and decoding the Base64 representation of - * binary data. See RFCs 2045 and 3548. - */ -public class Base64 { - /** - * Default values for encoder/decoder flags. - */ - public static final int DEFAULT = 0; - /** - * Encoder flag bit to omit the padding '=' characters at the end - * of the output (if any). - */ - public static final int NO_PADDING = 1; - /** - * Encoder flag bit to omit all line terminators (i.e., the output - * will be on one long line). - */ - public static final int NO_WRAP = 2; - /** - * Encoder flag bit to indicate lines should be terminated with a - * CRLF pair instead of just an LF. Has no effect if {@code - * NO_WRAP} is specified as well. - */ - public static final int CRLF = 4; - /** - * Encoder/decoder flag bit to indicate using the "URL and - * filename safe" variant of Base64 (see RFC 3548 section 4) where - * {@code -} and {@code _} are used in place of {@code +} and - * {@code /}. - */ - public static final int URL_SAFE = 8; - // -------------------------------------------------------- - // shared code - // -------------------------------------------------------- - /* package */ static abstract class Coder { - public byte[] output; - public int op; - /** - * Encode/decode another block of input data. this.output is - * provided by the caller, and must be big enough to hold all - * the coded data. On exit, this.opwill be set to the length - * of the coded data. - * - * @param finish true if this is the final call to process for - * this object. Will finalize the coder state and - * include any final bytes in the output. - * - * @return true if the input so far is good; false if some - * error has been detected in the input stream.. - */ - public abstract boolean process(byte[] input, int offset, int len, boolean finish); - /** - * @return the maximum number of bytes a call to process() - * could produce for the given number of input bytes. This may - * be an overestimate. - */ - public abstract int maxOutputSize(int len); - } - // -------------------------------------------------------- - // decoding - // -------------------------------------------------------- - /** - * Decode the Base64-encoded data in input and return the data in - * a new byte array. - * - *

    The padding '=' characters at the end are considered optional, but - * if any are present, there must be the correct number of them. - * - * @param str the input String to decode, which is converted to - * bytes using the default charset - * @param flags controls certain features of the decoded output. - * Pass {@code DEFAULT} to decode standard Base64. - * - * @throws IllegalArgumentException if the input contains - * incorrect padding - * - * @return byte[] - */ - public static byte[] decode(String str, int flags) { - return decode(str.getBytes(), flags); - } - /** - * Decode the Base64-encoded data in input and return the data in - * a new byte array. - * - *

    The padding '=' characters at the end are considered optional, but - * if any are present, there must be the correct number of them. - * - * @param input the input array to decode - * @param flags controls certain features of the decoded output. - * Pass {@code DEFAULT} to decode standard Base64. - * - * @throws IllegalArgumentException if the input contains - * incorrect padding - * - * @return byte[] - */ - public static byte[] decode(byte[] input, int flags) { - return decode(input, 0, input.length, flags); - } - /** - * Decode the Base64-encoded data in input and return the data in - * a new byte array. - * - *

    The padding '=' characters at the end are considered optional, but - * if any are present, there must be the correct number of them. - * - * @param input the data to decode - * @param offset the position within the input array at which to start - * @param len the number of bytes of input to decode - * @param flags controls certain features of the decoded output. - * Pass {@code DEFAULT} to decode standard Base64. - * - * @throws IllegalArgumentException if the input contains - * incorrect padding - * - * @return byte[] - */ - public static byte[] decode(byte[] input, int offset, int len, int flags) { - // Allocate space for the most data the input could represent. - // (It could contain less if it contains whitespace, etc.) - Decoder decoder = new Decoder(flags, new byte[len*3/4]); - if (!decoder.process(input, offset, len, true)) { - throw new IllegalArgumentException("bad base-64"); - } - // Maybe we got lucky and allocated exactly enough output space. - if (decoder.op == decoder.output.length) { - return decoder.output; - } - // Need to shorten the array, so allocate a new one of the - // right size and copy. - byte[] temp = new byte[decoder.op]; - System.arraycopy(decoder.output, 0, temp, 0, decoder.op); - return temp; - } - /* package */ static class Decoder extends Coder { - /** - * Lookup table for turning bytes into their position in the - * Base64 alphabet. - */ - private static final int DECODE[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - }; - /** - * Decode lookup table for the "web safe" variant (RFC 3548 - * sec. 4) where - and _ replace + and /. - */ - private static final int DECODE_WEBSAFE[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - }; - /** Non-data values in the DECODE arrays. */ - private static final int SKIP = -1; - private static final int EQUALS = -2; - /** - * States 0-3 are reading through the next input tuple. - * State 4 is having read one '=' and expecting exactly - * one more. - * State 5 is expecting no more data or padding characters - * in the input. - * State 6 is the error state; an error has been detected - * in the input and no future input can "fix" it. - */ - private int state; // state number (0 to 6) - private int value; - final private int[] alphabet; - public Decoder(int flags, byte[] output) { - this.output = output; - alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE; - state = 0; - value = 0; - } - /** - * @return an overestimate for the number of bytes {@code - * len} bytes could decode to. - */ - public int maxOutputSize(int len) { - return len * 3/4 + 10; - } - /** - * Decode another block of input data. - * - * @return true if the state machine is still healthy. false if - * bad base-64 data has been detected in the input stream. - */ - public boolean process(byte[] input, int offset, int len, boolean finish) { - if (this.state == 6) { - return false; - } - int p = offset; - len += offset; - // Using local variables makes the decoder about 12% - // faster than if we manipulate the member variables in - // the loop. (Even alphabet makes a measurable - // difference, which is somewhat surprising to me since - // the member variable is final.) - int state = this.state; - int value = this.value; - int op = 0; - final byte[] output = this.output; - final int[] alphabet = this.alphabet; - while (p < len) { - // Try the fast path: we're starting a new tuple and the - // next four bytes of the input stream are all data - // bytes. This corresponds to going through states - // 0-1-2-3-0. We expect to use this method for most of - // the data. - // - // If any of the next four bytes of input are non-data - // (whitespace, etc.), value will end up negative. (All - // the non-data values in decode are small negative - // numbers, so shifting any of them up and or'ing them - // together will result in a value with its top bit set.) - // - // You can remove this whole block and the output should - // be the same, just slower. - if (state == 0) { - while (p+4 <= len && - (value = ((alphabet[input[p] & 0xff] << 18) | - (alphabet[input[p+1] & 0xff] << 12) | - (alphabet[input[p+2] & 0xff] << 6) | - (alphabet[input[p+3] & 0xff]))) >= 0) { - output[op+2] = (byte) value; - output[op+1] = (byte) (value >> 8); - output[op] = (byte) (value >> 16); - op += 3; - p += 4; - } - if (p >= len) { - break; - } - } - // The fast path isn't available -- either we've read a - // partial tuple, or the next four input bytes aren't all - // data, or whatever. Fall back to the slower state - // machine implementation. - int d = alphabet[input[p++] & 0xff]; - switch (state) { - case 0: - if (d >= 0) { - value = d; - ++state; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - case 1: - if (d >= 0) { - value = (value << 6) | d; - ++state; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - case 2: - if (d >= 0) { - value = (value << 6) | d; - ++state; - } else if (d == EQUALS) { - // Emit the last (partial) output tuple; - // expect exactly one more padding character. - output[op++] = (byte) (value >> 4); - state = 4; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - case 3: - if (d >= 0) { - // Emit the output triple and return to state 0. - value = (value << 6) | d; - output[op+2] = (byte) value; - output[op+1] = (byte) (value >> 8); - output[op] = (byte) (value >> 16); - op += 3; - state = 0; - } else if (d == EQUALS) { - // Emit the last (partial) output tuple; - // expect no further data or padding characters. - output[op+1] = (byte) (value >> 2); - output[op] = (byte) (value >> 10); - op += 2; - state = 5; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - case 4: - if (d == EQUALS) { - ++state; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - case 5: - if (d != SKIP) { - this.state = 6; - return false; - } - break; - } - } - if (!finish) { - // We're out of input, but a future call could provide - // more. - this.state = state; - this.value = value; - this.op = op; - return true; - } - // Done reading input. Now figure out where we are left in - // the state machine and finish up. - switch (state) { - case 0: - // Output length is a multiple of three. Fine. - break; - case 1: - // Read one extra input byte, which isn't enough to - // make another output byte. Illegal. - this.state = 6; - return false; - case 2: - // Read two extra input bytes, enough to emit 1 more - // output byte. Fine. - output[op++] = (byte) (value >> 4); - break; - case 3: - // Read three extra input bytes, enough to emit 2 more - // output bytes. Fine. - output[op++] = (byte) (value >> 10); - output[op++] = (byte) (value >> 2); - break; - case 4: - // Read one padding '=' when we expected 2. Illegal. - this.state = 6; - return false; - case 5: - // Read all the padding '='s we expected and no more. - // Fine. - break; - } - this.state = state; - this.op = op; - return true; - } - } - // -------------------------------------------------------- - // encoding - // -------------------------------------------------------- - /** - * Base64-encode the given data and return a newly allocated - * String with the result. - * - * @param input the data to encode - * @param flags controls certain features of the encoded output. - * Passing {@code DEFAULT} results in output that - * adheres to RFC 2045. - * - * @return String - */ - public static String encodeToString(byte[] input, int flags) { - try { - return new String(encode(input, flags), "US-ASCII"); - } catch (UnsupportedEncodingException e) { - // US-ASCII is guaranteed to be available. - throw new AssertionError(e); - } - } - /** - * Base64-encode the given data and return a newly allocated - * String with the result. - * - * @param input the data to encode - * @param offset the position within the input array at which to - * start - * @param len the number of bytes of input to encode - * @param flags controls certain features of the encoded output. - * Passing {@code DEFAULT} results in output that - * adheres to RFC 2045. - * - * @return String - */ - public static String encodeToString(byte[] input, int offset, int len, int flags) { - try { - return new String(encode(input, offset, len, flags), "US-ASCII"); - } catch (UnsupportedEncodingException e) { - // US-ASCII is guaranteed to be available. - throw new AssertionError(e); - } - } - /** - * Base64-encode the given data and return a newly allocated - * byte[] with the result. - * - * @param input the data to encode - * @param flags controls certain features of the encoded output. - * Passing {@code DEFAULT} results in output that - * adheres to RFC 2045. - * - * @return byte[] - */ - public static byte[] encode(byte[] input, int flags) { - return encode(input, 0, input.length, flags); - } - /** - * Base64-encode the given data and return a newly allocated - * byte[] with the result. - * - * @param input the data to encode - * @param offset the position within the input array at which to - * start - * @param len the number of bytes of input to encode - * @param flags controls certain features of the encoded output. - * Passing {@code DEFAULT} results in output that - * adheres to RFC 2045. - * - * @return byte[] - */ - public static byte[] encode(byte[] input, int offset, int len, int flags) { - Encoder encoder = new Encoder(flags, null); - // Compute the exact length of the array we will produce. - int output_len = len / 3 * 4; - // Account for the tail of the data and the padding bytes, if any. - if (encoder.do_padding) { - if (len % 3 > 0) { - output_len += 4; - } - } else { - switch (len % 3) { - case 0: break; - case 1: output_len += 2; break; - case 2: output_len += 3; break; - } - } - // Account for the newlines, if any. - if (encoder.do_newline && len > 0) { - output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) * - (encoder.do_cr ? 2 : 1); - } - encoder.output = new byte[output_len]; - encoder.process(input, offset, len, true); - assert encoder.op == output_len; - return encoder.output; - } - /* package */ static class Encoder extends Coder { - /** - * Emit a new line every this many output tuples. Corresponds to - * a 76-character line length (the maximum allowable according to - * RFC 2045). - */ - public static final int LINE_GROUPS = 19; - /** - * Lookup table for turning Base64 alphabet positions (6 bits) - * into output bytes. - */ - private static final byte ENCODE[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', - }; - /** - * Lookup table for turning Base64 alphabet positions (6 bits) - * into output bytes. - */ - private static final byte ENCODE_WEBSAFE[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', - }; - final private byte[] tail; - /* package */ int tailLen; - private int count; - final public boolean do_padding; - final public boolean do_newline; - final public boolean do_cr; - final private byte[] alphabet; - public Encoder(int flags, byte[] output) { - this.output = output; - do_padding = (flags & NO_PADDING) == 0; - do_newline = (flags & NO_WRAP) == 0; - do_cr = (flags & CRLF) != 0; - alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE; - tail = new byte[2]; - tailLen = 0; - count = do_newline ? LINE_GROUPS : -1; - } - /** - * @return an overestimate for the number of bytes {@code - * len} bytes could encode to. - */ - public int maxOutputSize(int len) { - return len * 8/5 + 10; - } - public boolean process(byte[] input, int offset, int len, boolean finish) { - // Using local variables makes the encoder about 9% faster. - final byte[] alphabet = this.alphabet; - final byte[] output = this.output; - int op = 0; - int count = this.count; - int p = offset; - len += offset; - int v = -1; - // First we need to concatenate the tail of the previous call - // with any input bytes available now and see if we can empty - // the tail. - switch (tailLen) { - case 0: - // There was no tail. - break; - case 1: - if (p+2 <= len) { - // A 1-byte tail with at least 2 bytes of - // input available now. - v = ((tail[0] & 0xff) << 16) | - ((input[p++] & 0xff) << 8) | - (input[p++] & 0xff); - tailLen = 0; - }; - break; - case 2: - if (p+1 <= len) { - // A 2-byte tail with at least 1 byte of input. - v = ((tail[0] & 0xff) << 16) | - ((tail[1] & 0xff) << 8) | - (input[p++] & 0xff); - tailLen = 0; - } - break; - } - if (v != -1) { - output[op++] = alphabet[(v >> 18) & 0x3f]; - output[op++] = alphabet[(v >> 12) & 0x3f]; - output[op++] = alphabet[(v >> 6) & 0x3f]; - output[op++] = alphabet[v & 0x3f]; - if (--count == 0) { - if (do_cr) { - output[op++] = '\r'; - } - output[op++] = '\n'; - count = LINE_GROUPS; - } - } - // At this point either there is no tail, or there are fewer - // than 3 bytes of input available. - // The main loop, turning 3 input bytes into 4 output bytes on - // each iteration. - while (p+3 <= len) { - v = ((input[p] & 0xff) << 16) | - ((input[p+1] & 0xff) << 8) | - (input[p+2] & 0xff); - output[op] = alphabet[(v >> 18) & 0x3f]; - output[op+1] = alphabet[(v >> 12) & 0x3f]; - output[op+2] = alphabet[(v >> 6) & 0x3f]; - output[op+3] = alphabet[v & 0x3f]; - p += 3; - op += 4; - if (--count == 0) { - if (do_cr) { - output[op++] = '\r'; - } - output[op++] = '\n'; - count = LINE_GROUPS; - } - } - if (finish) { - // Finish up the tail of the input. Note that we need to - // consume any bytes in tail before any bytes - // remaining in input; there should be at most two bytes - // total. - if (p-tailLen == len-1) { - int t = 0; - v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4; - tailLen -= t; - output[op++] = alphabet[(v >> 6) & 0x3f]; - output[op++] = alphabet[v & 0x3f]; - if (do_padding) { - output[op++] = '='; - output[op++] = '='; - } - if (do_newline) { - if (do_cr) { - output[op++] = '\r'; - } - output[op++] = '\n'; - } - } else if (p-tailLen == len-2) { - int t = 0; - v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) | - (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2); - tailLen -= t; - output[op++] = alphabet[(v >> 12) & 0x3f]; - output[op++] = alphabet[(v >> 6) & 0x3f]; - output[op++] = alphabet[v & 0x3f]; - if (do_padding) { - output[op++] = '='; - } - if (do_newline) { - if (do_cr) { - output[op++] = '\r'; - } - output[op++] = '\n'; - } - } else if (do_newline && op > 0 && count != LINE_GROUPS) { - if (do_cr) { - output[op++] = '\r'; - } - output[op++] = '\n'; - } - assert tailLen == 0; - assert p == len; - } else { - // Save the leftovers in tail to be consumed on the next - // call to encodeInternal. - if (p == len-1) { - tail[tailLen++] = input[p]; - } else if (p == len-2) { - tail[tailLen++] = input[p]; - tail[tailLen++] = input[p+1]; - } - } - this.op = op; - this.count = count; - return true; - } - } - private Base64() { } // don't instantiate -} diff --git a/sentry/src/test/java/io/sentry/unmarshaller/Base64InputStream.java b/sentry/src/test/java/io/sentry/unmarshaller/Base64InputStream.java deleted file mode 100644 index 97d2737902f..00000000000 --- a/sentry/src/test/java/io/sentry/unmarshaller/Base64InputStream.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.sentry.unmarshaller; - -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * An InputStream that does Base64 decoding on the data read through - * it. - */ -public class Base64InputStream extends FilterInputStream { - private final Base64.Coder coder; - private static byte[] EMPTY = new byte[0]; - private static final int BUFFER_SIZE = 2048; - private boolean eof; - private byte[] inputBuffer; - private int outputStart; - private int outputEnd; - /** - * An InputStream that performs Base64 decoding on the data read - * from the wrapped stream. - * - * @param in the InputStream to read the source data from - * @param flags bit flags for controlling the decoder; see the - * constants in {@link Base64} - */ - public Base64InputStream(InputStream in, int flags) { - this(in, flags, false); - } - /** - * Performs Base64 encoding or decoding on the data read from the - * wrapped InputStream. - * - * @param in the InputStream to read the source data from - * @param flags bit flags for controlling the decoder; see the - * constants in {@link Base64} - * @param encode true to encode, false to decode - */ - public Base64InputStream(InputStream in, int flags, boolean encode) { - super(in); - eof = false; - inputBuffer = new byte[BUFFER_SIZE]; - if (encode) { - coder = new Base64.Encoder(flags, null); - } else { - coder = new Base64.Decoder(flags, null); - } - coder.output = new byte[coder.maxOutputSize(BUFFER_SIZE)]; - outputStart = 0; - outputEnd = 0; - } - public boolean markSupported() { - return false; - } - public void mark(int readlimit) { - throw new UnsupportedOperationException(); - } - public void reset() { - throw new UnsupportedOperationException(); - } - public void close() throws IOException { - in.close(); - inputBuffer = null; - } - public int available() { - return outputEnd - outputStart; - } - public long skip(long n) throws IOException { - if (outputStart >= outputEnd) { - refill(); - } - if (outputStart >= outputEnd) { - return 0; - } - long bytes = Math.min(n, outputEnd-outputStart); - outputStart += bytes; - return bytes; - } - public int read() throws IOException { - if (outputStart >= outputEnd) { - refill(); - } - if (outputStart >= outputEnd) { - return -1; - } else { - return coder.output[outputStart++] & 0xff; - } - } - public int read(byte[] b, int off, int len) throws IOException { - if (outputStart >= outputEnd) { - refill(); - } - if (outputStart >= outputEnd) { - return -1; - } - int bytes = Math.min(len, outputEnd-outputStart); - System.arraycopy(coder.output, outputStart, b, off, bytes); - outputStart += bytes; - return bytes; - } - /** - * Read data from the input stream into inputBuffer, then - * decode/encode it into the empty coder.output, and reset the - * outputStart and outputEnd pointers. - */ - private void refill() throws IOException { - if (eof) { - return; - } - int bytesRead = in.read(inputBuffer); - boolean success; - if (bytesRead == -1) { - eof = true; - success = coder.process(EMPTY, 0, 0, true); - } else { - success = coder.process(inputBuffer, 0, bytesRead, false); - } - if (!success) { - throw new IOException("bad base-64"); - } - outputEnd = coder.op; - outputStart = 0; - } -} diff --git a/sentry/src/test/java/io/sentry/unmarshaller/JsonDecoder.java b/sentry/src/test/java/io/sentry/unmarshaller/JsonDecoder.java index d012f92fb99..58cb3657fb2 100644 --- a/sentry/src/test/java/io/sentry/unmarshaller/JsonDecoder.java +++ b/sentry/src/test/java/io/sentry/unmarshaller/JsonDecoder.java @@ -8,47 +8,41 @@ import java.io.InputStream; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.zip.InflaterInputStream; +import java.util.zip.GZIPInputStream; /** * Decodes a Stream as a JSON stream. *

    * The supported stream formats are: *

      - *
    • JSON Stream (nothing to do) - *
    • Base 64'd JSON streams (base64 decoded) - *
    • Base 64'd and deflated JSON streams (base64 decoded and inflated) + *
    • Raw JSON Stream (nothing to do) + *
    • Gzipped JSON streams (must be ungzipped) *
    */ public class JsonDecoder { private static Logger logger = Logger.getLogger(JsonDecoder.class.getCanonicalName()); /** - * Attempts to read the content of the stream and determine if it's compressed, encoded or simple JSON. + * Attempts to read the content of the stream and determine if it's compressed or raw JSON. *

    * This method isn't efficient but it isn't really a problem as this part of the project is not about performances. * - * @param originalStream origin stream of information that can be compressed or encoded in base64. + * @param originalStream origin stream of information that can be compressed. * @return a Stream containing pure JSON. * @throws IOException if it's impossible to read the content of the Stream. */ public InputStream decapsulateContent(InputStream originalStream) throws IOException { //Hopefully the sent content isn't bigger than 1MB... final int messageSize = 1048576; - //Make it uncloseable to avoid issues with the InflaterInputStream. + //Make it uncloseable to avoid issues with the GZIPInputStream. originalStream = new Uncloseable(new BufferedInputStream(originalStream)); originalStream.mark(messageSize); InputStream inputStream = originalStream; if (!isJson(originalStream)) { - inputStream = new Base64InputStream(inputStream, Base64.NO_WRAP); + inputStream = new GZIPInputStream(inputStream); originalStream.reset(); - if (!isJson(new Base64InputStream(originalStream, Base64.NO_WRAP))) { - inputStream = new InflaterInputStream(inputStream); - originalStream.reset(); - if (!isJson(new InflaterInputStream(new Base64InputStream(originalStream, Base64.NO_WRAP)))) { - throw new IllegalArgumentException("The given Stream is neither JSON, Base64'd JSON " - + "nor Base64'd deflated JSON."); - } + if (!isJson(new GZIPInputStream(originalStream))) { + throw new IllegalArgumentException("The given Stream is neither JSON nor gzipped JSON."); } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCompression.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCompression.json new file mode 100644 index 00000000000..ec55c184ad6 --- /dev/null +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCompression.json @@ -0,0 +1,20 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": {}, + "checksum": null, + "sdk": { + "name": null, + "version": null + } +} diff --git a/src/checkstyle/suppressions.xml b/src/checkstyle/suppressions.xml index 4c3960263dd..8fabea49981 100644 --- a/src/checkstyle/suppressions.xml +++ b/src/checkstyle/suppressions.xml @@ -4,6 +4,5 @@ "http://www.puppycrawl.com/dtds/suppressions_1_1.dtd"> - From cf9d643a83eaaa68b52d85be1dac9d8417b93393 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 27 Apr 2017 08:57:45 -0500 Subject: [PATCH 1581/2152] Rename EventSendFailureCallback to EventSendCallback, add onSuccess callback. (#386) --- CHANGES | 1 + .../connection/AppEngineAsyncConnection.java | 6 +- .../sentry/connection/AbstractConnection.java | 27 +++++--- .../io/sentry/connection/AsyncConnection.java | 4 +- .../sentry/connection/BufferedConnection.java | 8 +-- .../java/io/sentry/connection/Connection.java | 4 +- ...reCallback.java => EventSendCallback.java} | 10 ++- .../connection/AbstractConnectionTest.java | 38 +++++++++-- .../connection/BufferedConnectionTest.java | 2 +- .../EventSendFailureCallbackTest.java | 66 ------------------- 10 files changed, 73 insertions(+), 93 deletions(-) rename sentry/src/main/java/io/sentry/connection/{EventSendFailureCallback.java => EventSendCallback.java} (60%) delete mode 100644 sentry/src/test/java/io/sentry/connection/EventSendFailureCallbackTest.java diff --git a/CHANGES b/CHANGES index 0805cafd287..f03a825c7f7 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,7 @@ Version 1.0.0 now ``SENTRY_FACTORY`` and the Java System Property is ``sentry.factory``. - Add ``dist`` field to Event. - Compressed JSON payloads are no longer base64 encoded. +- Rename ``EventSendFailureCallback`` to ``EventSendCallback``, add ``onSuccess`` callback. Version 8.0.2 ------------- diff --git a/sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java b/sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java index f383602608a..7bb94c6fdc3 100644 --- a/sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java +++ b/sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java @@ -1,6 +1,6 @@ package io.sentry.appengine.connection; -import io.sentry.connection.EventSendFailureCallback; +import io.sentry.connection.EventSendCallback; import com.google.appengine.api.taskqueue.DeferredTask; import com.google.appengine.api.taskqueue.Queue; import com.google.appengine.api.taskqueue.QueueFactory; @@ -78,8 +78,8 @@ public void send(Event event) { } @Override - public void addEventSendFailureCallback(EventSendFailureCallback eventSendFailureCallback) { - actualConnection.addEventSendFailureCallback(eventSendFailureCallback); + public void addEventSendCallback(EventSendCallback eventSendCallback) { + actualConnection.addEventSendCallback(eventSendCallback); } /** diff --git a/sentry/src/main/java/io/sentry/connection/AbstractConnection.java b/sentry/src/main/java/io/sentry/connection/AbstractConnection.java index 0e191c51a6d..dfdd82499a1 100644 --- a/sentry/src/main/java/io/sentry/connection/AbstractConnection.java +++ b/sentry/src/main/java/io/sentry/connection/AbstractConnection.java @@ -29,7 +29,7 @@ public abstract class AbstractConnection implements Connection { * Set of callbacks that will be called when an exception occurs while attempting to * send events to the Sentry server. */ - private Set eventSendFailureCallbacks; + private Set eventSendCallbacks; private LockdownManager lockdownManager; /** @@ -40,7 +40,7 @@ public abstract class AbstractConnection implements Connection { */ protected AbstractConnection(String publicKey, String secretKey) { this.lockdownManager = new LockdownManager(); - this.eventSendFailureCallbacks = new HashSet<>(); + this.eventSendCallbacks = new HashSet<>(); this.authHeader = "Sentry sentry_version=" + SENTRY_PROTOCOL_VERSION + "," + "sentry_client=" + SentryEnvironment.getSentryName() + "," + "sentry_key=" + publicKey + "," @@ -70,14 +70,23 @@ public final void send(Event event) throws ConnectionException { doSend(event); + for (EventSendCallback eventSendCallback : eventSendCallbacks) { + try { + eventSendCallback.onSuccess(event); + } catch (Exception exc) { + logger.warn("An exception occurred while running an EventSendCallback.onSuccess: " + + eventSendCallback.getClass().getName(), exc); + } + } + lockdownManager.resetState(); } catch (ConnectionException e) { - for (EventSendFailureCallback eventSendFailureCallback : eventSendFailureCallbacks) { + for (EventSendCallback eventSendCallback : eventSendCallbacks) { try { - eventSendFailureCallback.onFailure(event, e); + eventSendCallback.onFailure(event, e); } catch (Exception exc) { - logger.warn("An exception occurred while running an EventSendFailureCallback: " - + eventSendFailureCallback.getClass().getName(), exc); + logger.warn("An exception occurred while running an EventSendCallback.onFailure: " + + eventSendCallback.getClass().getName(), exc); } } @@ -100,10 +109,10 @@ public final void send(Event event) throws ConnectionException { * Add a callback that is called when an exception occurs while attempting to * send events to the Sentry server. * - * @param eventSendFailureCallback callback instance + * @param eventSendCallback callback instance */ - public void addEventSendFailureCallback(EventSendFailureCallback eventSendFailureCallback) { - eventSendFailureCallbacks.add(eventSendFailureCallback); + public void addEventSendCallback(EventSendCallback eventSendCallback) { + eventSendCallbacks.add(eventSendCallback); } } diff --git a/sentry/src/main/java/io/sentry/connection/AsyncConnection.java b/sentry/src/main/java/io/sentry/connection/AsyncConnection.java index e92d2d742b2..4c179abd2b7 100644 --- a/sentry/src/main/java/io/sentry/connection/AsyncConnection.java +++ b/sentry/src/main/java/io/sentry/connection/AsyncConnection.java @@ -95,8 +95,8 @@ public void send(Event event) { } @Override - public void addEventSendFailureCallback(EventSendFailureCallback eventSendFailureCallback) { - actualConnection.addEventSendFailureCallback(eventSendFailureCallback); + public void addEventSendCallback(EventSendCallback eventSendCallback) { + actualConnection.addEventSendCallback(eventSendCallback); } /** diff --git a/sentry/src/main/java/io/sentry/connection/BufferedConnection.java b/sentry/src/main/java/io/sentry/connection/BufferedConnection.java index c5319233e32..888eec28acf 100644 --- a/sentry/src/main/java/io/sentry/connection/BufferedConnection.java +++ b/sentry/src/main/java/io/sentry/connection/BufferedConnection.java @@ -106,8 +106,8 @@ public void send(Event event) { } @Override - public void addEventSendFailureCallback(EventSendFailureCallback eventSendFailureCallback) { - actualConnection.addEventSendFailureCallback(eventSendFailureCallback); + public void addEventSendCallback(EventSendCallback eventSendCallback) { + actualConnection.addEventSendCallback(eventSendCallback); } @Override @@ -174,8 +174,8 @@ public void send(Event event) throws ConnectionException { } @Override - public void addEventSendFailureCallback(EventSendFailureCallback eventSendFailureCallback) { - wrappedConnection.addEventSendFailureCallback(eventSendFailureCallback); + public void addEventSendCallback(EventSendCallback eventSendCallback) { + wrappedConnection.addEventSendCallback(eventSendCallback); } @Override diff --git a/sentry/src/main/java/io/sentry/connection/Connection.java b/sentry/src/main/java/io/sentry/connection/Connection.java index 63bcd49f6e3..405c33156e9 100644 --- a/sentry/src/main/java/io/sentry/connection/Connection.java +++ b/sentry/src/main/java/io/sentry/connection/Connection.java @@ -20,8 +20,8 @@ public interface Connection extends Closeable { * Add a callback that is called when an exception occurs while attempting to * send events to the Sentry server. * - * @param eventSendFailureCallback callback instance + * @param eventSendCallback callback instance */ - void addEventSendFailureCallback(EventSendFailureCallback eventSendFailureCallback); + void addEventSendCallback(EventSendCallback eventSendCallback); } diff --git a/sentry/src/main/java/io/sentry/connection/EventSendFailureCallback.java b/sentry/src/main/java/io/sentry/connection/EventSendCallback.java similarity index 60% rename from sentry/src/main/java/io/sentry/connection/EventSendFailureCallback.java rename to sentry/src/main/java/io/sentry/connection/EventSendCallback.java index da57575122c..5cf8b802193 100644 --- a/sentry/src/main/java/io/sentry/connection/EventSendFailureCallback.java +++ b/sentry/src/main/java/io/sentry/connection/EventSendCallback.java @@ -3,10 +3,10 @@ import io.sentry.event.Event; /** - * Callback that is called when an exception occurs while attempting to + * Callback that is called upon success or failure while attempting to * send events to the Sentry server. */ -public interface EventSendFailureCallback { +public interface EventSendCallback { /** * Called when an exception occurs while attempting to @@ -17,4 +17,10 @@ public interface EventSendFailureCallback { */ void onFailure(Event event, Exception exception); + /** + * Called when an event is successfully sent to the Sentry server. + * + * @param event Event that was sent + */ + void onSuccess(Event event); } diff --git a/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java b/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java index 08056cbcd36..d24fce3f4d6 100644 --- a/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java @@ -136,20 +136,50 @@ public void testLockDownDoesntDoubleItAtMax(@Injectable final Event mockEvent) t } @Test - public void testEventSendFailureCallback(@Injectable final Event mockEvent) throws Exception { + public void testEventSendCallbackSuccess(@Injectable final Event mockEvent) throws Exception { final AtomicBoolean callbackCalled = new AtomicBoolean(false); - EventSendFailureCallback callback = new EventSendFailureCallback() { + EventSendCallback callback = new EventSendCallback() { + + @Override + public void onFailure(Event event, Exception exception) { + + } + + @Override + public void onSuccess(Event event) { + callbackCalled.set(true); + } + + }; + HashSet callbacks = new HashSet<>(); + callbacks.add(callback); + + setField(abstractConnection, "eventSendCallbacks", callbacks); + abstractConnection.send(mockEvent); + + assertThat(callbackCalled.get(), is(true)); + } + + @Test + public void testEventSendCallbackFailure(@Injectable final Event mockEvent) throws Exception { + final AtomicBoolean callbackCalled = new AtomicBoolean(false); + EventSendCallback callback = new EventSendCallback() { @Override public void onFailure(Event event, Exception exception) { callbackCalled.set(true); } + @Override + public void onSuccess(Event event) { + + } + }; - HashSet callbacks = new HashSet<>(); + HashSet callbacks = new HashSet<>(); callbacks.add(callback); - setField(abstractConnection, "eventSendFailureCallbacks", callbacks); + setField(abstractConnection, "eventSendCallbacks", callbacks); new NonStrictExpectations() {{ abstractConnection.doSend((Event) any); result = new ConnectionException(); diff --git a/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java b/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java index ba5ee8c4d2c..bc404501eca 100644 --- a/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java @@ -53,7 +53,7 @@ protected void doSend(Event event) throws ConnectionException { } @Override - public void addEventSendFailureCallback(EventSendFailureCallback eventSendFailureCallback) { + public void addEventSendCallback(EventSendCallback eventSendCallback) { } diff --git a/sentry/src/test/java/io/sentry/connection/EventSendFailureCallbackTest.java b/sentry/src/test/java/io/sentry/connection/EventSendFailureCallbackTest.java deleted file mode 100644 index 0a27a57508b..00000000000 --- a/sentry/src/test/java/io/sentry/connection/EventSendFailureCallbackTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package io.sentry.connection; - -import io.sentry.DefaultSentryClientFactory; -import io.sentry.SentryClient; -import io.sentry.dsn.Dsn; -import io.sentry.event.Event; -import org.testng.annotations.Test; - -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -public class EventSendFailureCallbackTest { - - @Test - public void testSimpleCallback() { - final AtomicBoolean flag = new AtomicBoolean(false); - - DefaultSentryClientFactory factory = new DefaultSentryClientFactory() { - @Override - protected Connection createConnection(Dsn dsn) { - Connection connection = super.createConnection(dsn); - - connection.addEventSendFailureCallback(new EventSendFailureCallback() { - @Override - public void onFailure(Event event, Exception exception) { - flag.set(true); - } - }); - - return connection; - } - }; - - String dsn = "https://foo:bar@localhost:1234/1?async=false"; - SentryClient sentryClient = factory.createSentryClient(new Dsn(dsn)); - sentryClient.sendMessage("Message that will fail because DSN points to garbage."); - - assertThat(flag.get(), is(true)); - } - - @Test - public void testExceptionInsideCallback() { - DefaultSentryClientFactory factory = new DefaultSentryClientFactory() { - @Override - protected Connection createConnection(Dsn dsn) { - Connection connection = super.createConnection(dsn); - - connection.addEventSendFailureCallback(new EventSendFailureCallback() { - @Override - public void onFailure(Event event, Exception exception) { - throw new RuntimeException("Error inside of EventSendFailureCallback"); - } - }); - - return connection; - } - }; - - String dsn = "https://foo:bar@localhost:1234/1?async=false"; - SentryClient sentryClient = factory.createSentryClient(new Dsn(dsn)); - sentryClient.sendMessage("Message that will fail because DSN points to garbage."); - } - -} From fcd8b6ebf737d89096145c50fb36389d7f8a4641 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 27 Apr 2017 11:10:53 -0500 Subject: [PATCH 1582/2152] Add way to set release, environment, serverName, tags on SentryClient. (#387) --- CHANGES | 1 + .../src/main/java/io/sentry/SentryClient.java | 144 ++++++++++++++++-- .../test/java/io/sentry/SentryClientTest.java | 33 ++++ 3 files changed, 165 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index f03a825c7f7..f5372088368 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,7 @@ Version 1.0.0 - Add ``dist`` field to Event. - Compressed JSON payloads are no longer base64 encoded. - Rename ``EventSendFailureCallback`` to ``EventSendCallback``, add ``onSuccess`` callback. +- Add way to set release, environment, serverName, tags on ``SentryClient``. Version 8.0.2 ------------- diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index a3f6a21fbd8..079f89da3d3 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -1,19 +1,21 @@ package io.sentry; import io.sentry.connection.Connection; +import io.sentry.connection.EventSendCallback; import io.sentry.connection.LockedDownException; import io.sentry.context.Context; import io.sentry.context.ContextManager; -import io.sentry.environment.SentryEnvironment; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.helper.EventBuilderHelper; import io.sentry.event.interfaces.ExceptionInterface; +import io.sentry.util.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Collections; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -29,10 +31,40 @@ public class SentryClient { private static final Logger lockdownLogger = LoggerFactory.getLogger(SentryClient.class.getName() + ".lockdown"); // CHECKSTYLE.ON: ConstantName + /** + * Identifies the version of the application. + *

    + * Might be null in which case the release information will not be sent with the event. + */ + protected String release; + /** + * Identifies the distribution of the application. + *

    + * Might be null in which case the release distribution will not be sent with the event. + */ + protected String dist; + /** + * Identifies the environment the application is running in. + *

    + * Might be null in which case the environment information will not be sent with the event. + */ + protected String environment; + /** + * Server name to be sent to sentry. + *

    + * Might be null in which case the hostname is found via a reverse DNS lookup. + */ + protected String serverName; + /** + * Additional tags to be sent to sentry. + *

    + * Might be empty in which case no tags are sent. + */ + protected final Map tags = new ConcurrentHashMap<>(); /** * The underlying {@link Connection} to use for sending events to Sentry. */ - private volatile Connection connection; + private final Connection connection; /** * Set of {@link EventBuilderHelper}s. Note that we wrap a {@link ConcurrentHashMap} because there * isn't a concurrent set in the standard library. @@ -96,6 +128,25 @@ public void sendEvent(Event event) { * @param eventBuilder {@link EventBuilder} to send to Sentry. */ public void sendEvent(EventBuilder eventBuilder) { + if (!Util.isNullOrEmpty(release)) { + eventBuilder.withRelease(release.trim()); + if (!Util.isNullOrEmpty(dist)) { + eventBuilder.withDist(dist.trim()); + } + } + + if (!Util.isNullOrEmpty(environment)) { + eventBuilder.withEnvironment(environment.trim()); + } + + if (!Util.isNullOrEmpty(serverName)) { + eventBuilder.withServerName(serverName.trim()); + } + + for (Map.Entry tagEntry : tags.entrySet()) { + eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); + } + runBuilderHelpers(eventBuilder); Event event = eventBuilder.build(); sendEvent(event); @@ -111,9 +162,7 @@ public void sendEvent(EventBuilder eventBuilder) { public void sendMessage(String message) { EventBuilder eventBuilder = new EventBuilder().withMessage(message) .withLevel(Event.Level.INFO); - runBuilderHelpers(eventBuilder); - Event event = eventBuilder.build(); - sendEvent(event); + sendEvent(eventBuilder); } /** @@ -127,9 +176,7 @@ public void sendException(Throwable throwable) { EventBuilder eventBuilder = new EventBuilder().withMessage(throwable.getMessage()) .withLevel(Event.Level.ERROR) .withSentryInterface(new ExceptionInterface(throwable)); - runBuilderHelpers(eventBuilder); - Event event = eventBuilder.build(); - sendEvent(event); + sendEvent(eventBuilder); } /** @@ -171,12 +218,83 @@ public Context getContext() { return contextManager.getContext(); } + public String getRelease() { + return release; + } + + public String getDist() { + return dist; + } + + public String getEnvironment() { + return environment; + } + + public String getServerName() { + return serverName; + } + + public Map getTags() { + return Collections.unmodifiableMap(tags); + } + + public void setRelease(String release) { + this.release = release; + } + + public void setDist(String dist) { + this.dist = dist; + } + + public void setEnvironment(String environment) { + this.environment = environment; + } + + public void setServerName(String serverName) { + this.serverName = serverName; + } + + /** + * Add a tag that will be sent with all future {@link Event}s. + * + * @param name Tag name + * @param value Tag value + */ + public void addTag(String name, String value) { + this.tags.put(name, value); + } + + /** + * Set the tags that will be sent with all future {@link Event}s. + * + * @param tags Map of tags + */ + public void setTags(Map tags) { + this.tags.clear(); + this.tags.putAll(tags); + } + + /** + * Add a callback that is called when an exception occurs while attempting to + * send events to the Sentry server. + * + * @param eventSendCallback callback instance + */ + void addEventSendCallback(EventSendCallback eventSendCallback) { + connection.addEventSendCallback(eventSendCallback); + } + @Override public String toString() { - return "Sentry{" - + "name=" + SentryEnvironment.getSentryName() - + ", connection=" + connection - + ", contextManager=" + contextManager - + '}'; + return "SentryClient{" + + "release='" + release + '\'' + + ", dist='" + dist + '\'' + + ", environment='" + environment + '\'' + + ", serverName='" + serverName + '\'' + + ", tags=" + tags + + ", connection=" + connection + + ", builderHelpers=" + builderHelpers + + ", contextManager=" + contextManager + + '}'; } } diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.java b/sentry/src/test/java/io/sentry/SentryClientTest.java index 510a756fe64..8f17eb999ba 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.java +++ b/sentry/src/test/java/io/sentry/SentryClientTest.java @@ -15,6 +15,8 @@ import org.testng.annotations.Test; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -156,4 +158,35 @@ public void testCloseConnectionFailed() throws Exception { sentryClient.closeConnection(); } + + @Test + public void testFields() throws Exception { + sentryClient.addBuilderHelper(mockEventBuilderHelper); + + final String serverName = "serverName"; + sentryClient.setServerName(serverName); + final String environment = "environment"; + sentryClient.setEnvironment(environment); + final String dist = "dist"; + sentryClient.setDist(dist); + final String release = "release"; + sentryClient.setRelease(release); + final Map tags = new HashMap<>(); + tags.put("name", "value"); + sentryClient.setTags(tags); + + sentryClient.sendMessage("message"); + + new Verifications() {{ + Event event; + mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); + mockConnection.send(event = withCapture()); + assertThat(event.getLevel(), equalTo(Event.Level.INFO)); + assertThat(event.getServerName(), equalTo(serverName)); + assertThat(event.getEnvironment(), equalTo(environment)); + assertThat(event.getDist(), equalTo(dist)); + assertThat(event.getRelease(), equalTo(release)); + assertThat(event.getTags(), equalTo(tags)); + }}; + } } From 25f6d391d8fe1a8fb709b384f2d06f275dabbd5a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 8 May 2017 09:19:09 -0500 Subject: [PATCH 1583/2152] Make Context and User classes serializable. --- sentry/src/main/java/io/sentry/context/Context.java | 4 +++- sentry/src/main/java/io/sentry/event/User.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/sentry/src/main/java/io/sentry/context/Context.java b/sentry/src/main/java/io/sentry/context/Context.java index 4d9284991de..b13f8ea4908 100644 --- a/sentry/src/main/java/io/sentry/context/Context.java +++ b/sentry/src/main/java/io/sentry/context/Context.java @@ -3,6 +3,8 @@ import io.sentry.event.Breadcrumb; import io.sentry.event.User; import io.sentry.util.CircularFifoQueue; + +import java.io.Serializable; import java.util.Iterator; import java.util.UUID; @@ -11,7 +13,7 @@ * so that data may be collected in different parts of an application and * then sent together when an exception occurs. */ -public class Context { +public class Context implements Serializable { /** * The number of {@link Breadcrumb}s to keep in the ring buffer by default. */ diff --git a/sentry/src/main/java/io/sentry/event/User.java b/sentry/src/main/java/io/sentry/event/User.java index 3c99a9e88a2..73d322cbeca 100644 --- a/sentry/src/main/java/io/sentry/event/User.java +++ b/sentry/src/main/java/io/sentry/event/User.java @@ -1,12 +1,14 @@ package io.sentry.event; +import java.io.Serializable; + /** * An object that represents a user. Typically used to represent * the user in the current context, for whatever a context means * in your application (typically a web request). */ -public class User { +public class User implements Serializable { private final String id; private final String username; From 904a2568615ef21da2092a1b10e3360f71cdac9c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 8 May 2017 09:51:03 -0500 Subject: [PATCH 1584/2152] Log correct schema when using HTTP/HTTPS. --- sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index 1ed79067acd..bf11d1c30fe 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -219,7 +219,7 @@ protected Connection createConnection(Dsn dsn) { Connection connection; if (protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https")) { - logger.info("Using an HTTP connection to Sentry."); + logger.info("Using an {} connection to Sentry.", protocol.toUpperCase()); connection = createHttpConnection(dsn); } else if (protocol.equalsIgnoreCase("out")) { logger.info("Using StdOut to send events."); From 39bf5b67ac33ad8cf67efe82e91e71c7657815a6 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 8 May 2017 15:55:18 -0500 Subject: [PATCH 1585/2152] Drop HTTP auth information from default DSN to stop Android warnings. (#394) --- sentry/src/main/java/io/sentry/dsn/Dsn.java | 24 +++++++++++++-------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/sentry/src/main/java/io/sentry/dsn/Dsn.java b/sentry/src/main/java/io/sentry/dsn/Dsn.java index c2b2d9f29c0..205c894e5f3 100644 --- a/sentry/src/main/java/io/sentry/dsn/Dsn.java +++ b/sentry/src/main/java/io/sentry/dsn/Dsn.java @@ -17,7 +17,7 @@ public class Dsn { /** * Default DSN to use when auto detection fails. */ - public static final String DEFAULT_DSN = "noop://user:password@localhost:0/0"; + public static final String DEFAULT_DSN = "noop://localhost"; private static final Logger logger = LoggerFactory.getLogger(Dsn.class); private String secretKey; private String publicKey; @@ -192,14 +192,20 @@ private void validate() { if (host == null) { missingElements.add("host"); } - if (publicKey == null) { - missingElements.add("public key"); - } - if (secretKey == null) { - missingElements.add("secret key"); - } - if (projectId == null || projectId.isEmpty()) { - missingElements.add("project ID"); + + if (protocol != null + && !protocol.equalsIgnoreCase("noop") + && !protocol.equalsIgnoreCase("out")) { + + if (publicKey == null) { + missingElements.add("public key"); + } + if (secretKey == null) { + missingElements.add("secret key"); + } + if (projectId == null || projectId.isEmpty()) { + missingElements.add("project ID"); + } } if (!missingElements.isEmpty()) { From cf99d80d328e3d64d8195c4b6765ec9af42cb3cf Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 9 May 2017 15:42:32 -0500 Subject: [PATCH 1586/2152] =?UTF-8?q?Add=20way=20to=20set=20release,=20dis?= =?UTF-8?q?t,=20environment,=20serverName,=20tags,=20and=20extr=E2=80=A6?= =?UTF-8?q?=20(#391)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES | 3 +- docs/config.rst | 105 +++++++++++--- .../java/io/sentry/log4j/SentryAppender.java | 3 +- .../java/io/sentry/log4j2/SentryAppender.java | 4 +- .../io/sentry/logback/SentryAppender.java | 3 +- .../io/sentry/DefaultSentryClientFactory.java | 137 ++++++++++++++++++ .../src/main/java/io/sentry/SentryClient.java | 53 ++++++- .../java/io/sentry/jul/SentryHandler.java | 3 +- .../DefaultSentryClientFactoryTest.java | 34 ++++- 9 files changed, 315 insertions(+), 30 deletions(-) diff --git a/CHANGES b/CHANGES index f5372088368..b4e37049fd8 100644 --- a/CHANGES +++ b/CHANGES @@ -14,7 +14,8 @@ Version 1.0.0 - Add ``dist`` field to Event. - Compressed JSON payloads are no longer base64 encoded. - Rename ``EventSendFailureCallback`` to ``EventSendCallback``, add ``onSuccess`` callback. -- Add way to set release, environment, serverName, tags on ``SentryClient``. +- Add way to set release, dist, environment, serverName, tags, and extraTags on a ``SentryClient``. +- Add way to set release, dist, environment, serverName, tags, and extraTags via the DSN. Version 8.0.2 ------------- diff --git a/docs/config.rst b/docs/config.rst index 55ba0d7df06..0d614967197 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -84,7 +84,7 @@ useful inside shared application containers), :: - http://public:private@host:port/1?http.proxy.host=proxy.example.com&sentry.http.proxy.port=8080 + http://public:private@host:port/1?http.proxy.host=proxy.example.com&http.proxy.port=8080 Options ------- @@ -99,6 +99,75 @@ DSN: Some options do not require a value, just being declared signifies that the option is enabled. + +Release +~~~~~~~ + +To set the application version that will be sent with each event, use the +``release`` option: + +:: + + http://public:private@host:port/1?release=1.0.0 + + +Distribution +```````````` + +To set the application distribution that will be sent with each event, use the +``dist`` option: + +:: + + http://public:private@host:port/1?release=1.0.0&dist=x86 + +Note that the distribution is only useful (and used) if the ``release`` is also +set. + +Environment +~~~~~~~~~~~ + +To set the application environment that will be sent with each event, use the +``environment`` option: + +:: + + http://public:private@host:port/1?environment=staging + +Server Name +~~~~~~~~~~~ + +To set the server name that will be sent with each event, use the +``servername`` option: + +:: + + http://public:private@host:port/1?servername=host1 + +Tags +~~~~ + +To set tags that will be sent with each event, use the ``tags`` option with +comma separated pairs of keys and values that are joined by a colon: + +:: + + http://public:private@host:port/1?tags=tag1:value1,tag2:value2 + +Extra Tags +`````````` + +To set extras that are extracted and used as additional tags, use the +``extratags`` option with comma separated key names. + +:: + + http://public:private@host:port/1?extratags=foo,bar + +Note that how these extra tags are used depends on which integration you are +using. For example: when using a logging integration any SLF4J MDC keys that +are in the extra tags set will be extracted and set as tags on events. + Async Connection ~~~~~~~~~~~~~~~~ @@ -106,7 +175,7 @@ In order to avoid performance issues due to a large amount of logs being generated or a slow connection to the Sentry server, an asynchronous connection is set up, using a low priority thread pool to submit events to Sentry. -To disable the async mode, add ``sentry.async=false`` to the DSN: +To disable the async mode, add async=false`` to the DSN: :: @@ -118,7 +187,7 @@ Graceful Shutdown (Advanced) In order to shutdown the asynchronous connection gracefully, a ``ShutdownHook`` is created. By default, the asynchronous connection is given 1 second to shutdown gracefully, but this can be adjusted via -``sentry.async.shutdowntimeout`` (represented in milliseconds): +async.shutdowntimeout`` (represented in milliseconds): :: @@ -137,7 +206,7 @@ To avoid this behaviour, it is possible to disable the graceful shutdown. This might lead to some log entries being lost if the log application doesn't shut down the ``SentryClient`` instance nicely. -The option to do so is ``sentry.async.gracefulshutdown``: +The option to do so is async.gracefulshutdown``: :: @@ -152,7 +221,7 @@ never sent to the Sentry server. Depending on the environment (if the memory is sparse) it is important to be able to control the size of that queue to avoid memory issues. -It is possible to set a maximum with the option ``sentry.async.queuesize``: +It is possible to set a maximum with the option async.queuesize``: :: @@ -172,7 +241,7 @@ By default the thread pool used by the async connection contains one thread per processor available to the JVM. It's possible to manually set the number of threads (for example if you want -only one thread) with the option ``sentry.async.threads``: +only one thread) with the option async.threads``: :: @@ -186,7 +255,7 @@ running smoothly, so the threads have a `minimal priority `_. It is possible to customise this value to increase the priority of those threads -with the option ``sentry.async.priority``: +with the option async.priority``: :: @@ -196,7 +265,7 @@ Buffering Events to Disk ~~~~~~~~~~~~~~~~~~~~~~~~ Sentry can be configured to write events to a specified directory on disk -anytime communication with the Sentry server fails with the ``sentry.buffer.dir`` +anytime communication with the Sentry server fails with the buffer.dir`` option. If the directory doesn't exist, Sentry will attempt to create it on startup and may therefore need write permission on the parent directory. Sentry always requires write permission on the buffer directory itself. @@ -206,7 +275,7 @@ Sentry always requires write permission on the buffer directory itself. http://public:private@host:port/1?buffer.dir=sentry-events The maximum number of events that will be stored on disk defaults to 50, -but can also be configured with the option ``sentry.buffer.size``: +but can also be configured with the option buffer.size``: :: @@ -215,7 +284,7 @@ but can also be configured with the option ``sentry.buffer.size``: If a buffer directory is provided, a background thread will periodically attempt to re-send the events that are found on disk. By default it will attempt to send events every 60 seconds. You can change this with the -``sentry.buffer.flushtime`` option (in milliseconds): +buffer.flushtime`` option (in milliseconds): :: @@ -227,7 +296,7 @@ Graceful Shutdown (Advanced) In order to shutdown the buffer flushing thread gracefully, a ``ShutdownHook`` is created. By default, the buffer flushing thread is given 1 second to shutdown gracefully, but this can be adjusted via -``sentry.buffer.shutdowntimeout`` (represented in milliseconds): +buffer.shutdowntimeout`` (represented in milliseconds): :: @@ -243,7 +312,7 @@ An example would be in a JEE environment where the application using Sentry could be deployed and undeployed regularly. To avoid this behaviour, it is possible to disable the graceful shutdown -by setting the ``sentry.buffer.gracefulshutdown`` option: +by setting the buffer.gracefulshutdown`` option: :: @@ -252,7 +321,7 @@ by setting the ``sentry.buffer.gracefulshutdown`` option: Event Sampling ~~~~~~~~~~~~~~ -Sentry can be configured to sample events with the ``sentry.sample.rate`` option: +Sentry can be configured to sample events with the sample.rate`` option: :: @@ -272,7 +341,7 @@ is visible in the Sentry web interface where only the "in application" frames ar displayed by default. You can configure which package prefixes your application uses with the -``sentry.stacktrace.app.packages`` option, which takes a comma separated list. +stacktrace.app.packages`` option, which takes a comma separated list. :: @@ -303,7 +372,7 @@ StackTrace is printed, the result looks like this: Some frames are replaced by the ``... N more`` line as they are the same frames as in the enclosing exception. -To enable a similar behaviour in Sentry use the ``sentry.stacktrace.hidecommon`` option. +To enable a similar behaviour in Sentry use the stacktrace.hidecommon`` option. :: @@ -321,7 +390,7 @@ limited connection, Sentry hosted on an external network), it can be useful to compress the data beforehand or not. It's possible to manually enable/disable the compression with the option -``sentry.compression`` +compression`` :: @@ -331,7 +400,7 @@ Max Message Size ~~~~~~~~~~~~~~~~ By default only the first 1000 characters of a message will be sent to -the server. This can be changed with the ``sentry.maxmessagelength`` option. +the server. This can be changed with the maxmessagelength`` option. :: @@ -343,7 +412,7 @@ Timeout (Advanced) A timeout is set to avoid blocking Sentry threads because establishing a connection is taking too long. -It's possible to manually set the timeout length with ``sentry.timeout`` +It's possible to manually set the timeout length with timeout`` (in milliseconds): :: diff --git a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java index bac4a63c343..9aed17efaa3 100644 --- a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java +++ b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java @@ -308,10 +308,11 @@ protected Event buildEvent(LoggingEvent loggingEvent) { eventBuilder.withExtra(LOG4J_NDC, loggingEvent.getNDC()); } + Set clientExtraTags = sentryClient.getExtraTags(); @SuppressWarnings("unchecked") Map properties = (Map) loggingEvent.getProperties(); for (Map.Entry mdcEntry : properties.entrySet()) { - if (extraTags.contains(mdcEntry.getKey())) { + if (extraTags.contains(mdcEntry.getKey()) || clientExtraTags.contains(mdcEntry.getKey())) { eventBuilder.withTag(mdcEntry.getKey(), mdcEntry.getValue().toString()); } else { eventBuilder.withExtra(mdcEntry.getKey(), mdcEntry.getValue()); diff --git a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java index 12a641496b9..6ba4af5531a 100644 --- a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -385,9 +385,11 @@ protected Event buildEvent(LogEvent event) { eventBuilder.withExtra(LOG4J_NDC, event.getContextStack().asList()); } + Set clientExtraTags = sentryClient.getExtraTags(); if (event.getContextMap() != null) { for (Map.Entry contextEntry : event.getContextMap().entrySet()) { - if (extraTags.contains(contextEntry.getKey())) { + if (extraTags.contains(contextEntry.getKey()) + || clientExtraTags.contains(contextEntry.getKey())) { eventBuilder.withTag(contextEntry.getKey(), contextEntry.getValue()); } else { eventBuilder.withExtra(contextEntry.getKey(), contextEntry.getValue()); diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index 0c407b22d96..5cd5d4460e4 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -311,8 +311,9 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { eventBuilder.withExtra(contextEntry.getKey(), contextEntry.getValue()); } + Set clientExtraTags = sentryClient.getExtraTags(); for (Map.Entry mdcEntry : iLoggingEvent.getMDCPropertyMap().entrySet()) { - if (extraTags.contains(mdcEntry.getKey())) { + if (extraTags.contains(mdcEntry.getKey()) || clientExtraTags.contains(mdcEntry.getKey())) { eventBuilder.withTag(mdcEntry.getKey(), mdcEntry.getValue()); } else { eventBuilder.withExtra(mdcEntry.getKey(), mdcEntry.getValue()); diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index bf11d1c30fe..730c788e03f 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -6,6 +6,7 @@ import io.sentry.context.ContextManager; import io.sentry.context.ThreadLocalContextManager; import io.sentry.dsn.Dsn; +import io.sentry.event.Event; import io.sentry.event.helper.ContextBuilderHelper; import io.sentry.event.helper.HttpEventBuilderHelper; import io.sentry.event.interfaces.*; @@ -180,6 +181,30 @@ public class DefaultSentryClientFactory extends SentryClientFactory { * The default HTTP proxy port to use if an HTTP Proxy hostname is set but port is not. */ public static final int HTTP_PROXY_PORT_DEFAULT = 80; + /** + * Option to set the version of the application. + */ + public static final String RELEASE_OPTION = "release"; + /** + * Option to set the distribution of the application. + */ + public static final String DIST_OPTION = "dist"; + /** + * Option to set the environment of the application. + */ + public static final String ENVIRONMENT_OPTION = "environment"; + /** + * Option to set the server name. + */ + public static final String SERVERNAME_OPTION = "servername"; + /** + * Option to set additional tags to be sent to Sentry. + */ + public static final String TAGS_OPTION = "tags"; + /** + * Option to set extras to extract and send as tags, where applicable. + */ + public static final String EXTRA_TAGS_OPTION = "extratags"; private static final Logger logger = LoggerFactory.getLogger(DefaultSentryClientFactory.class); private static final String FALSE = Boolean.FALSE.toString(); @@ -205,6 +230,51 @@ public SentryClient createSentryClient(Dsn dsn) { + " or provides an unsupported version."); } sentryClient.addBuilderHelper(new ContextBuilderHelper(sentryClient)); + return configureSentryClient(sentryClient, dsn); + } + + /** + * Configures a {@link SentryClient} instance after it has been constructed. + * + * @param sentryClient The {@link SentryClient} to configure. + * @param dsn Data Source Name of the Sentry server to use. + * @return The same {@link SentryClient} instance, after configuration. + */ + protected SentryClient configureSentryClient(SentryClient sentryClient, Dsn dsn) { + String release = getRelease(dsn); + if (release != null) { + sentryClient.setRelease(release); + } + + String dist = getDist(dsn); + if (dist != null) { + sentryClient.setDist(dist); + } + + String environment = getEnvironment(dsn); + if (environment != null) { + sentryClient.setEnvironment(environment); + } + + String serverName = getServerName(dsn); + if (serverName != null) { + sentryClient.setServerName(serverName); + } + + Map tags = getTags(dsn); + if (!tags.isEmpty()) { + for (Map.Entry tagEntry : tags.entrySet()) { + sentryClient.addTag(tagEntry.getKey(), tagEntry.getValue()); + } + } + + Set extraTags = getExtraTags(dsn); + if (!extraTags.isEmpty()) { + for (String extraTag : extraTags) { + sentryClient.addExtraTag(extraTag); + } + } + return sentryClient; } @@ -587,6 +657,73 @@ protected String getProxyPass(Dsn dsn) { return dsn.getOptions().get(HTTP_PROXY_PASS_OPTION); } + /** + * Application version to send with {@link io.sentry.event.Event}s that don't already + * have a value for the field set. + * + * @param dsn Sentry server DSN which may contain options. + * @return Application version to send with {@link io.sentry.event.Event}s. + */ + protected String getRelease(Dsn dsn) { + return dsn.getOptions().get(RELEASE_OPTION); + } + + /** + * Application distribution to send with {@link io.sentry.event.Event}s that don't already + * have a value for the field set. + * + * @param dsn Sentry server DSN which may contain options. + * @return Application version to send with {@link io.sentry.event.Event}s. + */ + protected String getDist(Dsn dsn) { + return dsn.getOptions().get(DIST_OPTION); + } + + /** + * Application environmentribution to send with {@link io.sentry.event.Event}s that don't already + * have a value for the field set. + * + * @param dsn Sentry server DSN which may contain options. + * @return Application version to send with {@link io.sentry.event.Event}s. + */ + protected String getEnvironment(Dsn dsn) { + return dsn.getOptions().get(ENVIRONMENT_OPTION); + } + + /** + * Server name to send with {@link io.sentry.event.Event}s that don't already + * have a value for the field set. + * + * @param dsn Sentry server DSN which may contain options. + * @return Server name to send with {@link io.sentry.event.Event}s. + */ + protected String getServerName(Dsn dsn) { + return dsn.getOptions().get(SERVERNAME_OPTION); + } + + /** + * Additional tags to send with {@link io.sentry.event.Event}s. + * + * @param dsn Sentry server DSN which may contain options. + * @return Additional tags to send with {@link io.sentry.event.Event}s. + */ + protected Map getTags(Dsn dsn) { + return Util.parseTags(dsn.getOptions().get(TAGS_OPTION)); + } + + /** + * Extras to extract and send as tags on {@link io.sentry.event.Event}s, where applicable. + *

    + * For example: when using a logging integration any {@link org.slf4j.MDC} keys that are in + * the {@link #getExtraTags(Dsn)} set will be extracted and set as tags on the {@link Event}. + * + * @param dsn Sentry server DSN which may contain options. + * @return Extras to use as tags on {@link io.sentry.event.Event}s, where applicable. + */ + protected Set getExtraTags(Dsn dsn) { + return Util.parseExtraTags(dsn.getOptions().get(EXTRA_TAGS_OPTION)); + } + /** * Whether to compress requests sent to the Sentry Server. * diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 079f89da3d3..0d8519f9097 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -14,9 +14,7 @@ import org.slf4j.LoggerFactory; import java.io.IOException; -import java.util.Collections; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** @@ -60,7 +58,14 @@ public class SentryClient { *

    * Might be empty in which case no tags are sent. */ - protected final Map tags = new ConcurrentHashMap<>(); + protected Map tags = new HashMap<>(); + /** + * Extras to extract and use as tags, where applicable. + *

    + * For example: when using a logging integration any {@link org.slf4j.MDC} keys that are in + * the {@link #extraTags} set will be extracted and set as tags on the {@link Event}. + */ + protected Set extraTags = new HashSet<>(); /** * The underlying {@link Connection} to use for sending events to Sentry. */ @@ -238,6 +243,10 @@ public Map getTags() { return Collections.unmodifiableMap(tags); } + public Set getExtraTags() { + return Collections.unmodifiableSet(extraTags); + } + public void setRelease(String release) { this.release = release; } @@ -270,8 +279,39 @@ public void addTag(String name, String value) { * @param tags Map of tags */ public void setTags(Map tags) { - this.tags.clear(); - this.tags.putAll(tags); + if (tags == null) { + this.tags = new HashMap<>(); + } else { + this.tags = tags; + } + } + + /** + * Set the extras to extract and send as tags on all future {@link Event}s, where applicable. + *

    + * For example: when using a logging integration any {@link org.slf4j.MDC} keys that are in + * the {@link #extraTags} set will be extracted and set as tags on the {@link Event}. + * + * @param extraTags Set of extras + */ + public void setExtraTags(Set extraTags) { + if (extraTags == null) { + this.extraTags = new HashSet<>(); + } else { + this.extraTags = extraTags; + } + } + + /** + * Add an extra to extract and send as tags on all future {@link Event}s, where applicable. + *

    + * For example: when using a logging integration any {@link org.slf4j.MDC} keys that are in + * the {@link #extraTags} set will be extracted and set as tags on the {@link Event}. + * + * @param extraName Extra name + */ + public void addExtraTag(String extraName) { + this.extraTags.add(extraName); } /** @@ -292,6 +332,7 @@ public String toString() { + ", environment='" + environment + '\'' + ", serverName='" + serverName + '\'' + ", tags=" + tags + + ", extraTags=" + extraTags + ", connection=" + connection + ", builderHelpers=" + builderHelpers + ", contextManager=" + contextManager diff --git a/sentry/src/main/java/io/sentry/jul/SentryHandler.java b/sentry/src/main/java/io/sentry/jul/SentryHandler.java index 646680fa6ac..84239099ba5 100644 --- a/sentry/src/main/java/io/sentry/jul/SentryHandler.java +++ b/sentry/src/main/java/io/sentry/jul/SentryHandler.java @@ -338,9 +338,10 @@ protected Event buildEvent(LogRecord record) { } Map mdc = MDC.getMDCAdapter().getCopyOfContextMap(); + Set clientExtraTags = sentryClient.getExtraTags(); if (mdc != null) { for (Map.Entry mdcEntry : mdc.entrySet()) { - if (extraTags.contains(mdcEntry.getKey())) { + if (extraTags.contains(mdcEntry.getKey()) || clientExtraTags.contains(mdcEntry.getKey())) { eventBuilder.withTag(mdcEntry.getKey(), mdcEntry.getValue()); } else { eventBuilder.withExtra(mdcEntry.getKey(), mdcEntry.getValue()); diff --git a/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java b/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java index 9e7eb5b2623..86b2fc8d061 100644 --- a/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java +++ b/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java @@ -2,11 +2,12 @@ import org.testng.annotations.Test; -import java.util.ServiceLoader; +import java.util.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; public class DefaultSentryClientFactoryTest { @Test @@ -15,4 +16,35 @@ public void checkServiceLoaderProvidesFactory() throws Exception { assertThat(sentryFactories, contains(instanceOf(DefaultSentryClientFactory.class))); } + + @Test + public void testFieldsFromDsn() throws Exception { + String release = "rel"; + String dist = "dis"; + String environment = "env"; + String serverName = "serv"; + String tags = "foo:bar,qux:baz"; + String extraTags = "aaa,bbb"; + + String dsn = String.format("https://user:pass@example.com/1?" + + "release=%s&dist=%s&environment=%s&servername=%s&tags=%s&extratags=%s", + release, dist, environment, serverName, tags, extraTags); + SentryClient sentryClient = DefaultSentryClientFactory.sentryClient(dsn); + + assertThat(sentryClient.getRelease(), is(release)); + assertThat(sentryClient.getDist(), is(dist)); + assertThat(sentryClient.getEnvironment(), is(environment)); + assertThat(sentryClient.getServerName(), is(serverName)); + + Map tagsMap = new HashMap<>(); + tagsMap.put("foo", "bar"); + tagsMap.put("qux", "baz"); + assertThat(sentryClient.getTags(), is(tagsMap)); + + Set extraTagsSet = new HashSet<>(); + extraTagsSet.add("aaa"); + extraTagsSet.add("bbb"); + assertThat(sentryClient.getExtraTags(), is(extraTagsSet)); + + } } From 8d89a234fe6759fd9a06db836ecbccd97512a79f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 2 May 2017 14:59:40 -0500 Subject: [PATCH 1587/2152] Add BaseTest. --- sentry/src/test/java/io/sentry/BaseTest.java | 7 +++++++ .../src/test/java/io/sentry/ContextTest.java | 4 +--- .../DefaultSentryClientFactoryTest.java | 2 +- .../io/sentry/SentryClientFactoryTest.java | 2 +- .../test/java/io/sentry/SentryClientTest.java | 2 +- .../src/test/java/io/sentry/SentryTest.java | 2 +- .../java/io/sentry/buffer/BufferTest.java | 20 ------------------- .../java/io/sentry/buffer/DiskBufferTest.java | 18 ++++++++++++++++- .../connection/AbstractConnectionTest.java | 3 ++- .../connection/AsyncConnectionTest.java | 3 ++- .../sentry/connection/HttpConnectionTest.java | 3 ++- .../OutputStreamConnectionTest.java | 3 ++- .../connection/RandomEventSamplerTest.java | 3 ++- .../src/test/java/io/sentry/dsn/DsnTest.java | 3 ++- .../environment/SentryEnvironmentTest.java | 3 ++- .../java/io/sentry/event/BreadcrumbTest.java | 3 ++- .../event/EventBuilderHostnameCacheTest.java | 3 ++- .../io/sentry/event/EventBuilderTest.java | 3 ++- .../test/java/io/sentry/event/EventTest.java | 3 ++- .../test/java/io/sentry/event/UserTest.java | 3 ++- .../helper/HttpEventBuilderHelperTest.java | 3 ++- .../event/interfaces/HttpInterfaceTest.java | 3 ++- .../interfaces/MessageInterfaceTest.java | 3 ++- .../event/interfaces/SentryExceptionTest.java | 3 ++- .../interfaces/StackTraceInterfaceTest.java | 3 ++- .../event/interfaces/UserInterfaceTest.java | 3 ++- .../helper/RemoteAddressResolverTest.java | 3 ++- .../jul/SentryHandlerEventBuildingTest.java | 3 ++- .../UncloseableOutputStreamTest.java | 3 ++- .../json/ExceptionInterfaceBindingTest.java | 3 ++- .../json/HttpInterfaceBindingTest.java | 3 ++- .../marshaller/json/JsonComparisonUtil.java | 3 ++- .../marshaller/json/JsonMarshallerTest.java | 3 ++- .../json/MessageInterfaceBindingTest.java | 3 ++- .../json/StackTraceInterfaceBindingTest.java | 3 ++- .../json/UserInterfaceBindingTest.java | 3 ++- ...SentryServletContainerInitializerTest.java | 3 ++- .../SentryServletRequestListenerTest.java | 3 ++- .../test/java/io/sentry/time/ClockTest.java | 3 ++- 39 files changed, 91 insertions(+), 59 deletions(-) delete mode 100644 sentry/src/test/java/io/sentry/buffer/BufferTest.java diff --git a/sentry/src/test/java/io/sentry/BaseTest.java b/sentry/src/test/java/io/sentry/BaseTest.java index 16c574ef9d6..297533b5468 100644 --- a/sentry/src/test/java/io/sentry/BaseTest.java +++ b/sentry/src/test/java/io/sentry/BaseTest.java @@ -1,8 +1,15 @@ package io.sentry; +import org.testng.annotations.BeforeMethod; + import java.util.concurrent.Callable; public class BaseTest { + @BeforeMethod + public void baseTestSetup() { + Sentry.setStoredClient(null); + } + /** * To avoid littering tests with static Thread.sleep calls (because Android code must do async I/O), * we use this method to repeatedly test a predicate with a maximum wait time, returning as diff --git a/sentry/src/test/java/io/sentry/ContextTest.java b/sentry/src/test/java/io/sentry/ContextTest.java index 019f99f8589..2a232c5782f 100644 --- a/sentry/src/test/java/io/sentry/ContextTest.java +++ b/sentry/src/test/java/io/sentry/ContextTest.java @@ -15,8 +15,7 @@ import static org.hamcrest.Matchers.equalTo; @Test(singleThreaded = true) -public class ContextTest { - +public class ContextTest extends BaseTest { @Test public void testBreadcrumbs() { Context context = new Context(); @@ -87,5 +86,4 @@ public void testUser() { context.clearUser(); assertThat(context.getUser(), equalTo(null)); } - } diff --git a/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java b/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java index 86b2fc8d061..3ed6fe906cc 100644 --- a/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java +++ b/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java @@ -9,7 +9,7 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -public class DefaultSentryClientFactoryTest { +public class DefaultSentryClientFactoryTest extends BaseTest { @Test public void checkServiceLoaderProvidesFactory() throws Exception { ServiceLoader sentryFactories = ServiceLoader.load(SentryClientFactory.class); diff --git a/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java b/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java index 9a8de1944f1..a4455888c9e 100644 --- a/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java +++ b/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java @@ -16,7 +16,7 @@ import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; -public class SentryClientFactoryTest { +public class SentryClientFactoryTest extends BaseTest { @Tested private SentryClientFactory sentryClientFactory = null; @Injectable diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.java b/sentry/src/test/java/io/sentry/SentryClientTest.java index 8f17eb999ba..720c07cf03f 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.java +++ b/sentry/src/test/java/io/sentry/SentryClientTest.java @@ -21,7 +21,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -public class SentryClientTest { +public class SentryClientTest extends BaseTest { @Tested private SentryClient sentryClient = null; @Injectable diff --git a/sentry/src/test/java/io/sentry/SentryTest.java b/sentry/src/test/java/io/sentry/SentryTest.java index 3b40c4a7922..9e85d5cc95a 100644 --- a/sentry/src/test/java/io/sentry/SentryTest.java +++ b/sentry/src/test/java/io/sentry/SentryTest.java @@ -17,7 +17,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasKey; -public class SentryTest { +public class SentryTest extends BaseTest { @Tested private SentryClient sentryClient = null; @Injectable diff --git a/sentry/src/test/java/io/sentry/buffer/BufferTest.java b/sentry/src/test/java/io/sentry/buffer/BufferTest.java deleted file mode 100644 index 9a6b315af74..00000000000 --- a/sentry/src/test/java/io/sentry/buffer/BufferTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.sentry.buffer; - -import java.io.File; - -public class BufferTest { - protected void delete(File dir) { - if (!dir.exists()) { - return; - } - - if (dir.isDirectory()) { - for (File c : dir.listFiles()) { - delete(c); - } - } - if (!dir.delete()) { - throw new RuntimeException("Failed to delete dir: " + dir); - } - } -} diff --git a/sentry/src/test/java/io/sentry/buffer/DiskBufferTest.java b/sentry/src/test/java/io/sentry/buffer/DiskBufferTest.java index de2a703cdd3..33ebd3e7369 100644 --- a/sentry/src/test/java/io/sentry/buffer/DiskBufferTest.java +++ b/sentry/src/test/java/io/sentry/buffer/DiskBufferTest.java @@ -1,5 +1,6 @@ package io.sentry.buffer; +import io.sentry.BaseTest; import io.sentry.event.Breadcrumb; import io.sentry.event.BreadcrumbBuilder; import io.sentry.event.Event; @@ -17,7 +18,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; -public class DiskBufferTest extends BufferTest { +public class DiskBufferTest extends BaseTest { private static File BUFFER_DIR = new File("./sentry-test-buffer-dir"); private int maxEvents = 2; @@ -103,4 +104,19 @@ private int eventCount(Iterator events) { } return count; } + + private void delete(File dir) { + if (!dir.exists()) { + return; + } + + if (dir.isDirectory()) { + for (File c : dir.listFiles()) { + delete(c); + } + } + if (!dir.delete()) { + throw new RuntimeException("Failed to delete dir: " + dir); + } + } } diff --git a/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java b/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java index d24fce3f4d6..9387c267957 100644 --- a/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java @@ -1,5 +1,6 @@ package io.sentry.connection; +import io.sentry.BaseTest; import io.sentry.time.FixedClock; import mockit.*; import io.sentry.event.Event; @@ -17,7 +18,7 @@ import static org.hamcrest.Matchers.is; import static org.testng.AssertJUnit.fail; -public class AbstractConnectionTest { +public class AbstractConnectionTest extends BaseTest { private static final Date FIXED_DATE = new Date(1483228800L); @Injectable private final String publicKey = "9bcf4a8c-f353-4f25-9dda-76a873fff905"; diff --git a/sentry/src/test/java/io/sentry/connection/AsyncConnectionTest.java b/sentry/src/test/java/io/sentry/connection/AsyncConnectionTest.java index fc2601b8e81..b00939b6c54 100644 --- a/sentry/src/test/java/io/sentry/connection/AsyncConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/AsyncConnectionTest.java @@ -1,5 +1,6 @@ package io.sentry.connection; +import io.sentry.BaseTest; import io.sentry.SentryClient; import mockit.*; import io.sentry.environment.SentryEnvironment; @@ -10,7 +11,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; -public class AsyncConnectionTest { +public class AsyncConnectionTest extends BaseTest { @Tested private AsyncConnection asyncConnection = null; @Injectable diff --git a/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java b/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java index b202cbbe2e0..fd7ffeba784 100644 --- a/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java @@ -1,5 +1,6 @@ package io.sentry.connection; +import io.sentry.BaseTest; import mockit.*; import io.sentry.environment.SentryEnvironment; import io.sentry.event.Event; @@ -24,7 +25,7 @@ import static org.hamcrest.Matchers.not; import static org.testng.Assert.fail; -public class HttpConnectionTest { +public class HttpConnectionTest extends BaseTest { @Injectable private final String publicKey = "6cc48e8f-380c-44cc-986b-f566247a2af5"; @Injectable diff --git a/sentry/src/test/java/io/sentry/connection/OutputStreamConnectionTest.java b/sentry/src/test/java/io/sentry/connection/OutputStreamConnectionTest.java index 1d488f0b854..a657a5d1605 100644 --- a/sentry/src/test/java/io/sentry/connection/OutputStreamConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/OutputStreamConnectionTest.java @@ -1,5 +1,6 @@ package io.sentry.connection; +import io.sentry.BaseTest; import mockit.*; import io.sentry.environment.SentryEnvironment; import io.sentry.event.Event; @@ -20,7 +21,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class OutputStreamConnectionTest { +public class OutputStreamConnectionTest extends BaseTest { @Tested private OutputStreamConnection outputStreamConnection = null; @Injectable diff --git a/sentry/src/test/java/io/sentry/connection/RandomEventSamplerTest.java b/sentry/src/test/java/io/sentry/connection/RandomEventSamplerTest.java index 0ba5fba5334..d5187b57678 100644 --- a/sentry/src/test/java/io/sentry/connection/RandomEventSamplerTest.java +++ b/sentry/src/test/java/io/sentry/connection/RandomEventSamplerTest.java @@ -1,5 +1,6 @@ package io.sentry.connection; +import io.sentry.BaseTest; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import org.testng.annotations.BeforeMethod; @@ -11,7 +12,7 @@ import static org.hamcrest.Matchers.is; -public class RandomEventSamplerTest { +public class RandomEventSamplerTest extends BaseTest { private Event event = new EventBuilder().build(); private void testRandomEventSampler(double sampleRate, double fakeRandom, boolean expected) { diff --git a/sentry/src/test/java/io/sentry/dsn/DsnTest.java b/sentry/src/test/java/io/sentry/dsn/DsnTest.java index 4bbd95b36b0..292a7f791b3 100644 --- a/sentry/src/test/java/io/sentry/dsn/DsnTest.java +++ b/sentry/src/test/java/io/sentry/dsn/DsnTest.java @@ -1,5 +1,6 @@ package io.sentry.dsn; +import io.sentry.BaseTest; import io.sentry.config.JndiLookup; import mockit.Expectations; import mockit.Mocked; @@ -13,7 +14,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -public class DsnTest { +public class DsnTest extends BaseTest { @Mocked private Context mockContext = null; diff --git a/sentry/src/test/java/io/sentry/environment/SentryEnvironmentTest.java b/sentry/src/test/java/io/sentry/environment/SentryEnvironmentTest.java index dbb117c5cb9..63ab7a36822 100644 --- a/sentry/src/test/java/io/sentry/environment/SentryEnvironmentTest.java +++ b/sentry/src/test/java/io/sentry/environment/SentryEnvironmentTest.java @@ -1,12 +1,13 @@ package io.sentry.environment; +import io.sentry.BaseTest; import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class SentryEnvironmentTest { +public class SentryEnvironmentTest extends BaseTest { @AfterMethod public void tearDown() throws Exception { SentryEnvironment.SENTRY_THREAD.remove(); diff --git a/sentry/src/test/java/io/sentry/event/BreadcrumbTest.java b/sentry/src/test/java/io/sentry/event/BreadcrumbTest.java index 89b21a346e1..b7665df6f1b 100644 --- a/sentry/src/test/java/io/sentry/event/BreadcrumbTest.java +++ b/sentry/src/test/java/io/sentry/event/BreadcrumbTest.java @@ -1,5 +1,6 @@ package io.sentry.event; +import io.sentry.BaseTest; import io.sentry.SentryClient; import io.sentry.connection.Connection; import io.sentry.context.ContextManager; @@ -16,7 +17,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsEqual.equalTo; -public class BreadcrumbTest { +public class BreadcrumbTest extends BaseTest { @Tested private SentryClient sentryClient = null; diff --git a/sentry/src/test/java/io/sentry/event/EventBuilderHostnameCacheTest.java b/sentry/src/test/java/io/sentry/event/EventBuilderHostnameCacheTest.java index 2f2ee4b54d9..15b19505cae 100644 --- a/sentry/src/test/java/io/sentry/event/EventBuilderHostnameCacheTest.java +++ b/sentry/src/test/java/io/sentry/event/EventBuilderHostnameCacheTest.java @@ -1,5 +1,6 @@ package io.sentry.event; +import io.sentry.BaseTest; import mockit.*; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -13,7 +14,7 @@ import static org.hamcrest.Matchers.is; @Test(singleThreaded = true) -public class EventBuilderHostnameCacheTest { +public class EventBuilderHostnameCacheTest extends BaseTest { @Injectable private InetAddress mockLocalHost = null; @Injectable("serverName") diff --git a/sentry/src/test/java/io/sentry/event/EventBuilderTest.java b/sentry/src/test/java/io/sentry/event/EventBuilderTest.java index 50a8e829076..741b3bd5188 100644 --- a/sentry/src/test/java/io/sentry/event/EventBuilderTest.java +++ b/sentry/src/test/java/io/sentry/event/EventBuilderTest.java @@ -1,5 +1,6 @@ package io.sentry.event; +import io.sentry.BaseTest; import mockit.Injectable; import mockit.NonStrictExpectations; import io.sentry.event.interfaces.SentryInterface; @@ -16,7 +17,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -public class EventBuilderTest { +public class EventBuilderTest extends BaseTest { @Injectable private InetAddress mockLocalHost = null; diff --git a/sentry/src/test/java/io/sentry/event/EventTest.java b/sentry/src/test/java/io/sentry/event/EventTest.java index 1bb1552f7ec..b6f245f22ea 100644 --- a/sentry/src/test/java/io/sentry/event/EventTest.java +++ b/sentry/src/test/java/io/sentry/event/EventTest.java @@ -1,5 +1,6 @@ package io.sentry.event; +import io.sentry.BaseTest; import mockit.Injectable; import mockit.NonStrictExpectations; import org.hamcrest.Matchers; @@ -14,7 +15,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.sameInstance; -public class EventTest { +public class EventTest extends BaseTest { @Test(expectedExceptions = IllegalArgumentException.class) public void ensureEventIdCantBeNull() throws Exception { new Event(null); diff --git a/sentry/src/test/java/io/sentry/event/UserTest.java b/sentry/src/test/java/io/sentry/event/UserTest.java index f0ab5afd134..0683762ef8b 100644 --- a/sentry/src/test/java/io/sentry/event/UserTest.java +++ b/sentry/src/test/java/io/sentry/event/UserTest.java @@ -1,5 +1,6 @@ package io.sentry.event; +import io.sentry.BaseTest; import io.sentry.SentryClient; import io.sentry.connection.Connection; import io.sentry.context.ContextManager; @@ -15,7 +16,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsEqual.equalTo; -public class UserTest { +public class UserTest extends BaseTest { @Tested private SentryClient sentryClient = null; @Injectable diff --git a/sentry/src/test/java/io/sentry/event/helper/HttpEventBuilderHelperTest.java b/sentry/src/test/java/io/sentry/event/helper/HttpEventBuilderHelperTest.java index c253cce70de..ddd0c591575 100644 --- a/sentry/src/test/java/io/sentry/event/helper/HttpEventBuilderHelperTest.java +++ b/sentry/src/test/java/io/sentry/event/helper/HttpEventBuilderHelperTest.java @@ -1,5 +1,6 @@ package io.sentry.event.helper; +import io.sentry.BaseTest; import mockit.*; import io.sentry.event.EventBuilder; import io.sentry.event.interfaces.HttpInterface; @@ -11,7 +12,7 @@ import javax.servlet.http.HttpServletRequest; import java.security.Principal; -public class HttpEventBuilderHelperTest { +public class HttpEventBuilderHelperTest extends BaseTest { @Tested private HttpEventBuilderHelper httpEventBuilderHelper = null; @Injectable diff --git a/sentry/src/test/java/io/sentry/event/interfaces/HttpInterfaceTest.java b/sentry/src/test/java/io/sentry/event/interfaces/HttpInterfaceTest.java index a156efb6b7e..395fc6d8b68 100644 --- a/sentry/src/test/java/io/sentry/event/interfaces/HttpInterfaceTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/HttpInterfaceTest.java @@ -1,5 +1,6 @@ package io.sentry.event.interfaces; +import io.sentry.BaseTest; import mockit.Injectable; import mockit.NonStrictExpectations; import org.testng.annotations.BeforeMethod; @@ -13,7 +14,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -public class HttpInterfaceTest { +public class HttpInterfaceTest extends BaseTest { @Injectable private HttpServletRequest mockHttpServletRequest = null; @Injectable diff --git a/sentry/src/test/java/io/sentry/event/interfaces/MessageInterfaceTest.java b/sentry/src/test/java/io/sentry/event/interfaces/MessageInterfaceTest.java index 610bbcc2e2a..008b26ce45f 100644 --- a/sentry/src/test/java/io/sentry/event/interfaces/MessageInterfaceTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/MessageInterfaceTest.java @@ -1,5 +1,6 @@ package io.sentry.event.interfaces; +import io.sentry.BaseTest; import org.testng.annotations.Test; import java.util.Collections; @@ -8,7 +9,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -public class MessageInterfaceTest { +public class MessageInterfaceTest extends BaseTest { @Test public void testStandaloneMessage() throws Exception { diff --git a/sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java b/sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java index 82b366731ef..07b01428533 100644 --- a/sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java @@ -1,5 +1,6 @@ package io.sentry.event.interfaces; +import io.sentry.BaseTest; import mockit.Delegate; import mockit.Injectable; import mockit.NonStrictExpectations; @@ -10,7 +11,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class SentryExceptionTest { +public class SentryExceptionTest extends BaseTest { @Injectable private Throwable mockThrowable = null; diff --git a/sentry/src/test/java/io/sentry/event/interfaces/StackTraceInterfaceTest.java b/sentry/src/test/java/io/sentry/event/interfaces/StackTraceInterfaceTest.java index f1a153b5f49..cb3e82ed606 100644 --- a/sentry/src/test/java/io/sentry/event/interfaces/StackTraceInterfaceTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/StackTraceInterfaceTest.java @@ -1,12 +1,13 @@ package io.sentry.event.interfaces; +import io.sentry.BaseTest; import org.testng.annotations.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class StackTraceInterfaceTest { +public class StackTraceInterfaceTest extends BaseTest { @Test public void testCalculationCommonStackFrames() throws Exception { Exception exception = new RuntimeException("exception1"); diff --git a/sentry/src/test/java/io/sentry/event/interfaces/UserInterfaceTest.java b/sentry/src/test/java/io/sentry/event/interfaces/UserInterfaceTest.java index 1b3a1af4079..9a294145732 100644 --- a/sentry/src/test/java/io/sentry/event/interfaces/UserInterfaceTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/UserInterfaceTest.java @@ -1,11 +1,12 @@ package io.sentry.event.interfaces; +import io.sentry.BaseTest; import org.testng.annotations.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class UserInterfaceTest { +public class UserInterfaceTest extends BaseTest { @Test public void testListParameters() throws Exception { final String id = "a8750a8d-1f67-41d3-a83d-0f04c08a2760"; diff --git a/sentry/src/test/java/io/sentry/helper/RemoteAddressResolverTest.java b/sentry/src/test/java/io/sentry/helper/RemoteAddressResolverTest.java index f2071b67d6f..657b3bf5b10 100644 --- a/sentry/src/test/java/io/sentry/helper/RemoteAddressResolverTest.java +++ b/sentry/src/test/java/io/sentry/helper/RemoteAddressResolverTest.java @@ -1,5 +1,6 @@ package io.sentry.helper; +import io.sentry.BaseTest; import io.sentry.dsn.Dsn; import io.sentry.event.helper.BasicRemoteAddressResolver; import io.sentry.event.helper.ForwardedAddressResolver; @@ -12,7 +13,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class RemoteAddressResolverTest { +public class RemoteAddressResolverTest extends BaseTest { @Mocked private HttpServletRequest request; diff --git a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java index 717748f93c5..c9bf1143b3b 100644 --- a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java +++ b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java @@ -1,5 +1,6 @@ package io.sentry.jul; +import io.sentry.BaseTest; import io.sentry.SentryClient; import io.sentry.environment.SentryEnvironment; import io.sentry.event.interfaces.*; @@ -21,7 +22,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -public class SentryHandlerEventBuildingTest { +public class SentryHandlerEventBuildingTest extends BaseTest { @Tested private SentryHandler sentryHandler = null; @Injectable diff --git a/sentry/src/test/java/io/sentry/marshaller/UncloseableOutputStreamTest.java b/sentry/src/test/java/io/sentry/marshaller/UncloseableOutputStreamTest.java index 96305983d0f..a4f0ba622d0 100644 --- a/sentry/src/test/java/io/sentry/marshaller/UncloseableOutputStreamTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/UncloseableOutputStreamTest.java @@ -1,5 +1,6 @@ package io.sentry.marshaller; +import io.sentry.BaseTest; import mockit.Injectable; import mockit.Tested; import mockit.Verifications; @@ -7,7 +8,7 @@ import java.io.OutputStream; -public class UncloseableOutputStreamTest { +public class UncloseableOutputStreamTest extends BaseTest { @Tested private Marshaller.UncloseableOutputStream uncloseableOutputStream = null; @Injectable diff --git a/sentry/src/test/java/io/sentry/marshaller/json/ExceptionInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/ExceptionInterfaceBindingTest.java index 31ae3de6874..eab694c55db 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/ExceptionInterfaceBindingTest.java @@ -1,6 +1,7 @@ package io.sentry.marshaller.json; import com.fasterxml.jackson.core.JsonGenerator; +import io.sentry.BaseTest; import mockit.Delegate; import mockit.Injectable; import mockit.NonStrictExpectations; @@ -19,7 +20,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class ExceptionInterfaceBindingTest { +public class ExceptionInterfaceBindingTest extends BaseTest { @Tested private ExceptionInterfaceBinding interfaceBinding = null; @Injectable diff --git a/sentry/src/test/java/io/sentry/marshaller/json/HttpInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/HttpInterfaceBindingTest.java index 44ff91eadde..f82710b164e 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/HttpInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/HttpInterfaceBindingTest.java @@ -1,6 +1,7 @@ package io.sentry.marshaller.json; import com.fasterxml.jackson.databind.JsonNode; +import io.sentry.BaseTest; import io.sentry.event.interfaces.HttpInterface; import mockit.Injectable; import mockit.NonStrictExpectations; @@ -16,7 +17,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class HttpInterfaceBindingTest { +public class HttpInterfaceBindingTest extends BaseTest { @Tested private HttpInterfaceBinding interfaceBinding = null; @Injectable diff --git a/sentry/src/test/java/io/sentry/marshaller/json/JsonComparisonUtil.java b/sentry/src/test/java/io/sentry/marshaller/json/JsonComparisonUtil.java index ac774d83d03..473d9a45dd2 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/JsonComparisonUtil.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/JsonComparisonUtil.java @@ -3,11 +3,12 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import io.sentry.BaseTest; import java.io.ByteArrayOutputStream; import java.io.OutputStream; -public final class JsonComparisonUtil { +public final class JsonComparisonUtil extends BaseTest { private static final ObjectMapper objectMapper = new ObjectMapper(); private JsonComparisonUtil() { diff --git a/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java b/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java index 6e38a194743..223fadfda80 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import io.sentry.BaseTest; import io.sentry.event.Breadcrumb; import io.sentry.event.BreadcrumbBuilder; import mockit.*; @@ -25,7 +26,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class JsonMarshallerTest { +public class JsonMarshallerTest extends BaseTest { @Tested private JsonMarshaller jsonMarshaller = null; @Injectable diff --git a/sentry/src/test/java/io/sentry/marshaller/json/MessageInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/MessageInterfaceBindingTest.java index b7cdbbb6946..d00a95d6579 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/MessageInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/MessageInterfaceBindingTest.java @@ -1,5 +1,6 @@ package io.sentry.marshaller.json; +import io.sentry.BaseTest; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; @@ -13,7 +14,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class MessageInterfaceBindingTest { +public class MessageInterfaceBindingTest extends BaseTest { @Tested private MessageInterfaceBinding interfaceBinding = null; @Injectable diff --git a/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java index b87565643b4..245334b9223 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java @@ -1,5 +1,6 @@ package io.sentry.marshaller.json; +import io.sentry.BaseTest; import io.sentry.event.interfaces.SentryStackTraceElement; import mockit.Injectable; import mockit.NonStrictExpectations; @@ -11,7 +12,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class StackTraceInterfaceBindingTest { +public class StackTraceInterfaceBindingTest extends BaseTest { @Tested private StackTraceInterfaceBinding interfaceBinding = null; @Injectable diff --git a/sentry/src/test/java/io/sentry/marshaller/json/UserInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/UserInterfaceBindingTest.java index e60150064e4..1d4d1228a0e 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/UserInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/UserInterfaceBindingTest.java @@ -1,5 +1,6 @@ package io.sentry.marshaller.json; +import io.sentry.BaseTest; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; @@ -12,7 +13,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class UserInterfaceBindingTest { +public class UserInterfaceBindingTest extends BaseTest { @Tested private UserInterfaceBinding userInterfaceBinding = null; @Injectable diff --git a/sentry/src/test/java/io/sentry/servlet/SentryServletContainerInitializerTest.java b/sentry/src/test/java/io/sentry/servlet/SentryServletContainerInitializerTest.java index 87f7035964c..1e41ded6aca 100644 --- a/sentry/src/test/java/io/sentry/servlet/SentryServletContainerInitializerTest.java +++ b/sentry/src/test/java/io/sentry/servlet/SentryServletContainerInitializerTest.java @@ -1,5 +1,6 @@ package io.sentry.servlet; +import io.sentry.BaseTest; import mockit.Injectable; import mockit.Tested; import mockit.Verifications; @@ -13,7 +14,7 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.instanceOf; -public class SentryServletContainerInitializerTest { +public class SentryServletContainerInitializerTest extends BaseTest { @Tested private SentryServletContainerInitializer sentryServletContainerInitializer = null; diff --git a/sentry/src/test/java/io/sentry/servlet/SentryServletRequestListenerTest.java b/sentry/src/test/java/io/sentry/servlet/SentryServletRequestListenerTest.java index b5e290ea6e8..9a0331e2bec 100644 --- a/sentry/src/test/java/io/sentry/servlet/SentryServletRequestListenerTest.java +++ b/sentry/src/test/java/io/sentry/servlet/SentryServletRequestListenerTest.java @@ -1,5 +1,6 @@ package io.sentry.servlet; +import io.sentry.BaseTest; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; @@ -15,7 +16,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; -public class SentryServletRequestListenerTest { +public class SentryServletRequestListenerTest extends BaseTest { @Tested private SentryServletRequestListener sentryServletRequestListener = null; @Injectable diff --git a/sentry/src/test/java/io/sentry/time/ClockTest.java b/sentry/src/test/java/io/sentry/time/ClockTest.java index eb538d48ec1..30eb5cf8ba2 100644 --- a/sentry/src/test/java/io/sentry/time/ClockTest.java +++ b/sentry/src/test/java/io/sentry/time/ClockTest.java @@ -1,5 +1,6 @@ package io.sentry.time; +import io.sentry.BaseTest; import org.testng.annotations.Test; import java.util.Date; @@ -8,7 +9,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; -public class ClockTest { +public class ClockTest extends BaseTest { @Test public void testFixedClock() { From 811df631168b635600c6a18538102188b6644f9a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 22 May 2017 14:25:52 -0500 Subject: [PATCH 1588/2152] Update documentation for 1.0. (#397) --- docs/context.rst | 95 +++++++++++++-------- docs/modules/android.rst | 83 ++++++++++++++----- docs/usage.rst | 173 ++++++++++++++++++++------------------- 3 files changed, 210 insertions(+), 141 deletions(-) diff --git a/docs/context.rst b/docs/context.rst index daee057efb9..84d9e337fa9 100644 --- a/docs/context.rst +++ b/docs/context.rst @@ -21,50 +21,77 @@ To override the ``ContextManager`` you will need to override the ``getContextMan method in the ``DefaultSentryClientFactory``. A simpler API will likely be provided in the future. -Using Breadcrumbs ------------------ +Usage +----- Breadcrumbs can be used to describe actions that occurred in your application leading up to an event being sent. For example, whether external API requests were made, -or whether a user clicked on something in an Android application. +or whether a user clicked on something in an Android application. By default the last +100 breadcrumbs per context will be stored and sent with future events. -Once a ``SentryClient`` instance has been initialized, either via a logging framework or manually, -you can begin recording breadcrumbs. By default the last 100 breadcrumbs for a given -context instance will be stored and sent with future events. +The user can be set per context so that you know who was affected by each event. + +Once a ``SentryClient`` instance has been initialized you can begin setting state in +the current context. .. sourcecode:: java import io.sentry.Sentry; import io.sentry.context.Context; import io.sentry.event.BreadcrumbBuilder; - import io.sentry.event.Breadcrumbs; import io.sentry.event.UserBuilder; - public void example() { - // Record a breadcrumb without having to look up the context instance manually - Sentry.record( - new BreadcrumbBuilder().setMessage("User did something specific again!").build() - ); - - // ... or retrieve and manipulate the context instance manually - - // Retrieve the stored SentryClient instance - SentryClient sentryClient = getStoredClient(); - - // Get the current context instance - Context context = sentryClient.getContext(); - - // Set the current User in the context - context.setUser( - new UserBuilder().setUsername("user1").build() - ); - - // Record a breadcrumb in the context - context.recordBreadcrumb( - new BreadcrumbBuilder().setMessage("User did something specific!").build() - ); - - // Clear the context, useful if you need to add hooks in a framework - // to empty context between requests - context.clear() + public class MyClass { + + /** + * Examples using the (recommended) static API. + * + * Note that the ``Sentry.init`` method must be called before the static API + * is used, otherwise a ``NullPointerException`` will be thrown. + */ + public void staticAPIExample() { + Sentry.init(); + + // Set the current user in the context. + Sentry.setUser(new UserBuilder().setUsername("user1").build()); + + // Record a breadcrumb without having to look up the context instance manually. + Sentry.record( + new BreadcrumbBuilder().setMessage("User did something specific again!").build() + ); + + // Send an event with the context data attached. + Sentry.capture("New event message"); + + // Clear the context, useful if you need to add hooks in a framework + // to empty context between requests. + Sentry.clearContext(); + } + + /** + * Examples that use the SentryClient instance directly. + */ + public void instanceAPIExample() { + SentryClient sentryClient = SentryClientFactory.sentryClient(); + + // Get the current context instance. + Context context = sentryClient.getContext(); + + // Set the current user in the context. + context.setUser( + new UserBuilder().setUsername("user1").build() + ); + + // Record a breadcrumb in the context. + context.recordBreadcrumb( + new BreadcrumbBuilder().setMessage("User did something specific!").build() + ); + + // Send an event with the context data attached. + sentryClient.sendMessage("New event message"); + + // Clear the context, useful if you need to add hooks in a framework + // to empty context between requests. + context.clear(); + } } diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 1c234a33773..933b249f576 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -33,8 +33,8 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: For other dependency managers see the `central Maven repository `_. -Usage ------ +Initialization +-------------- Your application must have permission to access the internet in order to send events to the Sentry server. In your ``AndroidManifest.xml``: @@ -55,11 +55,10 @@ Then initialize the Sentry client in your application's main ``onCreate`` method public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Context ctx = this.getApplicationContext(); // Use the Sentry DSN (client key) from the Project Settings page on Sentry String sentryDsn = "https://publicKey:secretKey@host:port/1?options"; - - Sentry.init(ctx, sentryDsn); + Context ctx = this.getApplicationContext(); + Sentry.init(sentryDsn, new AndroidSentryClientFactory(ctx)); } } @@ -73,27 +72,69 @@ You can also configure your Sentry DSN (client key) in your ``AndroidManifest.xm android:value="https://publicKey:secretKey@host:port/1?options" /> +And then you don't need to manually provide the DSN in your code: + +.. sourcecode:: java + + import io.sentry.Sentry; + import io.sentry.android.AndroidSentryClientFactory; + + public class MainActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Context ctx = this.getApplicationContext(); + Sentry.init(new AndroidSentryClientFactory(ctx)); + } + } + +Usage +----- + Now you can use ``Sentry`` to capture events anywhere in your application: .. sourcecode:: java - // Send a simple event to the Sentry server - Sentry.capture("Error message"); + import io.sentry.context.Context; + import io.sentry.event.BreadcrumbBuilder; + import io.sentry.event.UserBuilder; - // Set a breadcrumb that will be sent with the next event(s) - Sentry.record( - new BreadcrumbBuilder().setMessage("User made an action").build() - ); + public class MyClass { + /** + * An example method that throws an exception. + */ + void unsafeMethod() { + throw new UnsupportedOperationException("You shouldn't call this!"); + } - try { - something() - } catch (Exception e) { - // Send an exception event to the Sentry server - Sentry.capture(e); + /** + * Note that the ``Sentry.init`` method must be called before the static API + * is used, otherwise a ``NullPointerException`` will be thrown. + */ + void logWithStaticAPI() { + /* + Record a breadcrumb in the current context which will be sent + with the next event(s). By default the last 100 breadcrumbs are kept. + */ + Sentry.record(new BreadcrumbBuilder().setMessage("User made an action").build()); + + // Set the user in the current context. + Sentry.setUser(new UserBuilder().setEmail("hello@sentry.io").build()); + + /* + This sends a simple event to Sentry using the statically stored instance + that was created in the ``main`` method. + */ + Sentry.capture("This is a test"); + + try { + unsafeMethod(); + } catch (Exception e) { + // This sends an exception event to Sentry using the statically stored instance + // that was created in the ``main`` method. + Sentry.capture(e); + } + } } - // Or build an event manually - EventBuilder eventBuilder = new EventBuilder() - .withMessage("Exception caught") - .withLevel(Event.Level.ERROR); - Sentry.capture(eventBuilder.build()); diff --git a/docs/usage.rst b/docs/usage.rst index 205843549ce..6ecbd1ef18f 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -35,56 +35,108 @@ For other dependency managers see the `central Maven repository Date: Wed, 24 May 2017 15:12:20 -0500 Subject: [PATCH 1589/2152] Initial commit. --- .gitignore | 8 ++++++++ CMakeLists.txt | 13 +++++++++++++ agent.cpp | 13 +++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 agent.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..bd813a179d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +CMakeCache.txt +CMakeFiles/ +Makefile +cmake-build-debug/ +cmake_install.cmake +sentry_java_agent.cbp +*.a +*.dylib diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000000..9eaec79f990 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.7) +project(sentry_java_agent) + +set(CMAKE_CXX_STANDARD 14) + +set(SOURCE_FILES agent.cpp) +add_library(sentry_agent SHARED ${SOURCE_FILES}) + +include_directories($ENV{JAVA_HOME}/include) + +if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + include_directories($ENV{JAVA_HOME}/include/darwin) +endif() diff --git a/agent.cpp b/agent.cpp new file mode 100644 index 00000000000..59039921fe1 --- /dev/null +++ b/agent.cpp @@ -0,0 +1,13 @@ +#include "jvmti.h" + +#include + +JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + std::cout << "Hello, World!" << std::endl; + + return JNI_OK; +} + +JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) { + std::cout << "Goodbye, World!" << std::endl; +} \ No newline at end of file From 59808bf28377a57e214355ffccb63e9a3211d64a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 25 May 2017 13:00:55 -0500 Subject: [PATCH 1590/2152] Setup logging and OnLoad. --- agent.cpp | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 5 deletions(-) diff --git a/agent.cpp b/agent.cpp index 59039921fe1..d06d28d7d88 100644 --- a/agent.cpp +++ b/agent.cpp @@ -1,13 +1,92 @@ #include "jvmti.h" - #include -JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { - std::cout << "Hello, World!" << std::endl; +enum Level { + DEBUG, + INFO, + WARN, + ERROR +}; + +static const std::string LEVEL_STRINGS[] = { + "DEBUG", + "INFO", + "WARN", + "ERROR" +}; + +static Level LOG_LEVEL; + +static void log(Level level, std::string message) { + if (level >= LOG_LEVEL) { + std::cerr << LEVEL_STRINGS[level] << " [Sentry Agent]: " << message << std::endl; + } +} + +static void JNICALL ExceptionCallback(jvmtiEnv *jvmti, JNIEnv *env, jthread thread, + jmethodID method, jlocation location, jobject exception, + jmethodID catch_method, jlocation catch_location) { + log(DEBUG, "ExceptionCallback called."); +} + +JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { + LOG_LEVEL = WARN; + char *env_log_level = std::getenv("SENTRY_AGENT_LOG_LEVEL"); + if (env_log_level != nullptr) { + std::string env_log_level_str(env_log_level); + for (auto &c: env_log_level_str) c = (char) toupper(c); + if (env_log_level_str.compare("DEBUG") == 0) { + LOG_LEVEL = DEBUG; + } else if (env_log_level_str.compare("INFO") == 0) { + LOG_LEVEL = INFO; + } else if (env_log_level_str.compare("WARN") == 0) { + LOG_LEVEL = WARN; + } else if (env_log_level_str.compare("ERROR") == 0) { + LOG_LEVEL = ERROR; + } else { + LOG_LEVEL = WARN; + log(ERROR, "Unknown log level: " + env_log_level_str); + } + } + + log(DEBUG, "OnLoad called."); + + jvmtiEnv *jvmti; + jint jvmti_error = vm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_0); + if (jvmti_error != JVMTI_ERROR_NONE || jvmti == nullptr) { + log(ERROR, "Unable to access JVMTI Version 1."); + return JNI_ABORT; + } + + jvmtiCapabilities capabilities; + memset(&capabilities, 0, sizeof(capabilities)); + capabilities.can_access_local_variables = 1; + capabilities.can_generate_exception_events = 1; + capabilities.can_get_line_numbers = 1; + jint capabilities_error = jvmti->AddCapabilities(&capabilities); + if (capabilities_error != JVMTI_ERROR_NONE) { + log(ERROR, "Unable to get the necessary JVMTI capabilities."); + return JNI_ABORT; + } + + jvmtiEventCallbacks callbacks; + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.Exception = &ExceptionCallback; + jint callbacks_error = jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)); + if (callbacks_error != JVMTI_ERROR_NONE) { + log(ERROR, "Unable to the necessary JVMTI callbacks."); + return JNI_ABORT; + } + + jint notification_error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, nullptr); + if (notification_error != JVMTI_ERROR_NONE) { + log(ERROR, "Unable to set the necessary JVMTI event notification mode."); + return JNI_ABORT; + } return JNI_OK; } JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) { - std::cout << "Goodbye, World!" << std::endl; -} \ No newline at end of file + log(DEBUG, "Unload called."); +} From ef181d83f78e470545605294b2701c1fa4005f8f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 25 May 2017 13:04:03 -0500 Subject: [PATCH 1591/2152] .gitignore and LICENSE --- .gitignore | 36 ++++++++++++++++++++++++++++++++++-- LICENSE | 21 +++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 LICENSE diff --git a/.gitignore b/.gitignore index bd813a179d2..110456abe75 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,40 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Custom CMakeCache.txt CMakeFiles/ Makefile cmake-build-debug/ cmake_install.cmake sentry_java_agent.cbp -*.a -*.dylib diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000000..0eaccd3e00e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2017 Sentry (https://sentry.io) +Copyright (c) 2017 Mike Clarke +Copyright (c) 2008 Tobias Ivarsson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 41a7fc9ae4ddf2414875ca5a2610afd854d1e52a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 25 May 2017 15:32:42 -0500 Subject: [PATCH 1592/2152] Pull in variable extraction from https://github.com/mikeclarke/raven-jvmti --- CMakeLists.txt | 2 +- agent.cpp | 83 +++++++++++--- lib.cpp | 295 +++++++++++++++++++++++++++++++++++++++++++++++++ lib.h | 8 ++ 4 files changed, 372 insertions(+), 16 deletions(-) create mode 100644 lib.cpp create mode 100644 lib.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9eaec79f990..4da94877df2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ project(sentry_java_agent) set(CMAKE_CXX_STANDARD 14) -set(SOURCE_FILES agent.cpp) +set(SOURCE_FILES agent.cpp lib.cpp lib.h) add_library(sentry_agent SHARED ${SOURCE_FILES}) include_directories($ENV{JAVA_HOME}/include) diff --git a/agent.cpp b/agent.cpp index d06d28d7d88..2d9588c5f9e 100644 --- a/agent.cpp +++ b/agent.cpp @@ -1,7 +1,9 @@ #include "jvmti.h" #include +#include "lib.h" enum Level { + TRACE, DEBUG, INFO, WARN, @@ -9,33 +11,72 @@ enum Level { }; static const std::string LEVEL_STRINGS[] = { + "TRACE", "DEBUG", "INFO", "WARN", "ERROR" }; -static Level LOG_LEVEL; +static Level LOG_LEVEL = WARN; -static void log(Level level, std::string message) { +void log(Level level, std::string message) { if (level >= LOG_LEVEL) { std::cerr << LEVEL_STRINGS[level] << " [Sentry Agent]: " << message << std::endl; } } -static void JNICALL ExceptionCallback(jvmtiEnv *jvmti, JNIEnv *env, jthread thread, +void JNICALL VMStart(jvmtiEnv *jvmti, JNIEnv *env) { + log(TRACE, "VMStart called."); + // TODO: do we need to do anything here? + log(TRACE, "VMStart exit."); +} + +void JNICALL ExceptionCallback(jvmtiEnv *jvmti, JNIEnv *env, jthread thread, jmethodID method, jlocation location, jobject exception, jmethodID catch_method, jlocation catch_location) { - log(DEBUG, "ExceptionCallback called."); + log(TRACE, "ExceptionCallback called."); + + char *class_name = (char *) "io/sentry/jvmti/LocalsCache"; + char *method_name = (char *) "setResult"; + char *signature = (char *) "([Lio/sentry/jvmti/Frame;)V"; + + jclass callback_class = nullptr; + jmethodID callback_method_id = nullptr; + + callback_class = env->FindClass(class_name); + if (callback_class == nullptr) { + env->ExceptionClear(); + log(TRACE, "Unable to locate callback class."); + return; + } + + callback_method_id = env->GetStaticMethodID(callback_class, method_name, signature); + if (callback_method_id == nullptr) { + log(TRACE, "Unable to locate static setResult method."); + return; + } + + jobjectArray frames; + jint start_depth = 0; + jboolean get_locals = JNI_TRUE; + frames = buildStackTraceFrames(jvmti, env, thread, start_depth, get_locals); + + env->CallStaticVoidMethod(callback_class, callback_method_id, frames); + + log(TRACE, "ExceptionCallback exit."); } JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { - LOG_LEVEL = WARN; + // TODO: JNI abort doesn't do I expect ... exit() or just mark a flag that the agent is disabled? + // TODO: use options instead of env char *env_log_level = std::getenv("SENTRY_AGENT_LOG_LEVEL"); if (env_log_level != nullptr) { std::string env_log_level_str(env_log_level); for (auto &c: env_log_level_str) c = (char) toupper(c); - if (env_log_level_str.compare("DEBUG") == 0) { + if (env_log_level_str.compare("TRACE") == 0) { + LOG_LEVEL = TRACE; + } else if (env_log_level_str.compare("DEBUG") == 0) { LOG_LEVEL = DEBUG; } else if (env_log_level_str.compare("INFO") == 0) { LOG_LEVEL = INFO; @@ -44,15 +85,17 @@ JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { } else if (env_log_level_str.compare("ERROR") == 0) { LOG_LEVEL = ERROR; } else { - LOG_LEVEL = WARN; log(ERROR, "Unknown log level: " + env_log_level_str); + return JNI_ABORT; } } - log(DEBUG, "OnLoad called."); + log(TRACE, "OnLoad called."); jvmtiEnv *jvmti; - jint jvmti_error = vm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_0); + jint jvmti_error; + + jvmti_error = vm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_0); if (jvmti_error != JVMTI_ERROR_NONE || jvmti == nullptr) { log(ERROR, "Unable to access JVMTI Version 1."); return JNI_ABORT; @@ -72,21 +115,31 @@ JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { jvmtiEventCallbacks callbacks; memset(&callbacks, 0, sizeof(callbacks)); callbacks.Exception = &ExceptionCallback; - jint callbacks_error = jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)); - if (callbacks_error != JVMTI_ERROR_NONE) { + callbacks.VMStart = &VMStart; + jvmti_error = jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)); + if (jvmti_error != JVMTI_ERROR_NONE) { log(ERROR, "Unable to the necessary JVMTI callbacks."); return JNI_ABORT; } - jint notification_error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, nullptr); - if (notification_error != JVMTI_ERROR_NONE) { - log(ERROR, "Unable to set the necessary JVMTI event notification mode."); + jvmti_error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_START, nullptr); + if (jvmti_error != JVMTI_ERROR_NONE) { + log(ERROR, "Unable to register the VMStart callback."); return JNI_ABORT; } + jvmti_error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, nullptr); + if (jvmti_error != JVMTI_ERROR_NONE) { + log(ERROR, "Unable to register the exception callback."); + return JNI_ABORT; + } + + log(TRACE, "OnLoad exit."); return JNI_OK; } JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) { - log(DEBUG, "Unload called."); + log(TRACE, "Unload called."); + + log(TRACE, "Unload exit."); } diff --git a/lib.cpp b/lib.cpp new file mode 100644 index 00000000000..47426adc8b4 --- /dev/null +++ b/lib.cpp @@ -0,0 +1,295 @@ +#include "jvmti.h" + +jint throwException(JNIEnv *env, const char *name, const char *message) { + jclass clazz; + clazz = env->FindClass(name); + return env->ThrowNew(clazz, message); +} + +jobject getLocalValue(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, + jvmtiLocalVariableEntry *table, int index) { + jobject result; + jint i_val; + jfloat f_val; + jdouble d_val; + jlong j_val; + jvmtiError jvmti_error; + jclass reflect_class; + jmethodID value_of; + + switch (table[index].signature[0]) { + case '[': // Array + case 'L': // Object + jvmti_error = jvmti->GetLocalObject(thread, depth, table[index].slot, &result); + break; + case 'J': // long + jvmti_error = jvmti->GetLocalLong(thread, depth, table[index].slot, &j_val); + break; + case 'F': // float + jvmti_error = jvmti->GetLocalFloat(thread, depth, table[index].slot, &f_val); + break; + case 'D': // double + jvmti_error = jvmti->GetLocalDouble(thread, depth, table[index].slot, &d_val); + break; + case 'I': // int + case 'S': // short + case 'C': // char + case 'B': // byte + case 'Z': // boolean + jvmti_error = jvmti->GetLocalInt(thread, depth, table[index].slot, &i_val); + break; + // error type + default: + return nullptr; + } + + if (jvmti_error != JVMTI_ERROR_NONE) { + return nullptr; + } + + switch (table[index].signature[0]) { + case 'J': // long + reflect_class = env->FindClass("java/lang/Long"); + value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(J)Ljava/lang/Long;"); + result = env->CallStaticObjectMethod(reflect_class, value_of, j_val); + break; + case 'F': // float + reflect_class = env->FindClass("java/lang/Float"); + value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(F)Ljava/lang/Float;"); + result = env->CallStaticObjectMethod(reflect_class, value_of, f_val); + break; + case 'D': // double + reflect_class = env->FindClass("java/lang/Double"); + value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(D)Ljava/lang/Double;"); + result = env->CallStaticObjectMethod(reflect_class, value_of, d_val); + break; + // INTEGER TYPES + case 'I': // int + reflect_class = env->FindClass("java/lang/Integer"); + value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(I)Ljava/lang/Integer;"); + result = env->CallStaticObjectMethod(reflect_class, value_of, i_val); + break; + case 'S': // short + reflect_class = env->FindClass("java/lang/Short"); + value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(S)Ljava/lang/Short;"); + result = env->CallStaticObjectMethod(reflect_class, value_of, i_val); + break; + case 'C': // char + reflect_class = env->FindClass("java/lang/Character"); + value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(C)Ljava/lang/Character;"); + result = env->CallStaticObjectMethod(reflect_class, value_of, i_val); + break; + case 'B': // byte + reflect_class = env->FindClass("java/lang/Byte"); + value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(B)Ljava/lang/Byte;"); + result = env->CallStaticObjectMethod(reflect_class, value_of, i_val); + break; + case 'Z': // boolean + reflect_class = env->FindClass("java/lang/Boolean"); + value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(Z)Ljava/lang/Boolean;"); + result = env->CallStaticObjectMethod(reflect_class, value_of, i_val); + break; + default: // jobject + break; + } + + return result; +} + +void makeLocalVariable(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, + jint depth, jclass local_class, jmethodID live, + jmethodID dead, jlocation location, + jobjectArray locals, jvmtiLocalVariableEntry *table, + int index) { + jstring name; + jstring sig; + jstring gensig; + jobject value; + jobject local; + + name = env->NewStringUTF(table[index].name); + sig = env->NewStringUTF(table[index].signature); + + if (table[index].generic_signature) { + gensig = env->NewStringUTF(table[index].generic_signature); + } else { + gensig = NULL; + } + + if (location >= table[index].start_location + && location <= (table[index].start_location + table[index].length)) { + value = getLocalValue(jvmti, env, thread, depth, table, index); + local = env->NewObject(local_class, live, name, sig, gensig, value); + } else { + local = env->NewObject(local_class, dead, name, sig, gensig); + } + + env->SetObjectArrayElement(locals, index, local); +} + +jobject makeFrameObject(jvmtiEnv* jvmti, JNIEnv *env, jmethodID method, + jobject value_ptr, jobjectArray locals, jlong pos, jint lineno) { + jvmtiError jvmti_error; + jclass method_class; + jint modifiers; + jobject frame_method; + jclass frame_class; + jmethodID ctor; + + jvmti_error = jvmti->GetMethodDeclaringClass(method, &method_class); + if (jvmti_error != JVMTI_ERROR_NONE) { + throwException(env, "java/lang/RuntimeException", "Could not get the declaring class of the method."); + return nullptr; + } + + jvmti_error = jvmti->GetMethodModifiers(method, &modifiers); + if (jvmti_error != JVMTI_ERROR_NONE) { + throwException(env, "java/lang/RuntimeException", "Could not get the modifiers of the method."); + return nullptr; + } + + frame_method = env->ToReflectedMethod(method_class, method, modifiers & 8); + if (frame_method == nullptr) { + return nullptr; // ToReflectedMethod raised an exception + } + + frame_class = env->FindClass("io/sentry/jvmti/Frame"); + if (frame_class == nullptr) { + return nullptr; + } + + ctor = env->GetMethodID(frame_class, "", + "(Ljava/lang/reflect/Method;Ljava/lang/Object;[Lio/sentry/jvmti/Frame$LocalVariable;II)V"); + if (ctor == nullptr) { + return nullptr; + } + + return env->NewObject(frame_class, ctor, frame_method, value_ptr, locals, pos, lineno); +} + +jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, jboolean get_locals, + jmethodID method, jlocation location) { + jvmtiError jvmti_error; + jvmtiLocalVariableEntry *table; + jvmtiLineNumberEntry* lineno_table; + jint num_entries; + jobject value_ptr; + jobjectArray locals; + jint lineno; + jclass local_class; + jmethodID live_ctor; + jmethodID dead_ctor; + int i; + value_ptr = nullptr; + + if (get_locals) { + jvmti_error = jvmti->GetLocalVariableTable(method, &num_entries, &table); + } else { + // If the information wasn't requested, it's absent; handle as same case + jvmti_error = JVMTI_ERROR_ABSENT_INFORMATION; + } + + if (jvmti_error != JVMTI_ERROR_NONE) { + locals = NULL; + switch(jvmti_error) { + // Pass cases + case JVMTI_ERROR_ABSENT_INFORMATION: + case JVMTI_ERROR_NATIVE_METHOD: + break; + // Error cases + case JVMTI_ERROR_MUST_POSSESS_CAPABILITY: + throwException(env, "java/lang/RuntimeException", "access_local_variables capability not enabled."); + return NULL; + case JVMTI_ERROR_INVALID_METHODID: + throwException(env, "java/lang/IllegalArgumentException", "Illegal jmethodID."); + return NULL; + case JVMTI_ERROR_NULL_POINTER: + throwException(env, "java/lang/NullPointerException", "passed null to GetLocalVariableTable()."); + return NULL; + default: + throwException(env, "java/lang/RuntimeException", "Unknown JVMTI Error."); + return NULL; + } + } else { + local_class = env->FindClass("io/sentry/jvmti/Frame$LocalVariable"); + live_ctor = env->GetMethodID(local_class, "", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V"); + dead_ctor = env->GetMethodID(local_class, "", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + locals = env->NewObjectArray(num_entries, local_class, NULL); + for (i = 0; i < num_entries; i++) { + makeLocalVariable(jvmti, env, thread, depth, local_class, live_ctor, dead_ctor, location, locals, table, i); + } + jvmti->Deallocate((unsigned char *)table); + } + + if (get_locals) { + jvmti_error = jvmti->GetLocalObject(thread, depth, 0, &value_ptr); + if (jvmti_error != JVMTI_ERROR_NONE) { + value_ptr = NULL; + } + } + + jvmti_error = jvmti->GetLineNumberTable(method, &num_entries, &lineno_table); + if (jvmti_error != JVMTI_ERROR_NONE) { + lineno = -1; // Not retreived + } else { + for (i = 0; i < num_entries; i++) { + if (location < lineno_table->start_location) break; + lineno = lineno_table->line_number; + } + jvmti->Deallocate((unsigned char *)lineno_table); + } + + return makeFrameObject(jvmti, env, method, value_ptr, locals, location, lineno); +} + +jobjectArray buildStackTraceFrames(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, + jint start_depth, jboolean include_locals) { + jclass result_class; + jint i; + jvmtiFrameInfo* frames; + jint count; + jvmtiError jvmti_error; + jobjectArray result; + jobject frame; + + jvmti_error = jvmti->GetFrameCount(thread, &i); + if (jvmti_error != JVMTI_ERROR_NONE) { + throwException(env, "java/lang/RuntimeException", "Could not get the frame count."); + return nullptr; + } + + jvmti_error = jvmti->Allocate(i*(int)sizeof(jvmtiFrameInfo), (unsigned char **)&frames); + if (jvmti_error != JVMTI_ERROR_NONE) { + throwException(env, "java/lang/RuntimeException", "Could not allocate frame buffer."); + return nullptr; + } + + jvmti_error = jvmti->GetStackTrace(thread, start_depth, i, frames, &count); + if (jvmti_error != JVMTI_ERROR_NONE) { + jvmti->Deallocate((unsigned char *)frames); + throwException(env, "java/lang/RuntimeException", "Could not get stack trace."); + return nullptr; + } + + result_class = env->FindClass("io/sentry/jvmti/Frame"); + result = env->NewObjectArray(count, result_class, nullptr); + if (result == nullptr) { + jvmti->Deallocate((unsigned char *)frames); + return nullptr; // OutOfMemory + } + + for (i = 0; i < count; i++) { + frame = buildFrame(jvmti, env, thread, start_depth + i, include_locals, frames[i].method, frames[i].location); + if (frame == nullptr) { + jvmti->Deallocate((unsigned char *)frames); + throwException(env, "java/lang/RuntimeException", "Error accessing frame object."); + return nullptr; + } + env->SetObjectArrayElement(result, i, frame); + } + + jvmti->Deallocate((unsigned char *)frames); + return result; +} diff --git a/lib.h b/lib.h new file mode 100644 index 00000000000..8402d88d111 --- /dev/null +++ b/lib.h @@ -0,0 +1,8 @@ +#ifndef SENTRY_JAVA_AGENT_LIB_H +#define SENTRY_JAVA_AGENT_LIB_H + + +jobjectArray buildStackTraceFrames(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, + jint start_depth, jboolean include_locals); + +#endif //SENTRY_JAVA_AGENT_LIB_H From 0aa0496793a9e2768fd078097318e0c5d74f00da Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 26 May 2017 09:08:47 -0500 Subject: [PATCH 1593/2152] Cleanup and renames. --- agent.cpp | 30 ++++++---------------- lib.cpp | 74 +++++++++++++++++++++++++++---------------------------- 2 files changed, 44 insertions(+), 60 deletions(-) diff --git a/agent.cpp b/agent.cpp index 2d9588c5f9e..0987c93bcd4 100644 --- a/agent.cpp +++ b/agent.cpp @@ -1,3 +1,8 @@ +// TODO: better error messageds? (void) jvmti->GetErrorName(errnum, &errnum_str); +// TODO: deal with threads? jrawMonitorID lock; jvmti->RawMonitorEnter(lock); jvmti->RawMonitorExit(lock); +// TODO: JNI abort doesn't do what I'd expect ... we should exit() or just mark a flag that the agent is disabled? +// TODO: use *options instead of env for log level? + #include "jvmti.h" #include #include "lib.h" @@ -20,19 +25,13 @@ static const std::string LEVEL_STRINGS[] = { static Level LOG_LEVEL = WARN; -void log(Level level, std::string message) { +static void log(Level level, std::string message) { if (level >= LOG_LEVEL) { std::cerr << LEVEL_STRINGS[level] << " [Sentry Agent]: " << message << std::endl; } } -void JNICALL VMStart(jvmtiEnv *jvmti, JNIEnv *env) { - log(TRACE, "VMStart called."); - // TODO: do we need to do anything here? - log(TRACE, "VMStart exit."); -} - -void JNICALL ExceptionCallback(jvmtiEnv *jvmti, JNIEnv *env, jthread thread, +static void JNICALL ExceptionCallback(jvmtiEnv *jvmti, JNIEnv *env, jthread thread, jmethodID method, jlocation location, jobject exception, jmethodID catch_method, jlocation catch_location) { log(TRACE, "ExceptionCallback called."); @@ -68,8 +67,6 @@ void JNICALL ExceptionCallback(jvmtiEnv *jvmti, JNIEnv *env, jthread thread, } JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { - // TODO: JNI abort doesn't do I expect ... exit() or just mark a flag that the agent is disabled? - // TODO: use options instead of env char *env_log_level = std::getenv("SENTRY_AGENT_LOG_LEVEL"); if (env_log_level != nullptr) { std::string env_log_level_str(env_log_level); @@ -115,19 +112,12 @@ JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { jvmtiEventCallbacks callbacks; memset(&callbacks, 0, sizeof(callbacks)); callbacks.Exception = &ExceptionCallback; - callbacks.VMStart = &VMStart; jvmti_error = jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)); if (jvmti_error != JVMTI_ERROR_NONE) { log(ERROR, "Unable to the necessary JVMTI callbacks."); return JNI_ABORT; } - jvmti_error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_START, nullptr); - if (jvmti_error != JVMTI_ERROR_NONE) { - log(ERROR, "Unable to register the VMStart callback."); - return JNI_ABORT; - } - jvmti_error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, nullptr); if (jvmti_error != JVMTI_ERROR_NONE) { log(ERROR, "Unable to register the exception callback."); @@ -137,9 +127,3 @@ JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { log(TRACE, "OnLoad exit."); return JNI_OK; } - -JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) { - log(TRACE, "Unload called."); - - log(TRACE, "Unload exit."); -} diff --git a/lib.cpp b/lib.cpp index 47426adc8b4..380023e4ae4 100644 --- a/lib.cpp +++ b/lib.cpp @@ -1,13 +1,13 @@ #include "jvmti.h" -jint throwException(JNIEnv *env, const char *name, const char *message) { +static jint throwException(JNIEnv *env, const char *name, const char *message) { jclass clazz; clazz = env->FindClass(name); return env->ThrowNew(clazz, message); } -jobject getLocalValue(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, - jvmtiLocalVariableEntry *table, int index) { +static jobject getLocalValue(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, + jvmtiLocalVariableEntry *table, int index) { jobject result; jint i_val; jfloat f_val; @@ -96,11 +96,11 @@ jobject getLocalValue(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, return result; } -void makeLocalVariable(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, - jint depth, jclass local_class, jmethodID live, - jmethodID dead, jlocation location, - jobjectArray locals, jvmtiLocalVariableEntry *table, - int index) { +static void makeLocalVariable(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, + jint depth, jclass local_class, jmethodID live, + jmethodID dead, jlocation location, + jobjectArray locals, jvmtiLocalVariableEntry *table, + int index) { jstring name; jstring sig; jstring gensig; @@ -127,8 +127,8 @@ void makeLocalVariable(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, env->SetObjectArrayElement(locals, index, local); } -jobject makeFrameObject(jvmtiEnv* jvmti, JNIEnv *env, jmethodID method, - jobject value_ptr, jobjectArray locals, jlong pos, jint lineno) { +static jobject makeFrameObject(jvmtiEnv* jvmti, JNIEnv *env, jmethodID method, + jobject value_ptr, jobjectArray locals, jlong pos, jint lineno) { jvmtiError jvmti_error; jclass method_class; jint modifiers; @@ -141,36 +141,36 @@ jobject makeFrameObject(jvmtiEnv* jvmti, JNIEnv *env, jmethodID method, throwException(env, "java/lang/RuntimeException", "Could not get the declaring class of the method."); return nullptr; } - + jvmti_error = jvmti->GetMethodModifiers(method, &modifiers); if (jvmti_error != JVMTI_ERROR_NONE) { throwException(env, "java/lang/RuntimeException", "Could not get the modifiers of the method."); return nullptr; } - + frame_method = env->ToReflectedMethod(method_class, method, modifiers & 8); if (frame_method == nullptr) { return nullptr; // ToReflectedMethod raised an exception } - + frame_class = env->FindClass("io/sentry/jvmti/Frame"); if (frame_class == nullptr) { return nullptr; } - + ctor = env->GetMethodID(frame_class, "", "(Ljava/lang/reflect/Method;Ljava/lang/Object;[Lio/sentry/jvmti/Frame$LocalVariable;II)V"); if (ctor == nullptr) { return nullptr; } - + return env->NewObject(frame_class, ctor, frame_method, value_ptr, locals, pos, lineno); } -jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, jboolean get_locals, - jmethodID method, jlocation location) { +static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, jboolean get_locals, + jmethodID method, jlocation location) { jvmtiError jvmti_error; - jvmtiLocalVariableEntry *table; + jvmtiLocalVariableEntry *local_var_table; jvmtiLineNumberEntry* lineno_table; jint num_entries; jobject value_ptr; @@ -183,7 +183,7 @@ jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, jbo value_ptr = nullptr; if (get_locals) { - jvmti_error = jvmti->GetLocalVariableTable(method, &num_entries, &table); + jvmti_error = jvmti->GetLocalVariableTable(method, &num_entries, &local_var_table); } else { // If the information wasn't requested, it's absent; handle as same case jvmti_error = JVMTI_ERROR_ABSENT_INFORMATION; @@ -213,23 +213,23 @@ jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, jbo } else { local_class = env->FindClass("io/sentry/jvmti/Frame$LocalVariable"); live_ctor = env->GetMethodID(local_class, "", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V"); + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V"); dead_ctor = env->GetMethodID(local_class, "", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); locals = env->NewObjectArray(num_entries, local_class, NULL); for (i = 0; i < num_entries; i++) { - makeLocalVariable(jvmti, env, thread, depth, local_class, live_ctor, dead_ctor, location, locals, table, i); + makeLocalVariable(jvmti, env, thread, depth, local_class, live_ctor, dead_ctor, location, locals, local_var_table, i); } - jvmti->Deallocate((unsigned char *)table); + jvmti->Deallocate((unsigned char *)local_var_table); } - + if (get_locals) { jvmti_error = jvmti->GetLocalObject(thread, depth, 0, &value_ptr); if (jvmti_error != JVMTI_ERROR_NONE) { value_ptr = NULL; } } - + jvmti_error = jvmti->GetLineNumberTable(method, &num_entries, &lineno_table); if (jvmti_error != JVMTI_ERROR_NONE) { lineno = -1; // Not retreived @@ -240,33 +240,33 @@ jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, jbo } jvmti->Deallocate((unsigned char *)lineno_table); } - + return makeFrameObject(jvmti, env, method, value_ptr, locals, location, lineno); } jobjectArray buildStackTraceFrames(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint start_depth, jboolean include_locals) { - jclass result_class; - jint i; + jint num_frames; jvmtiFrameInfo* frames; - jint count; + jclass result_class; + jint num_frames_returned; jvmtiError jvmti_error; jobjectArray result; jobject frame; - jvmti_error = jvmti->GetFrameCount(thread, &i); + jvmti_error = jvmti->GetFrameCount(thread, &num_frames); if (jvmti_error != JVMTI_ERROR_NONE) { throwException(env, "java/lang/RuntimeException", "Could not get the frame count."); return nullptr; } - jvmti_error = jvmti->Allocate(i*(int)sizeof(jvmtiFrameInfo), (unsigned char **)&frames); + jvmti_error = jvmti->Allocate(num_frames * (int)sizeof(jvmtiFrameInfo), (unsigned char **) &frames); if (jvmti_error != JVMTI_ERROR_NONE) { throwException(env, "java/lang/RuntimeException", "Could not allocate frame buffer."); return nullptr; } - jvmti_error = jvmti->GetStackTrace(thread, start_depth, i, frames, &count); + jvmti_error = jvmti->GetStackTrace(thread, start_depth, num_frames, frames, &num_frames_returned); if (jvmti_error != JVMTI_ERROR_NONE) { jvmti->Deallocate((unsigned char *)frames); throwException(env, "java/lang/RuntimeException", "Could not get stack trace."); @@ -274,22 +274,22 @@ jobjectArray buildStackTraceFrames(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, } result_class = env->FindClass("io/sentry/jvmti/Frame"); - result = env->NewObjectArray(count, result_class, nullptr); + result = env->NewObjectArray(num_frames_returned, result_class, nullptr); if (result == nullptr) { - jvmti->Deallocate((unsigned char *)frames); + jvmti->Deallocate((unsigned char *) frames); return nullptr; // OutOfMemory } - for (i = 0; i < count; i++) { + for (int i = 0; i < num_frames_returned; i++) { frame = buildFrame(jvmti, env, thread, start_depth + i, include_locals, frames[i].method, frames[i].location); if (frame == nullptr) { - jvmti->Deallocate((unsigned char *)frames); + jvmti->Deallocate((unsigned char *) frames); throwException(env, "java/lang/RuntimeException", "Error accessing frame object."); return nullptr; } env->SetObjectArrayElement(result, i, frame); } - jvmti->Deallocate((unsigned char *)frames); + jvmti->Deallocate((unsigned char *) frames); return result; } From 5b6de77651da2502833bd5158133a266584baa61 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 26 May 2017 09:34:00 -0500 Subject: [PATCH 1594/2152] Drop get_locals. --- agent.cpp | 3 +-- lib.cpp | 34 +++++++++++++--------------------- lib.h | 2 +- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/agent.cpp b/agent.cpp index 0987c93bcd4..dad8790db72 100644 --- a/agent.cpp +++ b/agent.cpp @@ -58,8 +58,7 @@ static void JNICALL ExceptionCallback(jvmtiEnv *jvmti, JNIEnv *env, jthread thre jobjectArray frames; jint start_depth = 0; - jboolean get_locals = JNI_TRUE; - frames = buildStackTraceFrames(jvmti, env, thread, start_depth, get_locals); + frames = buildStackTraceFrames(jvmti, env, thread, start_depth); env->CallStaticVoidMethod(callback_class, callback_method_id, frames); diff --git a/lib.cpp b/lib.cpp index 380023e4ae4..e3af3ea0a08 100644 --- a/lib.cpp +++ b/lib.cpp @@ -113,7 +113,7 @@ static void makeLocalVariable(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, if (table[index].generic_signature) { gensig = env->NewStringUTF(table[index].generic_signature); } else { - gensig = NULL; + gensig = nullptr; } if (location >= table[index].start_location @@ -167,7 +167,7 @@ static jobject makeFrameObject(jvmtiEnv* jvmti, JNIEnv *env, jmethodID method, return env->NewObject(frame_class, ctor, frame_method, value_ptr, locals, pos, lineno); } -static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, jboolean get_locals, +static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, jmethodID method, jlocation location) { jvmtiError jvmti_error; jvmtiLocalVariableEntry *local_var_table; @@ -182,15 +182,9 @@ static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint dep int i; value_ptr = nullptr; - if (get_locals) { - jvmti_error = jvmti->GetLocalVariableTable(method, &num_entries, &local_var_table); - } else { - // If the information wasn't requested, it's absent; handle as same case - jvmti_error = JVMTI_ERROR_ABSENT_INFORMATION; - } - + jvmti_error = jvmti->GetLocalVariableTable(method, &num_entries, &local_var_table); if (jvmti_error != JVMTI_ERROR_NONE) { - locals = NULL; + locals = nullptr; switch(jvmti_error) { // Pass cases case JVMTI_ERROR_ABSENT_INFORMATION: @@ -199,16 +193,16 @@ static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint dep // Error cases case JVMTI_ERROR_MUST_POSSESS_CAPABILITY: throwException(env, "java/lang/RuntimeException", "access_local_variables capability not enabled."); - return NULL; + return nullptr; case JVMTI_ERROR_INVALID_METHODID: throwException(env, "java/lang/IllegalArgumentException", "Illegal jmethodID."); - return NULL; + return nullptr; case JVMTI_ERROR_NULL_POINTER: throwException(env, "java/lang/NullPointerException", "passed null to GetLocalVariableTable()."); - return NULL; + return nullptr; default: throwException(env, "java/lang/RuntimeException", "Unknown JVMTI Error."); - return NULL; + return nullptr; } } else { local_class = env->FindClass("io/sentry/jvmti/Frame$LocalVariable"); @@ -223,11 +217,9 @@ static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint dep jvmti->Deallocate((unsigned char *)local_var_table); } - if (get_locals) { - jvmti_error = jvmti->GetLocalObject(thread, depth, 0, &value_ptr); - if (jvmti_error != JVMTI_ERROR_NONE) { - value_ptr = NULL; - } + jvmti_error = jvmti->GetLocalObject(thread, depth, 0, &value_ptr); + if (jvmti_error != JVMTI_ERROR_NONE) { + value_ptr = nullptr; } jvmti_error = jvmti->GetLineNumberTable(method, &num_entries, &lineno_table); @@ -245,7 +237,7 @@ static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint dep } jobjectArray buildStackTraceFrames(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, - jint start_depth, jboolean include_locals) { + jint start_depth) { jint num_frames; jvmtiFrameInfo* frames; jclass result_class; @@ -281,7 +273,7 @@ jobjectArray buildStackTraceFrames(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, } for (int i = 0; i < num_frames_returned; i++) { - frame = buildFrame(jvmti, env, thread, start_depth + i, include_locals, frames[i].method, frames[i].location); + frame = buildFrame(jvmti, env, thread, start_depth + i, frames[i].method, frames[i].location); if (frame == nullptr) { jvmti->Deallocate((unsigned char *) frames); throwException(env, "java/lang/RuntimeException", "Error accessing frame object."); diff --git a/lib.h b/lib.h index 8402d88d111..2407ed07c66 100644 --- a/lib.h +++ b/lib.h @@ -3,6 +3,6 @@ jobjectArray buildStackTraceFrames(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, - jint start_depth, jboolean include_locals); + jint start_depth); #endif //SENTRY_JAVA_AGENT_LIB_H From 95d338d09ab3b2597ffa2bee6f51a6b82418fedf Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 26 May 2017 09:38:54 -0500 Subject: [PATCH 1595/2152] Add init flag. --- agent.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/agent.cpp b/agent.cpp index dad8790db72..655a381fcd0 100644 --- a/agent.cpp +++ b/agent.cpp @@ -1,6 +1,5 @@ // TODO: better error messageds? (void) jvmti->GetErrorName(errnum, &errnum_str); // TODO: deal with threads? jrawMonitorID lock; jvmti->RawMonitorEnter(lock); jvmti->RawMonitorExit(lock); -// TODO: JNI abort doesn't do what I'd expect ... we should exit() or just mark a flag that the agent is disabled? // TODO: use *options instead of env for log level? #include "jvmti.h" @@ -24,6 +23,7 @@ static const std::string LEVEL_STRINGS[] = { }; static Level LOG_LEVEL = WARN; +static bool INIT_SUCCESS = false; static void log(Level level, std::string message) { if (level >= LOG_LEVEL) { @@ -36,6 +36,10 @@ static void JNICALL ExceptionCallback(jvmtiEnv *jvmti, JNIEnv *env, jthread thre jmethodID catch_method, jlocation catch_location) { log(TRACE, "ExceptionCallback called."); + if (!INIT_SUCCESS) { + return; + } + char *class_name = (char *) "io/sentry/jvmti/LocalsCache"; char *method_name = (char *) "setResult"; char *signature = (char *) "([Lio/sentry/jvmti/Frame;)V"; @@ -123,6 +127,7 @@ JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { return JNI_ABORT; } + INIT_SUCCESS = true; log(TRACE, "OnLoad exit."); return JNI_OK; } From 91610e27317a295af5813d548f784fbc58d657d1 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 26 May 2017 09:56:05 -0500 Subject: [PATCH 1596/2152] Minor cleanup. --- agent.cpp | 2 +- lib.cpp | 27 ++++++++++++++------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/agent.cpp b/agent.cpp index 655a381fcd0..7b3176806e9 100644 --- a/agent.cpp +++ b/agent.cpp @@ -1,4 +1,4 @@ -// TODO: better error messageds? (void) jvmti->GetErrorName(errnum, &errnum_str); +// TODO: better error messages? (void) jvmti->GetErrorName(errnum, &errnum_str); // TODO: deal with threads? jrawMonitorID lock; jvmti->RawMonitorEnter(lock); jvmti->RawMonitorExit(lock); // TODO: use *options instead of env for log level? diff --git a/lib.cpp b/lib.cpp index e3af3ea0a08..61a6e81b5cd 100644 --- a/lib.cpp +++ b/lib.cpp @@ -97,8 +97,8 @@ static jobject getLocalValue(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint } static void makeLocalVariable(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, - jint depth, jclass local_class, jmethodID live, - jmethodID dead, jlocation location, + jint depth, jclass local_class, jmethodID live_ctor, + jmethodID dead_ctor, jlocation location, jobjectArray locals, jvmtiLocalVariableEntry *table, int index) { jstring name; @@ -116,12 +116,11 @@ static void makeLocalVariable(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, gensig = nullptr; } - if (location >= table[index].start_location - && location <= (table[index].start_location + table[index].length)) { + if (location >= table[index].start_location && location <= (table[index].start_location + table[index].length)) { value = getLocalValue(jvmti, env, thread, depth, table, index); - local = env->NewObject(local_class, live, name, sig, gensig, value); + local = env->NewObject(local_class, live_ctor, name, sig, gensig, value); } else { - local = env->NewObject(local_class, dead, name, sig, gensig); + local = env->NewObject(local_class, dead_ctor, name, sig, gensig); } env->SetObjectArrayElement(locals, index, local); @@ -148,7 +147,7 @@ static jobject makeFrameObject(jvmtiEnv* jvmti, JNIEnv *env, jmethodID method, return nullptr; } - frame_method = env->ToReflectedMethod(method_class, method, modifiers & 8); + frame_method = env->ToReflectedMethod(method_class, method, (jboolean) true); if (frame_method == nullptr) { return nullptr; // ToReflectedMethod raised an exception } @@ -192,13 +191,13 @@ static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint dep break; // Error cases case JVMTI_ERROR_MUST_POSSESS_CAPABILITY: - throwException(env, "java/lang/RuntimeException", "access_local_variables capability not enabled."); + throwException(env, "java/lang/RuntimeException", "The access_local_variables capability is not enabled."); return nullptr; case JVMTI_ERROR_INVALID_METHODID: throwException(env, "java/lang/IllegalArgumentException", "Illegal jmethodID."); return nullptr; case JVMTI_ERROR_NULL_POINTER: - throwException(env, "java/lang/NullPointerException", "passed null to GetLocalVariableTable()."); + throwException(env, "java/lang/NullPointerException", "Passed null to GetLocalVariableTable()."); return nullptr; default: throwException(env, "java/lang/RuntimeException", "Unknown JVMTI Error."); @@ -210,11 +209,11 @@ static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint dep "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V"); dead_ctor = env->GetMethodID(local_class, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); - locals = env->NewObjectArray(num_entries, local_class, NULL); + locals = env->NewObjectArray(num_entries, local_class, nullptr); for (i = 0; i < num_entries; i++) { makeLocalVariable(jvmti, env, thread, depth, local_class, live_ctor, dead_ctor, location, locals, local_var_table, i); } - jvmti->Deallocate((unsigned char *)local_var_table); + jvmti->Deallocate((unsigned char *) local_var_table); } jvmti_error = jvmti->GetLocalObject(thread, depth, 0, &value_ptr); @@ -227,10 +226,12 @@ static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint dep lineno = -1; // Not retreived } else { for (i = 0; i < num_entries; i++) { - if (location < lineno_table->start_location) break; + if (location < lineno_table->start_location) { + break; + } lineno = lineno_table->line_number; } - jvmti->Deallocate((unsigned char *)lineno_table); + jvmti->Deallocate((unsigned char *) lineno_table); } return makeFrameObject(jvmti, env, method, value_ptr, locals, location, lineno); From e4a806b1c069e00c62c560e89090dd3d55ebb57e Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 26 May 2017 11:10:58 -0500 Subject: [PATCH 1597/2152] Move logging code. --- agent.cpp | 30 +++++------------------------- lib.cpp | 18 ++++++++++++++++++ lib.h | 12 ++++++++++++ 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/agent.cpp b/agent.cpp index 7b3176806e9..8c6fac45bec 100644 --- a/agent.cpp +++ b/agent.cpp @@ -1,35 +1,15 @@ // TODO: better error messages? (void) jvmti->GetErrorName(errnum, &errnum_str); // TODO: deal with threads? jrawMonitorID lock; jvmti->RawMonitorEnter(lock); jvmti->RawMonitorExit(lock); // TODO: use *options instead of env for log level? +// TODO: toString objects so we don't hold a reference? #include "jvmti.h" #include #include "lib.h" -enum Level { - TRACE, - DEBUG, - INFO, - WARN, - ERROR -}; - -static const std::string LEVEL_STRINGS[] = { - "TRACE", - "DEBUG", - "INFO", - "WARN", - "ERROR" -}; - -static Level LOG_LEVEL = WARN; -static bool INIT_SUCCESS = false; +extern Level LOG_LEVEL; -static void log(Level level, std::string message) { - if (level >= LOG_LEVEL) { - std::cerr << LEVEL_STRINGS[level] << " [Sentry Agent]: " << message << std::endl; - } -} +static bool INIT_SUCCESS = false; static void JNICALL ExceptionCallback(jvmtiEnv *jvmti, JNIEnv *env, jthread thread, jmethodID method, jlocation location, jobject exception, @@ -41,7 +21,7 @@ static void JNICALL ExceptionCallback(jvmtiEnv *jvmti, JNIEnv *env, jthread thre } char *class_name = (char *) "io/sentry/jvmti/LocalsCache"; - char *method_name = (char *) "setResult"; + char *method_name = (char *) "setCache"; char *signature = (char *) "([Lio/sentry/jvmti/Frame;)V"; jclass callback_class = nullptr; @@ -56,7 +36,7 @@ static void JNICALL ExceptionCallback(jvmtiEnv *jvmti, JNIEnv *env, jthread thre callback_method_id = env->GetStaticMethodID(callback_class, method_name, signature); if (callback_method_id == nullptr) { - log(TRACE, "Unable to locate static setResult method."); + log(TRACE, "Unable to locate static setCache method."); return; } diff --git a/lib.cpp b/lib.cpp index 61a6e81b5cd..ee5d03eea9b 100644 --- a/lib.cpp +++ b/lib.cpp @@ -1,5 +1,23 @@ +#include +#include "lib.h" #include "jvmti.h" +const std::string LEVEL_STRINGS[] = { + "TRACE", + "DEBUG", + "INFO", + "WARN", + "ERROR" +}; + +Level LOG_LEVEL = WARN; + +void log(Level level, std::string message) { + if (level >= LOG_LEVEL) { + std::cerr << LEVEL_STRINGS[level] << " [Sentry Agent]: " << message << std::endl; + } +} + static jint throwException(JNIEnv *env, const char *name, const char *message) { jclass clazz; clazz = env->FindClass(name); diff --git a/lib.h b/lib.h index 2407ed07c66..2de82887502 100644 --- a/lib.h +++ b/lib.h @@ -1,6 +1,18 @@ +#include +#include "jvmti.h" + #ifndef SENTRY_JAVA_AGENT_LIB_H #define SENTRY_JAVA_AGENT_LIB_H +enum Level { + TRACE, + DEBUG, + INFO, + WARN, + ERROR +}; + +void log(Level level, std::string message); jobjectArray buildStackTraceFrames(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint start_depth); From 9de446c364b5d91d9e4219b7d6520ec66d68c01e Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 26 May 2017 13:25:50 -0500 Subject: [PATCH 1598/2152] Create README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000000..df658a1db02 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# sentry-java-agent + +The Sentry Java Agent collects and stores local variable information for each stack frame +when an exception is created. If local variable information is available the Sentry Java +SDK will send the variable information along with events. + +Build: `cmake CMakeLists.txt && make` + +Run: `java -agentpath:libsentry_agent{.dylib,.so} ...` From 5a86b34efe03a5c659a3851358167d0a3ca2e7e7 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 26 May 2017 14:15:30 -0500 Subject: [PATCH 1599/2152] Convert objects to strings. --- agent.cpp | 1 - lib.cpp | 13 +++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/agent.cpp b/agent.cpp index 8c6fac45bec..bbaa4d48adb 100644 --- a/agent.cpp +++ b/agent.cpp @@ -1,7 +1,6 @@ // TODO: better error messages? (void) jvmti->GetErrorName(errnum, &errnum_str); // TODO: deal with threads? jrawMonitorID lock; jvmti->RawMonitorEnter(lock); jvmti->RawMonitorExit(lock); // TODO: use *options instead of env for log level? -// TODO: toString objects so we don't hold a reference? #include "jvmti.h" #include diff --git a/lib.cpp b/lib.cpp index ee5d03eea9b..a7ca5a1d525 100644 --- a/lib.cpp +++ b/lib.cpp @@ -35,10 +35,19 @@ static jobject getLocalValue(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint jclass reflect_class; jmethodID value_of; + jclass obj_class; + jmethodID to_string_method; + jobject stringified_result = nullptr; + switch (table[index].signature[0]) { case '[': // Array case 'L': // Object jvmti_error = jvmti->GetLocalObject(thread, depth, table[index].slot, &result); + + obj_class = env->GetObjectClass(result); + to_string_method = env->GetMethodID(obj_class, "toString", "()Ljava/lang/String;"); + stringified_result = (jstring) env->CallObjectMethod(result, to_string_method); + break; case 'J': // long jvmti_error = jvmti->GetLocalLong(thread, depth, table[index].slot, &j_val); @@ -65,6 +74,10 @@ static jobject getLocalValue(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint return nullptr; } + if (stringified_result != nullptr) { + return stringified_result; + } + switch (table[index].signature[0]) { case 'J': // long reflect_class = env->FindClass("java/lang/Long"); From 4fc51142ad87271e5fea6fdcd733ee5ddeb0da33 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 30 May 2017 15:29:47 -0500 Subject: [PATCH 1600/2152] Change EventBuilderHelper collection from a set to a list. (#404) --- sentry/src/main/java/io/sentry/SentryClient.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 0d8519f9097..b123d612d06 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -15,7 +15,7 @@ import java.io.IOException; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; /** * Sentry client, for sending {@link Event}s to a Sentry server. @@ -71,11 +71,9 @@ public class SentryClient { */ private final Connection connection; /** - * Set of {@link EventBuilderHelper}s. Note that we wrap a {@link ConcurrentHashMap} because there - * isn't a concurrent set in the standard library. + * List of {@link EventBuilderHelper}s. */ - private final Set builderHelpers = - Collections.newSetFromMap(new ConcurrentHashMap()); + private final List builderHelpers = new CopyOnWriteArrayList<>(); /** * The {@link ContextManager} to use for locating and storing data that is context specific, * such as {@link io.sentry.event.Breadcrumb}s. @@ -204,8 +202,8 @@ public void addBuilderHelper(EventBuilderHelper builderHelper) { builderHelpers.add(builderHelper); } - public Set getBuilderHelpers() { - return Collections.unmodifiableSet(builderHelpers); + public List getBuilderHelpers() { + return Collections.unmodifiableList(builderHelpers); } /** From a570302e31f08e1508b011d237f0942ba22415d5 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 30 May 2017 17:19:03 -0500 Subject: [PATCH 1601/2152] Change Appenders to create an EventBuilder rather than an Event, unifying the build codepath. (#398) --- .../java/io/sentry/log4j/SentryAppender.java | 13 ++- .../SentryAppenderEventBuildingTest.java | 61 ++++++++------ .../log4j/SentryAppenderFailuresTest.java | 5 +- .../java/io/sentry/log4j2/SentryAppender.java | 13 ++- .../SentryAppenderEventBuildingTest.java | 67 +++++++++------- .../log4j2/SentryAppenderFailuresTest.java | 5 +- .../io/sentry/logback/SentryAppender.java | 23 +++--- .../SentryAppenderEventBuildingTest.java | 80 ++++++++++--------- .../SentryAppenderEventLevelFilterTest.java | 5 +- .../logback/SentryAppenderFailuresTest.java | 7 +- .../java/io/sentry/jul/SentryHandler.java | 14 ++-- .../jul/SentryHandlerEventBuildingTest.java | 36 +++++---- 12 files changed, 180 insertions(+), 149 deletions(-) diff --git a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java index 9aed17efaa3..4e277ee3677 100644 --- a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java +++ b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java @@ -239,8 +239,8 @@ protected void append(LoggingEvent loggingEvent) { SentryEnvironment.startManagingThread(); try { lazyInit(); - Event event = buildEvent(loggingEvent); - sentryClient.sendEvent(event); + EventBuilder eventBuilder = createEventBuilder(loggingEvent); + sentryClient.sendEvent(eventBuilder); } catch (Exception e) { getErrorHandler().error("An exception occurred while creating a new event in Sentry", e, ErrorCode.WRITE_FAILURE); @@ -250,12 +250,12 @@ protected void append(LoggingEvent loggingEvent) { } /** - * Builds an Event based on the logging event. + * Builds an EventBuilder based on the logging event. * * @param loggingEvent Log generated. - * @return Event containing details provided by the logging system. + * @return EventBuilder containing details provided by the logging system. */ - protected Event buildEvent(LoggingEvent loggingEvent) { + protected EventBuilder createEventBuilder(LoggingEvent loggingEvent) { EventBuilder eventBuilder = new EventBuilder() .withSdkName(SentryEnvironment.SDK_NAME + ":log4j") .withTimestamp(new Date(loggingEvent.getTimeStamp())) @@ -323,8 +323,7 @@ protected Event buildEvent(LoggingEvent loggingEvent) { eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); } - sentryClient.runBuilderHelpers(eventBuilder); - return eventBuilder.build(); + return eventBuilder; } public void setFactory(String factory) { diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java index 8df1aba4fee..7fce87778f9 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java @@ -65,9 +65,9 @@ public void testSimpleMessageLogging() throws Exception { null, null, null, null)); new Verifications() {{ - Event event; - mockSentryClient.runBuilderHelpers((EventBuilder) any); - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getMessage(), is(message)); assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); @@ -93,8 +93,9 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th sentryAppender.append(new LoggingEvent(null, mockLogger, 0, level, null, null)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getLevel(), is(expectedLevel)); }}; assertNoErrorsInErrorHandler(); @@ -107,8 +108,9 @@ public void testExceptionLogging() throws Exception { sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, exception)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); SentryException sentryException = exceptionInterface.getExceptions().getFirst(); @@ -128,8 +130,9 @@ public void testMdcAddedToExtra() throws Exception { null, null, null, Collections.singletonMap(extraKey, extraValue))); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); }}; assertNoErrorsInErrorHandler(); @@ -143,8 +146,9 @@ public void testNdcAddedToExtra() throws Exception { null, ndcEntries, null, null)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.LOG4J_NDC, ndcEntries)); }}; assertNoErrorsInErrorHandler(); @@ -172,8 +176,9 @@ public void testSourceUsedAsStacktrace(@Injectable final LocationInfo locationIn null, null, locationInfo, null)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() .get(StackTraceInterface.STACKTRACE_INTERFACE); assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); @@ -205,8 +210,9 @@ public void testCulpritWithSource(@Injectable final LocationInfo locationInfo) t null, null, locationInfo, null)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getCulprit(), is("a.b(c:42)")); }}; assertNoErrorsInErrorHandler(); @@ -223,8 +229,9 @@ public void testCulpritWithoutSource() throws Exception { sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getCulprit(), is(loggerName)); }}; assertNoErrorsInErrorHandler(); @@ -239,8 +246,9 @@ public void testExtraTagObtainedFromMdc() throws Exception { sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, null, null, null, properties)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getTags().entrySet(), hasSize(1)); assertThat(event.getTags(), hasEntry(mockExtraTag, "ac84f38a-3889-41ed-9519-201402688abb")); assertThat(event.getExtra(), not(hasKey(mockExtraTag))); @@ -260,8 +268,9 @@ public void testExtraTagObtainedFromMdcConvertedToString(@Injectable final Objec sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, null, null, null, properties)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getTags().entrySet(), hasSize(1)); assertThat(event.getTags(), hasEntry(mockExtraTag, "3c8981b4-01ad-47ec-8a3a-77a0bbcb42e2")); assertThat(event.getExtra(), not(hasKey(mockExtraTag))); @@ -277,8 +286,9 @@ public void testReleaseAddedToEvent() throws Exception { sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getRelease(), is(release)); }}; assertNoErrorsInErrorHandler(); @@ -292,8 +302,9 @@ public void testEnvironmentAddedToEvent() throws Exception { sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getEnvironment(), is(environment)); }}; assertNoErrorsInErrorHandler(); diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java index 482c65c6c3a..88c2f476f3e 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java @@ -1,6 +1,7 @@ package io.sentry.log4j; import io.sentry.SentryClient; +import io.sentry.event.EventBuilder; import mockit.*; import io.sentry.SentryClientFactory; import io.sentry.dsn.Dsn; @@ -38,7 +39,7 @@ public void setUp() throws Exception { @Test public void testSentryFailureDoesNotPropagate() throws Exception { new NonStrictExpectations() {{ - mockSentryClient.sendEvent((Event) any); + mockSentryClient.sendEvent((EventBuilder) any); result = new UnsupportedOperationException(); }}; @@ -69,7 +70,7 @@ public void testAppendFailIfCurrentThreadSpawnedBySentry() throws Exception { sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); new Verifications() {{ - mockSentryClient.sendEvent((Event) any); + mockSentryClient.sendEvent((EventBuilder) any); times = 0; }}; assertThat(mockUpErrorHandler.getErrorCount(), is(0)); diff --git a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java index 6ba4af5531a..e751b2a43b3 100644 --- a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -301,8 +301,8 @@ public void append(LogEvent logEvent) { SentryEnvironment.startManagingThread(); try { lazyInit(); - Event event = buildEvent(logEvent); - sentryClient.sendEvent(event); + EventBuilder eventBuilder = createEventBuilder(logEvent); + sentryClient.sendEvent(eventBuilder); } catch (Exception e) { error("An exception occurred while creating a new event in Sentry", logEvent, e); } finally { @@ -328,12 +328,12 @@ protected synchronized void initSentry() { } /** - * Builds an Event based on the logging event. + * Builds an EventBuilder based on the logging event. * * @param event Log generated. - * @return Event containing details provided by the logging system. + * @return EventBuilder containing details provided by the logging system. */ - protected Event buildEvent(LogEvent event) { + protected EventBuilder createEventBuilder(LogEvent event) { Message eventMessage = event.getMessage(); EventBuilder eventBuilder = new EventBuilder() .withSdkName(SentryEnvironment.SDK_NAME + ":log4j2") @@ -405,8 +405,7 @@ protected Event buildEvent(LogEvent event) { eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); } - sentryClient.runBuilderHelpers(eventBuilder); - return eventBuilder.build(); + return eventBuilder; } public void setDsn(String dsn) { diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java index e8beae91cfe..33533ae6607 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java @@ -56,9 +56,9 @@ public void testSimpleMessageLogging() throws Exception { null, null, null, threadName, null, date.getTime())); new Verifications() {{ - Event event; - mockSentryClient.runBuilderHelpers((EventBuilder) any); - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getMessage(), is(message)); assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); @@ -84,8 +84,9 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th sentryAppender.append(new Log4jLogEvent(null, null, null, level, new SimpleMessage(""), null)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getLevel(), is(expectedLevel)); }}; assertNoErrorsInErrorHandler(); @@ -98,8 +99,9 @@ public void testExceptionLogging() throws Exception { sentryAppender.append(new Log4jLogEvent(null, null, null, Level.ERROR, new SimpleMessage(""), exception)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); SentryException sentryException = exceptionInterface.getExceptions().getFirst(); @@ -119,9 +121,9 @@ public void testLogParametrisedMessage() throws Exception { new FormattedMessage(messagePattern, parameters), null)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); - + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); MessageInterface messageInterface = (MessageInterface) event.getSentryInterfaces() .get(MessageInterface.MESSAGE_INTERFACE); assertThat(event.getMessage(), is("Formatted message first parameter [] null")); @@ -140,8 +142,9 @@ public void testMarkerAddedToTag() throws Exception { new SimpleMessage(""), null)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getTags(), Matchers.hasEntry(SentryAppender.LOG4J_MARKER, markerName)); }}; assertNoErrorsInErrorHandler(); @@ -156,8 +159,9 @@ public void testMdcAddedToExtra() throws Exception { Collections.singletonMap(extraKey, extraValue), null, null, null, 0)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); }}; assertNoErrorsInErrorHandler(); @@ -175,8 +179,9 @@ public void testNdcAddedToExtra() throws Exception { contextStack, null, null, 0)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat((List) event.getExtra().get(SentryAppender.LOG4J_NDC), equalTo(contextStack.asList())); }}; assertNoErrorsInErrorHandler(); @@ -191,8 +196,9 @@ public void testSourceUsedAsStacktrace() throws Exception { null, location, 0)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() .get(StackTraceInterface.STACKTRACE_INTERFACE); assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); @@ -209,8 +215,9 @@ public void testCulpritWithSource() throws Exception { null, location, 0)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getCulprit(), is("a.b(c:42)")); }}; assertNoErrorsInErrorHandler(); @@ -223,8 +230,9 @@ public void testCulpritWithoutSource() throws Exception { sentryAppender.append(new Log4jLogEvent(loggerName, null, null, Level.INFO, new SimpleMessage(""), null)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getCulprit(), is(loggerName)); }}; assertNoErrorsInErrorHandler(); @@ -239,8 +247,9 @@ public void testExtraTagObtainedFromMdc() throws Exception { sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, mdc, null, null, null, 0)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getTags().entrySet(), hasSize(1)); assertThat(event.getTags(), hasEntry(mockExtraTag, "565940d2-f4a4-42f6-9496-42e3c7c85c43")); assertThat(event.getExtra(), not(hasKey(mockExtraTag))); @@ -257,8 +266,9 @@ public void testReleaseAddedToEvent() throws Exception { sentryAppender.append(new Log4jLogEvent(null, null, null, Level.ERROR, new SimpleMessage(""), null)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getRelease(), is(release)); }}; assertNoErrorsInErrorHandler(); @@ -272,8 +282,9 @@ public void testEnvironmentAddedToEvent() throws Exception { sentryAppender.append(new Log4jLogEvent(null, null, null, Level.ERROR, new SimpleMessage(""), null)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getEnvironment(), is(environment)); }}; assertNoErrorsInErrorHandler(); diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java index ce66a703c41..6b17ec585b9 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java @@ -1,5 +1,6 @@ package io.sentry.log4j2; +import io.sentry.event.EventBuilder; import mockit.Injectable; import mockit.Mocked; import mockit.NonStrictExpectations; @@ -37,7 +38,7 @@ public void setUp() throws Exception { @Test public void testSentryFailureDoesNotPropagate() throws Exception { new NonStrictExpectations() {{ - mockSentryClient.sendEvent((Event) any); + mockSentryClient.sendEvent((EventBuilder) any); result = new UnsupportedOperationException(); }}; @@ -68,7 +69,7 @@ public void testAppendFailIfCurrentThreadSpawnedBySentry() throws Exception { sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); new Verifications() {{ - mockSentryClient.sendEvent((Event) any); + mockSentryClient.sendEvent((EventBuilder) any); times = 0; }}; assertThat(mockUpErrorHandler.getErrorCount(), is(0)); diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index 5cd5d4460e4..d823cfa7b2a 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -220,19 +220,15 @@ protected static Event.Level formatLevel(Level level) { @Override protected void append(ILoggingEvent iLoggingEvent) { // Do not log the event if the current thread is managed by sentry - if (SentryEnvironment.isManagingThread()) { + if (isNotLoggable(iLoggingEvent) || SentryEnvironment.isManagingThread()) { return; } SentryEnvironment.startManagingThread(); try { - if (minLevel != null && !iLoggingEvent.getLevel().isGreaterOrEqual(minLevel)) { - return; - } - lazyInit(); - Event event = buildEvent(iLoggingEvent); - sentryClient.sendEvent(event); + EventBuilder eventBuilder = createEventBuilder(iLoggingEvent); + sentryClient.sendEvent(eventBuilder); } catch (Exception e) { addError("An exception occurred while creating a new event in Sentry", e); } finally { @@ -240,6 +236,10 @@ protected void append(ILoggingEvent iLoggingEvent) { } } + private boolean isNotLoggable(ILoggingEvent iLoggingEvent) { + return minLevel != null && !iLoggingEvent.getLevel().isGreaterOrEqual(minLevel); + } + /** * Initialises the {@link SentryClient} instance. */ @@ -258,12 +258,12 @@ protected synchronized void initSentry() { } /** - * Builds an Event based on the logging event. + * Builds an EventBuilder based on the logging event. * * @param iLoggingEvent Log generated. - * @return Event containing details provided by the logging system. + * @return EventBuilder containing details provided by the logging system. */ - protected Event buildEvent(ILoggingEvent iLoggingEvent) { + protected EventBuilder createEventBuilder(ILoggingEvent iLoggingEvent) { EventBuilder eventBuilder = new EventBuilder() .withSdkName(SentryEnvironment.SDK_NAME + ":logback") .withTimestamp(new Date(iLoggingEvent.getTimeStamp())) @@ -328,8 +328,7 @@ protected Event buildEvent(ILoggingEvent iLoggingEvent) { eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); } - sentryClient.runBuilderHelpers(eventBuilder); - return eventBuilder.build(); + return eventBuilder; } /** diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java index e97ba2c2f27..86c36fb7831 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java @@ -70,9 +70,9 @@ public void testSimpleMessageLogging() throws Exception { sentryAppender.append(loggingEvent); new Verifications() {{ - Event event; - mockSentryClient.runBuilderHelpers((EventBuilder) any); - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getMessage(), is(message)); assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); @@ -97,8 +97,9 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th sentryAppender.append(new MockUpLoggingEvent(null, null, level, null, null, null).getMockInstance()); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getLevel(), is(expectedLevel)); }}; assertNoErrorsInStatusManager(); @@ -111,9 +112,9 @@ public void testExceptionLogging() throws Exception { sentryAppender.append(new MockUpLoggingEvent(null, null, Level.ERROR, null, null, exception).getMockInstance()); new Verifications() {{ - Event event; - mockSentryClient.runBuilderHelpers((EventBuilder) any); - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); SentryException sentryException = exceptionInterface.getExceptions().getFirst(); @@ -133,9 +134,9 @@ public void testLogParametrisedMessage() throws Exception { .getMockInstance()); new Verifications() {{ - Event event; - mockSentryClient.runBuilderHelpers((EventBuilder) any); - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); MessageInterface messageInterface = (MessageInterface) event.getSentryInterfaces() .get(MessageInterface.MESSAGE_INTERFACE); @@ -156,9 +157,9 @@ public void testMarkerAddedToTag() throws Exception { .getMockInstance()); new Verifications() {{ - Event event; - mockSentryClient.runBuilderHelpers((EventBuilder) any); - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getTags(), Matchers.hasEntry(SentryAppender.LOGBACK_MARKER, markerName)); }}; assertNoErrorsInStatusManager(); @@ -173,9 +174,9 @@ public void testMdcAddedToExtra() throws Exception { Collections.singletonMap(extraKey, extraValue), null, null, 0).getMockInstance()); new Verifications() {{ - Event event; - mockSentryClient.runBuilderHelpers((EventBuilder) any); - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); }}; assertNoErrorsInStatusManager(); @@ -190,9 +191,9 @@ public void testContextPropertiesAddedToExtra() throws Exception { null, null, null, 0, Collections.singletonMap(extraKey, extraValue)).getMockInstance()); new Verifications() {{ - Event event; - mockSentryClient.runBuilderHelpers((EventBuilder) any); - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); }}; assertNoErrorsInStatusManager(); @@ -210,9 +211,9 @@ public void testMdcTakesPrecedenceOverContextProperties() throws Exception { Collections.singletonMap(contextKey, contextValue)).getMockInstance()); new Verifications() {{ - Event event; - mockSentryClient.runBuilderHelpers((EventBuilder) any); - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getExtra(), Matchers.hasEntry(mdcKey, mdcValue)); }}; assertNoErrorsInStatusManager(); @@ -228,9 +229,9 @@ public void testSourceUsedAsStacktrace() throws Exception { .getMockInstance()); new Verifications() {{ - Event event; - mockSentryClient.runBuilderHelpers((EventBuilder) any); - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() .get(StackTraceInterface.STACKTRACE_INTERFACE); assertThat(stackTraceInterface.getStackTrace(), is(SentryStackTraceElement.fromStackTraceElements(location))); @@ -247,9 +248,9 @@ public void testCulpritWithSource() throws Exception { .getMockInstance()); new Verifications() {{ - Event event; - mockSentryClient.runBuilderHelpers((EventBuilder) any); - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getCulprit(), is("a.b(c:42)")); }}; assertNoErrorsInStatusManager(); @@ -262,9 +263,9 @@ public void testCulpritWithoutSource() throws Exception { sentryAppender.append(new MockUpLoggingEvent(loggerName, null, Level.INFO, null, null, null).getMockInstance()); new Verifications() {{ - Event event; - mockSentryClient.runBuilderHelpers((EventBuilder) any); - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getCulprit(), is(loggerName)); }}; assertNoErrorsInStatusManager(); @@ -280,8 +281,9 @@ public void testExtraTagObtainedFromMdc() throws Exception { null, 0).getMockInstance()); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getTags().entrySet(), hasSize(1)); assertThat(event.getTags(), hasEntry(mockExtraTag, "47008f35-50c8-4e40-94ca-c8c1a3ddb729")); assertThat(event.getExtra(), not(hasKey(mockExtraTag))); @@ -298,8 +300,9 @@ public void testReleaseAddedToEvent() throws Exception { sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null).getMockInstance()); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getRelease(), is(release)); }}; assertNoErrorsInStatusManager(); @@ -313,8 +316,9 @@ public void testEnvironmentAddedToEvent() throws Exception { sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null).getMockInstance()); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getEnvironment(), is(environment)); }}; assertNoErrorsInStatusManager(); diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java index 5b7abf0b36b..49e2b0c3fdf 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java @@ -2,6 +2,7 @@ import ch.qos.logback.classic.Level; import ch.qos.logback.core.Context; +import io.sentry.event.EventBuilder; import mockit.Injectable; import mockit.Tested; import mockit.Verifications; @@ -53,7 +54,7 @@ public void testLevelFilter(final String minLevel, final Integer expectedEvents) sentryAppender.append(new MockUpLoggingEvent(null, null, Level.ERROR, null, null, null).getMockInstance()); new Verifications() {{ - mockSentryClient.sendEvent((Event) any); + mockSentryClient.sendEvent((EventBuilder) any); minTimes = expectedEvents; maxTimes = expectedEvents; }}; @@ -68,7 +69,7 @@ public void testDefaultLevelFilter() throws Exception { sentryAppender.append(new MockUpLoggingEvent(null, null, Level.ERROR, null, null, null).getMockInstance()); new Verifications() {{ - mockSentryClient.sendEvent((Event) any); + mockSentryClient.sendEvent((EventBuilder) any); minTimes = 5; maxTimes = 5; }}; diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java index e537e96fb41..5c694f8991a 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java @@ -5,6 +5,7 @@ import ch.qos.logback.core.Context; import ch.qos.logback.core.status.OnConsoleStatusListener; import io.sentry.SentryClient; +import io.sentry.event.EventBuilder; import mockit.*; import io.sentry.SentryClientFactory; import io.sentry.dsn.Dsn; @@ -45,7 +46,7 @@ public void testSentryFailureDoesNotPropagate() throws Exception { sentryAppender.setContext(mockContext); sentryAppender.setMinLevel("ALL"); new NonStrictExpectations() {{ - mockSentryClient.sendEvent((Event) any); + mockSentryClient.sendEvent((EventBuilder) any); result = new UnsupportedOperationException(); }}; sentryAppender.start(); @@ -53,7 +54,7 @@ public void testSentryFailureDoesNotPropagate() throws Exception { sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null).getMockInstance()); new Verifications() {{ - mockSentryClient.sendEvent((Event) any); + mockSentryClient.sendEvent((EventBuilder) any); }}; assertThat(mockContext.getStatusManager().getCount(), is(1)); } @@ -86,7 +87,7 @@ public void testAppendFailIfCurrentThreadSpawnedBySentry() throws Exception { sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null).getMockInstance()); new Verifications() {{ - mockSentryClient.sendEvent((Event) any); + mockSentryClient.sendEvent((EventBuilder) any); times = 0; }}; assertThat(mockContext.getStatusManager().getCount(), is(0)); diff --git a/sentry/src/main/java/io/sentry/jul/SentryHandler.java b/sentry/src/main/java/io/sentry/jul/SentryHandler.java index 84239099ba5..67b72808c24 100644 --- a/sentry/src/main/java/io/sentry/jul/SentryHandler.java +++ b/sentry/src/main/java/io/sentry/jul/SentryHandler.java @@ -126,7 +126,6 @@ private void lazyInit() { if (!initialized) { synchronized (this) { if (!initialized) { - try { String sentryClientFactory = Lookup.lookup("factory"); if (sentryClientFactory != null) { @@ -261,8 +260,8 @@ public void publish(LogRecord record) { SentryEnvironment.startManagingThread(); try { lazyInit(); - Event event = buildEvent(record); - sentryClient.sendEvent(event); + EventBuilder eventBuilder = createEventBuilder(record); + sentryClient.sendEvent(eventBuilder); } catch (Exception e) { reportError("An exception occurred while creating a new event in Sentry", e, ErrorManager.WRITE_FAILURE); } finally { @@ -290,12 +289,12 @@ protected synchronized void initSentry() { } /** - * Builds an Event based on the log record. + * Builds an EventBuilder based on the log record. * * @param record Log generated. - * @return Event containing details provided by the logging system. + * @return EventBuilder containing details provided by the logging system. */ - protected Event buildEvent(LogRecord record) { + protected EventBuilder createEventBuilder(LogRecord record) { EventBuilder eventBuilder = new EventBuilder() .withSdkName(SentryEnvironment.SDK_NAME + ":jul") .withLevel(getLevel(record.getLevel())) @@ -370,8 +369,7 @@ protected Event buildEvent(LogRecord record) { eventBuilder.withServerName(serverName.trim()); } - sentryClient.runBuilderHelpers(eventBuilder); - return eventBuilder.build(); + return eventBuilder; } /** diff --git a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java index c9bf1143b3b..4739f59e5c3 100644 --- a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java +++ b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java @@ -48,9 +48,9 @@ public void testSimpleMessageLogging() throws Exception { sentryHandler.publish(newLogRecord(loggerName, Level.INFO, message, arguments, null, null, threadId, date.getTime())); new Verifications() {{ - Event event; - mockSentryClient.runBuilderHelpers((EventBuilder) any); - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getMessage(), is(message)); Map sentryInterfaces = event.getSentryInterfaces(); assertThat(sentryInterfaces, hasKey(MessageInterface.MESSAGE_INTERFACE)); @@ -81,8 +81,9 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th sentryHandler.publish(newLogRecord(null, level, null, null, null)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getLevel(), is(expectedLevel)); }}; assertNoErrorsInErrorManager(); @@ -95,8 +96,9 @@ public void testExceptionLogging() throws Exception { sentryHandler.publish(newLogRecord(null, Level.SEVERE, null, null, exception)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() .get(ExceptionInterface.EXCEPTION_INTERFACE); final SentryException sentryException = exceptionInterface.getExceptions().getFirst(); @@ -117,8 +119,9 @@ public void testCulpritWithSource() throws Exception { new StackTraceElement[]{stackTraceElement}, 0, 0)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getCulprit(), is("a.b")); }}; assertNoErrorsInErrorManager(); @@ -131,8 +134,9 @@ public void testCulpritWithoutSource() throws Exception { sentryHandler.publish(newLogRecord(loggerName, Level.SEVERE, null, null, null)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getCulprit(), is(loggerName)); }}; assertNoErrorsInErrorManager(); @@ -167,8 +171,9 @@ public void testReleaseAddedToEvent() throws Exception { sentryHandler.publish(newLogRecord(null, Level.INFO, null, null, null)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getRelease(), is(release)); }}; assertNoErrorsInErrorManager(); @@ -182,8 +187,9 @@ public void testEnvironmentAddedToEvent() throws Exception { sentryHandler.publish(newLogRecord(null, Level.INFO, null, null, null)); new Verifications() {{ - Event event; - mockSentryClient.sendEvent(event = withCapture()); + EventBuilder eventBuilder; + mockSentryClient.sendEvent(eventBuilder = withCapture()); + Event event = eventBuilder.build(); assertThat(event.getEnvironment(), is(environment)); }}; assertNoErrorsInErrorManager(); From 052cf38d60fe77b06f0222d4a344eab50edd3d16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Rocher?= Date: Wed, 31 May 2017 16:35:15 +0200 Subject: [PATCH 1602/2152] Use /proc/version for kernel information on Android. (thanks JeremR) --- .../event/helper/AndroidEventBuilderHelper.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java index e72489b29f5..97be88cfc85 100644 --- a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java +++ b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java @@ -22,8 +22,8 @@ import java.io.BufferedReader; import java.io.File; +import java.io.FileReader; import java.io.IOException; -import java.io.InputStreamReader; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; @@ -287,19 +287,16 @@ private static Boolean isCharging(Context ctx) { } /** - * Get the device's current kernel version, as a string (from uname -a). + * Get the device's current kernel version, as a string (from /proc/version). * - * @return the device's current kernel version, as a string (from uname -a) + * @return the device's current kernel version, as a string (from /proc/version) */ private static String getKernelVersion() { String errorMsg = "Exception while attempting to read kernel information"; BufferedReader br = null; try { - Process p = Runtime.getRuntime().exec("uname -a"); - if (p.waitFor() == 0) { - br = new BufferedReader(new InputStreamReader(p.getInputStream())); - return br.readLine(); - } + br = new BufferedReader(new FileReader("/proc/version")); + return br.readLine(); } catch (Exception e) { Log.e(TAG, errorMsg, e); } finally { From 7cd3415fbaa66118437fdd075a64be23a1f16ffc Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 31 May 2017 15:14:49 -0500 Subject: [PATCH 1603/2152] Remove SentryAndroid in favor of using the same static init system for all non-logging usages. (#389) --- CHANGES | 4 +- docs/modules/android.rst | 30 +--- .../android/AndroidSentryClientFactory.java | 37 +++- .../java/io/sentry/android/SentryAndroid.java | 166 ------------------ .../src/main/java/io/sentry/android/Util.java | 63 ------- .../helper/AndroidEventBuilderHelper.java | 18 +- .../io/sentry/android/SentryITActivity.java | 3 +- .../java/io/sentry/log4j/SentryAppender.java | 19 +- .../sentry/log4j/SentryAppenderCloseTest.java | 3 +- .../sentry/log4j/SentryAppenderDsnTest.java | 15 -- .../java/io/sentry/log4j2/SentryAppender.java | 19 +- .../log4j2/SentryAppenderCloseTest.java | 3 +- .../sentry/log4j2/SentryAppenderDsnTest.java | 15 -- .../io/sentry/logback/SentryAppender.java | 19 +- .../logback/SentryAppenderCloseTest.java | 3 +- .../sentry/logback/SentryAppenderDsnTest.java | 15 -- sentry/src/main/java/io/sentry/Sentry.java | 89 +++++++++- .../src/main/java/io/sentry/SentryClient.java | 2 - .../java/io/sentry/SentryClientFactory.java | 39 +++- .../SentryUncaughtExceptionHandler.java | 38 ++-- sentry/src/main/java/io/sentry/dsn/Dsn.java | 2 +- .../java/io/sentry/jul/SentryHandler.java | 19 +- .../io/sentry/SentryClientFactoryTest.java | 6 - .../src/test/java/io/sentry/SentryTest.java | 80 ++++++++- 24 files changed, 326 insertions(+), 381 deletions(-) delete mode 100644 sentry-android/src/main/java/io/sentry/android/SentryAndroid.java delete mode 100644 sentry-android/src/main/java/io/sentry/android/Util.java rename {sentry-android/src/main/java/io/sentry/android => sentry/src/main/java/io/sentry}/SentryUncaughtExceptionHandler.java (55%) diff --git a/CHANGES b/CHANGES index b4e37049fd8..cb3f7a9eced 100644 --- a/CHANGES +++ b/CHANGES @@ -7,8 +7,8 @@ Version 1.0.0 - Rename the ``SentryFactory`` class to ``SentryClientFactory``, and renamed all subclasses to now end in ``ClientFactory``. - Remove ``Breadcrumbs`` class. -- Add new ``Sentry`` class for static client access and usage. -- Rename ``android.Sentry`` to ``android.AndroidSentry`` to make it unambiguous when used in code. +- Add new ``Sentry`` class for static client initialization, access and usage. +- Remove ``android.Sentry`` in favor of using the single static ``Sentry`` class everywhere. - Rename ``sentryFactory`` option to ``factory`` so that the environment variable configuration is now ``SENTRY_FACTORY`` and the Java System Property is ``sentry.factory``. - Add ``dist`` field to Event. diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 933b249f576..ec7b9adb18b 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -48,7 +48,8 @@ Then initialize the Sentry client in your application's main ``onCreate`` method .. sourcecode:: java - import io.sentry.android.Sentry; + import io.sentry.Sentry; + import io.sentry.android.AndroidSentryClientFactory; public class MainActivity extends Activity { @Override @@ -62,33 +63,6 @@ Then initialize the Sentry client in your application's main ``onCreate`` method } } -You can also configure your Sentry DSN (client key) in your ``AndroidManifest.xml``: - -.. sourcecode:: xml - - - - - -And then you don't need to manually provide the DSN in your code: - -.. sourcecode:: java - - import io.sentry.Sentry; - import io.sentry.android.AndroidSentryClientFactory; - - public class MainActivity extends Activity { - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - Context ctx = this.getApplicationContext(); - Sentry.init(new AndroidSentryClientFactory(ctx)); - } - } - Usage ----- diff --git a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java index 276ff42f5ae..c1a15ff2644 100644 --- a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java +++ b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java @@ -1,6 +1,8 @@ package io.sentry.android; +import android.Manifest; import android.content.Context; +import android.content.pm.PackageManager; import android.util.Log; import io.sentry.*; import io.sentry.android.event.helper.AndroidEventBuilderHelper; @@ -35,15 +37,35 @@ public class AndroidSentryClientFactory extends DefaultSentryClientFactory { * @param ctx Android Context. */ public AndroidSentryClientFactory(Context ctx) { - this.ctx = ctx; - Log.d(TAG, "Construction of Android Sentry."); + + this.ctx = ctx.getApplicationContext(); } @Override public SentryClient createSentryClient(Dsn dsn) { + if (!checkPermission(Manifest.permission.INTERNET)) { + Log.e(TAG, Manifest.permission.INTERNET + " is required to connect to the Sentry server," + + " please add it to your AndroidManifest.xml"); + } + + Log.d(TAG, "Sentry init with ctx='" + ctx.toString() + "' and dsn='" + dsn + "'"); + + String protocol = dsn.getProtocol(); + if (!(protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https"))) { + throw new IllegalArgumentException("Only 'http' or 'https' connections are supported in" + + " Sentry Android, but received: " + protocol); + } + + String async = dsn.getOptions().get(DefaultSentryClientFactory.ASYNC_OPTION); + if (async != null && async.equalsIgnoreCase("false")) { + throw new IllegalArgumentException("Sentry Android cannot use synchronous connections, remove '" + + DefaultSentryClientFactory.ASYNC_OPTION + "=false' from your DSN."); + } + SentryClient sentryClient = super.createSentryClient(dsn); sentryClient.addBuilderHelper(new AndroidEventBuilderHelper(ctx)); + SentryUncaughtExceptionHandler.setup(); return sentryClient; } @@ -66,4 +88,15 @@ protected ContextManager getContextManager(Dsn dsn) { return new SingletonContextManager(); } + /** + * Check whether the application has been granted a certain permission. + * + * @param permission Permission as a string + * @return true if permissions is granted + */ + private boolean checkPermission(String permission) { + int res = ctx.checkCallingOrSelfPermission(permission); + return (res == PackageManager.PERMISSION_GRANTED); + } + } diff --git a/sentry-android/src/main/java/io/sentry/android/SentryAndroid.java b/sentry-android/src/main/java/io/sentry/android/SentryAndroid.java deleted file mode 100644 index 0ee6db4226b..00000000000 --- a/sentry-android/src/main/java/io/sentry/android/SentryAndroid.java +++ /dev/null @@ -1,166 +0,0 @@ -package io.sentry.android; - -import android.Manifest; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.text.TextUtils; -import android.util.Log; -import io.sentry.DefaultSentryClientFactory; -import io.sentry.SentryClient; -import io.sentry.SentryClientFactory; -import io.sentry.dsn.Dsn; - -/** - * Android specific class to interface with Sentry. Supplements the default Java classes - * with Android specific state and features. - */ -public final class SentryAndroid { - - /** - * Logger tag. - */ - public static final String TAG = SentryAndroid.class.getName(); - - /** - * Hide constructor. - */ - private SentryAndroid() { - - } - - /** - * Initialize Sentry using a DSN set in the AndroidManifest. - * - * @param ctx Android application ctx - * @return SentryClient - */ - public static SentryClient init(Context ctx) { - return init(ctx, getDefaultSentryClientFactory(ctx)); - } - - /** - * Initialize Sentry using a DSN set in the AndroidManifest. - * - * @param ctx Android application ctx - * @param sentryClientFactory the SentryClientFactory to be used to generate the {@link SentryClient} instance - * @return SentryClient - */ - public static SentryClient init(Context ctx, AndroidSentryClientFactory sentryClientFactory) { - ctx = ctx.getApplicationContext(); - String dsn = ""; - - // attempt to get DSN from AndroidManifest - ApplicationInfo appInfo = null; - try { - PackageManager packageManager = ctx.getPackageManager(); - appInfo = packageManager.getApplicationInfo(ctx.getPackageName(), PackageManager.GET_META_DATA); - dsn = appInfo.metaData.getString("io.sentry.android.DSN"); - } catch (PackageManager.NameNotFoundException e) { - // skip - } - - if (TextUtils.isEmpty(dsn)) { - throw new NullPointerException("Sentry DSN is not set, you must provide it via" - + "the constructor or AndroidManifest."); - } - - return init(ctx, dsn, sentryClientFactory); - } - - /** - * Initialize Sentry using a string DSN. - * - * @param ctx Android application ctx - * @param dsn Sentry DSN string - * @return SentryClient - */ - public static SentryClient init(Context ctx, String dsn) { - return init(ctx, new Dsn(dsn), getDefaultSentryClientFactory(ctx)); - } - - /** - * Initialize Sentry using a string DSN. - * - * @param ctx Android application ctx - * @param dsn Sentry DSN string - * @param sentryClientFactory the SentryClientFactory to be used to generate the SentryClient - * @return SentryClient - */ - public static SentryClient init(Context ctx, String dsn, AndroidSentryClientFactory sentryClientFactory) { - return init(ctx, new Dsn(dsn), sentryClientFactory); - } - - /** - * Initialize Sentry using a DSN object. - * - * @param ctx Android application ctx - * @param dsn Sentry DSN object - * @return SentryClient - */ - public static SentryClient init(Context ctx, Dsn dsn) { - return init(ctx, dsn, getDefaultSentryClientFactory(ctx)); - } - - /** - * Initialize Sentry using a DSN object. This is the 'main' initializer that other methods - * eventually call. - * - * @param ctx Android application ctx - * @param dsn Sentry DSN object - * @param sentryClientFactory the SentryClientFactory to be used to generate the {@link SentryClient} instance - * @return SentryClient - */ - public static SentryClient init(Context ctx, Dsn dsn, AndroidSentryClientFactory sentryClientFactory) { - // Ensure we have the application context - Context context = ctx.getApplicationContext(); - - if (!Util.checkPermission(context, Manifest.permission.INTERNET)) { - Log.e(TAG, Manifest.permission.INTERNET + " is required to connect to the Sentry server," - + " please add it to your AndroidManifest.xml"); - } - - Log.d(TAG, "Sentry init with ctx='" + ctx.toString() + "' and dsn='" + dsn + "'"); - - String protocol = dsn.getProtocol(); - if (!(protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https"))) { - throw new IllegalArgumentException("Only 'http' or 'https' connections are supported in" - + " Sentry Android, but received: " + protocol); - } - - if ("false".equalsIgnoreCase(dsn.getOptions().get(DefaultSentryClientFactory.ASYNC_OPTION))) { - throw new IllegalArgumentException("Sentry Android cannot use synchronous connections, remove '" - + DefaultSentryClientFactory.ASYNC_OPTION + "=false' from your DSN."); - } - - SentryClientFactory.registerFactory(sentryClientFactory); - - // SentryClient will store the instance statically on the Sentry utility class. - SentryClient sentryClient = SentryClientFactory.sentryClient(dsn); - setupUncaughtExceptionHandler(); - return sentryClient; - } - - private static AndroidSentryClientFactory getDefaultSentryClientFactory(Context ctx) { - return new AndroidSentryClientFactory(ctx); - } - - /** - * Configures an Android uncaught exception handler which sends events to - * Sentry, then calls the preexisting uncaught exception handler. - */ - private static void setupUncaughtExceptionHandler() { - Thread.UncaughtExceptionHandler currentHandler = Thread.getDefaultUncaughtExceptionHandler(); - if (currentHandler != null) { - Log.d(TAG, "default UncaughtExceptionHandler class='" + currentHandler.getClass().getName() + "'"); - } - - // don't double register - if (!(currentHandler instanceof SentryUncaughtExceptionHandler)) { - // register as default exception handler - Thread.setDefaultUncaughtExceptionHandler( - new SentryUncaughtExceptionHandler(currentHandler)); - } - } - -} diff --git a/sentry-android/src/main/java/io/sentry/android/Util.java b/sentry-android/src/main/java/io/sentry/android/Util.java deleted file mode 100644 index f0ad61b9593..00000000000 --- a/sentry-android/src/main/java/io/sentry/android/Util.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.sentry.android; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; - -/** - * Sentry Android utility methods. - */ -public final class Util { - - private static final String TAG = Util.class.getName(); - - /** - * Hide constructor. - */ - private Util() { - - } - - /** - * Check whether the application has been granted a certain permission. - * - * @param ctx Android application context - * @param permission Permission as a string - * @return true if permissions is granted - */ - public static boolean checkPermission(Context ctx, String permission) { - int res = ctx.checkCallingOrSelfPermission(permission); - return (res == PackageManager.PERMISSION_GRANTED); - } - - /** - * Check whether the application has internet access at a point in time. - * - * @param ctx Android application context - * @return true if the application has internet access - */ - public static boolean isConnected(Context ctx) { - ConnectivityManager connectivityManager = - (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); - return activeNetworkInfo != null && activeNetworkInfo.isConnected(); - } - - /** - * Check whether Sentry should attempt to send an event, or just immediately store it. - * - * @param ctx Android application context - * @return true if Sentry should attempt to send an event - */ - public static boolean shouldAttemptToSend(Context ctx) { - if (!checkPermission(ctx, android.Manifest.permission.ACCESS_NETWORK_STATE)) { - // we can't check whether the connection is up, so the - // best we can do is try - return true; - } - - return isConnected(ctx); - } - -} diff --git a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java index 97be88cfc85..a3c383fa393 100644 --- a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java +++ b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java @@ -7,6 +7,8 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.os.BatteryManager; import android.os.Build; import android.os.Environment; @@ -14,7 +16,6 @@ import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; -import io.sentry.android.Util; import io.sentry.environment.SentryEnvironment; import io.sentry.event.EventBuilder; import io.sentry.event.helper.EventBuilderHelper; @@ -98,7 +99,7 @@ private Map> getContexts() { deviceMap.put("external_storage_size", getTotalExternalStorage()); deviceMap.put("external_free_storage", getUnusedExternalStorage()); deviceMap.put("charging", isCharging(ctx)); - deviceMap.put("online", Util.isConnected(ctx)); + deviceMap.put("online", isConnected(ctx)); DisplayMetrics displayMetrics = getDisplayMetrics(ctx); if (displayMetrics != null) { @@ -491,4 +492,17 @@ private static String getApplicationName(Context ctx) { return null; } + /** + * Check whether the application has internet access at a point in time. + * + * @param ctx Android application context + * @return true if the application has internet access + */ + private static boolean isConnected(Context ctx) { + ConnectivityManager connectivityManager = + (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); + return activeNetworkInfo != null && activeNetworkInfo.isConnected(); + } + } diff --git a/sentry-android/src/test/java/io/sentry/android/SentryITActivity.java b/sentry-android/src/test/java/io/sentry/android/SentryITActivity.java index ecb99391ebc..d009c975012 100644 --- a/sentry-android/src/test/java/io/sentry/android/SentryITActivity.java +++ b/sentry-android/src/test/java/io/sentry/android/SentryITActivity.java @@ -26,8 +26,7 @@ public CustomAndroidSentryClientFactory(Context ctx) { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - SentryAndroid.init( - this.getApplicationContext(), + Sentry.init( "http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1", new CustomAndroidSentryClientFactory(getApplicationContext())); } diff --git a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java index 4e277ee3677..a64dc958ad7 100644 --- a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java +++ b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java @@ -47,7 +47,7 @@ public class SentryAppender extends AppenderSkeleton { *

    * Might be null in which case the DSN should be detected automatically. */ - protected String dsn; + protected Dsn dsn; /** * Name of the {@link SentryClientFactory} being used. *

    @@ -215,11 +215,7 @@ public void activateOptions() { */ protected synchronized void initSentry() { try { - if (dsn == null) { - dsn = Dsn.dsnLookup(); - } - - sentryClient = SentryClientFactory.sentryClient(new Dsn(dsn), sentryClientFactory); + sentryClient = SentryClientFactory.sentryClient(dsn, sentryClientFactory); } catch (InvalidDsnException e) { getErrorHandler().error("An exception occurred during the retrieval of the DSN for Sentry", e, ErrorCode.ADDRESS_PARSE_FAILURE); @@ -330,8 +326,17 @@ public void setFactory(String factory) { this.sentryClientFactory = factory; } + /** + * Sets the DSN field if the provided string is not null or empty, + * otherwise leaves the field null so that a DSN lookup will be + * done on client creation. + * + * @param dsn Dsn as a string + */ public void setDsn(String dsn) { - this.dsn = dsn; + if (!Util.isNullOrEmpty(dsn)) { + this.dsn = new Dsn(dsn); + } } public void setRelease(String release) { diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java index 2b1258f48fa..20c0150a19e 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java @@ -48,10 +48,9 @@ public void testConnectionClosedWhenAppenderClosed() throws Exception { public void testClosedIfSentryClientNotProvided() throws Exception { final String dsnUri = "protocol://public:private@host/1"; final SentryAppender sentryAppender = new SentryAppender(); + sentryAppender.setDsn(dsnUri); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); new Expectations() {{ - Dsn.dsnLookup(); - result = dsnUri; SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); result = mockSentryClient; }}; diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderDsnTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderDsnTest.java index dfa817bd35c..a7ec9804a21 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderDsnTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderDsnTest.java @@ -37,21 +37,6 @@ private void assertNoErrorsInErrorHandler() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } - @Test - public void testDsnDetected() throws Exception { - final String dsnUri = "protocol://public:private@host/1"; - new Expectations() {{ - Dsn.dsnLookup(); - result = dsnUri; - SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); - result = mockSentryClient; - }}; - - sentryAppender.initSentry(); - - assertNoErrorsInErrorHandler(); - } - @Test public void testDsnProvided() throws Exception { final String dsnUri = "protocol://public:private@host/2"; diff --git a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java index e751b2a43b3..d6201c251a1 100644 --- a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -64,7 +64,7 @@ public class SentryAppender extends AbstractAppender { *

    * Might be null in which case the DSN should be detected automatically. */ - protected String dsn; + protected Dsn dsn; /** * Name of the {@link SentryClientFactory} being used. *

    @@ -315,11 +315,7 @@ public void append(LogEvent logEvent) { */ protected synchronized void initSentry() { try { - if (dsn == null) { - dsn = Dsn.dsnLookup(); - } - - sentryClient = SentryClientFactory.sentryClient(new Dsn(dsn), sentryClientFactory); + sentryClient = SentryClientFactory.sentryClient(dsn, sentryClientFactory); } catch (InvalidDsnException e) { error("An exception occurred during the retrieval of the DSN for Sentry", e); } catch (Exception e) { @@ -408,8 +404,17 @@ protected EventBuilder createEventBuilder(LogEvent event) { return eventBuilder; } + /** + * Sets the DSN field if the provided string is not null or empty, + * otherwise leaves the field null so that a DSN lookup will be + * done on client creation. + * + * @param dsn Dsn as a string + */ public void setDsn(String dsn) { - this.dsn = dsn; + if (!Util.isNullOrEmpty(dsn)) { + this.dsn = new Dsn(dsn); + } } public void setFactory(String factory) { diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java index 65f21a37b61..fd58c8ce60c 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java @@ -48,10 +48,9 @@ public void testConnectionClosedWhenAppenderStopped() throws Exception { public void testStopIfSentryClientNotProvided() throws Exception { final String dsnUri = "protocol://public:private@host/1"; final SentryAppender sentryAppender = new SentryAppender(); + sentryAppender.setDsn(dsnUri); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); new Expectations() {{ - Dsn.dsnLookup(); - result = dsnUri; SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); result = mockSentryClient; }}; diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderDsnTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderDsnTest.java index 11a588ad355..cd45f8cfa58 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderDsnTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderDsnTest.java @@ -36,21 +36,6 @@ private void assertNoErrorsInErrorHandler() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } - @Test - public void testDsnDetected() throws Exception { - final String dsnUri = "protocol://public:private@host/1"; - new Expectations() {{ - Dsn.dsnLookup(); - result = dsnUri; - SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); - result = mockSentryClient; - }}; - - sentryAppender.initSentry(); - - assertNoErrorsInErrorHandler(); - } - @Test public void testDsnProvided() throws Exception { final String dsnUri = "protocol://public:private@host/2"; diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index d823cfa7b2a..2a4856b4ab0 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -55,7 +55,7 @@ public class SentryAppender extends AppenderBase { *

    * Might be null in which case the DSN should be detected automatically. */ - protected String dsn; + protected Dsn dsn; /** * Name of the {@link SentryClientFactory} being used. *

    @@ -245,11 +245,7 @@ private boolean isNotLoggable(ILoggingEvent iLoggingEvent) { */ protected synchronized void initSentry() { try { - if (dsn == null) { - dsn = Dsn.dsnLookup(); - } - - sentryClient = SentryClientFactory.sentryClient(new Dsn(dsn), sentryClientFactory); + sentryClient = SentryClientFactory.sentryClient(dsn, sentryClientFactory); } catch (InvalidDsnException e) { addError("An exception occurred during the retrieval of the DSN for Sentry", e); } catch (Exception e) { @@ -428,8 +424,17 @@ protected StackTraceElement[] toStackTraceElements(IThrowableProxy throwableProx return stackTraceElements; } + /** + * Sets the DSN field if the provided string is not null or empty, + * otherwise leaves the field null so that a DSN lookup will be + * done on client creation. + * + * @param dsn Dsn as a string + */ public void setDsn(String dsn) { - this.dsn = dsn; + if (!Util.isNullOrEmpty(dsn)) { + this.dsn = new Dsn(dsn); + } } public void setFactory(String factory) { diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java index 878ed0fceec..1d85ed6f375 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java @@ -61,10 +61,9 @@ public void testConnectionClosedWhenAppenderStopped() throws Exception { public void testStopIfSentryClientNotProvided() throws Exception { final String dsnUri = "protocol://public:private@host/1"; final SentryAppender sentryAppender = new SentryAppender(); + sentryAppender.setDsn(dsnUri); sentryAppender.setContext(mockContext); new Expectations() {{ - Dsn.dsnLookup(); - result = dsnUri; SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); result = mockSentryClient; }}; diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderDsnTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderDsnTest.java index 0d6b915c774..d36c273a228 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderDsnTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderDsnTest.java @@ -48,21 +48,6 @@ private void assertNoErrorsInStatusManager() throws Exception { assertThat(mockContext.getStatusManager().getCount(), is(0)); } - @Test - public void testDsnDetected() throws Exception { - final String dsnUri = "protocol://public:private@host/1"; - new Expectations() {{ - Dsn.dsnLookup(); - result = dsnUri; - SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); - result = mockSentryClient; - }}; - - sentryAppender.initSentry(); - - assertNoErrorsInStatusManager(); - } - @Test public void testDsnProvided() throws Exception { final String dsnUri = "protocol://public:private@host/2"; diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 61c36bde9cc..05376b05722 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -1,14 +1,19 @@ package io.sentry; +import io.sentry.dsn.Dsn; import io.sentry.event.Breadcrumb; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.User; +import io.sentry.util.Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Sentry provides easy access to a statically stored {@link SentryClient} instance. */ public final class Sentry { + private static final Logger logger = LoggerFactory.getLogger(Sentry.class); /** * The most recently constructed {@link SentryClient} instance, used by static helper * methods like {@link Sentry#capture(Event)}. @@ -22,18 +27,91 @@ private Sentry() { } + /** + * Initialize and statically store a {@link SentryClient} by looking up + * a {@link Dsn} and automatically choosing a {@link SentryClientFactory}. + * + * @return SentryClient + */ + public static SentryClient init() { + return init(null, null); + } + + /** + * Initialize and statically store a {@link SentryClient} by looking up + * a {@link Dsn} and using the provided {@link SentryClientFactory}. + * + * @param sentryClientFactory SentryClientFactory to use. + * @return SentryClient + */ + public static SentryClient init(SentryClientFactory sentryClientFactory) { + return init(null, sentryClientFactory); + } + + /** + * Initialize and statically store a {@link SentryClient} by using the provided + * {@link Dsn} and automatically choosing a {@link SentryClientFactory}. + * + * @param dsn Data Source Name of the Sentry server. + * @return SentryClient + */ + public static SentryClient init(String dsn) { + return init(dsn, null); + } + + /** + * Initialize and statically store a {@link SentryClient} by using the provided + * {@link Dsn} and {@link SentryClientFactory}. + *

    + * Note that the Dsn or SentryClientFactory may be null, at which a best effort attempt + * is made to look up or choose the best value(s). + * + * @param dsn Data Source Name of the Sentry server. + * @param sentryClientFactory SentryClientFactory to use. + * @return SentryClient + */ + public static SentryClient init(String dsn, SentryClientFactory sentryClientFactory) { + SentryClient sentryClient; + if (sentryClientFactory != null) { + Dsn realDsn; + if (!Util.isNullOrEmpty(dsn)) { + realDsn = new Dsn(dsn); + } else { + realDsn = new Dsn(Dsn.dsnLookup()); + } + + // use the factory instance directly + sentryClient = sentryClientFactory.createSentryClient(realDsn); + } else { + // do static factory lookup + sentryClient = SentryClientFactory.sentryClient(dsn); + } + + setStoredClient(sentryClient); + return sentryClient; + } + /** * Returns the last statically stored {@link SentryClient} instance or null if one has * never been stored. * - * @return statically stored {@link SentryClient} instance + * @return statically stored {@link SentryClient} instance. */ public static SentryClient getStoredClient() { return storedClient; } - public static void setStoredClient(SentryClient storedClient) { - Sentry.storedClient = storedClient; + /** + * Set the statically stored {@link SentryClient} instance. + * + * @param client {@link SentryClient} instance to store. + */ + public static void setStoredClient(SentryClient client) { + if (storedClient != null) { + logger.warn("Overwriting statically stored SentryClient instance {} with {}.", + storedClient, client); + } + storedClient = client; } private static void verifyStoredClient() { @@ -46,7 +124,7 @@ private static void verifyStoredClient() { /** * Send an Event using the statically stored {@link SentryClient} instance. * - * @param event Event to send to the Sentry server + * @param event Event to send to the Sentry server. */ public static void capture(Event event) { verifyStoredClient(); @@ -92,7 +170,7 @@ public static void capture(EventBuilder eventBuilder) { /** * Record a {@link Breadcrumb}. * - * @param breadcrumb Breadcrumb to record + * @param breadcrumb Breadcrumb to record. */ public static void record(Breadcrumb breadcrumb) { verifyStoredClient(); @@ -117,5 +195,4 @@ public static void clearContext() { getStoredClient().getContext().clear(); } - } diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index b123d612d06..2420f22c1e4 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -92,8 +92,6 @@ public class SentryClient { public SentryClient(Connection connection, ContextManager contextManager) { this.connection = connection; this.contextManager = contextManager; - - Sentry.setStoredClient(this); } /** diff --git a/sentry/src/main/java/io/sentry/SentryClientFactory.java b/sentry/src/main/java/io/sentry/SentryClientFactory.java index 5f0cc26f7c5..2185640c532 100644 --- a/sentry/src/main/java/io/sentry/SentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/SentryClientFactory.java @@ -1,6 +1,7 @@ package io.sentry; import io.sentry.dsn.Dsn; +import io.sentry.util.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,12 +46,13 @@ private static Iterable getRegisteredFactories() { } /** - * Creates an instance of Sentry using the DSN obtain through {@link io.sentry.dsn.Dsn#dsnLookup()}. + * Creates an instance of Sentry using the DSN obtained through the + * {@link Dsn#dsnLookup()} method. * * @return an instance of Sentry. */ public static SentryClient sentryClient() { - return sentryClient(Dsn.dsnLookup()); + return sentryClient((Dsn) null, null); } /** @@ -60,7 +62,7 @@ public static SentryClient sentryClient() { * @return an instance of Sentry. */ public static SentryClient sentryClient(String dsn) { - return sentryClient(new Dsn(dsn)); + return sentryClient(dsn, null); } /** @@ -76,7 +78,23 @@ public static SentryClient sentryClient(Dsn dsn) { /** * Creates an instance of Sentry using the provided DSN and the specified factory. * - * @param dsn Data Source Name of the Sentry server. + * @param dsn Data Source Name of the Sentry server. + * @param sentryClientFactoryName name of the SentryClientFactory to use to generate an instance of Sentry. + * @return an instance of Sentry. + * @throws IllegalStateException when no instance of Sentry has been created. + */ + public static SentryClient sentryClient(String dsn, String sentryClientFactoryName) { + if (!Util.isNullOrEmpty(dsn)) { + return sentryClient(new Dsn(dsn), sentryClientFactoryName); + } else { + return sentryClient((Dsn) null, sentryClientFactoryName); + } + } + + /** + * Creates an instance of Sentry using the provided DSN and the specified factory. + * + * @param dsn Data Source Name of the Sentry server. * @param sentryClientFactoryName name of the SentryClientFactory to use to generate an instance of Sentry. * @return an instance of Sentry. * @throws IllegalStateException when no instance of Sentry has been created. @@ -84,6 +102,10 @@ public static SentryClient sentryClient(Dsn dsn) { public static SentryClient sentryClient(Dsn dsn, String sentryClientFactoryName) { logger.debug("Attempting to find a working SentryClientFactory"); + if (dsn == null) { + dsn = new Dsn(Dsn.dsnLookup()); + } + // Loop through registered factories, keeping track of which classes we skip, which we try to instantiate, // and the last exception thrown. ArrayList skippedFactories = new ArrayList<>(); @@ -116,8 +138,9 @@ public static SentryClient sentryClient(Dsn dsn, String sentryClientFactoryName) Class.forName(sentryClientFactoryName); logger.error( "The SentryClientFactory class '{}' was found on your classpath but was not " - + "registered with Sentry, see: " - + "https://github.com/getsentry/sentry-java/#custom-sentryClientFactory", sentryClientFactoryName); + + "registered with Sentry, see: " + + "https://docs.sentry.io/clients/java/config/#custom-sentryfactory", + sentryClientFactoryName); } catch (ClassNotFoundException e) { logger.error("The SentryClientFactory class name '{}' was specified but " + "the class was not found on your classpath.", sentryClientFactoryName); @@ -175,7 +198,7 @@ public static SentryClient sentryClient(Dsn dsn, String sentryClientFactoryName) @Override public String toString() { return "SentryClientFactory{" - + "name='" + this.getClass().getName() + '\'' - + '}'; + + "name='" + this.getClass().getName() + '\'' + + '}'; } } diff --git a/sentry-android/src/main/java/io/sentry/android/SentryUncaughtExceptionHandler.java b/sentry/src/main/java/io/sentry/SentryUncaughtExceptionHandler.java similarity index 55% rename from sentry-android/src/main/java/io/sentry/android/SentryUncaughtExceptionHandler.java rename to sentry/src/main/java/io/sentry/SentryUncaughtExceptionHandler.java index 12d0986fd57..813d60b4be4 100644 --- a/sentry-android/src/main/java/io/sentry/android/SentryUncaughtExceptionHandler.java +++ b/sentry/src/main/java/io/sentry/SentryUncaughtExceptionHandler.java @@ -1,21 +1,17 @@ -package io.sentry.android; +package io.sentry; -import android.util.Log; -import io.sentry.Sentry; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.interfaces.ExceptionInterface; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Sends any uncaught exception to Sentry, then passes the exception on to the pre-existing * uncaught exception handler. */ -class SentryUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { - - /** - * Logger tag. - */ - private static final String TAG = SentryUncaughtExceptionHandler.class.getName(); +public class SentryUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { + private static final Logger logger = LoggerFactory.getLogger(SentryClientFactory.class); /** * Reference to the pre-existing uncaught exception handler. @@ -28,7 +24,7 @@ class SentryUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler * * @param defaultExceptionHandler pre-existing uncaught exception handler */ - SentryUncaughtExceptionHandler(Thread.UncaughtExceptionHandler defaultExceptionHandler) { + public SentryUncaughtExceptionHandler(Thread.UncaughtExceptionHandler defaultExceptionHandler) { this.defaultExceptionHandler = defaultExceptionHandler; } @@ -41,7 +37,7 @@ class SentryUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler */ @Override public void uncaughtException(Thread thread, Throwable thrown) { - Log.d(TAG, "Uncaught exception received."); + logger.trace("Uncaught exception received."); EventBuilder eventBuilder = new EventBuilder() .withMessage(thrown.getMessage()) @@ -51,7 +47,7 @@ public void uncaughtException(Thread thread, Throwable thrown) { try { Sentry.capture(eventBuilder); } catch (Exception e) { - Log.e(TAG, "Error sending excepting to Sentry.", e); + logger.error("Error sending uncaught exception to Sentry.", e); } if (defaultExceptionHandler != null) { @@ -60,4 +56,22 @@ public void uncaughtException(Thread thread, Throwable thrown) { } } + /** + * Configures an uncaught exception handler which sends events to + * Sentry, then calls the preexisting uncaught exception handler. + */ + public static void setup() { + Thread.UncaughtExceptionHandler currentHandler = Thread.getDefaultUncaughtExceptionHandler(); + if (currentHandler != null) { + logger.debug("default UncaughtExceptionHandler class='" + currentHandler.getClass().getName() + "'"); + } + + // don't double register + if (!(currentHandler instanceof SentryUncaughtExceptionHandler)) { + // register as default exception handler + Thread.setDefaultUncaughtExceptionHandler( + new SentryUncaughtExceptionHandler(currentHandler)); + } + } + } diff --git a/sentry/src/main/java/io/sentry/dsn/Dsn.java b/sentry/src/main/java/io/sentry/dsn/Dsn.java index 205c894e5f3..4b54162264f 100644 --- a/sentry/src/main/java/io/sentry/dsn/Dsn.java +++ b/sentry/src/main/java/io/sentry/dsn/Dsn.java @@ -17,7 +17,7 @@ public class Dsn { /** * Default DSN to use when auto detection fails. */ - public static final String DEFAULT_DSN = "noop://localhost"; + public static final String DEFAULT_DSN = "noop://localhost?async=false"; private static final Logger logger = LoggerFactory.getLogger(Dsn.class); private String secretKey; private String publicKey; diff --git a/sentry/src/main/java/io/sentry/jul/SentryHandler.java b/sentry/src/main/java/io/sentry/jul/SentryHandler.java index 67b72808c24..94cb54dc186 100644 --- a/sentry/src/main/java/io/sentry/jul/SentryHandler.java +++ b/sentry/src/main/java/io/sentry/jul/SentryHandler.java @@ -46,7 +46,7 @@ public class SentryHandler extends Handler { *

    * Might be null in which case the DSN should be detected automatically. */ - protected String dsn; + protected Dsn dsn; /** * If true, String.format() is used to render parameterized log * messages instead of MessageFormat.format(); Defaults to @@ -274,11 +274,7 @@ public void publish(LogRecord record) { */ protected synchronized void initSentry() { try { - if (dsn == null) { - dsn = Dsn.dsnLookup(); - } - - sentryClient = SentryClientFactory.sentryClient(new Dsn(dsn), sentryClientFactory); + sentryClient = SentryClientFactory.sentryClient(dsn, sentryClientFactory); } catch (InvalidDsnException e) { reportError("An exception occurred during the retrieval of the DSN for Sentry", e, ErrorManager.OPEN_FAILURE); @@ -408,8 +404,17 @@ public void close() throws SecurityException { } } + /** + * Sets the DSN field if the provided string is not null or empty, + * otherwise leaves the field null so that a DSN lookup will be + * done on client creation. + * + * @param dsn Dsn as a string + */ public void setDsn(String dsn) { - this.dsn = dsn; + if (!Util.isNullOrEmpty(dsn)) { + this.dsn = new Dsn(dsn); + } } public void setPrintfStyle(boolean printfStyle) { diff --git a/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java b/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java index a4455888c9e..c402360e175 100644 --- a/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java +++ b/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java @@ -125,9 +125,6 @@ public void testAutoDetectDsnIfNotProvided(@Injectable final SentryClient mockSe final String dsn = "protocol://user:password@host:port/3"; SentryClientFactory.registerFactory(sentryClientFactory); new NonStrictExpectations() {{ - Dsn.dsnLookup(); - result = dsn; - sentryClientFactory.createSentryClient((Dsn) any); result = mockSentryClient; }}; @@ -135,9 +132,6 @@ public void testAutoDetectDsnIfNotProvided(@Injectable final SentryClient mockSe SentryClient sentryClient = SentryClientFactory.sentryClient(); assertThat(sentryClient, is(mockSentryClient)); - new Verifications() {{ - new Dsn(dsn); - }}; } @Test diff --git a/sentry/src/test/java/io/sentry/SentryTest.java b/sentry/src/test/java/io/sentry/SentryTest.java index 9e85d5cc95a..ad66017a548 100644 --- a/sentry/src/test/java/io/sentry/SentryTest.java +++ b/sentry/src/test/java/io/sentry/SentryTest.java @@ -1,8 +1,11 @@ package io.sentry; import io.sentry.connection.Connection; +import io.sentry.connection.HttpConnection; +import io.sentry.connection.NoopConnection; import io.sentry.context.ContextManager; import io.sentry.context.SingletonContextManager; +import io.sentry.dsn.Dsn; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.helper.EventBuilderHelper; @@ -13,9 +16,11 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import java.net.URL; + import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasKey; +import static mockit.Deencapsulation.getField; +import static org.hamcrest.Matchers.*; public class SentryTest extends BaseTest { @Tested @@ -37,6 +42,7 @@ public void testSendEventStatically() throws Exception { final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; Event event = new EventBuilder().withMessage(message).withLevel(Event.Level.ERROR).build(); + Sentry.setStoredClient(sentryClient); Sentry.capture(event); new Verifications() {{ @@ -53,6 +59,7 @@ public void testSendEventBuilderStatically() throws Exception { EventBuilder eventBuilder = new EventBuilder().withMessage(message).withLevel(Event.Level.ERROR); sentryClient.addBuilderHelper(mockEventBuilderHelper); + Sentry.setStoredClient(sentryClient); Sentry.capture(eventBuilder); new Verifications() {{ @@ -69,6 +76,7 @@ public void testSendMessageStatically() throws Exception { final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; sentryClient.addBuilderHelper(mockEventBuilderHelper); + Sentry.setStoredClient(sentryClient); Sentry.capture(message); new Verifications() {{ @@ -86,6 +94,7 @@ public void testSendExceptionStatically() throws Exception { final Exception exception = new Exception(message); sentryClient.addBuilderHelper(mockEventBuilderHelper); + Sentry.setStoredClient(sentryClient); Sentry.capture(exception); new Verifications() {{ @@ -98,4 +107,71 @@ public void testSendExceptionStatically() throws Exception { }}; } + @Test + public void testInitNoDsn() throws Exception { + SentryClient sentryClient = Sentry.init(); + Object connection = getField(sentryClient, "connection"); + assertThat(connection, instanceOf(NoopConnection.class)); + } + + @Test + public void testInitNullDsn() throws Exception { + SentryClient sentryClient = Sentry.init((String) null); + NoopConnection connection = getField(sentryClient, "connection"); + assertThat(connection, instanceOf(NoopConnection.class)); + } + + @Test + public void testInitNullFactory() throws Exception { + SentryClient sentryClient = Sentry.init((SentryClientFactory) null); + NoopConnection connection = getField(sentryClient, "connection"); + assertThat(connection, instanceOf(NoopConnection.class)); + } + + @Test + public void testInitStringDsn() throws Exception { + SentryClient sentryClient = Sentry.init("http://public:private@localhost:4567/1?async=false"); + HttpConnection connection = getField(sentryClient, "connection"); + assertThat(connection, instanceOf(HttpConnection.class)); + + URL sentryUrl = getField(connection, "sentryUrl"); + assertThat(sentryUrl.getHost(), equalTo("localhost")); + assertThat(sentryUrl.getProtocol(), equalTo("http")); + assertThat(sentryUrl.getPort(), equalTo(4567)); + assertThat(sentryUrl.getPath(), equalTo("/api/1/store/")); + + String authHeader = getField(connection, "authHeader"); + assertThat(authHeader, equalTo("Sentry sentry_version=6,sentry_client=sentry-java/test,sentry_key=public,sentry_secret=private")); + } + + @Test + public void testInitStringDsnAndFactory() throws Exception { + SentryClient sentryClient = Sentry.init("http://public:private@localhost:4567/1?async=false", new DefaultSentryClientFactory()); + HttpConnection connection = getField(sentryClient, "connection"); + assertThat(connection, instanceOf(HttpConnection.class)); + + URL sentryUrl = getField(connection, "sentryUrl"); + assertThat(sentryUrl.getHost(), equalTo("localhost")); + assertThat(sentryUrl.getProtocol(), equalTo("http")); + assertThat(sentryUrl.getPort(), equalTo(4567)); + assertThat(sentryUrl.getPath(), equalTo("/api/1/store/")); + + String authHeader = getField(connection, "authHeader"); + assertThat(authHeader, equalTo("Sentry sentry_version=6,sentry_client=sentry-java/test,sentry_key=public,sentry_secret=private")); + } + + @Test + public void testInitProvidingFactory() throws Exception { + final SentryClient specificInstance = new SentryClient(null, null); + SentryClientFactory specificInstanceFactory = new SentryClientFactory() { + @Override + public SentryClient createSentryClient(Dsn dsn) { + return specificInstance; + } + }; + + SentryClient sentryClient = Sentry.init(specificInstanceFactory); + assertThat(sentryClient, sameInstance(specificInstance)); + } + } From b0a3610e2cc9d3564a1c45427b27a66986a4392c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 18 May 2017 14:55:09 -0500 Subject: [PATCH 1604/2152] Use Sentry static API in all logging integrations. --- CHANGES | 1 + sentry-log4j/pom.xml | 3 + .../java/io/sentry/log4j/SentryAppender.java | 236 +-------------- .../sentry/log4j/SentryAppenderCloseTest.java | 38 +-- .../sentry/log4j/SentryAppenderDsnTest.java | 53 ---- .../SentryAppenderEventBuildingTest.java | 55 ++-- .../log4j/SentryAppenderFailuresTest.java | 22 +- .../resources/log4j-integration.properties | 1 - sentry-log4j2/pom.xml | 3 + .../java/io/sentry/log4j2/SentryAppender.java | 272 +----------------- .../log4j2/SentryAppenderCloseTest.java | 44 +-- .../sentry/log4j2/SentryAppenderDsnTest.java | 52 ---- .../SentryAppenderEventBuildingTest.java | 49 +--- .../log4j2/SentryAppenderFailuresTest.java | 23 +- .../src/test/resources/log4j2-integration.xml | 4 +- sentry-logback/pom.xml | 3 + .../io/sentry/logback/SentryAppender.java | 229 +-------------- .../logback/SentryAppenderCloseTest.java | 44 +-- .../sentry/logback/SentryAppenderDsnTest.java | 64 ----- .../SentryAppenderEventBuildingTest.java | 48 +--- .../SentryAppenderEventLevelFilterTest.java | 7 +- .../logback/SentryAppenderFailuresTest.java | 27 +- .../test/resources/logback-integration.xml | 2 - sentry/pom.xml | 3 + sentry/src/main/java/io/sentry/Sentry.java | 52 ++-- .../java/io/sentry/jul/SentryHandler.java | 261 +---------------- .../jul/SentryHandlerEventBuildingTest.java | 40 +-- .../resources/logging-integration.properties | 1 - 28 files changed, 177 insertions(+), 1460 deletions(-) delete mode 100644 sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderDsnTest.java delete mode 100644 sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderDsnTest.java delete mode 100644 sentry-logback/src/test/java/io/sentry/logback/SentryAppenderDsnTest.java diff --git a/CHANGES b/CHANGES index cb3f7a9eced..9a2313c33d1 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,7 @@ Version 1.0.0 - Rename ``EventSendFailureCallback`` to ``EventSendCallback``, add ``onSuccess`` callback. - Add way to set release, dist, environment, serverName, tags, and extraTags on a ``SentryClient``. - Add way to set release, dist, environment, serverName, tags, and extraTags via the DSN. +- Logging integrations now use the new ``Sentry`` static API. Version 8.0.2 ------------- diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 197cd1d1b4b..00ba43d48ff 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -108,6 +108,9 @@ maven-failsafe-plugin + + http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?async=false + log4j-integration.properties diff --git a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java index a64dc958ad7..b37ed5bb7eb 100644 --- a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java +++ b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java @@ -1,16 +1,11 @@ package io.sentry.log4j; -import io.sentry.SentryClient; -import io.sentry.SentryClientFactory; -import io.sentry.config.Lookup; -import io.sentry.dsn.Dsn; -import io.sentry.dsn.InvalidDsnException; +import io.sentry.Sentry; import io.sentry.environment.SentryEnvironment; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.interfaces.ExceptionInterface; import io.sentry.event.interfaces.StackTraceInterface; -import io.sentry.util.Util; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; import org.apache.log4j.spi.ErrorCode; @@ -19,7 +14,6 @@ import org.apache.log4j.spi.LoggingEvent; import org.apache.log4j.spi.ThrowableInformation; -import java.util.Collections; import java.util.Date; import java.util.Map; import java.util.Set; @@ -36,64 +30,6 @@ public class SentryAppender extends AppenderSkeleton { * Name of the {@link Event#extra} property containing the Thread name. */ public static final String THREAD_NAME = "Sentry-Threadname"; - /** - * Current instance of {@link SentryClient}. - * - * @see #initSentry() - */ - protected volatile SentryClient sentryClient; - /** - * DSN property of the appender. - *

    - * Might be null in which case the DSN should be detected automatically. - */ - protected Dsn dsn; - /** - * Name of the {@link SentryClientFactory} being used. - *

    - * Might be null in which case the factory should be defined automatically. - */ - protected String sentryClientFactory; - /** - * Identifies the version of the application. - *

    - * Might be null in which case the release information will not be sent with the event. - */ - protected String release; - /** - * Identifies the distribution of the application. - *

    - * Might be null in which case the release distribution will not be sent with the event. - */ - protected String dist; - /** - * Identifies the environment the application is running in. - *

    - * Might be null in which case the environment information will not be sent with the event. - */ - protected String environment; - /** - * Server name to be sent to sentry. - *

    - * Might be null in which case the hostname is found via a reverse DNS lookup. - */ - protected String serverName; - /** - * Additional tags to be sent to sentry. - *

    - * Might be empty in which case no tags are sent. - */ - protected Map tags = Collections.emptyMap(); - /** - * List of tags to look for in the MDC. These will be added as tags to be sent to sentry. - *

    - * Might be empty in which case no mapped tags are set. - */ - protected Set extraTags = Collections.emptySet(); - /** - * Used for lazy initialization of appender state, see {@link #lazyInit()}. - */ - private volatile boolean initialized = false; /** * Creates an instance of SentryAppender. @@ -102,73 +38,6 @@ public SentryAppender() { this.addFilter(new DropSentryFilter()); } - /** - * Creates an instance of SentryAppender. - * - * @param sentryClient instance of Sentry to use with this appender. - */ - public SentryAppender(SentryClient sentryClient) { - this(); - this.sentryClient = sentryClient; - } - - /** - * Do some appender initialization *after* instance construction, so that we don't - * log in the constructor (which can cause annoying messages) and so that system - * properties and environment variables override hardcoded appender configuration. - */ - @SuppressWarnings("checkstyle:hiddenfield") - private void lazyInit() { - if (!initialized) { - synchronized (this) { - if (!initialized) { - try { - String sentryClientFactory = Lookup.lookup("factory"); - if (sentryClientFactory != null) { - setFactory(sentryClientFactory); - } - - String release = Lookup.lookup("release"); - if (release != null) { - setRelease(release); - } - - String dist = Lookup.lookup("dist"); - if (dist != null) { - setDist(dist); - } - - String environment = Lookup.lookup("environment"); - if (environment != null) { - setEnvironment(environment); - } - - String serverName = Lookup.lookup("serverName"); - if (serverName != null) { - setServerName(serverName); - } - - String tags = Lookup.lookup("tags"); - if (tags != null) { - setTags(tags); - } - - String extraTags = Lookup.lookup("extraTags"); - if (extraTags != null) { - setExtraTags(extraTags); - } - } finally { - initialized = true; - } - } - } - } - - if (sentryClient == null) { - initSentry(); - } - } - /** * Transforms a {@link Level} into an {@link Event.Level}. * @@ -203,28 +72,6 @@ protected static StackTraceElement asStackTraceElement(LocationInfo location) { return new StackTraceElement(location.getClassName(), location.getMethodName(), fileName, line); } - @Override - public void activateOptions() { - super.activateOptions(); - - lazyInit(); - } - - /** - * Initialises the {@link SentryClient} instance. - */ - protected synchronized void initSentry() { - try { - sentryClient = SentryClientFactory.sentryClient(dsn, sentryClientFactory); - } catch (InvalidDsnException e) { - getErrorHandler().error("An exception occurred during the retrieval of the DSN for Sentry", e, - ErrorCode.ADDRESS_PARSE_FAILURE); - } catch (Exception e) { - getErrorHandler().error("An exception occurred during the creation of a " - + "SentryClient instance", e, ErrorCode.FILE_OPEN_FAILURE); - } - } - @Override protected void append(LoggingEvent loggingEvent) { // Do not log the event if the current thread is managed by sentry @@ -234,9 +81,8 @@ protected void append(LoggingEvent loggingEvent) { SentryEnvironment.startManagingThread(); try { - lazyInit(); EventBuilder eventBuilder = createEventBuilder(loggingEvent); - sentryClient.sendEvent(eventBuilder); + Sentry.capture(eventBuilder); } catch (Exception e) { getErrorHandler().error("An exception occurred while creating a new event in Sentry", e, ErrorCode.WRITE_FAILURE); @@ -260,21 +106,6 @@ protected EventBuilder createEventBuilder(LoggingEvent loggingEvent) { .withLevel(formatLevel(loggingEvent.getLevel())) .withExtra(THREAD_NAME, loggingEvent.getThreadName()); - if (!Util.isNullOrEmpty(serverName)) { - eventBuilder.withServerName(serverName.trim()); - } - - if (!Util.isNullOrEmpty(release)) { - eventBuilder.withRelease(release.trim()); - if (!Util.isNullOrEmpty(dist)) { - eventBuilder.withDist(dist.trim()); - } - } - - if (!Util.isNullOrEmpty(environment)) { - eventBuilder.withEnvironment(environment.trim()); - } - ThrowableInformation throwableInformation = null; try { throwableInformation = loggingEvent.getThrowableInformation(); @@ -304,75 +135,20 @@ protected EventBuilder createEventBuilder(LoggingEvent loggingEvent) { eventBuilder.withExtra(LOG4J_NDC, loggingEvent.getNDC()); } - Set clientExtraTags = sentryClient.getExtraTags(); + Set extraTags = Sentry.getStoredClient().getExtraTags(); @SuppressWarnings("unchecked") Map properties = (Map) loggingEvent.getProperties(); for (Map.Entry mdcEntry : properties.entrySet()) { - if (extraTags.contains(mdcEntry.getKey()) || clientExtraTags.contains(mdcEntry.getKey())) { + if (extraTags.contains(mdcEntry.getKey())) { eventBuilder.withTag(mdcEntry.getKey(), mdcEntry.getValue().toString()); } else { eventBuilder.withExtra(mdcEntry.getKey(), mdcEntry.getValue()); } } - for (Map.Entry tagEntry : tags.entrySet()) { - eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); - } - return eventBuilder; } - public void setFactory(String factory) { - this.sentryClientFactory = factory; - } - - /** - * Sets the DSN field if the provided string is not null or empty, - * otherwise leaves the field null so that a DSN lookup will be - * done on client creation. - * - * @param dsn Dsn as a string - */ - public void setDsn(String dsn) { - if (!Util.isNullOrEmpty(dsn)) { - this.dsn = new Dsn(dsn); - } - } - - public void setRelease(String release) { - this.release = release; - } - - public void setDist(String dist) { - this.dist = dist; - } - - public void setEnvironment(String environment) { - this.environment = environment; - } - - public void setServerName(String serverName) { - this.serverName = serverName; - } - - /** - * Set the tags that should be sent along with the events. - * - * @param tags A String of tags. key/values are separated by colon(:) and tags are separated by commas(,). - */ - public void setTags(String tags) { - this.tags = Util.parseTags(tags); - } - - /** - * Set the mapped extras that will be used to search MDC and upgrade key pair to a tag sent along with the events. - * - * @param extraTags A String of extraTags. extraTags are separated by commas(,). - */ - public void setExtraTags(String extraTags) { - this.extraTags = Util.parseExtraTags(extraTags); - } - @Override public void close() { SentryEnvironment.startManagingThread(); @@ -381,9 +157,7 @@ public void close() { return; } this.closed = true; - if (sentryClient != null) { - sentryClient.closeConnection(); - } + Sentry.close(); } catch (Exception e) { getErrorHandler().error("An exception occurred while closing the Sentry connection", e, ErrorCode.CLOSE_FAILURE); diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java index 20c0150a19e..64dd4c3f0ca 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java @@ -1,5 +1,7 @@ package io.sentry.log4j; +import io.sentry.BaseTest; +import io.sentry.Sentry; import mockit.*; import io.sentry.SentryClient; import io.sentry.SentryClientFactory; @@ -10,7 +12,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class SentryAppenderCloseTest { +public class SentryAppenderCloseTest extends BaseTest { private MockUpErrorHandler mockUpErrorHandler; @Injectable private SentryClient mockSentryClient = null; @@ -32,28 +34,9 @@ private void assertNoErrorsInErrorHandler() throws Exception { @Test public void testConnectionClosedWhenAppenderClosed() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockSentryClient); - sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.activateOptions(); - - sentryAppender.close(); - - new Verifications() {{ - mockSentryClient.closeConnection(); - }}; - assertNoErrorsInErrorHandler(); - } - - @Test - public void testClosedIfSentryClientNotProvided() throws Exception { - final String dsnUri = "protocol://public:private@host/1"; + Sentry.setStoredClient(mockSentryClient); final SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setDsn(dsnUri); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); - new Expectations() {{ - SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); - result = mockSentryClient; - }}; sentryAppender.activateOptions(); sentryAppender.close(); @@ -65,19 +48,15 @@ public void testClosedIfSentryClientNotProvided() throws Exception { } @Test - public void testCloseDoNotFailIfInitFailed() throws Exception { + public void testCloseDoNotFailIfSentryNull() throws Exception { // This checks that even if sentry wasn't setup correctly its appender can still be closed. + Sentry.setStoredClient(null); final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); - new NonStrictExpectations() {{ - SentryClientFactory.sentryClient((Dsn) any, anyString); - result = new UnsupportedOperationException(); - }}; - sentryAppender.activateOptions(); sentryAppender.close(); - assertThat(mockUpErrorHandler.getErrorCount(), is(1)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -93,7 +72,8 @@ public void testCloseDoNotFailIfNoInit() @Test public void testCloseDoNotFailWhenMultipleCalls() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockSentryClient); + Sentry.setStoredClient(mockSentryClient); + final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.activateOptions(); diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderDsnTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderDsnTest.java deleted file mode 100644 index a7ec9804a21..00000000000 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderDsnTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package io.sentry.log4j; - -import mockit.Expectations; -import mockit.Injectable; -import mockit.Mocked; -import mockit.Tested; -import io.sentry.SentryClient; -import io.sentry.SentryClientFactory; -import io.sentry.dsn.Dsn; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -public class SentryAppenderDsnTest { - @Tested - private SentryAppender sentryAppender = null; - private MockUpErrorHandler mockUpErrorHandler; - @Injectable - private SentryClient mockSentryClient = null; - @SuppressWarnings("unused") - @Mocked("sentryClient") - private SentryClientFactory mockSentryClientFactory = null; - @SuppressWarnings("unused") - @Mocked("dsnLookup") - private Dsn mockDsn = null; - - @BeforeMethod - public void setUp() throws Exception { - sentryAppender = new SentryAppender(); - mockUpErrorHandler = new MockUpErrorHandler(); - sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); - } - - private void assertNoErrorsInErrorHandler() throws Exception { - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - } - - @Test - public void testDsnProvided() throws Exception { - final String dsnUri = "protocol://public:private@host/2"; - sentryAppender.setDsn(dsnUri); - new Expectations() {{ - SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); - result = mockSentryClient; - }}; - - sentryAppender.initSentry(); - - assertNoErrorsInErrorHandler(); - } -} diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java index 7fce87778f9..254336fbdf3 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java @@ -1,5 +1,7 @@ package io.sentry.log4j; +import io.sentry.BaseTest; +import io.sentry.Sentry; import io.sentry.SentryClient; import io.sentry.environment.SentryEnvironment; import io.sentry.event.interfaces.SentryStackTraceElement; @@ -18,16 +20,13 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -public class SentryAppenderEventBuildingTest { +public class SentryAppenderEventBuildingTest extends BaseTest { @Tested private SentryAppender sentryAppender = null; private MockUpErrorHandler mockUpErrorHandler; @@ -36,13 +35,16 @@ public class SentryAppenderEventBuildingTest { @Injectable private Logger mockLogger = null; private String mockExtraTag = "a8e0ad33-3c11-4899-b8c7-c99926c6d7b8"; + private Set extraTags; @BeforeMethod public void setUp() throws Exception { - sentryAppender = new SentryAppender(mockSentryClient); + Sentry.setStoredClient(mockSentryClient); + sentryAppender = new SentryAppender(); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.setExtraTags(mockExtraTag); + extraTags = new HashSet<>(); + extraTags.add(mockExtraTag); sentryAppender.activateOptions(); } @@ -243,6 +245,11 @@ public void testExtraTagObtainedFromMdc() throws Exception { properties.put(mockExtraTag, "ac84f38a-3889-41ed-9519-201402688abb"); properties.put("other_property", "10ebc4f6-a915-46d0-bb60-75bc9bd71371"); + new NonStrictExpectations() {{ + mockSentryClient.getExtraTags(); + result = extraTags; + }}; + sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, null, null, null, properties)); new Verifications() {{ @@ -263,6 +270,8 @@ public void testExtraTagObtainedFromMdcConvertedToString(@Injectable final Objec new NonStrictExpectations() {{ extraTagValue.toString(); result = "3c8981b4-01ad-47ec-8a3a-77a0bbcb42e2"; + mockSentryClient.getExtraTags(); + result = extraTags; }}; sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, null, null, null, properties)); @@ -277,36 +286,4 @@ public void testExtraTagObtainedFromMdcConvertedToString(@Injectable final Objec }}; assertNoErrorsInErrorHandler(); } - - @Test - public void testReleaseAddedToEvent() throws Exception { - final String release = "d7b4a6a0-1a0a-4381-a519-e2ccab609003"; - sentryAppender.setRelease(release); - - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getRelease(), is(release)); - }}; - assertNoErrorsInErrorHandler(); - } - - @Test - public void testEnvironmentAddedToEvent() throws Exception { - final String environment = "d7b4a6a0-1a0a-4381-a519-e2ccab609003"; - sentryAppender.setEnvironment(environment); - - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getEnvironment(), is(environment)); - }}; - assertNoErrorsInErrorHandler(); - } } diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java index 88c2f476f3e..9d206d35ca3 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java @@ -1,5 +1,7 @@ package io.sentry.log4j; +import io.sentry.BaseTest; +import io.sentry.Sentry; import io.sentry.SentryClient; import io.sentry.event.EventBuilder; import mockit.*; @@ -16,7 +18,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class SentryAppenderFailuresTest { +public class SentryAppenderFailuresTest extends BaseTest { @Tested private SentryAppender sentryAppender = null; private MockUpErrorHandler mockUpErrorHandler; @@ -30,7 +32,8 @@ public class SentryAppenderFailuresTest { @BeforeMethod public void setUp() throws Exception { - sentryAppender = new SentryAppender(mockSentryClient); + Sentry.setStoredClient(mockSentryClient); + sentryAppender = new SentryAppender(); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.activateOptions(); @@ -48,21 +51,6 @@ public void testSentryFailureDoesNotPropagate() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(1)); } - @Test - public void testSentryClientFactoryFailureDoesNotPropagate() throws Exception { - new NonStrictExpectations() {{ - SentryClientFactory.sentryClient((Dsn) any, anyString); - result = new UnsupportedOperationException(); - }}; - SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.setDsn("protocol://public:private@host/1"); - - sentryAppender.initSentry(); - - assertThat(mockUpErrorHandler.getErrorCount(), is(1)); - } - @Test public void testAppendFailIfCurrentThreadSpawnedBySentry() throws Exception { SentryEnvironment.startManagingThread(); diff --git a/sentry-log4j/src/test/resources/log4j-integration.properties b/sentry-log4j/src/test/resources/log4j-integration.properties index 58ef7437c5d..848acef4fed 100644 --- a/sentry-log4j/src/test/resources/log4j-integration.properties +++ b/sentry-log4j/src/test/resources/log4j-integration.properties @@ -3,7 +3,6 @@ log4j.appender.ConsoleAppender.layout=org.apache.log4j.EnhancedPatternLayout log4j.appender.ConsoleAppender.layout.ConversionPattern=[SENTRY] [%-5p] %c - %m%n%throwable{none} log4j.appender.SentryAppender=io.sentry.log4j.SentryAppender -log4j.appender.SentryAppender.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?async=false # Set Sentry to WARNING level, as we recommend this as the lowest users go in their own configuration log4j.appender.SentryAppender.threshold=WARN diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 443febce722..fe730a6e52a 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -104,6 +104,9 @@ maven-failsafe-plugin + + http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?async=false + ${project.build.directory}/test-classes/log4j2-integration.xml diff --git a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java index d6201c251a1..2f45e66c9f5 100644 --- a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -1,17 +1,12 @@ package io.sentry.log4j2; -import io.sentry.SentryClient; -import io.sentry.SentryClientFactory; -import io.sentry.config.Lookup; -import io.sentry.dsn.Dsn; -import io.sentry.dsn.InvalidDsnException; +import io.sentry.Sentry; import io.sentry.environment.SentryEnvironment; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.interfaces.ExceptionInterface; import io.sentry.event.interfaces.MessageInterface; import io.sentry.event.interfaces.StackTraceInterface; -import io.sentry.util.Util; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.Filter; @@ -26,11 +21,9 @@ import org.apache.logging.log4j.message.Message; import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; -import java.util.Set; /** * Appender for log4j2 in charge of sending the logged events to a Sentry server. @@ -53,65 +46,6 @@ public class SentryAppender extends AbstractAppender { * Name of the {@link Event#extra} property containing the Thread name. */ public static final String THREAD_NAME = "Sentry-Threadname"; - /** - * Current instance of {@link SentryClient}. - * - * @see #initSentry() - */ - protected volatile SentryClient sentryClient; - /** - * DSN property of the appender. - *

    - * Might be null in which case the DSN should be detected automatically. - */ - protected Dsn dsn; - /** - * Name of the {@link SentryClientFactory} being used. - *

    - * Might be null in which case the factory should be defined automatically. - */ - protected String sentryClientFactory; - /** - * Identifies the version of the application. - *

    - * Might be null in which case the release information will not be sent with the event. - */ - protected String release; - /** - * Identifies the distribution of the application. - *

    - * Might be null in which case the release distribution will not be sent with the event. - */ - protected String dist; - /** - * Identifies the environment the application is running in. - *

    - * Might be null in which case the environment information will not be sent with the event. - */ - protected String environment; - /** - * Server name to be sent to sentry. - *

    - * Might be null in which case the hostname is found via a reverse DNS lookup. - */ - protected String serverName; - /** - * Additional tags to be sent to sentry. - *

    - * Might be empty in which case no tags are sent. - */ - protected Map tags = Collections.emptyMap(); - /** - * Set of tags to look for in the Thread Context Map. These will be added as tags to be sent to Sentry. - *

    - * Might be empty in which case no mapped tags are set. - *

    - */ - protected Set extraTags = Collections.emptySet(); - /** - * Used for lazy initialization of appender state, see {@link #lazyInit()}. - */ - private volatile boolean initialized = false; /** * Creates an instance of SentryAppender. @@ -120,16 +54,6 @@ public SentryAppender() { this(APPENDER_NAME, null); } - /** - * Creates an instance of SentryAppender. - * - * @param sentryClient instance of Sentry to use with this appender. - */ - public SentryAppender(SentryClient sentryClient) { - this(); - this.sentryClient = sentryClient; - } - /** * Creates an instance of SentryAppender. * @@ -145,114 +69,19 @@ protected SentryAppender(String name, Filter filter) { * Create a Sentry Appender. * * @param name The name of the Appender. - * @param dsn Data Source Name to access the Sentry server. - * @param factory Name of the factory to use to build the {@link SentryClient} instance. - * @param release Release to be sent to Sentry. - * @param dist Dist to be sent to Sentry. - * @param environment Environment to be sent to Sentry. - * @param serverName serverName to be sent to Sentry. - * @param tags Tags to add to each event. - * @param extraTags Tags to search through the Thread Context Map. * @param filter The filter, if any, to use. * @return The SentryAppender. */ @PluginFactory @SuppressWarnings("checkstyle:parameternumber") public static SentryAppender createAppender(@PluginAttribute("name") final String name, - @PluginAttribute("dsn") final String dsn, - @PluginAttribute("factory") final String factory, - @PluginAttribute("release") final String release, - @PluginAttribute("dist") final String dist, - @PluginAttribute("environment") final String environment, - @PluginAttribute("serverName") final String serverName, - @PluginAttribute("tags") final String tags, - @PluginAttribute("extraTags") final String extraTags, - @PluginElement("filters") final Filter filter) { + @PluginElement("filter") final Filter filter) { if (name == null) { LOGGER.error("No name provided for SentryAppender"); return null; } - SentryAppender sentryAppender = new SentryAppender(name, filter); - sentryAppender.setDsn(dsn); - - if (release != null) { - sentryAppender.setRelease(release); - } - if (dist != null) { - sentryAppender.setDist(dist); - } - if (environment != null) { - sentryAppender.setEnvironment(environment); - } - if (serverName != null) { - sentryAppender.setServerName(serverName); - } - if (tags != null) { - sentryAppender.setTags(tags); - } - if (extraTags != null) { - sentryAppender.setExtraTags(extraTags); - } - sentryAppender.setFactory(factory); - return sentryAppender; - } - - /** - * Do some appender initialization *after* instance construction, so that we don't - * log in the constructor (which can cause annoying messages) and so that system - * properties and environment variables override hardcoded appender configuration. - */ - @SuppressWarnings("checkstyle:hiddenfield") - private void lazyInit() { - if (!initialized) { - synchronized (this) { - if (!initialized) { - try { - String sentryClientFactory = Lookup.lookup("factory"); - if (sentryClientFactory != null) { - setFactory(sentryClientFactory); - } - - String release = Lookup.lookup("release"); - if (release != null) { - setRelease(release); - } - - String dist = Lookup.lookup("dist"); - if (dist != null) { - setDist(dist); - } - - String environment = Lookup.lookup("environment"); - if (environment != null) { - setEnvironment(environment); - } - - String serverName = Lookup.lookup("serverName"); - if (serverName != null) { - setServerName(serverName); - } - - String tags = Lookup.lookup("tags"); - if (tags != null) { - setTags(tags); - } - - String extraTags = Lookup.lookup("extraTags"); - if (extraTags != null) { - setExtraTags(extraTags); - } - } finally { - initialized = true; - } - } - } - } - - if (sentryClient == null) { - initSentry(); - } + return new SentryAppender(name, filter); } /** @@ -300,9 +129,8 @@ public void append(LogEvent logEvent) { SentryEnvironment.startManagingThread(); try { - lazyInit(); EventBuilder eventBuilder = createEventBuilder(logEvent); - sentryClient.sendEvent(eventBuilder); + Sentry.capture(eventBuilder); } catch (Exception e) { error("An exception occurred while creating a new event in Sentry", logEvent, e); } finally { @@ -310,19 +138,6 @@ public void append(LogEvent logEvent) { } } - /** - * Initialises the {@link SentryClient} instance. - */ - protected synchronized void initSentry() { - try { - sentryClient = SentryClientFactory.sentryClient(dsn, sentryClientFactory); - } catch (InvalidDsnException e) { - error("An exception occurred during the retrieval of the DSN for Sentry", e); - } catch (Exception e) { - error("An exception occurred during the creation of a SentryClient instance", e); - } - } - /** * Builds an EventBuilder based on the logging event. * @@ -339,21 +154,6 @@ protected EventBuilder createEventBuilder(LogEvent event) { .withLevel(formatLevel(event.getLevel())) .withExtra(THREAD_NAME, event.getThreadName()); - if (!Util.isNullOrEmpty(serverName)) { - eventBuilder.withServerName(serverName.trim()); - } - - if (!Util.isNullOrEmpty(release)) { - eventBuilder.withRelease(release.trim()); - if (!Util.isNullOrEmpty(dist)) { - eventBuilder.withDist(dist.trim()); - } - } - - if (!Util.isNullOrEmpty(environment)) { - eventBuilder.withEnvironment(environment.trim()); - } - if (eventMessage.getFormat() != null && !eventMessage.getFormat().equals("") && !eventMessage.getFormattedMessage().equals(eventMessage.getFormat())) { @@ -381,11 +181,9 @@ protected EventBuilder createEventBuilder(LogEvent event) { eventBuilder.withExtra(LOG4J_NDC, event.getContextStack().asList()); } - Set clientExtraTags = sentryClient.getExtraTags(); if (event.getContextMap() != null) { for (Map.Entry contextEntry : event.getContextMap().entrySet()) { - if (extraTags.contains(contextEntry.getKey()) - || clientExtraTags.contains(contextEntry.getKey())) { + if (Sentry.getStoredClient().getExtraTags().contains(contextEntry.getKey())) { eventBuilder.withTag(contextEntry.getKey(), contextEntry.getValue()); } else { eventBuilder.withExtra(contextEntry.getKey(), contextEntry.getValue()); @@ -397,65 +195,9 @@ protected EventBuilder createEventBuilder(LogEvent event) { eventBuilder.withTag(LOG4J_MARKER, event.getMarker().getName()); } - for (Map.Entry tagEntry : tags.entrySet()) { - eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); - } - return eventBuilder; } - /** - * Sets the DSN field if the provided string is not null or empty, - * otherwise leaves the field null so that a DSN lookup will be - * done on client creation. - * - * @param dsn Dsn as a string - */ - public void setDsn(String dsn) { - if (!Util.isNullOrEmpty(dsn)) { - this.dsn = new Dsn(dsn); - } - } - - public void setFactory(String factory) { - this.sentryClientFactory = factory; - } - - public void setRelease(String release) { - this.release = release; - } - - public void setDist(String dist) { - this.dist = dist; - } - - public void setEnvironment(String environment) { - this.environment = environment; - } - - public void setServerName(String serverName) { - this.serverName = serverName; - } - - /** - * Set the tags that should be sent along with the events. - * - * @param tags A String of tags. key/values are separated by colon(:) and tags are separated by commas(,). - */ - public void setTags(String tags) { - this.tags = Util.parseTags(tags); - } - - /** - * Set the mapped extras that will be used to search the Thread Context Map and upgrade key pair to - * a tag sent along with the events. - * - * @param extraTags A String of extraTags. extraTags are separated by commas(,). - */ - public void setExtraTags(String extraTags) { - this.extraTags = Util.parseExtraTags(extraTags); - } - @Override public void stop() { SentryEnvironment.startManagingThread(); @@ -464,9 +206,7 @@ public void stop() { return; } super.stop(); - if (sentryClient != null) { - sentryClient.closeConnection(); - } + Sentry.close(); } catch (Exception e) { error("An exception occurred while closing the Sentry connection", e); } finally { diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java index fd58c8ce60c..962aa02a013 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java @@ -1,5 +1,7 @@ package io.sentry.log4j2; +import io.sentry.BaseTest; +import io.sentry.Sentry; import io.sentry.SentryClient; import mockit.*; import io.sentry.SentryClientFactory; @@ -10,7 +12,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class SentryAppenderCloseTest { +public class SentryAppenderCloseTest extends BaseTest { private MockUpErrorHandler mockUpErrorHandler; @Injectable private SentryClient mockSentryClient = null; @@ -32,56 +34,31 @@ private void assertNoErrorsInErrorHandler() throws Exception { @Test public void testConnectionClosedWhenAppenderStopped() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockSentryClient); - sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.start(); - - sentryAppender.stop(); - - new Verifications() {{ - mockSentryClient.closeConnection(); - }}; - assertNoErrorsInErrorHandler(); - } - - @Test - public void testStopIfSentryClientNotProvided() throws Exception { - final String dsnUri = "protocol://public:private@host/1"; + Sentry.setStoredClient(mockSentryClient); final SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setDsn(dsnUri); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); - new Expectations() {{ - SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); - result = mockSentryClient; - }}; sentryAppender.start(); - sentryAppender.append(null); sentryAppender.stop(); new Verifications() {{ mockSentryClient.closeConnection(); }}; - //One error, because of the null event. - assertThat(mockUpErrorHandler.getErrorCount(), is(1)); + assertNoErrorsInErrorHandler(); } @Test - public void testStopDoNotFailIfInitFailed() throws Exception { + public void testStopDoNotFailIfSentryNull() throws Exception { // This checks that even if sentry wasn't setup correctly its appender can still be closed. + Sentry.setStoredClient(null); final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); - new NonStrictExpectations() {{ - SentryClientFactory.sentryClient((Dsn) any, anyString); - result = new UnsupportedOperationException(); - }}; - sentryAppender.start(); - sentryAppender.append(null); + sentryAppender.start(); sentryAppender.stop(); //Two errors, one because of the exception, one because of the null event. - assertThat(mockUpErrorHandler.getErrorCount(), is(2)); + assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test @@ -97,7 +74,8 @@ public void testStopDoNotFailIfNoInit() @Test public void testStopDoNotFailWhenMultipleCalls() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockSentryClient); + Sentry.setStoredClient(mockSentryClient); + final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); sentryAppender.start(); diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderDsnTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderDsnTest.java deleted file mode 100644 index cd45f8cfa58..00000000000 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderDsnTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package io.sentry.log4j2; - -import mockit.Expectations; -import mockit.Injectable; -import mockit.Mocked; -import mockit.Tested; -import io.sentry.SentryClient; -import io.sentry.SentryClientFactory; -import io.sentry.dsn.Dsn; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -public class SentryAppenderDsnTest { - @Tested - private SentryAppender sentryAppender = null; - private MockUpErrorHandler mockUpErrorHandler = new MockUpErrorHandler(); - @Injectable - private SentryClient mockSentryClient = null; - @SuppressWarnings("unused") - @Mocked("sentryClient") - private SentryClientFactory mockSentryClientFactory = null; - @SuppressWarnings("unused") - @Mocked("dsnLookup") - private Dsn mockDsn = null; - - @BeforeMethod - public void setUp() throws Exception { - sentryAppender = new SentryAppender(); - sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); - } - - private void assertNoErrorsInErrorHandler() throws Exception { - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); - } - - @Test - public void testDsnProvided() throws Exception { - final String dsnUri = "protocol://public:private@host/2"; - sentryAppender.setDsn(dsnUri); - new Expectations() {{ - SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); - result = mockSentryClient; - }}; - - sentryAppender.initSentry(); - - assertNoErrorsInErrorHandler(); - } -} diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java index 33533ae6607..03dde7b0a0e 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java @@ -1,9 +1,12 @@ package io.sentry.log4j2; +import io.sentry.BaseTest; +import io.sentry.Sentry; import io.sentry.SentryClient; import io.sentry.environment.SentryEnvironment; import io.sentry.event.interfaces.*; import mockit.Injectable; +import mockit.NonStrictExpectations; import mockit.Tested; import mockit.Verifications; import io.sentry.event.Event; @@ -25,20 +28,23 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -public class SentryAppenderEventBuildingTest { +public class SentryAppenderEventBuildingTest extends BaseTest { @Tested private SentryAppender sentryAppender = null; private MockUpErrorHandler mockUpErrorHandler; @Injectable private SentryClient mockSentryClient = null; private String mockExtraTag = "d421627f-7a25-4d43-8210-140dfe73ff10"; + private Set extraTags; @BeforeMethod public void setUp() throws Exception { - sentryAppender = new SentryAppender(mockSentryClient); + Sentry.setStoredClient(mockSentryClient); + sentryAppender = new SentryAppender(); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.setExtraTags(mockExtraTag); + extraTags = new HashSet<>(); + extraTags.add(mockExtraTag); } private void assertNoErrorsInErrorHandler() throws Exception { @@ -244,6 +250,11 @@ public void testExtraTagObtainedFromMdc() throws Exception { mdc.put(mockExtraTag, "565940d2-f4a4-42f6-9496-42e3c7c85c43"); mdc.put("other_property", "395856e8-fa1d-474f-8fa9-c062b4886527"); + new NonStrictExpectations() {{ + mockSentryClient.getExtraTags(); + result = extraTags; + }}; + sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, mdc, null, null, null, 0)); new Verifications() {{ @@ -257,36 +268,4 @@ public void testExtraTagObtainedFromMdc() throws Exception { }}; assertNoErrorsInErrorHandler(); } - - @Test - public void testReleaseAddedToEvent() throws Exception { - final String release = "d7b4a6a0-1a0a-4381-a519-e2ccab609003"; - sentryAppender.setRelease(release); - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.ERROR, new SimpleMessage(""), null)); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getRelease(), is(release)); - }}; - assertNoErrorsInErrorHandler(); - } - - @Test - public void testEnvironmentAddedToEvent() throws Exception { - final String environment = "d7b4a6a0-1a0a-4381-a519-e2ccab609003"; - sentryAppender.setEnvironment(environment); - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.ERROR, new SimpleMessage(""), null)); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getEnvironment(), is(environment)); - }}; - assertNoErrorsInErrorHandler(); - } } diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java index 6b17ec585b9..1192d8031f2 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java @@ -1,5 +1,8 @@ package io.sentry.log4j2; + +import io.sentry.BaseTest; +import io.sentry.Sentry; import io.sentry.event.EventBuilder; import mockit.Injectable; import mockit.Mocked; @@ -19,7 +22,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class SentryAppenderFailuresTest { +public class SentryAppenderFailuresTest extends BaseTest { private SentryAppender sentryAppender; private MockUpErrorHandler mockUpErrorHandler; @Injectable @@ -30,7 +33,8 @@ public class SentryAppenderFailuresTest { @BeforeMethod public void setUp() throws Exception { - sentryAppender = new SentryAppender(mockSentryClient); + Sentry.setStoredClient(mockSentryClient); + sentryAppender = new SentryAppender(); mockUpErrorHandler = new MockUpErrorHandler(); sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); } @@ -47,21 +51,6 @@ public void testSentryFailureDoesNotPropagate() throws Exception { assertThat(mockUpErrorHandler.getErrorCount(), is(1)); } - @Test - public void testSentryClientFactoryFailureDoesNotPropagate() throws Exception { - new NonStrictExpectations() {{ - SentryClientFactory.sentryClient((Dsn) any, anyString); - result = new UnsupportedOperationException(); - }}; - SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); - sentryAppender.setDsn("protocol://public:private@host/1"); - - sentryAppender.initSentry(); - - assertThat(mockUpErrorHandler.getErrorCount(), is(1)); - } - @Test public void testAppendFailIfCurrentThreadSpawnedBySentry() throws Exception { SentryEnvironment.startManagingThread(); diff --git a/sentry-log4j2/src/test/resources/log4j2-integration.xml b/sentry-log4j2/src/test/resources/log4j2-integration.xml index f5fa96839d1..eb6cefd7646 100644 --- a/sentry-log4j2/src/test/resources/log4j2-integration.xml +++ b/sentry-log4j2/src/test/resources/log4j2-integration.xml @@ -4,9 +4,7 @@ - - http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?async=false - + diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 03b503399d6..7c35e6656e0 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -97,6 +97,9 @@ maven-failsafe-plugin + + http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?async=false + ${project.build.directory}/test-classes/logback-integration.xml diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index 2a4856b4ab0..df245798750 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -7,11 +7,7 @@ import ch.qos.logback.core.AppenderBase; import ch.qos.logback.core.filter.Filter; import ch.qos.logback.core.spi.FilterReply; -import io.sentry.SentryClient; -import io.sentry.SentryClientFactory; -import io.sentry.config.Lookup; -import io.sentry.dsn.Dsn; -import io.sentry.dsn.InvalidDsnException; +import io.sentry.Sentry; import io.sentry.environment.SentryEnvironment; import io.sentry.event.Event; import io.sentry.event.EventBuilder; @@ -19,11 +15,9 @@ import io.sentry.event.interfaces.MessageInterface; import io.sentry.event.interfaces.SentryException; import io.sentry.event.interfaces.StackTraceInterface; -import io.sentry.util.Util; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.Deque; import java.util.HashSet; @@ -44,68 +38,12 @@ public class SentryAppender extends AppenderBase { * Name of the {@link Event#extra} property containing the Thread name. */ public static final String THREAD_NAME = "Sentry-Threadname"; - /** - * Current instance of {@link SentryClient}. - * - * @see #initSentry() - */ - protected volatile SentryClient sentryClient; - /** - * DSN property of the appender. - *

    - * Might be null in which case the DSN should be detected automatically. - */ - protected Dsn dsn; - /** - * Name of the {@link SentryClientFactory} being used. - *

    - * Might be null in which case the factory should be defined automatically. - */ - protected String sentryClientFactory; - /** - * Identifies the version of the application. - *

    - * Might be null in which case the release information will not be sent with the event. - */ - protected String release; - /** - * Identifies the distribution of the application. - *

    - * Might be null in which case the release distribution will not be sent with the event. - */ - protected String dist; - /** - * Identifies the environment the application is running in. - *

    - * Might be null in which case the environment information will not be sent with the event. - */ - protected String environment; - /** - * Server name to be sent to sentry. - *

    - * Might be null in which case the hostname is found via a reverse DNS lookup. - */ - protected String serverName; /** * If set, only events with level = minLevel and up will be recorded. (This * configuration parameter is deprecated in favor of using Logback * Filters.) */ protected Level minLevel; - /** - * Additional tags to be sent to sentry. - *

    - * Might be empty in which case no tags are sent. - */ - protected Map tags = Collections.emptyMap(); - /** - * Extras to use as tags. - */ - protected Set extraTags = Collections.emptySet(); - /** - * Used for lazy initialization of appender state, see {@link #lazyInit()}. - */ - private volatile boolean initialized = false; /** * Creates an instance of SentryAppender. @@ -114,73 +52,6 @@ public SentryAppender() { this.addFilter(new DropSentryFilter()); } - /** - * Creates an instance of SentryAppender. - * - * @param sentryClient instance of Sentry to use with this appender. - */ - public SentryAppender(SentryClient sentryClient) { - this(); - this.sentryClient = sentryClient; - } - - /** - * Do some appender initialization *after* instance construction, so that we don't - * log in the constructor (which can cause annoying messages) and so that system - * properties and environment variables override hardcoded appender configuration. - */ - @SuppressWarnings("checkstyle:hiddenfield") - private void lazyInit() { - if (!initialized) { - synchronized (this) { - if (!initialized) { - try { - String sentryClientFactory = Lookup.lookup("factory"); - if (sentryClientFactory != null) { - setFactory(sentryClientFactory); - } - - String release = Lookup.lookup("release"); - if (release != null) { - setRelease(release); - } - - String dist = Lookup.lookup("dist"); - if (dist != null) { - setDist(dist); - } - - String environment = Lookup.lookup("environment"); - if (environment != null) { - setEnvironment(environment); - } - - String serverName = Lookup.lookup("serverName"); - if (serverName != null) { - setServerName(serverName); - } - - String tags = Lookup.lookup("tags"); - if (tags != null) { - setTags(tags); - } - - String extraTags = Lookup.lookup("extraTags"); - if (extraTags != null) { - setExtraTags(extraTags); - } - } finally { - initialized = true; - } - } - } - } - - if (sentryClient == null) { - initSentry(); - } - } - /** * Extracts message parameters into a List of Strings. *

    @@ -226,9 +97,12 @@ protected void append(ILoggingEvent iLoggingEvent) { SentryEnvironment.startManagingThread(); try { - lazyInit(); + if (minLevel != null && !iLoggingEvent.getLevel().isGreaterOrEqual(minLevel)) { + return; + } + EventBuilder eventBuilder = createEventBuilder(iLoggingEvent); - sentryClient.sendEvent(eventBuilder); + Sentry.capture(eventBuilder); } catch (Exception e) { addError("An exception occurred while creating a new event in Sentry", e); } finally { @@ -240,19 +114,6 @@ private boolean isNotLoggable(ILoggingEvent iLoggingEvent) { return minLevel != null && !iLoggingEvent.getLevel().isGreaterOrEqual(minLevel); } - /** - * Initialises the {@link SentryClient} instance. - */ - protected synchronized void initSentry() { - try { - sentryClient = SentryClientFactory.sentryClient(dsn, sentryClientFactory); - } catch (InvalidDsnException e) { - addError("An exception occurred during the retrieval of the DSN for Sentry", e); - } catch (Exception e) { - addError("An exception occurred during the creation of a SentryClient instance", e); - } - } - /** * Builds an EventBuilder based on the logging event. * @@ -268,22 +129,6 @@ protected EventBuilder createEventBuilder(ILoggingEvent iLoggingEvent) { .withLevel(formatLevel(iLoggingEvent.getLevel())) .withExtra(THREAD_NAME, iLoggingEvent.getThreadName()); - - if (!Util.isNullOrEmpty(serverName)) { - eventBuilder.withServerName(serverName.trim()); - } - - if (!Util.isNullOrEmpty(release)) { - eventBuilder.withRelease(release.trim()); - if (!Util.isNullOrEmpty(dist)) { - eventBuilder.withDist(dist.trim()); - } - } - - if (!Util.isNullOrEmpty(environment)) { - eventBuilder.withEnvironment(environment.trim()); - } - if (iLoggingEvent.getArgumentArray() != null) { eventBuilder.withSentryInterface(new MessageInterface( iLoggingEvent.getMessage(), @@ -307,9 +152,8 @@ protected EventBuilder createEventBuilder(ILoggingEvent iLoggingEvent) { eventBuilder.withExtra(contextEntry.getKey(), contextEntry.getValue()); } - Set clientExtraTags = sentryClient.getExtraTags(); for (Map.Entry mdcEntry : iLoggingEvent.getMDCPropertyMap().entrySet()) { - if (extraTags.contains(mdcEntry.getKey()) || clientExtraTags.contains(mdcEntry.getKey())) { + if (Sentry.getStoredClient().getExtraTags().contains(mdcEntry.getKey())) { eventBuilder.withTag(mdcEntry.getKey(), mdcEntry.getValue()); } else { eventBuilder.withExtra(mdcEntry.getKey(), mdcEntry.getValue()); @@ -320,10 +164,6 @@ protected EventBuilder createEventBuilder(ILoggingEvent iLoggingEvent) { eventBuilder.withTag(LOGBACK_MARKER, iLoggingEvent.getMarker().getName()); } - for (Map.Entry tagEntry : tags.entrySet()) { - eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); - } - return eventBuilder; } @@ -424,61 +264,10 @@ protected StackTraceElement[] toStackTraceElements(IThrowableProxy throwableProx return stackTraceElements; } - /** - * Sets the DSN field if the provided string is not null or empty, - * otherwise leaves the field null so that a DSN lookup will be - * done on client creation. - * - * @param dsn Dsn as a string - */ - public void setDsn(String dsn) { - if (!Util.isNullOrEmpty(dsn)) { - this.dsn = new Dsn(dsn); - } - } - - public void setFactory(String factory) { - this.sentryClientFactory = factory; - } - - public void setRelease(String release) { - this.release = release; - } - - public void setDist(String dist) { - this.dist = dist; - } - - public void setEnvironment(String environment) { - this.environment = environment; - } - - public void setServerName(String serverName) { - this.serverName = serverName; - } - public void setMinLevel(String minLevel) { this.minLevel = minLevel != null ? Level.toLevel(minLevel) : null; } - /** - * Set the tags that should be sent along with the events. - * - * @param tags A String of tags. key/values are separated by colon(:) and tags are separated by commas(,). - */ - public void setTags(String tags) { - this.tags = Util.parseTags(tags); - } - - /** - * Set the mapped extras that will be used to search MDC and upgrade key pair to a tag sent along with the events. - * - * @param extraTags A String of extraTags. extraTags are separated by commas(,). - */ - public void setExtraTags(String extraTags) { - this.extraTags = Util.parseExtraTags(extraTags); - } - @Override public void stop() { SentryEnvironment.startManagingThread(); @@ -487,9 +276,7 @@ public void stop() { return; } super.stop(); - if (sentryClient != null) { - sentryClient.closeConnection(); - } + Sentry.close(); } catch (Exception e) { addError("An exception occurred while closing the Sentry connection", e); } finally { diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java index 1d85ed6f375..0ad83566e9b 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java @@ -3,6 +3,8 @@ import ch.qos.logback.core.BasicStatusManager; import ch.qos.logback.core.Context; import ch.qos.logback.core.status.OnConsoleStatusListener; +import io.sentry.BaseTest; +import io.sentry.Sentry; import mockit.*; import io.sentry.SentryClient; import io.sentry.SentryClientFactory; @@ -13,7 +15,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class SentryAppenderCloseTest { +public class SentryAppenderCloseTest extends BaseTest { @Injectable private SentryClient mockSentryClient = null; @Injectable @@ -45,56 +47,31 @@ private void assertNoErrorsInStatusManager() throws Exception { @Test public void testConnectionClosedWhenAppenderStopped() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockSentryClient); - sentryAppender.setContext(mockContext); - sentryAppender.start(); - - sentryAppender.stop(); - - new Verifications() {{ - mockSentryClient.closeConnection(); - }}; - assertNoErrorsInStatusManager(); - } - - @Test - public void testStopIfSentryClientNotProvided() throws Exception { - final String dsnUri = "protocol://public:private@host/1"; + Sentry.setStoredClient(mockSentryClient); final SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setDsn(dsnUri); sentryAppender.setContext(mockContext); - new Expectations() {{ - SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); - result = mockSentryClient; - }}; sentryAppender.start(); - sentryAppender.append(null); sentryAppender.stop(); new Verifications() {{ mockSentryClient.closeConnection(); }}; - //One error, because of the null event. - assertThat(mockContext.getStatusManager().getCount(), is(1)); + assertNoErrorsInStatusManager(); } @Test - public void testStopDoNotFailIfInitFailed() throws Exception { + public void testStopDoNotFailIfSentryNull() throws Exception { // This checks that even if sentry wasn't setup correctly its appender can still be closed. + Sentry.setStoredClient(null); final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setContext(mockContext); - new NonStrictExpectations() {{ - SentryClientFactory.sentryClient((Dsn) any, anyString); - result = new UnsupportedOperationException(); - }}; - sentryAppender.start(); - sentryAppender.append(null); + sentryAppender.start(); sentryAppender.stop(); //Two errors, one because of the exception, one because of the null event. - assertThat(mockContext.getStatusManager().getCount(), is(2)); + assertThat(mockContext.getStatusManager().getCount(), is(0)); } @Test @@ -110,7 +87,8 @@ public void testStopDoNotFailIfNoInit() @Test public void testStopDoNotFailWhenMultipleCalls() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockSentryClient); + Sentry.setStoredClient(mockSentryClient); + final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setContext(mockContext); sentryAppender.start(); diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderDsnTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderDsnTest.java deleted file mode 100644 index d36c273a228..00000000000 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderDsnTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.sentry.logback; - -import ch.qos.logback.core.BasicStatusManager; -import ch.qos.logback.core.Context; -import ch.qos.logback.core.status.OnConsoleStatusListener; -import mockit.*; -import io.sentry.SentryClient; -import io.sentry.SentryClientFactory; -import io.sentry.dsn.Dsn; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -public class SentryAppenderDsnTest { - @Tested - private SentryAppender sentryAppender = null; - @Injectable - private SentryClient mockSentryClient = null; - @Injectable - private Context mockContext = null; - @SuppressWarnings("unused") - @Mocked("sentryClient") - private SentryClientFactory mockSentryClientFactory = null; - @SuppressWarnings("unused") - @Mocked("dsnLookup") - private Dsn mockDsn = null; - - @BeforeMethod - public void setUp() throws Exception { - new MockUpStatusPrinter(); - sentryAppender = new SentryAppender(); - sentryAppender.setContext(mockContext); - - new NonStrictExpectations() {{ - final BasicStatusManager statusManager = new BasicStatusManager(); - final OnConsoleStatusListener listener = new OnConsoleStatusListener(); - listener.start(); - statusManager.add(listener); - - mockContext.getStatusManager(); - result = statusManager; - }}; - } - - private void assertNoErrorsInStatusManager() throws Exception { - assertThat(mockContext.getStatusManager().getCount(), is(0)); - } - - @Test - public void testDsnProvided() throws Exception { - final String dsnUri = "protocol://public:private@host/2"; - sentryAppender.setDsn(dsnUri); - new Expectations() {{ - SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); - result = mockSentryClient; - }}; - - sentryAppender.initSentry(); - - assertNoErrorsInStatusManager(); - } -} diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java index 86c36fb7831..20e446a0698 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java @@ -5,6 +5,8 @@ import ch.qos.logback.core.BasicStatusManager; import ch.qos.logback.core.Context; import ch.qos.logback.core.status.OnConsoleStatusListener; +import io.sentry.BaseTest; +import io.sentry.Sentry; import io.sentry.SentryClient; import io.sentry.environment.SentryEnvironment; import io.sentry.event.interfaces.*; @@ -25,7 +27,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -public class SentryAppenderEventBuildingTest { +public class SentryAppenderEventBuildingTest extends BaseTest { @Tested private SentryAppender sentryAppender = null; @Injectable @@ -34,14 +36,17 @@ public class SentryAppenderEventBuildingTest { private Context mockContext = null; private String mockExtraTag = "60f42409-c029-447d-816a-fb2722913c93"; private String mockMinLevel = "ALL"; + private Set extraTags; @BeforeMethod public void setUp() throws Exception { new MockUpStatusPrinter(); - sentryAppender = new SentryAppender(mockSentryClient); + Sentry.setStoredClient(mockSentryClient); + sentryAppender = new SentryAppender(); sentryAppender.setContext(mockContext); - sentryAppender.setExtraTags(mockExtraTag); sentryAppender.setMinLevel(mockMinLevel); + extraTags = new HashSet<>(); + extraTags.add(mockExtraTag); new NonStrictExpectations() {{ final BasicStatusManager statusManager = new BasicStatusManager(); @@ -277,6 +282,11 @@ public void testExtraTagObtainedFromMdc() throws Exception { mdcPropertyMap.put(mockExtraTag, "47008f35-50c8-4e40-94ca-c8c1a3ddb729"); mdcPropertyMap.put("other_property", "cb9c92a1-0182-4e9c-866f-b06b271cd196"); + new NonStrictExpectations() {{ + mockSentryClient.getExtraTags(); + result = extraTags; + }}; + sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null, mdcPropertyMap, null, null, 0).getMockInstance()); @@ -291,36 +301,4 @@ public void testExtraTagObtainedFromMdc() throws Exception { }}; assertNoErrorsInStatusManager(); } - - @Test - public void testReleaseAddedToEvent() throws Exception { - final String release = "d7b4a6a0-1a0a-4381-a519-e2ccab609003"; - sentryAppender.setRelease(release); - - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null).getMockInstance()); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getRelease(), is(release)); - }}; - assertNoErrorsInStatusManager(); - } - - @Test - public void testEnvironmentAddedToEvent() throws Exception { - final String environment = "d7b4a6a0-1a0a-4381-a519-e2ccab609003"; - sentryAppender.setEnvironment(environment); - - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null).getMockInstance()); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getEnvironment(), is(environment)); - }}; - assertNoErrorsInStatusManager(); - } } diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java index 49e2b0c3fdf..56d543a242c 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java @@ -2,6 +2,8 @@ import ch.qos.logback.classic.Level; import ch.qos.logback.core.Context; +import io.sentry.BaseTest; +import io.sentry.Sentry; import io.sentry.event.EventBuilder; import mockit.Injectable; import mockit.Tested; @@ -15,7 +17,7 @@ /** * @author Felipe G Almeida */ -public class SentryAppenderEventLevelFilterTest { +public class SentryAppenderEventLevelFilterTest extends BaseTest { @Tested private SentryAppender sentryAppender = null; @Injectable @@ -26,7 +28,8 @@ public class SentryAppenderEventLevelFilterTest { @BeforeMethod public void setUp() throws Exception { new MockUpStatusPrinter(); - sentryAppender = new SentryAppender(mockSentryClient); + Sentry.setStoredClient(mockSentryClient); + sentryAppender = new SentryAppender(); sentryAppender.setContext(mockContext); } diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java index 5c694f8991a..d4ee0e39933 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java @@ -4,6 +4,8 @@ import ch.qos.logback.core.BasicStatusManager; import ch.qos.logback.core.Context; import ch.qos.logback.core.status.OnConsoleStatusListener; +import io.sentry.BaseTest; +import io.sentry.Sentry; import io.sentry.SentryClient; import io.sentry.event.EventBuilder; import mockit.*; @@ -17,7 +19,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public class SentryAppenderFailuresTest { +public class SentryAppenderFailuresTest extends BaseTest { @Injectable private SentryClient mockSentryClient = null; @Injectable @@ -42,7 +44,8 @@ public void setUp() throws Exception { @Test public void testSentryFailureDoesNotPropagate() throws Exception { - final SentryAppender sentryAppender = new SentryAppender(mockSentryClient); + Sentry.setStoredClient(mockSentryClient); + final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setContext(mockContext); sentryAppender.setMinLevel("ALL"); new NonStrictExpectations() {{ @@ -59,28 +62,12 @@ public void testSentryFailureDoesNotPropagate() throws Exception { assertThat(mockContext.getStatusManager().getCount(), is(1)); } - @Test - public void testSentryClientFactoryFailureDoesNotPropagate() throws Exception { - final String dsnUri = "proto://private:public@host/1"; - final SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setContext(mockContext); - sentryAppender.setDsn(dsnUri); - new Expectations() {{ - SentryClientFactory.sentryClient(withEqual(new Dsn(dsnUri)), anyString); - result = new UnsupportedOperationException(); - }}; - sentryAppender.start(); - - sentryAppender.initSentry(); - - assertThat(mockContext.getStatusManager().getCount(), is(1)); - } - @Test public void testAppendFailIfCurrentThreadSpawnedBySentry() throws Exception { SentryEnvironment.startManagingThread(); try { - final SentryAppender sentryAppender = new SentryAppender(mockSentryClient); + Sentry.setStoredClient(mockSentryClient); + final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setContext(mockContext); sentryAppender.start(); diff --git a/sentry-logback/src/test/resources/logback-integration.xml b/sentry-logback/src/test/resources/logback-integration.xml index 9b64b25b751..a5c7b8c5278 100644 --- a/sentry-logback/src/test/resources/logback-integration.xml +++ b/sentry-logback/src/test/resources/logback-integration.xml @@ -6,8 +6,6 @@ - http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?async=false - WARN diff --git a/sentry/pom.xml b/sentry/pom.xml index 8db9e7c1390..66a848b36f8 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -147,6 +147,9 @@ maven-failsafe-plugin + + http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?async=false + ${project.build.directory}/test-classes/logging-integration.properties diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 05376b05722..a27a8276e74 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -9,6 +9,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.concurrent.atomic.AtomicBoolean; + /** * Sentry provides easy access to a statically stored {@link SentryClient} instance. */ @@ -19,6 +21,11 @@ public final class Sentry { * methods like {@link Sentry#capture(Event)}. */ private static volatile SentryClient storedClient = null; + /** + * Tracks whether the {@link #init()} method has already been attempted automatically + * by {@link #getStoredClient()}. + */ + private static AtomicBoolean autoInitAttempted = new AtomicBoolean(false); /** * Hide constructor. @@ -92,12 +99,25 @@ public static SentryClient init(String dsn, SentryClientFactory sentryClientFact } /** - * Returns the last statically stored {@link SentryClient} instance or null if one has - * never been stored. + * Returns the last statically stored {@link SentryClient} instance. If no instance + * is already stored, the {@link #init()} method will be called one time in an attempt to + * create a {@link SentryClient}. * - * @return statically stored {@link SentryClient} instance. + * @return statically stored {@link SentryClient} instance, or null. */ public static SentryClient getStoredClient() { + if (storedClient != null) { + return storedClient; + } + + synchronized (Sentry.class) { + if (storedClient == null && !autoInitAttempted.get()) { + // attempt initialization by using configuration found in the environment + autoInitAttempted.set(true); + init(); + } + } + return storedClient; } @@ -114,20 +134,12 @@ public static void setStoredClient(SentryClient client) { storedClient = client; } - private static void verifyStoredClient() { - if (storedClient == null) { - throw new NullPointerException("No stored SentryClient instance is available to use." - + " You must construct a SentryClient instance before using the static Sentry methods."); - } - } - /** * Send an Event using the statically stored {@link SentryClient} instance. * * @param event Event to send to the Sentry server. */ public static void capture(Event event) { - verifyStoredClient(); getStoredClient().sendEvent(event); } @@ -140,7 +152,6 @@ public static void capture(Event event) { * @param throwable exception to send to Sentry. */ public static void capture(Throwable throwable) { - verifyStoredClient(); getStoredClient().sendException(throwable); } @@ -152,7 +163,6 @@ public static void capture(Throwable throwable) { * @param message message to send to Sentry. */ public static void capture(String message) { - verifyStoredClient(); getStoredClient().sendMessage(message); } @@ -163,7 +173,6 @@ public static void capture(String message) { * @param eventBuilder {@link EventBuilder} to send to Sentry. */ public static void capture(EventBuilder eventBuilder) { - verifyStoredClient(); getStoredClient().sendEvent(eventBuilder); } @@ -173,7 +182,6 @@ public static void capture(EventBuilder eventBuilder) { * @param breadcrumb Breadcrumb to record. */ public static void record(Breadcrumb breadcrumb) { - verifyStoredClient(); getStoredClient().getContext().recordBreadcrumb(breadcrumb); } @@ -183,7 +191,6 @@ public static void record(Breadcrumb breadcrumb) { * @param user User to store. */ public static void setUser(User user) { - verifyStoredClient(); getStoredClient().getContext().setUser(user); } @@ -191,8 +198,19 @@ public static void setUser(User user) { * Clears the current context. */ public static void clearContext() { - verifyStoredClient(); getStoredClient().getContext().clear(); } + /** + * Close the stored {@link SentryClient}'s connections and remove it from static storage. + */ + public static void close() { + if (storedClient == null) { + return; + } + + storedClient.closeConnection(); + storedClient = null; + } + } diff --git a/sentry/src/main/java/io/sentry/jul/SentryHandler.java b/sentry/src/main/java/io/sentry/jul/SentryHandler.java index 94cb54dc186..5171110da3a 100644 --- a/sentry/src/main/java/io/sentry/jul/SentryHandler.java +++ b/sentry/src/main/java/io/sentry/jul/SentryHandler.java @@ -1,31 +1,19 @@ package io.sentry.jul; -import io.sentry.SentryClient; -import io.sentry.SentryClientFactory; -import io.sentry.config.Lookup; -import io.sentry.dsn.Dsn; -import io.sentry.dsn.InvalidDsnException; +import io.sentry.Sentry; import io.sentry.environment.SentryEnvironment; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.interfaces.ExceptionInterface; import io.sentry.event.interfaces.MessageInterface; -import io.sentry.util.Util; import org.slf4j.MDC; import java.text.MessageFormat; import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.logging.ErrorManager; -import java.util.logging.Filter; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.LogRecord; +import java.util.logging.*; /** * Logging handler in charge of sending the java.util.logging records to a Sentry server. @@ -35,68 +23,12 @@ public class SentryHandler extends Handler { * Name of the {@link Event#extra} property containing the Thread id. */ public static final String THREAD_ID = "Sentry-ThreadId"; - /** - * Current instance of {@link SentryClient}. - * - * @see #initSentry() - */ - protected volatile SentryClient sentryClient; - /** - * DSN property of the appender. - *

    - * Might be null in which case the DSN should be detected automatically. - */ - protected Dsn dsn; /** * If true, String.format() is used to render parameterized log * messages instead of MessageFormat.format(); Defaults to * false. */ protected boolean printfStyle; - /** - * Name of the {@link SentryClientFactory} being used. - *

    - * Might be null in which case the factory should be defined automatically. - */ - protected String sentryClientFactory; - /** - * Identifies the version of the application. - *

    - * Might be null in which case the release information will not be sent with the event. - */ - protected String release; - /** - * Identifies the distribution of the application. - *

    - * Might be null in which case the release distribution will not be sent with the event. - */ - protected String dist; - /** - * Identifies the environment the application is running in. - *

    - * Might be null in which case the environment information will not be sent with the event. - */ - protected String environment; - /** - * Server name to be sent to sentry. - *

    - * Might be null in which case the hostname is found via a reverse DNS lookup. - */ - protected String serverName; - /** - * Tags to add to every event. - */ - protected Map tags = Collections.emptyMap(); - /** - * Set of tags to look for in the MDC. These will be added as tags to be sent to sentry. - *

    - * Might be empty in which case no mapped tags are set. - */ - protected Set extraTags = Collections.emptySet(); - /** - * Used for lazy initialization of appender state, see {@link #lazyInit()}. - */ - private volatile boolean initialized = false; /** * Creates an instance of SentryHandler. @@ -106,73 +38,6 @@ public SentryHandler() { this.setFilter(new DropSentryFilter()); } - /** - * Creates an instance of SentryHandler. - * - * @param sentryClient instance of Sentry to use with this appender. - */ - public SentryHandler(SentryClient sentryClient) { - this(); - this.sentryClient = sentryClient; - } - - /** - * Do some appender initialization *after* instance construction, so that we don't - * log in the constructor (which can cause annoying messages) and so that system - * properties and environment variables override hardcoded appender configuration. - */ - @SuppressWarnings("checkstyle:hiddenfield") - private void lazyInit() { - if (!initialized) { - synchronized (this) { - if (!initialized) { - try { - String sentryClientFactory = Lookup.lookup("factory"); - if (sentryClientFactory != null) { - setFactory(sentryClientFactory); - } - - String release = Lookup.lookup("release"); - if (release != null) { - setRelease(release); - } - - String dist = Lookup.lookup("dist"); - if (dist != null) { - setDist(dist); - } - - String environment = Lookup.lookup("environment"); - if (environment != null) { - setEnvironment(environment); - } - - String serverName = Lookup.lookup("serverName"); - if (serverName != null) { - setServerName(serverName); - } - - String tags = Lookup.lookup("tags"); - if (tags != null) { - setTags(tags); - } - - String extraTags = Lookup.lookup("extraTags"); - if (extraTags != null) { - setExtraTags(extraTags); - } - } finally { - initialized = true; - } - } - } - } - - if (sentryClient == null) { - initSentry(); - } - } - /** * Transforms a {@link Level} into an {@link Event.Level}. * @@ -215,38 +80,6 @@ protected static List formatMessageParameters(Object[] parameters) { protected void retrieveProperties() { LogManager manager = LogManager.getLogManager(); String className = SentryHandler.class.getName(); - String dsnProperty = manager.getProperty(className + ".dsn"); - if (dsnProperty != null) { - setDsn(dsnProperty); - } - String sentryClientFactoryProperty = manager.getProperty(className + ".factory"); - if (sentryClientFactoryProperty != null) { - setFactory(sentryClientFactoryProperty); - } - String releaseProperty = manager.getProperty(className + ".release"); - if (releaseProperty != null) { - setRelease(releaseProperty); - } - String distProperty = manager.getProperty(className + ".dist"); - if (distProperty != null) { - setDist(distProperty); - } - String environmentProperty = manager.getProperty(className + ".environment"); - if (environmentProperty != null) { - setEnvironment(environmentProperty); - } - String serverNameProperty = manager.getProperty(className + ".serverName"); - if (serverNameProperty != null) { - setServerName(serverNameProperty); - } - String tagsProperty = manager.getProperty(className + ".tags"); - if (tagsProperty != null) { - setTags(tagsProperty); - } - String extraTagsProperty = manager.getProperty(className + ".extraTags"); - if (extraTagsProperty != null) { - setExtraTags(extraTagsProperty); - } setPrintfStyle(Boolean.valueOf(manager.getProperty(className + ".printfStyle"))); } @@ -259,9 +92,8 @@ public void publish(LogRecord record) { SentryEnvironment.startManagingThread(); try { - lazyInit(); EventBuilder eventBuilder = createEventBuilder(record); - sentryClient.sendEvent(eventBuilder); + Sentry.capture(eventBuilder); } catch (Exception e) { reportError("An exception occurred while creating a new event in Sentry", e, ErrorManager.WRITE_FAILURE); } finally { @@ -269,21 +101,6 @@ public void publish(LogRecord record) { } } - /** - * Initialises the {@link SentryClient} instance. - */ - protected synchronized void initSentry() { - try { - sentryClient = SentryClientFactory.sentryClient(dsn, sentryClientFactory); - } catch (InvalidDsnException e) { - reportError("An exception occurred during the retrieval of the DSN for Sentry", - e, ErrorManager.OPEN_FAILURE); - } catch (Exception e) { - reportError("An exception occurred during the creation of a SentryClient instance", - e, ErrorManager.OPEN_FAILURE); - } - } - /** * Builds an EventBuilder based on the log record. * @@ -333,10 +150,9 @@ protected EventBuilder createEventBuilder(LogRecord record) { } Map mdc = MDC.getMDCAdapter().getCopyOfContextMap(); - Set clientExtraTags = sentryClient.getExtraTags(); if (mdc != null) { for (Map.Entry mdcEntry : mdc.entrySet()) { - if (extraTags.contains(mdcEntry.getKey()) || clientExtraTags.contains(mdcEntry.getKey())) { + if (Sentry.getStoredClient().getExtraTags().contains(mdcEntry.getKey())) { eventBuilder.withTag(mdcEntry.getKey(), mdcEntry.getValue()); } else { eventBuilder.withExtra(mdcEntry.getKey(), mdcEntry.getValue()); @@ -344,27 +160,8 @@ protected EventBuilder createEventBuilder(LogRecord record) { } } - for (Map.Entry tagEntry : tags.entrySet()) { - eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); - } - eventBuilder.withExtra(THREAD_ID, record.getThreadID()); - if (!Util.isNullOrEmpty(release)) { - eventBuilder.withRelease(release.trim()); - if (!Util.isNullOrEmpty(dist)) { - eventBuilder.withDist(dist.trim()); - } - } - - if (!Util.isNullOrEmpty(environment)) { - eventBuilder.withEnvironment(environment.trim()); - } - - if (!Util.isNullOrEmpty(serverName)) { - eventBuilder.withServerName(serverName.trim()); - } - return eventBuilder; } @@ -394,9 +191,7 @@ public void flush() { public void close() throws SecurityException { SentryEnvironment.startManagingThread(); try { - if (sentryClient != null) { - sentryClient.closeConnection(); - } + Sentry.close(); } catch (Exception e) { reportError("An exception occurred while closing the Sentry connection", e, ErrorManager.CLOSE_FAILURE); } finally { @@ -404,56 +199,10 @@ public void close() throws SecurityException { } } - /** - * Sets the DSN field if the provided string is not null or empty, - * otherwise leaves the field null so that a DSN lookup will be - * done on client creation. - * - * @param dsn Dsn as a string - */ - public void setDsn(String dsn) { - if (!Util.isNullOrEmpty(dsn)) { - this.dsn = new Dsn(dsn); - } - } - public void setPrintfStyle(boolean printfStyle) { this.printfStyle = printfStyle; } - public void setFactory(String factory) { - this.sentryClientFactory = factory; - } - - public void setRelease(String release) { - this.release = release; - } - - public void setDist(String dist) { - this.dist = dist; - } - - public void setEnvironment(String environment) { - this.environment = environment; - } - - public void setServerName(String serverName) { - this.serverName = serverName; - } - - /** - * Populates the tags map by parsing the given tags property string. - * @param tags comma-delimited key-value pairs, e.g. - * "tag1:value1,tag2:value2". - */ - public void setTags(String tags) { - this.tags = Util.parseTags(tags); - } - - public void setExtraTags(String extraTags) { - this.extraTags = Util.parseExtraTags(extraTags); - } - private class DropSentryFilter implements Filter { @Override public boolean isLoggable(LogRecord record) { diff --git a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java index 4739f59e5c3..1739b233f3e 100644 --- a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java +++ b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java @@ -1,6 +1,7 @@ package io.sentry.jul; import io.sentry.BaseTest; +import io.sentry.Sentry; import io.sentry.SentryClient; import io.sentry.environment.SentryEnvironment; import io.sentry.event.interfaces.*; @@ -10,6 +11,7 @@ import io.sentry.event.Event; import io.sentry.event.EventBuilder; import org.hamcrest.Matchers; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -30,6 +32,11 @@ public class SentryHandlerEventBuildingTest extends BaseTest { @Injectable private SentryClient mockSentryClient = null; + @BeforeMethod + public void setup() { + Sentry.setStoredClient(mockSentryClient); + } + private void assertNoErrorsInErrorManager() throws Exception { new Verifications() {{ errorManager.error(anyString, (Exception) any, anyInt); @@ -162,37 +169,4 @@ private LogRecord newLogRecord(String loggerName, Level level, String message, O } return logRecord; } - - @Test - public void testReleaseAddedToEvent() throws Exception { - final String release = "d7b4a6a0-1a0a-4381-a519-e2ccab609003"; - sentryHandler.setRelease(release); - - sentryHandler.publish(newLogRecord(null, Level.INFO, null, null, null)); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getRelease(), is(release)); - }}; - assertNoErrorsInErrorManager(); - } - - @Test - public void testEnvironmentAddedToEvent() throws Exception { - final String environment = "d7b4a6a0-1a0a-4381-a519-e2ccab609003"; - sentryHandler.setEnvironment(environment); - - sentryHandler.publish(newLogRecord(null, Level.INFO, null, null, null)); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getEnvironment(), is(environment)); - }}; - assertNoErrorsInErrorManager(); - } - } diff --git a/sentry/src/test/resources/logging-integration.properties b/sentry/src/test/resources/logging-integration.properties index 29e5eda5213..b0ced93983e 100644 --- a/sentry/src/test/resources/logging-integration.properties +++ b/sentry/src/test/resources/logging-integration.properties @@ -3,7 +3,6 @@ java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter java.util.logging.ConsoleHandler.level=INFO java.util.logging.SimpleFormatter.format=[SENTRY] [%4$.4s] %3$s - %5$s%n -io.sentry.jul.SentryHandler.dsn=http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1?async=false # Set Sentry to WARNING level, as we recommend this as the lowest users go in their own configuration io.sentry.jul.SentryHandler.level=WARNING From 18ef200f47885eec09d513d5b3338e591b36e61e Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 31 May 2017 15:43:25 -0500 Subject: [PATCH 1605/2152] Fix EXTRATAGS documentation. --- docs/modules/log4j.rst | 2 +- docs/modules/log4j2.rst | 2 +- docs/modules/logback.rst | 2 +- docs/modules/sentry.rst | 2 +- .../src/main/java/io/sentry/DefaultSentryClientFactory.java | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 5344929b634..0451a98d492 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -127,7 +127,7 @@ Environment variable Java System Property Example value ``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) ``SENTRY_FACTORY`` ``sentry.factory`` ``com.foo.SentryClientFactory`` Optional, select the SentryClientFactory class ``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags -``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC +``SENTRY_EXTRATAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC ======================== ======================== =============================== =========== Configuration via Static File diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 926c534feaa..b590ad906eb 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -101,7 +101,7 @@ Environment variable Java System Property Example value ``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) ``SENTRY_FACTORY`` ``sentry.factory`` ``com.foo.SentryClientFactory`` Optional, select the SentryClientFactory class ``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags -``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC +``SENTRY_EXTRATAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC ======================== ======================== =============================== =========== Configuration via Static File diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 4f81b516d98..e2782c25e57 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -105,7 +105,7 @@ Environment variable Java System Property Example value ``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) ``SENTRY_FACTORY`` ``sentry.factory`` ``com.foo.SentryClientFactory`` Optional, select the SentryClientFactory class ``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags -``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC +``SENTRY_EXTRATAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC ======================== ======================== =============================== =========== Configuration via Static File diff --git a/docs/modules/sentry.rst b/docs/modules/sentry.rst index a49f0c793c0..97bcd04fbea 100644 --- a/docs/modules/sentry.rst +++ b/docs/modules/sentry.rst @@ -96,7 +96,7 @@ Environment variable Java System Property Example value ``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) ``SENTRY_FACTORY`` ``sentry.factory`` ``com.foo.SentryClientFactory`` Optional, select the SentryClientFactory class ``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags -``SENTRY_EXTRA_TAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC +``SENTRY_EXTRATAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC ======================== ======================== =============================== =========== Configuration via Static File diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index 730c788e03f..13f5d33d3cf 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -204,7 +204,7 @@ public class DefaultSentryClientFactory extends SentryClientFactory { /** * Option to set extras to extract and send as tags, where applicable. */ - public static final String EXTRA_TAGS_OPTION = "extratags"; + public static final String EXTRATAGS_OPTION = "extratags"; private static final Logger logger = LoggerFactory.getLogger(DefaultSentryClientFactory.class); private static final String FALSE = Boolean.FALSE.toString(); @@ -721,7 +721,7 @@ protected Map getTags(Dsn dsn) { * @return Extras to use as tags on {@link io.sentry.event.Event}s, where applicable. */ protected Set getExtraTags(Dsn dsn) { - return Util.parseExtraTags(dsn.getOptions().get(EXTRA_TAGS_OPTION)); + return Util.parseExtraTags(dsn.getOptions().get(EXTRATAGS_OPTION)); } /** From cdd9c15671ff8930df670e7f888540afab93da45 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 31 May 2017 15:29:24 -0500 Subject: [PATCH 1606/2152] Load options from a sentry.properties configuration file. --- CHANGES | 1 + .../android/AndroidSentryClientFactory.java | 5 +- .../AppEngineSentryClientFactory.java | 12 +-- .../io/sentry/DefaultSentryClientFactory.java | 65 ++++++++------- .../java/io/sentry/SentryClientFactory.java | 2 +- .../java/io/sentry/config/JndiLookup.java | 4 +- .../main/java/io/sentry/config/Lookup.java | 83 ++++++++++++++++++- sentry/src/main/java/io/sentry/dsn/Dsn.java | 9 +- 8 files changed, 127 insertions(+), 54 deletions(-) diff --git a/CHANGES b/CHANGES index 9a2313c33d1..3e326edba79 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,7 @@ Version 1.0.0 - Add way to set release, dist, environment, serverName, tags, and extraTags on a ``SentryClient``. - Add way to set release, dist, environment, serverName, tags, and extraTags via the DSN. - Logging integrations now use the new ``Sentry`` static API. +- Configuration can now be provided by a ``sentry.properties`` file provided in resources. Version 8.0.2 ------------- diff --git a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java index c1a15ff2644..cd6a71285a0 100644 --- a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java +++ b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java @@ -8,6 +8,7 @@ import io.sentry.android.event.helper.AndroidEventBuilderHelper; import io.sentry.buffer.Buffer; import io.sentry.buffer.DiskBuffer; +import io.sentry.config.Lookup; import io.sentry.context.ContextManager; import io.sentry.context.SingletonContextManager; import io.sentry.dsn.Dsn; @@ -57,7 +58,7 @@ public SentryClient createSentryClient(Dsn dsn) { + " Sentry Android, but received: " + protocol); } - String async = dsn.getOptions().get(DefaultSentryClientFactory.ASYNC_OPTION); + String async = Lookup.lookup(DefaultSentryClientFactory.ASYNC_OPTION, dsn); if (async != null && async.equalsIgnoreCase("false")) { throw new IllegalArgumentException("Sentry Android cannot use synchronous connections, remove '" + DefaultSentryClientFactory.ASYNC_OPTION + "=false' from your DSN."); @@ -72,7 +73,7 @@ public SentryClient createSentryClient(Dsn dsn) { @Override protected Buffer getBuffer(Dsn dsn) { File bufferDir; - String bufferDirOpt = dsn.getOptions().get(BUFFER_DIR_OPTION); + String bufferDirOpt = Lookup.lookup(BUFFER_DIR_OPTION, dsn); if (bufferDirOpt != null) { bufferDir = new File(bufferDirOpt); } else { diff --git a/sentry-appengine/src/main/java/io/sentry/appengine/AppEngineSentryClientFactory.java b/sentry-appengine/src/main/java/io/sentry/appengine/AppEngineSentryClientFactory.java index 8f9de122a8a..98afbb2be56 100644 --- a/sentry-appengine/src/main/java/io/sentry/appengine/AppEngineSentryClientFactory.java +++ b/sentry-appengine/src/main/java/io/sentry/appengine/AppEngineSentryClientFactory.java @@ -5,6 +5,7 @@ import io.sentry.SentryClient; import io.sentry.appengine.connection.AppEngineAsyncConnection; import io.sentry.appengine.event.helper.AppEngineEventBuilderHelper; +import io.sentry.config.Lookup; import io.sentry.connection.Connection; import io.sentry.dsn.Dsn; @@ -46,18 +47,17 @@ public SentryClient createSentryClient(Dsn dsn) { */ @Override protected Connection createAsyncConnection(Dsn dsn, Connection connection) { - String connectionIdentifier; - if (dsn.getOptions().containsKey(CONNECTION_IDENTIFIER)) { - connectionIdentifier = dsn.getOptions().get(CONNECTION_IDENTIFIER); - } else { + String connectionIdentifier = Lookup.lookup(CONNECTION_IDENTIFIER, dsn); + if (connectionIdentifier == null) { connectionIdentifier = AppEngineSentryClientFactory.class.getCanonicalName() + dsn + SystemProperty.version.get(); } AppEngineAsyncConnection asyncConnection = new AppEngineAsyncConnection(connectionIdentifier, connection); - if (dsn.getOptions().containsKey(QUEUE_NAME)) { - asyncConnection.setQueue(dsn.getOptions().get(QUEUE_NAME)); + String queueName = Lookup.lookup(QUEUE_NAME, dsn); + if (queueName != null) { + asyncConnection.setQueue(queueName); } return asyncConnection; diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index 13f5d33d3cf..c901084915a 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -2,6 +2,7 @@ import io.sentry.buffer.Buffer; import io.sentry.buffer.DiskBuffer; +import io.sentry.config.Lookup; import io.sentry.connection.*; import io.sentry.context.ContextManager; import io.sentry.context.ThreadLocalContextManager; @@ -471,12 +472,13 @@ protected ContextManager getContextManager(Dsn dsn) { * @return the list of package names to consider "in-app". */ protected Collection getInAppFrames(Dsn dsn) { - if (!dsn.getOptions().containsKey(IN_APP_FRAMES_OPTION)) { + String inAppFramesOption = Lookup.lookup(IN_APP_FRAMES_OPTION, dsn); + if (Util.isNullOrEmpty(inAppFramesOption)) { return Collections.emptyList(); } ArrayList inAppPackages = new ArrayList<>(); - for (String inAppPackage : dsn.getOptions().get(IN_APP_FRAMES_OPTION).split(",")) { + for (String inAppPackage : inAppFramesOption.split(",")) { if (!inAppPackage.trim().equals("")) { inAppPackages.add(inAppPackage); } @@ -491,7 +493,7 @@ protected Collection getInAppFrames(Dsn dsn) { * @return Whether or not to wrap the underlying connection in an {@link AsyncConnection}. */ protected boolean getAsyncEnabled(Dsn dsn) { - return !FALSE.equalsIgnoreCase(dsn.getOptions().get(ASYNC_OPTION)); + return !FALSE.equalsIgnoreCase(Lookup.lookup(ASYNC_OPTION, dsn)); } /** @@ -502,8 +504,9 @@ protected boolean getAsyncEnabled(Dsn dsn) { */ protected RejectedExecutionHandler getRejectedExecutionHandler(Dsn dsn) { String overflowName = ASYNC_QUEUE_OVERFLOW_DEFAULT; - if (dsn.getOptions().containsKey(ASYNC_QUEUE_OVERFLOW_OPTION)) { - overflowName = dsn.getOptions().get(ASYNC_QUEUE_OVERFLOW_OPTION).toLowerCase(); + String asyncQueueOverflowOption = Lookup.lookup(ASYNC_QUEUE_OVERFLOW_OPTION, dsn); + if (!Util.isNullOrEmpty(asyncQueueOverflowOption)) { + overflowName = asyncQueueOverflowOption.toLowerCase(); } RejectedExecutionHandler handler = REJECT_EXECUTION_HANDLERS.get(overflowName); @@ -523,7 +526,7 @@ protected RejectedExecutionHandler getRejectedExecutionHandler(Dsn dsn) { * @return Maximum time to wait for {@link BufferedConnection} shutdown when closed, in milliseconds. */ protected long getBufferedConnectionShutdownTimeout(Dsn dsn) { - return Util.parseLong(dsn.getOptions().get(BUFFER_SHUTDOWN_TIMEOUT_OPTION), BUFFER_SHUTDOWN_TIMEOUT_DEFAULT); + return Util.parseLong(Lookup.lookup(BUFFER_SHUTDOWN_TIMEOUT_OPTION, dsn), BUFFER_SHUTDOWN_TIMEOUT_DEFAULT); } /** @@ -533,7 +536,7 @@ protected long getBufferedConnectionShutdownTimeout(Dsn dsn) { * @return Whether or not to attempt a graceful shutdown of the {@link BufferedConnection} upon close. */ protected boolean getBufferedConnectionGracefulShutdownEnabled(Dsn dsn) { - return !FALSE.equalsIgnoreCase(dsn.getOptions().get(BUFFER_GRACEFUL_SHUTDOWN_OPTION)); + return !FALSE.equalsIgnoreCase(Lookup.lookup(BUFFER_GRACEFUL_SHUTDOWN_OPTION, dsn)); } /** @@ -543,7 +546,7 @@ protected boolean getBufferedConnectionGracefulShutdownEnabled(Dsn dsn) { * @return ow long to wait between attempts to flush the disk buffer, in milliseconds. */ protected long getBufferFlushtime(Dsn dsn) { - return Util.parseLong(dsn.getOptions().get(BUFFER_FLUSHTIME_OPTION), BUFFER_FLUSHTIME_DEFAULT); + return Util.parseLong(Lookup.lookup(BUFFER_FLUSHTIME_OPTION, dsn), BUFFER_FLUSHTIME_DEFAULT); } /** @@ -553,7 +556,7 @@ protected long getBufferFlushtime(Dsn dsn) { * @return The graceful shutdown timeout of the async executor, in milliseconds. */ protected long getAsyncShutdownTimeout(Dsn dsn) { - return Util.parseLong(dsn.getOptions().get(ASYNC_SHUTDOWN_TIMEOUT_OPTION), ASYNC_SHUTDOWN_TIMEOUT_DEFAULT); + return Util.parseLong(Lookup.lookup(ASYNC_SHUTDOWN_TIMEOUT_OPTION, dsn), ASYNC_SHUTDOWN_TIMEOUT_DEFAULT); } /** @@ -563,7 +566,7 @@ protected long getAsyncShutdownTimeout(Dsn dsn) { * @return Whether or not to attempt the graceful shutdown of the {@link AsyncConnection} upon close. */ protected boolean getAsyncGracefulShutdownEnabled(Dsn dsn) { - return !FALSE.equalsIgnoreCase(dsn.getOptions().get(ASYNC_GRACEFUL_SHUTDOWN_OPTION)); + return !FALSE.equalsIgnoreCase(Lookup.lookup(ASYNC_GRACEFUL_SHUTDOWN_OPTION, dsn)); } /** @@ -573,7 +576,7 @@ protected boolean getAsyncGracefulShutdownEnabled(Dsn dsn) { * @return Maximum size of the async send queue. */ protected int getAsyncQueueSize(Dsn dsn) { - return Util.parseInteger(dsn.getOptions().get(ASYNC_QUEUE_SIZE_OPTION), QUEUE_SIZE_DEFAULT); + return Util.parseInteger(Lookup.lookup(ASYNC_QUEUE_SIZE_OPTION, dsn), QUEUE_SIZE_DEFAULT); } /** @@ -583,7 +586,7 @@ protected int getAsyncQueueSize(Dsn dsn) { * @return Priority of threads used for the async connection. */ protected int getAsyncPriority(Dsn dsn) { - return Util.parseInteger(dsn.getOptions().get(ASYNC_PRIORITY_OPTION), Thread.MIN_PRIORITY); + return Util.parseInteger(Lookup.lookup(ASYNC_PRIORITY_OPTION, dsn), Thread.MIN_PRIORITY); } /** @@ -593,7 +596,7 @@ protected int getAsyncPriority(Dsn dsn) { * @return The number of threads used for the async connection. */ protected int getAsyncThreads(Dsn dsn) { - return Util.parseInteger(dsn.getOptions().get(ASYNC_THREADS_OPTION), + return Util.parseInteger(Lookup.lookup(ASYNC_THREADS_OPTION, dsn), Runtime.getRuntime().availableProcessors()); } @@ -614,7 +617,7 @@ protected boolean getBypassSecurityEnabled(Dsn dsn) { * @return The ratio of events to allow through to server, or null if sampling is disabled. */ protected Double getSampleRate(Dsn dsn) { - return Util.parseDouble(dsn.getOptions().get(SAMPLE_RATE_OPTION), null); + return Util.parseDouble(Lookup.lookup(SAMPLE_RATE_OPTION, dsn), null); } /** @@ -624,7 +627,7 @@ protected Double getSampleRate(Dsn dsn) { * @return HTTP proxy port for Sentry connections. */ protected int getProxyPort(Dsn dsn) { - return Util.parseInteger(dsn.getOptions().get(HTTP_PROXY_PORT_OPTION), HTTP_PROXY_PORT_DEFAULT); + return Util.parseInteger(Lookup.lookup(HTTP_PROXY_PORT_OPTION, dsn), HTTP_PROXY_PORT_DEFAULT); } /** @@ -634,7 +637,7 @@ protected int getProxyPort(Dsn dsn) { * @return HTTP proxy hostname for Sentry connections. */ protected String getProxyHost(Dsn dsn) { - return dsn.getOptions().get(HTTP_PROXY_HOST_OPTION); + return Lookup.lookup(HTTP_PROXY_HOST_OPTION, dsn); } /** @@ -644,7 +647,7 @@ protected String getProxyHost(Dsn dsn) { * @return HTTP proxy username for Sentry connections. */ protected String getProxyUser(Dsn dsn) { - return dsn.getOptions().get(HTTP_PROXY_USER_OPTION); + return Lookup.lookup(HTTP_PROXY_USER_OPTION, dsn); } /** @@ -654,7 +657,7 @@ protected String getProxyUser(Dsn dsn) { * @return HTTP proxy password for Sentry connections. */ protected String getProxyPass(Dsn dsn) { - return dsn.getOptions().get(HTTP_PROXY_PASS_OPTION); + return Lookup.lookup(HTTP_PROXY_PASS_OPTION, dsn); } /** @@ -665,7 +668,7 @@ protected String getProxyPass(Dsn dsn) { * @return Application version to send with {@link io.sentry.event.Event}s. */ protected String getRelease(Dsn dsn) { - return dsn.getOptions().get(RELEASE_OPTION); + return Lookup.lookup(RELEASE_OPTION, dsn); } /** @@ -676,7 +679,7 @@ protected String getRelease(Dsn dsn) { * @return Application version to send with {@link io.sentry.event.Event}s. */ protected String getDist(Dsn dsn) { - return dsn.getOptions().get(DIST_OPTION); + return Lookup.lookup(DIST_OPTION, dsn); } /** @@ -687,7 +690,7 @@ protected String getDist(Dsn dsn) { * @return Application version to send with {@link io.sentry.event.Event}s. */ protected String getEnvironment(Dsn dsn) { - return dsn.getOptions().get(ENVIRONMENT_OPTION); + return Lookup.lookup(ENVIRONMENT_OPTION, dsn); } /** @@ -698,7 +701,7 @@ protected String getEnvironment(Dsn dsn) { * @return Server name to send with {@link io.sentry.event.Event}s. */ protected String getServerName(Dsn dsn) { - return dsn.getOptions().get(SERVERNAME_OPTION); + return Lookup.lookup(SERVERNAME_OPTION, dsn); } /** @@ -708,7 +711,7 @@ protected String getServerName(Dsn dsn) { * @return Additional tags to send with {@link io.sentry.event.Event}s. */ protected Map getTags(Dsn dsn) { - return Util.parseTags(dsn.getOptions().get(TAGS_OPTION)); + return Util.parseTags(Lookup.lookup(TAGS_OPTION, dsn)); } /** @@ -721,7 +724,7 @@ protected Map getTags(Dsn dsn) { * @return Extras to use as tags on {@link io.sentry.event.Event}s, where applicable. */ protected Set getExtraTags(Dsn dsn) { - return Util.parseExtraTags(dsn.getOptions().get(EXTRATAGS_OPTION)); + return Util.parseExtraTags(Lookup.lookup(EXTRATAGS_OPTION, dsn)); } /** @@ -731,7 +734,7 @@ protected Set getExtraTags(Dsn dsn) { * @return Whether to compress requests sent to the Sentry Server. */ protected boolean getCompressionEnabled(Dsn dsn) { - return !FALSE.equalsIgnoreCase(dsn.getOptions().get(COMPRESSION_OPTION)); + return !FALSE.equalsIgnoreCase(Lookup.lookup(COMPRESSION_OPTION, dsn)); } /** @@ -741,7 +744,7 @@ protected boolean getCompressionEnabled(Dsn dsn) { * @return Whether to hide common stackframes with enclosing exceptions. */ protected boolean getHideCommonFramesEnabled(Dsn dsn) { - return !FALSE.equalsIgnoreCase(dsn.getOptions().get(HIDE_COMMON_FRAMES_OPTION)); + return !FALSE.equalsIgnoreCase(Lookup.lookup(HIDE_COMMON_FRAMES_OPTION, dsn)); } /** @@ -752,7 +755,7 @@ protected boolean getHideCommonFramesEnabled(Dsn dsn) { */ protected int getMaxMessageLength(Dsn dsn) { return Util.parseInteger( - dsn.getOptions().get(MAX_MESSAGE_LENGTH_OPTION), JsonMarshaller.DEFAULT_MAX_MESSAGE_LENGTH); + Lookup.lookup(MAX_MESSAGE_LENGTH_OPTION, dsn), JsonMarshaller.DEFAULT_MAX_MESSAGE_LENGTH); } /** @@ -762,7 +765,7 @@ protected int getMaxMessageLength(Dsn dsn) { * @return Timeout for requests to the Sentry server, in milliseconds. */ protected int getTimeout(Dsn dsn) { - return Util.parseInteger(dsn.getOptions().get(TIMEOUT_OPTION), TIMEOUT_DEFAULT); + return Util.parseInteger(Lookup.lookup(TIMEOUT_OPTION, dsn), TIMEOUT_DEFAULT); } /** @@ -772,7 +775,7 @@ protected int getTimeout(Dsn dsn) { * @return Whether or not buffering is enabled. */ protected boolean getBufferEnabled(Dsn dsn) { - String bufferEnabled = dsn.getOptions().get(BUFFER_ENABLED_OPTION); + String bufferEnabled = Lookup.lookup(BUFFER_ENABLED_OPTION, dsn); if (bufferEnabled != null) { return Boolean.parseBoolean(bufferEnabled); } @@ -786,7 +789,7 @@ protected boolean getBufferEnabled(Dsn dsn) { * @return the {@link Buffer} where events are stored when network is down. */ protected Buffer getBuffer(Dsn dsn) { - String bufferDir = dsn.getOptions().get(BUFFER_DIR_OPTION); + String bufferDir = Lookup.lookup(BUFFER_DIR_OPTION, dsn); if (bufferDir != null) { return new DiskBuffer(new File(bufferDir), getBufferSize(dsn)); } @@ -800,7 +803,7 @@ protected Buffer getBuffer(Dsn dsn) { * @return the maximum number of events to cache offline when network is down. */ protected int getBufferSize(Dsn dsn) { - return Util.parseInteger(dsn.getOptions().get(BUFFER_SIZE_OPTION), BUFFER_SIZE_DEFAULT); + return Util.parseInteger(Lookup.lookup(BUFFER_SIZE_OPTION, dsn), BUFFER_SIZE_DEFAULT); } /** diff --git a/sentry/src/main/java/io/sentry/SentryClientFactory.java b/sentry/src/main/java/io/sentry/SentryClientFactory.java index 2185640c532..637166f751d 100644 --- a/sentry/src/main/java/io/sentry/SentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/SentryClientFactory.java @@ -100,7 +100,7 @@ public static SentryClient sentryClient(String dsn, String sentryClientFactoryNa * @throws IllegalStateException when no instance of Sentry has been created. */ public static SentryClient sentryClient(Dsn dsn, String sentryClientFactoryName) { - logger.debug("Attempting to find a working SentryClientFactory"); + logger.debug("Attempting to find a working SentryClientFactory."); if (dsn == null) { dsn = new Dsn(Dsn.dsnLookup()); diff --git a/sentry/src/main/java/io/sentry/config/JndiLookup.java b/sentry/src/main/java/io/sentry/config/JndiLookup.java index 8abdb862bd4..d60e86b5d4b 100644 --- a/sentry/src/main/java/io/sentry/config/JndiLookup.java +++ b/sentry/src/main/java/io/sentry/config/JndiLookup.java @@ -34,9 +34,9 @@ public static String jndiLookup(String key) { Context c = new InitialContext(); value = (String) c.lookup(JNDI_PREFIX + key); } catch (NoInitialContextException e) { - logger.debug("JNDI not configured for Sentry (NoInitialContextEx)"); + logger.trace("JNDI not configured for Sentry (NoInitialContextEx)"); } catch (NamingException e) { - logger.debug("No /sentry/" + key + " in JNDI"); + logger.trace("No /sentry/" + key + " in JNDI"); } catch (RuntimeException e) { logger.warn("Odd RuntimeException while testing for JNDI", e); } diff --git a/sentry/src/main/java/io/sentry/config/Lookup.java b/sentry/src/main/java/io/sentry/config/Lookup.java index bf506ed539d..9d8f0eb4362 100644 --- a/sentry/src/main/java/io/sentry/config/Lookup.java +++ b/sentry/src/main/java/io/sentry/config/Lookup.java @@ -4,23 +4,72 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.InputStream; +import java.util.Properties; + /** * Handle lookup of configuration keys by trying JNDI, System Environment, and Java System Properties. */ public final class Lookup { - private static final Logger logger = LoggerFactory.getLogger(JndiLookup.class); + private static final Logger logger = LoggerFactory.getLogger(Lookup.class); + /** + * The filename of the Sentry configuration file. + */ + private static final String CONFIG_FILE_NAME = "sentry.properties"; + /** + * Properties loaded from the Sentry configuration file, or null if no file was + * found or it failed to parse. + */ + private static Properties configProps; + + static { + try { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + InputStream input = classLoader.getResourceAsStream(CONFIG_FILE_NAME); + + if (input != null) { + configProps = new Properties(); + configProps.load(input); + } else { + logger.debug("Sentry configuration file '{}' not found.", CONFIG_FILE_NAME); + } + } catch (Exception e) { + logger.error("Error loading Sentry configuration file '{}': ", CONFIG_FILE_NAME, e); + } + } + + /** + * Hidden constructor for static utility class. + */ private Lookup() { } /** - * Attempt to lookup a configuration key. + * Attempt to lookup a configuration key, without checking any DSN options. * * @param key name of configuration key, e.g. "dsn" * @return value of configuration key, if found, otherwise null */ public static String lookup(String key) { + return lookup(key, null); + } + + /** + * Attempt to lookup a configuration key using the following order: + * + * 1. JNDI, if available + * 2. Java System Properties + * 3. System Environment Variables + * 4. DSN options, if a non-null DSN is provided + * 5. Sentry properties file found in resources + * + * @param key name of configuration key, e.g. "dsn" + * @param dsn an optional DSN to retrieve options from + * @return value of configuration key, if found, otherwise null + */ + public static String lookup(String key, Dsn dsn) { String value = null; // Try to obtain from JNDI @@ -28,18 +77,44 @@ public static String lookup(String key) { // Check that JNDI is available (not available on Android) by loading InitialContext Class.forName("javax.naming.InitialContext", false, Dsn.class.getClassLoader()); value = JndiLookup.jndiLookup(key); + if (value != null) { + logger.debug("Found {}={} in JNDI.", key, value); + } + } catch (ClassNotFoundException | NoClassDefFoundError e) { - logger.debug("JNDI not available", e); + logger.trace("JNDI not available", e); } // Try to obtain from a Java System Property if (value == null) { value = System.getProperty("sentry." + key.toLowerCase()); + if (value != null) { + logger.debug("Found {}={} in Java System Properties.", key, value); + } } // Try to obtain from a System Environment Variable if (value == null) { - value = System.getenv("SENTRY_" + key.toUpperCase()); + value = System.getenv("SENTRY_" + key.replace(".", "_").toUpperCase()); + if (value != null) { + logger.debug("Found {}={} in System Environment Variables.", key, value); + } + } + + // Try to obtain from the provided DSN, if set + if (value == null && dsn != null) { + value = dsn.getOptions().get(key); + if (value != null) { + logger.debug("Found {}={} in DSN.", key, value); + } + } + + // Try to obtain from config file + if (value == null && configProps != null) { + value = configProps.getProperty(key); + if (value != null) { + logger.debug("Found {}={} in {}.", key, value, CONFIG_FILE_NAME); + } } if (value != null) { diff --git a/sentry/src/main/java/io/sentry/dsn/Dsn.java b/sentry/src/main/java/io/sentry/dsn/Dsn.java index 4b54162264f..f116b9acabb 100644 --- a/sentry/src/main/java/io/sentry/dsn/Dsn.java +++ b/sentry/src/main/java/io/sentry/dsn/Dsn.java @@ -30,13 +30,6 @@ public class Dsn { private Map options; private URI uri; - /** - * Creates a DSN based on the {@link #dsnLookup()} result. - */ - public Dsn() { - this(dsnLookup()); - } - /** * Creates a DSN based on a String. * @@ -87,7 +80,7 @@ public static String dsnLookup() { String dsn = Lookup.lookup("dsn"); if (dsn == null) { - logger.warn("Couldn't find a suitable DSN, defaulting to a Noop one."); + logger.warn("*** Couldn't find a suitable DSN, Sentry operations will do nothing! ***"); dsn = DEFAULT_DSN; } From 3b93a52150eca395e65ec9b29ba96f25a4681d5d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 1 Jun 2017 15:46:35 -0500 Subject: [PATCH 1607/2152] Set less useless fields. --- agent.cpp | 1 + lib.cpp | 53 +++++++++++++++-------------------------------------- 2 files changed, 16 insertions(+), 38 deletions(-) diff --git a/agent.cpp b/agent.cpp index bbaa4d48adb..292b78f7b59 100644 --- a/agent.cpp +++ b/agent.cpp @@ -1,3 +1,4 @@ +// TODO: cache all FindClass/Find* calls globally? // TODO: better error messages? (void) jvmti->GetErrorName(errnum, &errnum_str); // TODO: deal with threads? jrawMonitorID lock; jvmti->RawMonitorEnter(lock); jvmti->RawMonitorExit(lock); // TODO: use *options instead of env for log level? diff --git a/lib.cpp b/lib.cpp index a7ca5a1d525..e8725116e74 100644 --- a/lib.cpp +++ b/lib.cpp @@ -129,36 +129,27 @@ static jobject getLocalValue(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint static void makeLocalVariable(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, jclass local_class, jmethodID live_ctor, - jmethodID dead_ctor, jlocation location, - jobjectArray locals, jvmtiLocalVariableEntry *table, - int index) { + jlocation location, jobjectArray locals, + jvmtiLocalVariableEntry *table, int index) { jstring name; - jstring sig; - jstring gensig; jobject value; jobject local; name = env->NewStringUTF(table[index].name); - sig = env->NewStringUTF(table[index].signature); - - if (table[index].generic_signature) { - gensig = env->NewStringUTF(table[index].generic_signature); - } else { - gensig = nullptr; - } if (location >= table[index].start_location && location <= (table[index].start_location + table[index].length)) { value = getLocalValue(jvmti, env, thread, depth, table, index); - local = env->NewObject(local_class, live_ctor, name, sig, gensig, value); + local = env->NewObject(local_class, live_ctor, name, value); } else { - local = env->NewObject(local_class, dead_ctor, name, sig, gensig); + // dead object, use null + local = nullptr; } env->SetObjectArrayElement(locals, index, local); } static jobject makeFrameObject(jvmtiEnv* jvmti, JNIEnv *env, jmethodID method, - jobject value_ptr, jobjectArray locals, jlong pos, jint lineno) { + jobjectArray locals) { jvmtiError jvmti_error; jclass method_class; jint modifiers; @@ -189,12 +180,12 @@ static jobject makeFrameObject(jvmtiEnv* jvmti, JNIEnv *env, jmethodID method, } ctor = env->GetMethodID(frame_class, "", - "(Ljava/lang/reflect/Method;Ljava/lang/Object;[Lio/sentry/jvmti/Frame$LocalVariable;II)V"); + "(Ljava/lang/reflect/Method;[Lio/sentry/jvmti/Frame$LocalVariable;)V"); if (ctor == nullptr) { return nullptr; } - return env->NewObject(frame_class, ctor, frame_method, value_ptr, locals, pos, lineno); + return env->NewObject(frame_class, ctor, frame_method, locals); } static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, @@ -205,10 +196,8 @@ static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint dep jint num_entries; jobject value_ptr; jobjectArray locals; - jint lineno; jclass local_class; jmethodID live_ctor; - jmethodID dead_ctor; int i; value_ptr = nullptr; @@ -236,13 +225,10 @@ static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint dep } } else { local_class = env->FindClass("io/sentry/jvmti/Frame$LocalVariable"); - live_ctor = env->GetMethodID(local_class, "", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V"); - dead_ctor = env->GetMethodID(local_class, "", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + live_ctor = env->GetMethodID(local_class, "", "(Ljava/lang/String;Ljava/lang/Object;)V"); locals = env->NewObjectArray(num_entries, local_class, nullptr); for (i = 0; i < num_entries; i++) { - makeLocalVariable(jvmti, env, thread, depth, local_class, live_ctor, dead_ctor, location, locals, local_var_table, i); + makeLocalVariable(jvmti, env, thread, depth, local_class, live_ctor, location, locals, local_var_table, i); } jvmti->Deallocate((unsigned char *) local_var_table); } @@ -252,24 +238,13 @@ static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint dep value_ptr = nullptr; } - jvmti_error = jvmti->GetLineNumberTable(method, &num_entries, &lineno_table); - if (jvmti_error != JVMTI_ERROR_NONE) { - lineno = -1; // Not retreived - } else { - for (i = 0; i < num_entries; i++) { - if (location < lineno_table->start_location) { - break; - } - lineno = lineno_table->line_number; - } - jvmti->Deallocate((unsigned char *) lineno_table); - } - - return makeFrameObject(jvmti, env, method, value_ptr, locals, location, lineno); + return makeFrameObject(jvmti, env, method, locals); } jobjectArray buildStackTraceFrames(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint start_depth) { + log(TRACE, "buildStackTraceFrames called."); + jint num_frames; jvmtiFrameInfo* frames; jclass result_class; @@ -315,5 +290,7 @@ jobjectArray buildStackTraceFrames(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, } jvmti->Deallocate((unsigned char *) frames); + + log(TRACE, "buildStackTraceFrames exit."); return result; } From 5936f4f54f17bf3d9877f3d2b27b5771534084ee Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 1 Jun 2017 15:50:54 -0500 Subject: [PATCH 1608/2152] Update TODOs. --- agent.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/agent.cpp b/agent.cpp index 292b78f7b59..e9105ea45ad 100644 --- a/agent.cpp +++ b/agent.cpp @@ -1,7 +1,8 @@ -// TODO: cache all FindClass/Find* calls globally? -// TODO: better error messages? (void) jvmti->GetErrorName(errnum, &errnum_str); -// TODO: deal with threads? jrawMonitorID lock; jvmti->RawMonitorEnter(lock); jvmti->RawMonitorExit(lock); -// TODO: use *options instead of env for log level? +// TODO: cache all FindClass/Find* calls globally +// TODO: trim object string length +// TODO: better error messages with (void) jvmti->GetErrorName(errnum, &errnum_str); +// TODO: do we need any locking? jrawMonitorID lock; jvmti->RawMonitorEnter(lock); jvmti->RawMonitorExit(lock); +// TODO: use *options instead of env for log level #include "jvmti.h" #include From f1424b866a9b5dc08c0e6d11ff3a2a095c0ba881 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 5 Jun 2017 19:24:36 -0500 Subject: [PATCH 1609/2152] Unify SentryClient creation code, remove the need for factory registration. (#406) --- docs/config.rst | 42 +--- .../services/io.sentry.SentryClientFactory | 1 - .../AppEngineSentryClientFactoryTest.java | 10 - sentry/src/main/java/io/sentry/Sentry.java | 18 +- .../java/io/sentry/SentryClientFactory.java | 183 +++--------------- .../services/io.sentry.SentryClientFactory | 1 - .../DefaultSentryClientFactoryTest.java | 7 - .../io/sentry/SentryClientFactoryTest.java | 144 +++----------- .../src/test/java/io/sentry/TestFactory.java | 11 ++ 9 files changed, 70 insertions(+), 347 deletions(-) delete mode 100644 sentry-appengine/src/main/resources/META-INF/services/io.sentry.SentryClientFactory delete mode 100644 sentry/src/main/resources/META-INF/services/io.sentry.SentryClientFactory create mode 100644 sentry/src/test/java/io/sentry/TestFactory.java diff --git a/docs/config.rst b/docs/config.rst index 0d614967197..9b95cb57a3a 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -424,8 +424,8 @@ Custom SentryClientFactory At times, you may require custom functionality that is not included in ``sentry-java`` already. The most common way to do this is to create your own ``SentryClientFactory`` instance -as seen in the example below. Note that you'll also need to register it with Sentry and -possibly configure your integration to use it, as shown below. +as seen in the example below. See the documentation for the integration you use to find out how to +configure it to use your custom ``SentryClientFactory``. Implementation ~~~~~~~~~~~~~~ @@ -433,7 +433,6 @@ Implementation .. sourcecode:: java public class MySentryClientFactory extends DefaultSentryClientFactory { - @Override public SentryClient createSentryClient(Dsn dsn) { SentryClient sentry = new SentryClient(createConnection(dsn)); @@ -447,42 +446,5 @@ Implementation return sentry; } - - } - -Next, you'll need to register your class with Sentry in one of two ways. - -Registration -~~~~~~~~~~~~ - -Java ServiceLoader Provider (Recommended) -````````````````````````````````````````` - -You'll need to add a ``ServiceLoader`` provider file to your project at -``src/main/resources/META-INF/services/io.sentry.SentryClientFactory`` that contains -the name of your class so that it will be considered as a candidate ``SentryClientFactory``. For an example, see -`how we configure the DefaultSentryClientFactory itself -`_. - -Manual Registration -``````````````````` - -You can also manually register your ``SentryClientFactory`` instance. If you are using -an integration that builds its own Sentry client, such as a logging integration, this should -be done early in your application lifecycle so that your factory is available the first time -you attempt to send an event to the Sentry server. - -.. sourcecode:: java - - class MyApp { - public static void main(String[] args) { - SentryClientFactory.registerFactory(new MySentryClientFactory()); - // ... your app code ... - } } -Configuration -~~~~~~~~~~~~~ - -Finally, see the documentation for the integration you use to find out how to -configure it to use your custom ``SentryClientFactory``. diff --git a/sentry-appengine/src/main/resources/META-INF/services/io.sentry.SentryClientFactory b/sentry-appengine/src/main/resources/META-INF/services/io.sentry.SentryClientFactory deleted file mode 100644 index 462523b696e..00000000000 --- a/sentry-appengine/src/main/resources/META-INF/services/io.sentry.SentryClientFactory +++ /dev/null @@ -1 +0,0 @@ -io.sentry.appengine.AppEngineSentryClientFactory diff --git a/sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryClientFactoryTest.java b/sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryClientFactoryTest.java index bf097478f1d..4d9cdb92f35 100644 --- a/sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryClientFactoryTest.java +++ b/sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryClientFactoryTest.java @@ -1,15 +1,12 @@ package io.sentry.appengine; import mockit.*; -import io.sentry.SentryClientFactory; import io.sentry.appengine.connection.AppEngineAsyncConnection; import io.sentry.connection.Connection; import io.sentry.dsn.Dsn; -import org.hamcrest.Matchers; import org.testng.annotations.Test; import java.util.Collections; -import java.util.ServiceLoader; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; @@ -23,13 +20,6 @@ public class AppEngineSentryClientFactoryTest { @Injectable private Dsn mockDsn; - @Test - public void checkServiceLoaderProvidesFactory() throws Exception { - ServiceLoader sentryFactories = ServiceLoader.load(SentryClientFactory.class); - - assertThat(sentryFactories, Matchers.hasItem(instanceOf(AppEngineSentryClientFactory.class))); - } - @Test public void asyncConnectionCreatedByAppEngineSentryClientFactoryIsForAppEngine() throws Exception { Connection connection = appEngineSentryClientFactory.createAsyncConnection(mockDsn, mockConnection); diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index a27a8276e74..95c3e5c99ae 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -5,7 +5,6 @@ import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.User; -import io.sentry.util.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -78,22 +77,7 @@ public static SentryClient init(String dsn) { * @return SentryClient */ public static SentryClient init(String dsn, SentryClientFactory sentryClientFactory) { - SentryClient sentryClient; - if (sentryClientFactory != null) { - Dsn realDsn; - if (!Util.isNullOrEmpty(dsn)) { - realDsn = new Dsn(dsn); - } else { - realDsn = new Dsn(Dsn.dsnLookup()); - } - - // use the factory instance directly - sentryClient = sentryClientFactory.createSentryClient(realDsn); - } else { - // do static factory lookup - sentryClient = SentryClientFactory.sentryClient(dsn); - } - + SentryClient sentryClient = SentryClientFactory.sentryClient(dsn, sentryClientFactory); setStoredClient(sentryClient); return sentryClient; } diff --git a/sentry/src/main/java/io/sentry/SentryClientFactory.java b/sentry/src/main/java/io/sentry/SentryClientFactory.java index 637166f751d..b393006f580 100644 --- a/sentry/src/main/java/io/sentry/SentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/SentryClientFactory.java @@ -1,60 +1,17 @@ package io.sentry; +import io.sentry.config.Lookup; import io.sentry.dsn.Dsn; import io.sentry.util.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.ServiceLoader; -import java.util.Set; - /** * Factory in charge of creating {@link SentryClient} instances. - *

    - * The factories register themselves through the {@link ServiceLoader} system. */ public abstract class SentryClientFactory { - private static final ServiceLoader AUTO_REGISTERED_FACTORIES = - ServiceLoader.load(SentryClientFactory.class, SentryClientFactory.class.getClassLoader()); - private static final Set MANUALLY_REGISTERED_FACTORIES = new HashSet<>(); private static final Logger logger = LoggerFactory.getLogger(SentryClientFactory.class); - /** - * Manually adds a SentryClientFactory to the system. - *

    - * Usually SentryFactories are automatically detected with the {@link ServiceLoader} system, but some systems - * such as Android do not provide a fully working ServiceLoader.
    - * If the factory isn't detected automatically, it's possible to add it through this method. - * - * @param sentryClientFactory sentryClientFactory to support. - */ - public static void registerFactory(SentryClientFactory sentryClientFactory) { - MANUALLY_REGISTERED_FACTORIES.add(sentryClientFactory); - } - - private static Iterable getRegisteredFactories() { - List sentryFactories = new LinkedList<>(); - sentryFactories.addAll(MANUALLY_REGISTERED_FACTORIES); - for (SentryClientFactory autoRegisteredFactory : AUTO_REGISTERED_FACTORIES) { - sentryFactories.add(autoRegisteredFactory); - } - return sentryFactories; - } - - /** - * Creates an instance of Sentry using the DSN obtained through the - * {@link Dsn#dsnLookup()} method. - * - * @return an instance of Sentry. - */ - public static SentryClient sentryClient() { - return sentryClient((Dsn) null, null); - } - /** * Creates an instance of Sentry using the provided DSN. * @@ -65,125 +22,47 @@ public static SentryClient sentryClient(String dsn) { return sentryClient(dsn, null); } - /** - * Creates an instance of Sentry using the provided DSN. - * - * @param dsn Data Source Name of the Sentry server. - * @return an instance of Sentry. - */ - public static SentryClient sentryClient(Dsn dsn) { - return sentryClient(dsn, null); - } - - /** - * Creates an instance of Sentry using the provided DSN and the specified factory. - * - * @param dsn Data Source Name of the Sentry server. - * @param sentryClientFactoryName name of the SentryClientFactory to use to generate an instance of Sentry. - * @return an instance of Sentry. - * @throws IllegalStateException when no instance of Sentry has been created. - */ - public static SentryClient sentryClient(String dsn, String sentryClientFactoryName) { - if (!Util.isNullOrEmpty(dsn)) { - return sentryClient(new Dsn(dsn), sentryClientFactoryName); - } else { - return sentryClient((Dsn) null, sentryClientFactoryName); - } - } - /** * Creates an instance of Sentry using the provided DSN and the specified factory. * - * @param dsn Data Source Name of the Sentry server. - * @param sentryClientFactoryName name of the SentryClientFactory to use to generate an instance of Sentry. - * @return an instance of Sentry. - * @throws IllegalStateException when no instance of Sentry has been created. + * @param dsn Data Source Name of the Sentry server. + * @param sentryClientFactory SentryClientFactory instance to use, or null to do a config lookup. + * @return SentryClient instance, or null if one couldn't be constructed. */ - public static SentryClient sentryClient(Dsn dsn, String sentryClientFactoryName) { - logger.debug("Attempting to find a working SentryClientFactory."); - - if (dsn == null) { - dsn = new Dsn(Dsn.dsnLookup()); - } - - // Loop through registered factories, keeping track of which classes we skip, which we try to instantiate, - // and the last exception thrown. - ArrayList skippedFactories = new ArrayList<>(); - ArrayList triedFactories = new ArrayList<>(); - RuntimeException lastExc = null; - - for (SentryClientFactory sentryClientFactory : getRegisteredFactories()) { - String name = sentryClientFactory.getClass().getName(); - if (sentryClientFactoryName != null && !sentryClientFactoryName.equals(name)) { - skippedFactories.add(name); - continue; - } - - logger.debug("Attempting to use '{}' as a SentryClientFactory.", sentryClientFactory); - triedFactories.add(name); - try { - SentryClient sentryClientInstance = sentryClientFactory.createSentryClient(dsn); - logger.debug("The SentryClientFactory '{}' created an instance of Sentry.", sentryClientFactory); - return sentryClientInstance; - } catch (RuntimeException e) { - lastExc = e; - logger.debug("The SentryClientFactory '{}' couldn't create an instance of Sentry.", - sentryClientFactory, e); - } - } - - if (sentryClientFactoryName != null && triedFactories.isEmpty()) { - try { - // see if the provided class exists on the classpath at all - Class.forName(sentryClientFactoryName); - logger.error( - "The SentryClientFactory class '{}' was found on your classpath but was not " - + "registered with Sentry, see: " - + "https://docs.sentry.io/clients/java/config/#custom-sentryfactory", - sentryClientFactoryName); - } catch (ClassNotFoundException e) { - logger.error("The SentryClientFactory class name '{}' was specified but " - + "the class was not found on your classpath.", sentryClientFactoryName); - } - } - - // Throw an IllegalStateException that attempts to be helpful. - StringBuilder sb = new StringBuilder(); - sb.append("Couldn't create a SentryClient instance for: '"); - sb.append(dsn); - sb.append('\''); - if (sentryClientFactoryName != null) { - sb.append("; sentryClientFactoryName: "); - sb.append(sentryClientFactoryName); - - if (skippedFactories.isEmpty()) { - sb.append("; no skipped factories"); + public static SentryClient sentryClient(String dsn, SentryClientFactory sentryClientFactory) { + Dsn realDsn = resolveDsn(dsn); + + // If the caller didn't pass a factory, try to look one up + if (sentryClientFactory == null) { + String sentryClientFactoryName = Lookup.lookup("factory", realDsn); + if (Util.isNullOrEmpty(sentryClientFactoryName)) { + // no name specified, use the default factory + sentryClientFactory = new DefaultSentryClientFactory(); } else { - sb.append("; skipped factories: "); - String delim = ""; - for (String skippedFactory : skippedFactories) { - sb.append(delim); - sb.append(skippedFactory); - delim = ", "; + // attempt to construct the user specified factory class + Class factoryClass = null; + try { + factoryClass = (Class) Class.forName(sentryClientFactoryName); + sentryClientFactory = factoryClass.newInstance(); + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { + logger.error("Error creating SentryClient using factory class: '" + + sentryClientFactoryName + "'.", e); + return null; } } } - if (triedFactories.isEmpty()) { - sb.append("; no factories tried!"); - throw new IllegalStateException(sb.toString()); - } + return sentryClientFactory.createSentryClient(realDsn); + } - sb.append("; tried factories: "); - String delim = ""; - for (String triedFactory : triedFactories) { - sb.append(delim); - sb.append(triedFactory); - delim = ", "; + private static Dsn resolveDsn(String dsn) { + Dsn realDsn; + if (!Util.isNullOrEmpty(dsn)) { + realDsn = new Dsn(dsn); + } else { + realDsn = new Dsn(Dsn.dsnLookup()); } - - sb.append("; cause contains exception thrown by the last factory tried."); - throw new IllegalStateException(sb.toString(), lastExc); + return realDsn; } /** diff --git a/sentry/src/main/resources/META-INF/services/io.sentry.SentryClientFactory b/sentry/src/main/resources/META-INF/services/io.sentry.SentryClientFactory deleted file mode 100644 index 3e935271875..00000000000 --- a/sentry/src/main/resources/META-INF/services/io.sentry.SentryClientFactory +++ /dev/null @@ -1 +0,0 @@ -io.sentry.DefaultSentryClientFactory diff --git a/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java b/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java index 3ed6fe906cc..adaef981fe4 100644 --- a/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java +++ b/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java @@ -10,13 +10,6 @@ import static org.hamcrest.Matchers.is; public class DefaultSentryClientFactoryTest extends BaseTest { - @Test - public void checkServiceLoaderProvidesFactory() throws Exception { - ServiceLoader sentryFactories = ServiceLoader.load(SentryClientFactory.class); - - assertThat(sentryFactories, contains(instanceOf(DefaultSentryClientFactory.class))); - } - @Test public void testFieldsFromDsn() throws Exception { String release = "rel"; diff --git a/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java b/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java index c402360e175..27dcdd74e76 100644 --- a/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java +++ b/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java @@ -14,141 +14,47 @@ import static mockit.Deencapsulation.setField; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; public class SentryClientFactoryTest extends BaseTest { - @Tested - private SentryClientFactory sentryClientFactory = null; - @Injectable - private ServiceLoader mockServiceLoader = null; - - @BeforeMethod - public void setUp() throws Exception { - setField(SentryClientFactory.class, "AUTO_REGISTERED_FACTORIES", mockServiceLoader); - - new NonStrictExpectations() {{ - mockServiceLoader.iterator(); - result = Collections.emptyIterator(); - }}; - } - - @AfterMethod - public void tearDown() throws Exception { - // Reset the registered factories - setField(SentryClientFactory.class, "MANUALLY_REGISTERED_FACTORIES", new HashSet<>()); - setField(SentryClientFactory.class, "AUTO_REGISTERED_FACTORIES", ServiceLoader.load(SentryClientFactory.class)); - } - - @Test - public void testGetFactoriesFromServiceLoader(@Injectable final SentryClient mockSentryClient, - @Injectable final Dsn mockDsn) throws Exception { - new NonStrictExpectations() {{ - mockServiceLoader.iterator(); - result = new Delegate>() { - @SuppressWarnings("unused") - public Iterator iterator() { - return Collections.singletonList(sentryClientFactory).iterator(); - } - }; - sentryClientFactory.createSentryClient(mockDsn); - result = mockSentryClient; - }}; - - SentryClient sentryClient = SentryClientFactory.sentryClient(mockDsn); - - assertThat(sentryClient, is(mockSentryClient)); - } - @Test - public void testGetFactoriesManuallyAdded(@Injectable final SentryClient mockSentryClient, - @Injectable final Dsn mockDsn) throws Exception { - SentryClientFactory.registerFactory(sentryClientFactory); - new NonStrictExpectations() {{ - sentryClientFactory.createSentryClient(mockDsn); - result = mockSentryClient; - }}; - - SentryClient sentryClient = SentryClientFactory.sentryClient(mockDsn); - - assertThat(sentryClient, is(mockSentryClient)); + public void testSentryClientForFactoryNameSucceedsIfFactoryFound() throws Exception { + String dsn = "noop://localhost/1?factory=io.sentry.TestFactory"; + SentryClient sentryClient = SentryClientFactory.sentryClient(dsn); + assertThat(sentryClient.getRelease(), is("312407214120")); } @Test - public void testSentryClientForFactoryNameSucceedsIfFactoryFound(@Injectable final SentryClient mockSentryClient, - @Injectable final Dsn mockDsn) throws Exception { - String factoryName = sentryClientFactory.getClass().getName(); - SentryClientFactory.registerFactory(sentryClientFactory); - new NonStrictExpectations() {{ - sentryClientFactory.createSentryClient(mockDsn); - result = mockSentryClient; - }}; - - SentryClient sentryClient = SentryClientFactory.sentryClient(mockDsn, factoryName); - - assertThat(sentryClient, is(mockSentryClient)); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void testSentryClientForFactoryNameFailsIfNoFactoryFound(@Injectable final SentryClient mockSentryClient, - @Injectable final Dsn mockDsn) throws Exception { - String factoryName = "invalidName"; - SentryClientFactory.registerFactory(sentryClientFactory); - new NonStrictExpectations() {{ - sentryClientFactory.createSentryClient(mockDsn); - result = mockSentryClient; - }}; - - SentryClientFactory.sentryClient(mockDsn, factoryName); + public void testSentryClientForFactoryReturnsNullIfNoFactoryFound() throws Exception { + String dsn = "noop://localhost/1?factory=invalid"; + SentryClient sentryClient = SentryClientFactory.sentryClient(dsn); + assertThat(sentryClient, is(nullValue())); } @Test - public void testSentryInstantiationFailureCaught(@Injectable final Dsn mockDsn) throws Exception { - SentryClientFactory.registerFactory(sentryClientFactory); - Exception exception = null; - new NonStrictExpectations() {{ - sentryClientFactory.createSentryClient(mockDsn); - result = new RuntimeException(); - }}; - + public void testAutoDetectDsnIfNotProvided() throws Exception { + SentryClient sentryClient; + String propName = "sentry.dsn"; + String previous = System.getProperty(propName); try { - SentryClientFactory.sentryClient(mockDsn); - } catch (IllegalStateException e) { - exception = e; + System.setProperty(propName, "noop://localhost/1?release=xyz"); + sentryClient = SentryClientFactory.sentryClient(null); + } finally { + if (previous == null) { + System.clearProperty(propName); + } else { + System.setProperty(propName, previous); + } } - assertThat(exception, notNullValue()); + assertThat(sentryClient.getRelease(), is("xyz")); } @Test - public void testAutoDetectDsnIfNotProvided(@Injectable final SentryClient mockSentryClient, - @SuppressWarnings("unused") @Mocked final Dsn mockDsn) throws Exception { - final String dsn = "protocol://user:password@host:port/3"; - SentryClientFactory.registerFactory(sentryClientFactory); - new NonStrictExpectations() {{ - sentryClientFactory.createSentryClient((Dsn) any); - result = mockSentryClient; - }}; - - SentryClient sentryClient = SentryClientFactory.sentryClient(); - - assertThat(sentryClient, is(mockSentryClient)); - } - - @Test - public void testCreateDsnIfStringProvided(@Injectable final SentryClient mockSentryClient, - @SuppressWarnings("unused") @Mocked final Dsn mockDsn) throws Exception { - final String dsn = "protocol://user:password@host:port/2"; - SentryClientFactory.registerFactory(sentryClientFactory); - new NonStrictExpectations() {{ - sentryClientFactory.createSentryClient((Dsn) any); - result = mockSentryClient; - }}; - + public void testCreateDsnIfStringProvided() throws Exception { + final String dsn = "noop://localhost/1?release=abc"; SentryClient sentryClient = SentryClientFactory.sentryClient(dsn); - - assertThat(sentryClient, is(mockSentryClient)); - new Verifications() {{ - new Dsn(dsn); - }}; + assertThat(sentryClient.getRelease(), is("abc")); } } diff --git a/sentry/src/test/java/io/sentry/TestFactory.java b/sentry/src/test/java/io/sentry/TestFactory.java new file mode 100644 index 00000000000..82881a9e567 --- /dev/null +++ b/sentry/src/test/java/io/sentry/TestFactory.java @@ -0,0 +1,11 @@ +package io.sentry; + +import io.sentry.dsn.Dsn; + +public class TestFactory extends DefaultSentryClientFactory { + @Override + protected SentryClient configureSentryClient(SentryClient sentryClient, Dsn dsn) { + sentryClient.setRelease("312407214120"); + return super.configureSentryClient(sentryClient, dsn); + } +} From 9c17b1daed826040a094a0ca348e0f15f7dc6db4 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 5 Jun 2017 19:24:46 -0500 Subject: [PATCH 1610/2152] Add 'integrations' array field to 'sdk' in JSON. (#408) --- .../helper/AndroidEventBuilderHelper.java | 3 +- .../java/io/sentry/log4j/SentryAppender.java | 2 +- .../SentryAppenderEventBuildingTest.java | 2 +- .../java/io/sentry/log4j2/SentryAppender.java | 2 +- .../SentryAppenderEventBuildingTest.java | 2 +- .../io/sentry/logback/SentryAppender.java | 2 +- .../SentryAppenderEventBuildingTest.java | 2 +- .../src/main/java/io/sentry/event/Event.java | 25 +++------- .../java/io/sentry/event/EventBuilder.java | 40 +++++----------- sentry/src/main/java/io/sentry/event/Sdk.java | 47 +++++++++++++++++++ .../java/io/sentry/jul/SentryHandler.java | 2 +- .../marshaller/json/JsonMarshaller.java | 16 +++++-- .../jul/SentryHandlerEventBuildingTest.java | 6 ++- .../marshaller/json/JsonMarshallerTest.java | 14 +++--- .../unmarshaller/event/UnmarshalledEvent.java | 2 +- .../json/jsonmarshallertest/testSdk.json | 3 +- 16 files changed, 102 insertions(+), 68 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/event/Sdk.java diff --git a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java index a3c383fa393..02c2dabd836 100644 --- a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java +++ b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java @@ -16,7 +16,6 @@ import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; -import io.sentry.environment.SentryEnvironment; import io.sentry.event.EventBuilder; import io.sentry.event.helper.EventBuilderHelper; import io.sentry.event.interfaces.UserInterface; @@ -58,7 +57,7 @@ public AndroidEventBuilderHelper(Context ctx) { @Override public void helpBuildingEvent(EventBuilder eventBuilder) { - eventBuilder.withSdkName(SentryEnvironment.SDK_NAME + ":android"); + eventBuilder.withSdkIntegration("android"); PackageInfo packageInfo = getPackageInfo(ctx); if (packageInfo != null) { eventBuilder.withRelease(packageInfo.packageName + "-" + packageInfo.versionName); diff --git a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java index b37ed5bb7eb..c8466a6cf74 100644 --- a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java +++ b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java @@ -99,7 +99,7 @@ protected void append(LoggingEvent loggingEvent) { */ protected EventBuilder createEventBuilder(LoggingEvent loggingEvent) { EventBuilder eventBuilder = new EventBuilder() - .withSdkName(SentryEnvironment.SDK_NAME + ":log4j") + .withSdkIntegration("log4j") .withTimestamp(new Date(loggingEvent.getTimeStamp())) .withMessage(loggingEvent.getRenderedMessage()) .withLogger(loggingEvent.getLoggerName()) diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java index 254336fbdf3..d862cea3b51 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java @@ -74,7 +74,7 @@ public void testSimpleMessageLogging() throws Exception { assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); - assertThat(event.getSdkName(), is(SentryEnvironment.SDK_NAME + ":log4j")); + assertThat(event.getSdk().getIntegrations(), contains("log4j")); }}; assertNoErrorsInErrorHandler(); } diff --git a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java index 2f45e66c9f5..3b3fc68ecc6 100644 --- a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -147,7 +147,7 @@ public void append(LogEvent logEvent) { protected EventBuilder createEventBuilder(LogEvent event) { Message eventMessage = event.getMessage(); EventBuilder eventBuilder = new EventBuilder() - .withSdkName(SentryEnvironment.SDK_NAME + ":log4j2") + .withSdkIntegration("log4j2") .withTimestamp(new Date(event.getTimeMillis())) .withMessage(eventMessage.getFormattedMessage()) .withLogger(event.getLoggerName()) diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java index 03dde7b0a0e..138bd12e15d 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java @@ -69,7 +69,7 @@ public void testSimpleMessageLogging() throws Exception { assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); - assertThat(event.getSdkName(), is(SentryEnvironment.SDK_NAME + ":log4j2")); + assertThat(event.getSdk().getIntegrations(), contains("log4j2")); }}; assertNoErrorsInErrorHandler(); } diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index df245798750..4552b73aac0 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -122,7 +122,7 @@ private boolean isNotLoggable(ILoggingEvent iLoggingEvent) { */ protected EventBuilder createEventBuilder(ILoggingEvent iLoggingEvent) { EventBuilder eventBuilder = new EventBuilder() - .withSdkName(SentryEnvironment.SDK_NAME + ":logback") + .withSdkIntegration("logback") .withTimestamp(new Date(iLoggingEvent.getTimeStamp())) .withMessage(iLoggingEvent.getFormattedMessage()) .withLogger(iLoggingEvent.getLoggerName()) diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java index 20e446a0698..cadaac06aec 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java @@ -82,7 +82,7 @@ public void testSimpleMessageLogging() throws Exception { assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); assertThat(event.getTimestamp(), is(date)); - assertThat(event.getSdkName(), is(SentryEnvironment.SDK_NAME + ":logback")); + assertThat(event.getSdk().getIntegrations(), contains("logback")); }}; assertNoErrorsInStatusManager(); } diff --git a/sentry/src/main/java/io/sentry/event/Event.java b/sentry/src/main/java/io/sentry/event/Event.java index cfcc4f8baaa..5a9feb97ba9 100644 --- a/sentry/src/main/java/io/sentry/event/Event.java +++ b/sentry/src/main/java/io/sentry/event/Event.java @@ -55,13 +55,10 @@ public class Event implements Serializable { */ private String platform; /** - * A string representing the name of the SDK used to create the event. + * An {@link Sdk} instance representing the version and integrations used to send + * the event. */ - private String sdkName; - /** - * A string representing the version of the SDK used to create the event. - */ - private String sdkVersion; + private Sdk sdk; /** * Function call which was the primary perpetrator of this event. */ @@ -177,20 +174,12 @@ void setPlatform(String platform) { this.platform = platform; } - public String getSdkName() { - return sdkName; - } - - public void setSdkName(String sdkName) { - this.sdkName = sdkName; - } - - public String getSdkVersion() { - return sdkVersion; + public Sdk getSdk() { + return sdk; } - public void setSdkVersion(String sdkVersion) { - this.sdkVersion = sdkVersion; + public void setSdk(Sdk sdk) { + this.sdk = sdk; } public String getCulprit() { diff --git a/sentry/src/main/java/io/sentry/event/EventBuilder.java b/sentry/src/main/java/io/sentry/event/EventBuilder.java index f7a7bdd76ec..a6c1144e81d 100644 --- a/sentry/src/main/java/io/sentry/event/EventBuilder.java +++ b/sentry/src/main/java/io/sentry/event/EventBuilder.java @@ -36,6 +36,7 @@ public class EventBuilder { private static final HostnameCache HOSTNAME_CACHE = new HostnameCache(HOSTNAME_CACHE_DURATION); private final Event event; private boolean alreadyBuilt = false; + private Set sdkIntegrations = new HashSet<>(); /** * Creates a new EventBuilder to prepare a new {@link Event}. @@ -70,10 +71,8 @@ private static String calculateChecksum(String string) { /** * Sets default values for each field that hasn't been provided manually. - * - * @param event currently handled event. */ - private static void autoSetMissingValues(Event event) { + private void autoSetMissingValues() { // Ensure that a timestamp is set (to now at least!) if (event.getTimestamp() == null) { event.setTimestamp(new Date()); @@ -85,11 +84,9 @@ private static void autoSetMissingValues(Event event) { } // Ensure that an SDK is set - if (event.getSdkName() == null) { - event.setSdkName(SentryEnvironment.SDK_NAME); - } - if (event.getSdkVersion() == null) { - event.setSdkVersion(SentryEnvironment.SDK_VERSION); + if (event.getSdk() == null) { + event.setSdk(new Sdk(SentryEnvironment.SDK_NAME, SentryEnvironment.SDK_VERSION, + sdkIntegrations)); } // Ensure that a hostname is set @@ -100,10 +97,8 @@ private static void autoSetMissingValues(Event event) { /** * Ensures that every field in the {@code Event} are immutable to avoid confusion later. - * - * @param event event to make immutable. */ - private static void makeImmutable(Event event) { + private void makeImmutable() { // Make the tags unmodifiable event.setTags(Collections.unmodifiableMap(event.getTags())); @@ -213,24 +208,13 @@ public EventBuilder withPlatform(String platform) { } /** - * Sets the SDK name in the event. - * - * @param sdkName name of the SDK that created the event. - * @return the current {@code EventBuilder} for chained calls. - */ - public EventBuilder withSdkName(String sdkName) { - event.setSdkName(sdkName); - return this; - } - - /** - * Sets the SDK version in the event. + * Add an integration to the {@link Sdk}. * - * @param sdkVersion version of the SDK that created the event. + * @param integration Name of the integration used. * @return the current {@code EventBuilder} for chained calls. */ - public EventBuilder withSdkVersion(String sdkVersion) { - event.setSdkVersion(sdkVersion); + public EventBuilder withSdkIntegration(String integration) { + sdkIntegrations.add(integration); return this; } @@ -418,8 +402,8 @@ public synchronized Event build() { throw new IllegalStateException("A message can't be built twice"); } - autoSetMissingValues(event); - makeImmutable(event); + autoSetMissingValues(); + makeImmutable(); // Lock it only when everything has been set, in case of exception it should be possible to try to build again. alreadyBuilt = true; diff --git a/sentry/src/main/java/io/sentry/event/Sdk.java b/sentry/src/main/java/io/sentry/event/Sdk.java new file mode 100644 index 00000000000..0bd052ffc55 --- /dev/null +++ b/sentry/src/main/java/io/sentry/event/Sdk.java @@ -0,0 +1,47 @@ +package io.sentry.event; + +import java.io.Serializable; +import java.util.Set; + +/** + * Represents the current SDK and any integrations used to create an {@link Event}. + */ +public class Sdk implements Serializable { + /** + * Name of the SDK. + */ + private String name; + /** + * Version of the SDK. + */ + private String version; + /** + * Set of integrations used. + */ + private Set integrations; + + /** + * Build an {@link Sdk} instance. + * + * @param name Name of the SDK. + * @param version Version of the SDK. + * @param integrations Set of integrations used. + */ + public Sdk(String name, String version, Set integrations) { + this.name = name; + this.version = version; + this.integrations = integrations; + } + + public String getName() { + return name; + } + + public String getVersion() { + return version; + } + + public Set getIntegrations() { + return integrations; + } +} diff --git a/sentry/src/main/java/io/sentry/jul/SentryHandler.java b/sentry/src/main/java/io/sentry/jul/SentryHandler.java index 5171110da3a..e784f2c351d 100644 --- a/sentry/src/main/java/io/sentry/jul/SentryHandler.java +++ b/sentry/src/main/java/io/sentry/jul/SentryHandler.java @@ -109,7 +109,7 @@ public void publish(LogRecord record) { */ protected EventBuilder createEventBuilder(LogRecord record) { EventBuilder eventBuilder = new EventBuilder() - .withSdkName(SentryEnvironment.SDK_NAME + ":jul") + .withSdkIntegration("java.util.logging") .withLevel(getLevel(record.getLevel())) .withTimestamp(new Date(record.getMillis())) .withLogger(record.getLoggerName()); diff --git a/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java b/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java index 5dccc1b9076..454c9c48fc7 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import io.sentry.event.Breadcrumb; import io.sentry.event.Event; +import io.sentry.event.Sdk; import io.sentry.event.interfaces.SentryInterface; import io.sentry.marshaller.Marshaller; import io.sentry.util.Util; @@ -188,7 +189,7 @@ private void writeContent(JsonGenerator generator, Event event) throws IOExcepti generator.writeStringField(LOGGER, event.getLogger()); generator.writeStringField(PLATFORM, event.getPlatform()); generator.writeStringField(CULPRIT, event.getCulprit()); - writeSdk(generator, event.getSdkName(), event.getSdkVersion()); + writeSdk(generator, event.getSdk()); writeTags(generator, event.getTags()); writeBreadcumbs(generator, event.getBreadcrumbs()); writeContexts(generator, event.getContexts()); @@ -280,10 +281,17 @@ private void safelyWriteObject(JsonGenerator generator, Object value) throws IOE } } - private void writeSdk(JsonGenerator generator, String sdkName, String sdkVersion) throws IOException { + private void writeSdk(JsonGenerator generator, Sdk sdk) throws IOException { generator.writeObjectFieldStart(SDK); - generator.writeStringField("name", sdkName); - generator.writeStringField("version", sdkVersion); + generator.writeStringField("name", sdk.getName()); + generator.writeStringField("version", sdk.getVersion()); + if (sdk.getIntegrations() != null && !sdk.getIntegrations().isEmpty()) { + generator.writeArrayFieldStart("integrations"); + for (String integration : sdk.getIntegrations()) { + generator.writeString(integration); + } + generator.writeEndArray(); + } generator.writeEndObject(); } diff --git a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java index 1739b233f3e..c6b7088f7a7 100644 --- a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java +++ b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java @@ -54,6 +54,8 @@ public void testSimpleMessageLogging() throws Exception { sentryHandler.publish(newLogRecord(loggerName, Level.INFO, message, arguments, null, null, threadId, date.getTime())); + + new Verifications() {{ EventBuilder eventBuilder; mockSentryClient.sendEvent(eventBuilder = withCapture()); @@ -66,7 +68,9 @@ public void testSimpleMessageLogging() throws Exception { assertThat(event.getLogger(), is(loggerName)); assertThat(event.getExtra(), Matchers.hasEntry(SentryHandler.THREAD_ID, (int) threadId)); assertThat(event.getTimestamp(), is(date)); - assertThat(event.getSdkName(), is(SentryEnvironment.SDK_NAME + ":jul")); + event.getSdk(); + event.getSdk().getIntegrations(); + assertThat(event.getSdk().getIntegrations(), contains("java.util.logging")); }}; assertNoErrorsInErrorManager(); } diff --git a/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java b/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java index 223fadfda80..47a88654092 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java @@ -6,6 +6,7 @@ import io.sentry.BaseTest; import io.sentry.event.Breadcrumb; import io.sentry.event.BreadcrumbBuilder; +import io.sentry.event.Sdk; import mockit.*; import io.sentry.event.Event; import io.sentry.event.interfaces.SentryInterface; @@ -143,14 +144,15 @@ public void testEventPlaftormWrittenProperly(@Injectable("platform") final Strin } @Test - public void testEventPlaftormWrittenProperly(@Injectable("sdkName") final String mockSdkName, - @Injectable("sdkVersion") final String mockSdkVersion) throws Exception { + public void testEventSdkWrittenProperly() throws Exception { + HashSet integrations = new HashSet<>(); + integrations.add("integration1"); + integrations.add("integration2"); + final Sdk sdk = new Sdk("sdkName", "sdkVersion", integrations); final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); new NonStrictExpectations() {{ - mockEvent.getSdkName(); - result = mockSdkName; - mockEvent.getSdkVersion(); - result = mockSdkVersion; + mockEvent.getSdk(); + result = sdk; }}; jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); diff --git a/sentry/src/test/java/io/sentry/unmarshaller/event/UnmarshalledEvent.java b/sentry/src/test/java/io/sentry/unmarshaller/event/UnmarshalledEvent.java index bc8341695b5..5e937a62309 100644 --- a/sentry/src/test/java/io/sentry/unmarshaller/event/UnmarshalledEvent.java +++ b/sentry/src/test/java/io/sentry/unmarshaller/event/UnmarshalledEvent.java @@ -26,7 +26,7 @@ public class UnmarshalledEvent { @JsonProperty(value = "platform") private String platform; @JsonProperty(value = "sdk") - private Map sdk; + private Map sdk; @JsonProperty(value = "culprit") private String culprit; @JsonProperty(value = "tags") diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json index 55b02f1e80b..55ea6a5344c 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json @@ -15,6 +15,7 @@ "checksum": null, "sdk": { "name": "sdkName", - "version": "sdkVersion" + "version": "sdkVersion", + "integrations": ["integration2", "integration1"] } } From 2f18bd97395d2ca8ac610a0444438b83856c24ad Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 6 Jun 2017 18:08:51 -0500 Subject: [PATCH 1611/2152] Allow the static client to be auto-initialized again after a call to close. (#409) --- sentry/src/main/java/io/sentry/Sentry.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 95c3e5c99ae..652eca7eb19 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -195,6 +195,9 @@ public static void close() { storedClient.closeConnection(); storedClient = null; + + // Allow the client to be auto initialized on the next use. + autoInitAttempted.set(false); } } From 4df5218deab6c874458fa21ed696af503ea161ef Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 6 Jun 2017 18:09:02 -0500 Subject: [PATCH 1612/2152] Overhaul documentation for new configuration style. (#410) --- docs/config.rst | 217 ++++++++++++++------ docs/context.rst | 7 +- docs/index.rst | 5 + docs/modules/android.rst | 2 +- docs/modules/index.rst | 2 + docs/modules/log4j.rst | 146 +------------ docs/modules/log4j2.rst | 99 +-------- docs/modules/logback.rst | 103 +--------- docs/modules/sentry.rst | 84 +------- docs/usage.rst | 6 + sentry/src/main/java/io/sentry/dsn/Dsn.java | 5 +- 11 files changed, 204 insertions(+), 472 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 9b95cb57a3a..593e663133d 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -1,13 +1,95 @@ +.. _configuration: + Configuration ============= **Note:** Sentry's library and framework integration documentation explains how to to do -basic Sentry configuration for each of the supported integrations. The configuration -below is typically for more advanced use cases and can be used in combination any of the other -integrations *once you set Sentry up with the integration*. Please check the integration -documentation before you attempt to do any advanced configuration. +the initial Sentry configuration for each of the supported integrations. The configuration +below can be used in combination with any of the integrations *once you set Sentry up with +the integration*. Please check :ref:`the integration documentation ` before +you attempt to do any advanced configuration. + +Configuration via properties file +--------------------------------- + +The ``sentry-java`` SDK can be configured via a ``sentry.properties`` file placed at the root of +your classpath, which is typically achieved by adding a ``src/main/resources/sentry.properties`` file +to your project. This file follows the standard `.properties file format `_ +and thus contains one option per line. + +Because this file is bundled with your application, the values cannot be changed easily at +runtime. For this reason, the properties file is useful for setting defaults or options +that you don't expect to change often. The properties file is the last place checked for +each option value, so runtime configuration (described below) will override it if available. + +Option names in the property file exactly match the examples given below. For example, to enable +sampling, in your ``sentry.properties`` file: + +.. sourcecode:: properties + + sample.rate=0.75 + +Configuration via the runtime environment +----------------------------------------- + +This is the most flexible method for configuring the Sentry client +because it can be easily changed based on the environment you run your +application in. + +Two methods are available for runtime configuration, checked in this order: Java System Properties +and System Environment Variables. + +Java System Property option names are exactly like the examples given below except that they are +prefixed with ``sentry.``. For example, to enable sampling: + +.. sourcecode:: shell + + java -Dsentry.sample.rate=0.75 -jar app.jar + +System Environment Variable option names require that you replace the ``.`` with ``_``, capitalize +them, and add a ``SENTRY_`` prefix. For example, to enable sampling: + +.. sourcecode:: shell + + SENTRY_SAMPLE_RATE=0.75 java -jar app.jar + +Configuration via the DSN +------------------------- + +The SDK can also be configured by setting querystring parameters on the DSN itself. This is a bit +recursive because your DSN itself is an option that you must set somewhere (and not in the DSN!). + +Option names in the DSN exactly match the examples given below. For example, to enable sampling +if you are setting your DSN via the environment: + +.. sourcecode:: shell + + SENTRY_DSN=http://public:private@host:port/1?sample.rate=0.75 java -jar app.jar + +DSN (Data Source Name) +====================== + +The DSN is the first and most important option to configure because it tells the SDK where +to send events. You can find a basic DSN in the "Client Keys" section of your "Project Settings" +in Sentry. It can be configured anywhere except for in the DSN itself. + +In your ``sentry.properties``: -Most of Sentry's advanced configuration happens by setting options in your DSN, as seen below. +.. sourcecode:: properties + + dsn=http://public:private@host:port/1 + +Via the Java System Properties: + +.. sourcecode:: shell + + java -Dsentry.dsn=http://public:private@host:port/1 -jar app.jar + +Via a System Environment Variable: + +.. sourcecode:: shell + + SENTRY_DSN=http://public:private@host:port/1 java -jar app.jar Connection and Protocol ----------------------- @@ -51,7 +133,7 @@ It is possible to use an unencrypted connection to Sentry via HTTP: If not provided, the port will default to ``80``. Using a Proxy -~~~~~~~~~~~~~ +------------- If your application needs to send outbound requests through an HTTP proxy, you can configure the proxy information via JVM networking properties or @@ -79,97 +161,90 @@ See `Java Networking and Proxies `_ for more information about the proxy properties. -Alternatively, using the Sentry DSN (only affects the Sentry HTTP client, +Alternatively, using Sentry options (only affects the Sentry HTTP client, useful inside shared application containers), :: - http://public:private@host:port/1?http.proxy.host=proxy.example.com&http.proxy.port=8080 + http.proxy.host=proxy.example.com + http.proxy.port=8080 Options -------- - -It is possible to enable some options by adding data to the query string of the -DSN: - -:: - - http://public:private@host:port/1?option1=value1&option2&option3=value3 - -Some options do not require a value, just being declared signifies that the -option is enabled. +======= +The following options can all be configured as described above: via a ``sentry.properties`` file, via +Java System Properties, via System Environment variables, or via the DSN. Release -~~~~~~~ +------- To set the application version that will be sent with each event, use the ``release`` option: :: - http://public:private@host:port/1?release=1.0.0 - + release=1.0.0 Distribution -```````````` +~~~~~~~~~~~~ To set the application distribution that will be sent with each event, use the ``dist`` option: :: - http://public:private@host:port/1?release=1.0.0&dist=x86 + release=1.0.0 + dist=x86 Note that the distribution is only useful (and used) if the ``release`` is also set. Environment -~~~~~~~~~~~ +----------- To set the application environment that will be sent with each event, use the ``environment`` option: :: - http://public:private@host:port/1?environment=staging + environment=staging Server Name -~~~~~~~~~~~ +----------- To set the server name that will be sent with each event, use the ``servername`` option: :: - http://public:private@host:port/1?servername=host1 + servername=host1 Tags -~~~~ +---- To set tags that will be sent with each event, use the ``tags`` option with comma separated pairs of keys and values that are joined by a colon: :: - http://public:private@host:port/1?tags=tag1:value1,tag2:value2 + tags=tag1:value1,tag2:value2 Extra Tags -`````````` +---------- To set extras that are extracted and used as additional tags, use the ``extratags`` option with comma separated key names. :: - http://public:private@host:port/1?extratags=foo,bar + extratags=foo,bar Note that how these extra tags are used depends on which integration you are using. For example: when using a logging integration any SLF4J MDC keys that are in the extra tags set will be extracted and set as tags on events. Async Connection -~~~~~~~~~~~~~~~~ +---------------- In order to avoid performance issues due to a large amount of logs being generated or a slow connection to the Sentry server, an asynchronous connection @@ -179,10 +254,10 @@ To disable the async mode, add async=false`` to the DSN: :: - http://public:private@host:port/1?async=false + async=false Graceful Shutdown (Advanced) -```````````````````````````` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In order to shutdown the asynchronous connection gracefully, a ``ShutdownHook`` is created. By default, the asynchronous connection is given 1 second @@ -191,7 +266,7 @@ async.shutdowntimeout`` (represented in milliseconds): :: - http://public:private@host:port/1?async.shutdowntimeout=5000 + async.shutdowntimeout=5000 The special value ``-1`` can be used to disable the timeout and wait indefinitely for the executor to terminate. @@ -210,10 +285,10 @@ The option to do so is async.gracefulshutdown``: :: - http://public:private@host:port/1?async.gracefulshutdown=false + async.gracefulshutdown=false Queue Size (Advanced) -````````````````````` +~~~~~~~~~~~~~~~~~~~~~ The default queue used to store unprocessed events is limited to 50 items. Additional items added once the queue is full are dropped and @@ -225,7 +300,7 @@ It is possible to set a maximum with the option async.queuesize``: :: - http://public:private@host:port/1?async.queuesize=100 + async.queuesize=100 This means that if the connection to the Sentry server is down, only the 100 most recent events will be stored and processed as soon as the server is back up. @@ -235,7 +310,7 @@ that network connectivity or Sentry server issues could mean your process will run out of memory. Threads Count (Advanced) -```````````````````````` +~~~~~~~~~~~~~~~~~~~~~~~~ By default the thread pool used by the async connection contains one thread per processor available to the JVM. @@ -245,10 +320,10 @@ only one thread) with the option async.threads``: :: - http://public:private@host:port/1?async.threads=1 + async.threads=1 Threads Priority (Advanced) -``````````````````````````` +~~~~~~~~~~~~~~~~~~~~~~~~~~~ In most cases sending logs to Sentry isn't as important as an application running smoothly, so the threads have a @@ -259,10 +334,10 @@ with the option async.priority``: :: - http://public:private@host:port/1?async.priority=10 + async.priority=10 Buffering Events to Disk -~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------ Sentry can be configured to write events to a specified directory on disk anytime communication with the Sentry server fails with the buffer.dir`` @@ -272,14 +347,14 @@ Sentry always requires write permission on the buffer directory itself. :: - http://public:private@host:port/1?buffer.dir=sentry-events + buffer.dir=sentry-events The maximum number of events that will be stored on disk defaults to 50, but can also be configured with the option buffer.size``: :: - http://public:private@host:port/1?buffer.size=100 + buffer.size=100 If a buffer directory is provided, a background thread will periodically attempt to re-send the events that are found on disk. By default it will @@ -288,10 +363,10 @@ buffer.flushtime`` option (in milliseconds): :: - http://public:private@host:port/1?buffer.flushtime=10000 + buffer.flushtime=10000 Graceful Shutdown (Advanced) -```````````````````````````` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In order to shutdown the buffer flushing thread gracefully, a ``ShutdownHook`` is created. By default, the buffer flushing thread is given 1 second @@ -300,7 +375,7 @@ buffer.shutdowntimeout`` (represented in milliseconds): :: - http://public:private@host:port/1?buffer.shutdowntimeout=5000 + buffer.shutdowntimeout=5000 The special value ``-1`` can be used to disable the timeout and wait indefinitely for the executor to terminate. @@ -316,23 +391,23 @@ by setting the buffer.gracefulshutdown`` option: :: - http://public:private@host:port/1?buffer.gracefulshutdown=false + buffer.gracefulshutdown=false Event Sampling -~~~~~~~~~~~~~~ +-------------- Sentry can be configured to sample events with the sample.rate`` option: :: - http://public:private@host:port/1?sample.rate=0.75 + sample.rate=0.75 This option takes a number from 0.0 to 1.0, representing the percent of events to allow through to server (from 0% to 100%). By default all events will be sent to the Sentry server. "In Application" Stack Frames -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +----------------------------- Sentry differentiates stack frames that are directly related to your application ("in application") from stack frames that come from other packages such as the @@ -345,10 +420,10 @@ stacktrace.app.packages`` option, which takes a comma separated list. :: - http://public:private@host:port/1?stacktrace.app.packages=com.mycompany,com.other.name + stacktrace.app.packages=com.mycompany,com.other.name Same Frame as Enclosing Exception -````````````````````````````````` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Sentry can use the "in application" system to hide frames in chained exceptions. Usually when a StackTrace is printed, the result looks like this: @@ -376,10 +451,10 @@ To enable a similar behaviour in Sentry use the stacktrace.hidecommon`` option. :: - http://public:private@host:port/1?stacktrace.hidecommon + stacktrace.hidecommon Compression -~~~~~~~~~~~ +----------- By default the content sent to Sentry is compressed before being sent. However, compressing and encoding the data adds a small CPU and memory hit which @@ -394,20 +469,20 @@ compression`` :: - http://public:private@host:port/1?compression=false + compression=false Max Message Size -~~~~~~~~~~~~~~~~ +---------------- By default only the first 1000 characters of a message will be sent to the server. This can be changed with the maxmessagelength`` option. :: - http://public:private@host:port/1?maxmessagelength=1500 + maxmessagelength=1500 Timeout (Advanced) -~~~~~~~~~~~~~~~~~~ +------------------ A timeout is set to avoid blocking Sentry threads because establishing a connection is taking too long. @@ -417,18 +492,17 @@ It's possible to manually set the timeout length with timeout`` :: - http://public:private@host:port/1?timeout=10000 + timeout=10000 Custom SentryClientFactory --------------------------- +========================== At times, you may require custom functionality that is not included in ``sentry-java`` already. The most common way to do this is to create your own ``SentryClientFactory`` instance -as seen in the example below. See the documentation for the integration you use to find out how to -configure it to use your custom ``SentryClientFactory``. +as seen in the example below. Implementation -~~~~~~~~~~~~~~ +-------------- .. sourcecode:: java @@ -448,3 +522,14 @@ Implementation } } +Usage +----- + +To use your custom ``SentryClientFactory`` implementation, use the ``factory`` option: + +:: + + factory=my.company.SentryClientFactory + +Your factory class will need to be available on your classpath with a zero argument constructor +or an error will be thrown. \ No newline at end of file diff --git a/docs/context.rst b/docs/context.rst index 84d9e337fa9..0b654943339 100644 --- a/docs/context.rst +++ b/docs/context.rst @@ -45,11 +45,11 @@ the current context. /** * Examples using the (recommended) static API. - * - * Note that the ``Sentry.init`` method must be called before the static API - * is used, otherwise a ``NullPointerException`` will be thrown. */ public void staticAPIExample() { + // Manually initialize the static client, you may also pass in a DSN and/or + // SentryClientFactory to use. Note that the client will attempt to automatically + // initialize on the first use of the static API, so this isn't strictly necessary. Sentry.init(); // Set the current user in the context. @@ -72,6 +72,7 @@ the current context. * Examples that use the SentryClient instance directly. */ public void instanceAPIExample() { + // Create a SentryClient instance that you manage manually. SentryClient sentryClient = SentryClientFactory.sentryClient(); // Get the current context instance. diff --git a/docs/index.rst b/docs/index.rst index 27dfe41096f..28c9e8bcb6f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,6 +14,11 @@ Sentry for Java (``sentry-java``) is the official Java SDK for Sentry. At its co a raw client for sending events to Sentry, but it is highly recommended that you use one of the included library or framework integrations listed below if at all possible. +**Note:** ``raven-java`` is no longer maintained. It is highly recommended that +you migrate to ``sentry-java`` (which this documentation covers). If you are still +using ``raven-java`` you can +`find the old documentation here `_. + .. toctree:: :maxdepth: 2 :titlesonly: diff --git a/docs/modules/android.rst b/docs/modules/android.rst index ec7b9adb18b..8262147998f 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -12,7 +12,7 @@ Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ (in the application's cache directory) by default. This allows events to be sent at a later time if the device does not have connectivity when an event is created. This can -be disabled by setting the DSN option ``sentry.buffer.enabled`` to ``false``. +be disabled by setting the DSN option ``buffer.enabled`` to ``false``. An ``UncaughtExceptionHandler`` is configured so that crash events will be stored to disk and sent the next time the application is run. diff --git a/docs/modules/index.rst b/docs/modules/index.rst index 884eb1362d6..9a003a39d3c 100644 --- a/docs/modules/index.rst +++ b/docs/modules/index.rst @@ -1,3 +1,5 @@ +.. _integrations: + Integrations ============ diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 0451a98d492..f65c609a9f7 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -9,6 +9,11 @@ that sends logged exceptions to Sentry. The source can be found `on Github `_. +**Note:** ``raven-log4j`` is no longer maintained. It is highly recommended that +you migrate to ``sentry-log4j`` (which this documentation covers). If you are still +using ``raven-log4j`` you can +`find the old documentation here `_. + Installation ------------ @@ -95,138 +100,7 @@ Alternatively, using the ``log4j.xml`` format: Next, **you'll need to configure your DSN** (client key) and optionally other values such as -``environment`` and ``release``. See below for the two ways you can do this. - -Configuration via Runtime Environment -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This is the most flexible method for configuring the ``SentryAppender``, -because it can be easily changed based on the environment you run your -application in. - -The following can be set as System Environment variables: - -.. sourcecode:: shell - - SENTRY_EXAMPLE=xxx java -jar app.jar - -Or as Java System Properties: - -.. sourcecode:: shell - - java -Dsentry.example=xxx -jar app.jar - -Configuration parameters follow: - -======================== ======================== =============================== =========== -Environment variable Java System Property Example value Description -======================== ======================== =============================== =========== -``SENTRY_DSN`` ``sentry.dsn`` ``https://host:port/1?options`` Your Sentry DSN (client key), if left blank Sentry will no-op -``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application -``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in -``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) -``SENTRY_FACTORY`` ``sentry.factory`` ``com.foo.SentryClientFactory`` Optional, select the SentryClientFactory class -``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags -``SENTRY_EXTRATAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC -======================== ======================== =============================== =========== - -Configuration via Static File -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can also configure everything statically within the ``log4j.properties`` or ``log4j.xml`` -file itself. This is less flexible and not recommended because it's more difficult to change -the values when you run your application in different environments. - -Example configuration in the ``log4j.properties`` file: - -.. sourcecode:: ini - - # Enable the Console and Sentry appenders - log4j.rootLogger=INFO, Console, Sentry - - # Configure the Console appender - log4j.appender.Console=org.apache.log4j.ConsoleAppender - log4j.appender.Console.layout=org.apache.log4j.PatternLayout - log4j.appender.Console.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5p: %m%n - - # Configure the Sentry appender, overriding the logging threshold to the WARN level - log4j.appender.Sentry=io.sentry.log4j.SentryAppender - log4j.appender.Sentry.threshold=WARN - - # Set the Sentry DSN - log4j.appender.Sentry.dsn=https://host:port/1?options - - # Optional, provide release version of your application - log4j.appender.Sentry.release=1.0.0 - - # Optional, provide environment your application is running in - log4j.appender.Sentry.environment=production - - # Optional, override the server name (rather than looking it up dynamically) - log4j.appender.Sentry.serverName=server1 - - # Optional, select the SentryClientFactory class - log4j.appender.Sentry.factory=com.foo.SentryClientFactory - - # Optional, provide tags - log4j.appender.Sentry.tags=tag1:value1,tag2:value2 - - # Optional, provide tag names to be extracted from MDC - log4j.appender.Sentry.extraTags=foo,bar,baz - -Alternatively, using the ``log4j.xml`` format: - -.. sourcecode:: xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +``environment`` and ``release``. :ref:`See the configuration page ` for ways you can do this. Additional Data --------------- @@ -241,14 +115,10 @@ Mapped Tags ~~~~~~~~~~~ By default all MDC parameters are stored under the "Additional Data" tab in Sentry. By -specifying the ``extraTags`` parameter in your configuration file you can +specifying the ``extratags`` option in your configuration you can choose which MDC keys to send as tags instead, which allows them to be used as filters within the Sentry UI. -.. sourcecode:: ini - - log4j.appender.SentryAppender.extraTags=Environment,OS - .. sourcecode:: java void logWithExtras() { @@ -256,7 +126,7 @@ filters within the Sentry UI. MDC.put("Environment", "Development"); MDC.put("OS", "Linux"); - // This sends an event where the Environment and OS MDC values are set as tags + // This sends an event where the Environment and OS MDC values are set as additional data logger.error("This is a test"); } diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index b590ad906eb..bdef1bfd146 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -9,6 +9,11 @@ that sends logged exceptions to Sentry. The source can be found `on Github `_. +**Note:** ``raven-log4j2`` is no longer maintained. It is highly recommended that +you migrate to ``sentry-log4j2`` (which this documentation covers). If you are still +using ``raven-log4j2`` you can +`find the old documentation here `_. + Installation ------------ @@ -69,91 +74,7 @@ Example configuration using the ``log4j2.xml`` format: Next, **you'll need to configure your DSN** (client key) and optionally other values such as -``environment`` and ``release``. See below for the two ways you can do this. - -Configuration via Runtime Environment -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This is the most flexible method for configuring the ``SentryAppender``, -because it can be easily changed based on the environment you run your -application in. - -The following can be set as System Environment variables: - -.. sourcecode:: shell - - SENTRY_EXAMPLE=xxx java -jar app.jar - -Or as Java System Properties: - -.. sourcecode:: shell - - java -Dsentry.example=xxx -jar app.jar - -Configuration parameters follow: - -======================== ======================== =============================== =========== -Environment variable Java System Property Example value Description -======================== ======================== =============================== =========== -``SENTRY_DSN`` ``sentry.dsn`` ``https://host:port/1?options`` Your Sentry DSN (client key), if left blank Sentry will no-op -``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application -``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in -``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) -``SENTRY_FACTORY`` ``sentry.factory`` ``com.foo.SentryClientFactory`` Optional, select the SentryClientFactory class -``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags -``SENTRY_EXTRATAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC -======================== ======================== =============================== =========== - -Configuration via Static File -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can also configure everything statically within the ``log4j2.xml`` -file itself. This is less flexible and not recommended because it's more difficult to change -the values when you run your application in different environments. - -Example configuration in the ``log4j.properties`` file: - -.. sourcecode:: xml - - - - - - - - - - - https://host:port/1?options - - - 1.0.0 - - - production - - - server1 - - - com.foo.SentryClientFactory - - - tag1:value1,tag2:value2 - - - foo,bar,baz - - - - - - - - - - - +``environment`` and ``release``. :ref:`See the configuration page ` for ways you can do this. Additional Data --------------- @@ -166,14 +87,10 @@ Mapped Tags ~~~~~~~~~~~ By default all MDC parameters are stored under the "Additional Data" tab in Sentry. By -specifying the ``extraTags`` parameter in your configuration file you can +specifying the ``extratags`` option in your configuration you can choose which MDC keys to send as tags instead, which allows them to be used as filters within the Sentry UI. -.. sourcecode:: xml - - Environment,OS - .. sourcecode:: java void logWithExtras() { @@ -181,7 +98,7 @@ filters within the Sentry UI. MDC.put("Environment", "Development"); MDC.put("OS", "Linux"); - // This sends an event where the Environment and OS MDC values are set as tags + // This sends an event where the Environment and OS MDC values are set as additional data logger.error("This is a test"); } diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index e2782c25e57..0ba09dd2c73 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -9,6 +9,11 @@ that sends logged exceptions to Sentry. The source can be found `on Github `_. +**Note:** ``raven-logback`` is no longer maintained. It is highly recommended that +you migrate to ``sentry-logback`` (which this documentation covers). If you are still +using ``raven-logback`` you can +`find the old documentation here `_. + Installation ------------ @@ -73,95 +78,7 @@ Example configuration using the ``logback.xml`` format: Next, **you'll need to configure your DSN** (client key) and optionally other values such as -``environment`` and ``release``. See below for the two ways you can do this. - -Configuration via Runtime Environment -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This is the most flexible method for configuring the ``SentryAppender``, -because it can be easily changed based on the environment you run your -application in. - -The following can be set as System Environment variables: - -.. sourcecode:: shell - - SENTRY_EXAMPLE=xxx java -jar app.jar - -Or as Java System Properties: - -.. sourcecode:: shell - - java -Dsentry.example=xxx -jar app.jar - -Configuration parameters follow: - -======================== ======================== =============================== =========== -Environment variable Java System Property Example value Description -======================== ======================== =============================== =========== -``SENTRY_DSN`` ``sentry.dsn`` ``https://host:port/1?options`` Your Sentry DSN (client key), if left blank Sentry will no-op -``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application -``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in -``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) -``SENTRY_FACTORY`` ``sentry.factory`` ``com.foo.SentryClientFactory`` Optional, select the SentryClientFactory class -``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags -``SENTRY_EXTRATAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC -======================== ======================== =============================== =========== - -Configuration via Static File -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can also configure everything statically within the ``logback.xml`` -file itself. This is less flexible and not recommended because it's more difficult to change -the values when you run your application in different environments. - -Example configuration in the ``logback.xml`` file: - -.. sourcecode:: xml - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - WARN - - - - https://host:port/1?options - - - 1.0.0 - - - production - - - server1 - - - com.foo.SentryClientFactory - - - tag1:value1,tag2:value2 - - - foo,bar,baz - - - - - - - - +``environment`` and ``release``. :ref:`See the configuration page ` for ways you can do this. Additional Data --------------- @@ -173,14 +90,10 @@ Mapped Tags ~~~~~~~~~~~ By default all MDC parameters are stored under the "Additional Data" tab in Sentry. By -specifying the ``extraTags`` parameter in your configuration file you can +specifying the ``extratags`` option in your configuration you can choose which MDC keys to send as tags instead, which allows them to be used as filters within the Sentry UI. -.. sourcecode:: xml - - Environment,OS - .. sourcecode:: java void logWithExtras() { @@ -188,7 +101,7 @@ filters within the Sentry UI. MDC.put("Environment", "Development"); MDC.put("OS", "Linux"); - // This sends an event where the Environment and OS MDC values are set as tags + // This sends an event where the Environment and OS MDC values are set as additional data logger.error("This is a test"); } diff --git a/docs/modules/sentry.rst b/docs/modules/sentry.rst index 97bcd04fbea..7d77233e00c 100644 --- a/docs/modules/sentry.rst +++ b/docs/modules/sentry.rst @@ -5,9 +5,15 @@ The ``sentry`` library provides a `java.util.logging Handler `_ that sends logged exceptions to Sentry. -The source for ``sentry-java`` can be found `on Github +The source for ``sentry`` can be found `on Github `_. +**Note:** ``raven`` is no longer maintained. It is highly recommended that +you migrate to ``sentry`` (which this documentation covers). If you are still +using ``raven`` you can +`find the old documentation here `_. +\ + Installation ------------ @@ -64,81 +70,7 @@ its value:: $ java -Djava.util.logging.config.file=/path/to/app.properties MyClass Next, **you'll need to configure your DSN** (client key) and optionally other values such as -``environment`` and ``release``. See below for the two ways you can do this. - -Configuration via Runtime Environment -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This is the most flexible method for configuring the ``SentryHandler``, -because it can be easily changed based on the environment you run your -application in. - -The following can be set as System Environment variables: - -.. sourcecode:: shell - - SENTRY_EXAMPLE=xxx java -jar app.jar - -Or as Java System Properties: - -.. sourcecode:: shell - - java -Dsentry.example=xxx -jar app.jar - -Configuration parameters follow: - -======================== ======================== =============================== =========== -Environment variable Java System Property Example value Description -======================== ======================== =============================== =========== -``SENTRY_DSN`` ``sentry.dsn`` ``https://host:port/1?options`` Your Sentry DSN (client key), if left blank Sentry will no-op -``SENTRY_RELEASE`` ``sentry.release`` ``1.0.0`` Optional, provide release version of your application -``SENTRY_ENVIRONMENT`` ``sentry.environment`` ``production`` Optional, provide environment your application is running in -``SENTRY_SERVERNAME`` ``sentry.servername`` ``server1`` Optional, override the server name (rather than looking it up dynamically) -``SENTRY_FACTORY`` ``sentry.factory`` ``com.foo.SentryClientFactory`` Optional, select the SentryClientFactory class -``SENTRY_TAGS`` ``sentry.tags`` ``tag1:value1,tag2:value2`` Optional, provide tags -``SENTRY_EXTRATAGS`` ``sentry.extratags`` ``foo,bar,baz`` Optional, provide tag names to be extracted from MDC -======================== ======================== =============================== =========== - -Configuration via Static File -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can also configure everything statically within the ``logging.properties`` -file itself. This is less flexible and not recommended because it's more difficult to change -the values when you run your application in different environments. - -Example configuration in the ``logging.properties`` file: - -.. sourcecode:: ini - - # Enable the Console and Sentry handlers - handlers=java.util.logging.ConsoleHandler, io.sentry.jul.SentryHandler - - # Set the default log level to INFO - .level=INFO - - # Override the Sentry handler log level to WARNING - io.sentry.jul.SentryHandler.level=WARNING - - # Set Sentry DSN - io.sentry.jul.SentryHandler.dsn=https://host:port/1?options - - # Optional, provide tags - io.sentry.jul.SentryHandler.tags=tag1:value1,tag2:value2 - - # Optional, provide release version of your application - io.sentry.jul.SentryHandler.release=1.0.0 - - # Optional, provide environment your application is running in - io.sentry.jul.SentryHandler.environment=production - - # Optional, override the server name (rather than looking it up dynamically) - io.sentry.jul.SentryHandler.serverName=server1 - - # Optional, select the SentryClientFactorclass - io.sentry.jul.SentryHandler.factory=com.foo.SentryClientFactory - - # Optional, provide tag names to be extracted from MDC - io.sentry.jul.SentryHandler.extraTags=foo,bar,baz +``environment`` and ``release``. :ref:`See the configuration page ` for ways you can do this. In Practice ----------- diff --git a/docs/usage.rst b/docs/usage.rst index 6ecbd1ef18f..fc72d977657 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -167,6 +167,9 @@ For more complex messages, you'll need to build an ``Event`` with the .withMessage("This is a test") .withLevel(Event.Level.INFO) .withLogger(MyClass.class.getName()); + + // Note that the *unbuilt* EventBuilder instance is passed in so that + // EventBuilderHelpers are run to add extra information to your event. Sentry.capture(eventBuilder); } @@ -180,6 +183,9 @@ For more complex messages, you'll need to build an ``Event`` with the .withLevel(Event.Level.ERROR) .withLogger(MyClass.class.getName()) .withSentryInterface(new ExceptionInterface(e)); + + // Note that the *unbuilt* EventBuilder instance is passed in so that + // EventBuilderHelpers are run to add extra information to your event. Sentry.capture(eventBuilder); } } diff --git a/sentry/src/main/java/io/sentry/dsn/Dsn.java b/sentry/src/main/java/io/sentry/dsn/Dsn.java index f116b9acabb..5d6b8bb0686 100644 --- a/sentry/src/main/java/io/sentry/dsn/Dsn.java +++ b/sentry/src/main/java/io/sentry/dsn/Dsn.java @@ -48,7 +48,7 @@ public Dsn(String dsn) throws InvalidDsnException { */ public Dsn(URI dsn) throws InvalidDsnException { if (dsn == null) { - throw new InvalidDsnException("The sentry DSN must be provided and not be null"); + throw new InvalidDsnException("DSN constructed with null value!"); } options = new HashMap<>(); @@ -80,7 +80,8 @@ public static String dsnLookup() { String dsn = Lookup.lookup("dsn"); if (dsn == null) { - logger.warn("*** Couldn't find a suitable DSN, Sentry operations will do nothing! ***"); + logger.warn("*** Couldn't find a suitable DSN, Sentry operations will do nothing!" + + " See documentation: https://docs.sentry.io/clients/java/ ***"); dsn = DEFAULT_DSN; } From 087ccf0fd3d215044367f146ef9fe59200d5b183 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 6 Jun 2017 18:21:39 -0500 Subject: [PATCH 1613/2152] [maven-release-plugin] prepare release v1.0.0 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 85dae501014..be881ad7ef8 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.0.0-SNAPSHOT + 1.0.0 pom Sentry-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.0.0 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index c1ca5831e00..ce33c70ffe3 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.0.0-SNAPSHOT + 1.0.0 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 222ac5dd3b8..7ba6a5a642d 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.0.0-SNAPSHOT + 1.0.0 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 00ba43d48ff..9191ea878b9 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.0.0-SNAPSHOT + 1.0.0 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index fe730a6e52a..9be60c0c965 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.0.0-SNAPSHOT + 1.0.0 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 7c35e6656e0..bfdfa9effe8 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.0.0-SNAPSHOT + 1.0.0 sentry-logback diff --git a/sentry/pom.xml b/sentry/pom.xml index 66a848b36f8..3645c472f60 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.0.0-SNAPSHOT + 1.0.0 sentry From a2b192d529e47e525ca681a4764c61ae94d6ea14 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 6 Jun 2017 18:21:39 -0500 Subject: [PATCH 1614/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index be881ad7ef8..3318ce36d49 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.0.0 + 1.0.1-SNAPSHOT pom Sentry-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.0.0 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index ce33c70ffe3..1d3079e01dd 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.0.0 + 1.0.1-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 7ba6a5a642d..29facb34e81 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.0.0 + 1.0.1-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 9191ea878b9..b4ae08af309 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.0.0 + 1.0.1-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 9be60c0c965..85304583a03 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.0.0 + 1.0.1-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index bfdfa9effe8..12cf2c8bb6c 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.0.0 + 1.0.1-SNAPSHOT sentry-logback diff --git a/sentry/pom.xml b/sentry/pom.xml index 3645c472f60..fd1ac6d5514 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.0.0 + 1.0.1-SNAPSHOT sentry From 64caf96628b999212d6bb482c502cec55cba570c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 6 Jun 2017 18:21:58 -0500 Subject: [PATCH 1615/2152] Bump CHANGES to 1.0.1 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 3e326edba79..68102c10c54 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.0.1 +------------- + +- + Version 1.0.0 ------------- From 96f4b3b1c8e41582dcf2dc3e09c433a3fe00ab0d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 6 Jun 2017 19:42:12 -0500 Subject: [PATCH 1616/2152] Rename jul docs. --- docs/modules/{sentry.rst => jul.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/modules/{sentry.rst => jul.rst} (100%) diff --git a/docs/modules/sentry.rst b/docs/modules/jul.rst similarity index 100% rename from docs/modules/sentry.rst rename to docs/modules/jul.rst From 176008067e0c553217bdf43bbde83bd07efd199b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 6 Jun 2017 20:05:18 -0500 Subject: [PATCH 1617/2152] Fix docs index. --- docs/modules/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/index.rst b/docs/modules/index.rst index 9a003a39d3c..46ea6c31305 100644 --- a/docs/modules/index.rst +++ b/docs/modules/index.rst @@ -11,7 +11,7 @@ you don't have to capture and send errors manually. android appengine - sentry + jul log4j log4j2 logback From 308db0831ac447ee42e5c2047fe50f1f2ae8e3d2 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 7 Jun 2017 09:37:32 -0500 Subject: [PATCH 1618/2152] Adjust doc headers. --- docs/config.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 593e663133d..903b7dbb032 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -1,7 +1,7 @@ .. _configuration: -Configuration -============= +Configuration methods +===================== **Note:** Sentry's library and framework integration documentation explains how to to do the initial Sentry configuration for each of the supported integrations. The configuration @@ -66,8 +66,8 @@ if you are setting your DSN via the environment: SENTRY_DSN=http://public:private@host:port/1?sample.rate=0.75 java -jar app.jar -DSN (Data Source Name) -====================== +Setting the DSN (Data Source Name) +================================== The DSN is the first and most important option to configure because it tells the SDK where to send events. You can find a basic DSN in the "Client Keys" section of your "Project Settings" @@ -494,8 +494,8 @@ It's possible to manually set the timeout length with timeout`` timeout=10000 -Custom SentryClientFactory -========================== +Custom functionality +==================== At times, you may require custom functionality that is not included in ``sentry-java`` already. The most common way to do this is to create your own ``SentryClientFactory`` instance From 73424a2c2baa29f7954637fa113615574c341758 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 7 Jun 2017 15:37:59 -0500 Subject: [PATCH 1619/2152] Cleanup and re-order docs. --- docs/config.rst | 437 ++++++++++++++++++--------------------- docs/modules/logback.rst | 2 +- docs/usage.rst | 4 +- 3 files changed, 205 insertions(+), 238 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 903b7dbb032..6a99e9a7871 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -1,7 +1,7 @@ .. _configuration: -Configuration methods -===================== +Configuration +============= **Note:** Sentry's library and framework integration documentation explains how to to do the initial Sentry configuration for each of the supported integrations. The configuration @@ -9,10 +9,43 @@ below can be used in combination with any of the integrations *once you set Sent the integration*. Please check :ref:`the integration documentation ` before you attempt to do any advanced configuration. +.. _setting_the_dsn: + +Setting the DSN (Data Source Name) +---------------------------------- + +The DSN is the first and most important thing to configure because it tells the SDK where +to send events. You can find a basic DSN in the "Client Keys" section of your "Project Settings" +in Sentry. It can be configured in multiple ways. Explanations of the :ref:`configuration methods are +detailed below `. + +In your ``sentry.properties``: + +.. sourcecode:: properties + + dsn=https://public:private@host:port/1 + +Via the Java System Properties: + +.. sourcecode:: shell + + java -Dsentry.dsn=https://public:private@host:port/1 -jar app.jar + +Via a System Environment Variable: + +.. sourcecode:: shell + + SENTRY_DSN=https://public:private@host:port/1 java -jar app.jar + +.. _configuration_methods: + +Configuration methods +--------------------- + Configuration via properties file ---------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``sentry-java`` SDK can be configured via a ``sentry.properties`` file placed at the root of +The Java SDK can be configured via a ``sentry.properties`` file placed at the root of your classpath, which is typically achieved by adding a ``src/main/resources/sentry.properties`` file to your project. This file follows the standard `.properties file format `_ and thus contains one option per line. @@ -30,7 +63,7 @@ sampling, in your ``sentry.properties`` file: sample.rate=0.75 Configuration via the runtime environment ------------------------------------------ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is the most flexible method for configuring the Sentry client because it can be easily changed based on the environment you run your @@ -54,7 +87,7 @@ them, and add a ``SENTRY_`` prefix. For example, to enable sampling: SENTRY_SAMPLE_RATE=0.75 java -jar app.jar Configuration via the DSN -------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~ The SDK can also be configured by setting querystring parameters on the DSN itself. This is a bit recursive because your DSN itself is an option that you must set somewhere (and not in the DSN!). @@ -64,119 +97,16 @@ if you are setting your DSN via the environment: .. sourcecode:: shell - SENTRY_DSN=http://public:private@host:port/1?sample.rate=0.75 java -jar app.jar - -Setting the DSN (Data Source Name) -================================== - -The DSN is the first and most important option to configure because it tells the SDK where -to send events. You can find a basic DSN in the "Client Keys" section of your "Project Settings" -in Sentry. It can be configured anywhere except for in the DSN itself. - -In your ``sentry.properties``: - -.. sourcecode:: properties - - dsn=http://public:private@host:port/1 - -Via the Java System Properties: - -.. sourcecode:: shell - - java -Dsentry.dsn=http://public:private@host:port/1 -jar app.jar - -Via a System Environment Variable: - -.. sourcecode:: shell - - SENTRY_DSN=http://public:private@host:port/1 java -jar app.jar - -Connection and Protocol ------------------------ - -It is possible to send events to Sentry over different protocols, depending -on the security and performance requirements. - -HTTPS -~~~~~ - -The most common way to send events to Sentry is via HTTPS, this can be done by -using a DSN of this form: - -:: - - https://public:private@host:port/1 - -If not provided, the port will default to ``443``. - -HTTPS (naive) -~~~~~~~~~~~~~ - -If the certificate used over HTTPS is a wildcard certificate (which is not -handled by every version of Java), and the certificate isn't added to the -truststore, you can add a protocol setting to tell the client to be -naive and ignore hostname verification: - -:: - - naive+https://public:private@host:port/1 - -HTTP -~~~~ - -It is possible to use an unencrypted connection to Sentry via HTTP: - -:: - - http://public:private@host:port/1 - -If not provided, the port will default to ``80``. - -Using a Proxy -------------- - -If your application needs to send outbound requests through an HTTP proxy, -you can configure the proxy information via JVM networking properties or -as part of the Sentry DSN. - -For example, using JVM networking properties (affects the entire JVM process), - -:: - - java \ - # if you are using the HTTP protocol \ - -Dhttp.proxyHost=proxy.example.com \ - -Dhttp.proxyPort=8080 \ - \ - # if you are using the HTTPS protocol \ - -Dhttps.proxyHost=proxy.example.com \ - -Dhttps.proxyPort=8080 \ - \ - # relevant to both HTTP and HTTPS - -Dhttp.nonProxyHosts=”localhost|host.example.com” \ - \ - MyApp - -See `Java Networking and -Proxies `_ -for more information about the proxy properties. - -Alternatively, using Sentry options (only affects the Sentry HTTP client, -useful inside shared application containers), - -:: - - http.proxy.host=proxy.example.com - http.proxy.port=8080 + SENTRY_DSN=https://public:private@host:port/1?sample.rate=0.75 java -jar app.jar Options -======= +------- The following options can all be configured as described above: via a ``sentry.properties`` file, via Java System Properties, via System Environment variables, or via the DSN. Release -------- +~~~~~~~ To set the application version that will be sent with each event, use the ``release`` option: @@ -186,7 +116,7 @@ To set the application version that will be sent with each event, use the release=1.0.0 Distribution -~~~~~~~~~~~~ +```````````` To set the application distribution that will be sent with each event, use the ``dist`` option: @@ -200,7 +130,7 @@ Note that the distribution is only useful (and used) if the ``release`` is also set. Environment ------------ +~~~~~~~~~~~ To set the application environment that will be sent with each event, use the ``environment`` option: @@ -210,7 +140,7 @@ To set the application environment that will be sent with each event, use the environment=staging Server Name ------------ +~~~~~~~~~~~ To set the server name that will be sent with each event, use the ``servername`` option: @@ -220,7 +150,7 @@ To set the server name that will be sent with each event, use the servername=host1 Tags ----- +~~~~ To set tags that will be sent with each event, use the ``tags`` option with comma separated pairs of keys and values that are joined by a colon: @@ -230,7 +160,7 @@ comma separated pairs of keys and values that are joined by a colon: tags=tag1:value1,tag2:value2 Extra Tags ----------- +~~~~~~~~~~ To set extras that are extracted and used as additional tags, use the ``extratags`` option with comma separated key names. @@ -243,101 +173,68 @@ Note that how these extra tags are used depends on which integration you are using. For example: when using a logging integration any SLF4J MDC keys that are in the extra tags set will be extracted and set as tags on events. -Async Connection ----------------- - -In order to avoid performance issues due to a large amount of logs being -generated or a slow connection to the Sentry server, an asynchronous connection -is set up, using a low priority thread pool to submit events to Sentry. - -To disable the async mode, add async=false`` to the DSN: - -:: - - async=false +"In Application" Stack Frames +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Graceful Shutdown (Advanced) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Sentry differentiates stack frames that are directly related to your application +("in application") from stack frames that come from other packages such as the +standard library, frameworks, or other dependencies. The difference +is visible in the Sentry web interface where only the "in application" frames are +displayed by default. -In order to shutdown the asynchronous connection gracefully, a ``ShutdownHook`` -is created. By default, the asynchronous connection is given 1 second -to shutdown gracefully, but this can be adjusted via -async.shutdowntimeout`` (represented in milliseconds): +You can configure which package prefixes your application uses with the +stacktrace.app.packages`` option, which takes a comma separated list. :: - async.shutdowntimeout=5000 - -The special value ``-1`` can be used to disable the timeout and wait -indefinitely for the executor to terminate. - -The ``ShutdownHook`` could lead to memory leaks in an environment where -the life cycle of Sentry doesn't match the life cycle of the JVM. - -An example would be in a JEE environment where the application using Sentry -could be deployed and undeployed regularly. + stacktrace.app.packages=com.mycompany,com.other.name -To avoid this behaviour, it is possible to disable the graceful shutdown. -This might lead to some log entries being lost if the log application -doesn't shut down the ``SentryClient`` instance nicely. +Same Frame as Enclosing Exception +````````````````````````````````` -The option to do so is async.gracefulshutdown``: +Sentry can use the "in application" system to hide frames in chained exceptions. Usually when a +StackTrace is printed, the result looks like this: :: - async.gracefulshutdown=false - -Queue Size (Advanced) -~~~~~~~~~~~~~~~~~~~~~ + HighLevelException: MidLevelException: LowLevelException + at Main.a(Main.java:13) + at Main.main(Main.java:4) + Caused by: MidLevelException: LowLevelException + at Main.c(Main.java:23) + at Main.b(Main.java:17) + at Main.a(Main.java:11) + ... 1 more + Caused by: LowLevelException + at Main.e(Main.java:30) + at Main.d(Main.java:27) + at Main.c(Main.java:21) + ... 3 more -The default queue used to store unprocessed events is limited to 50 -items. Additional items added once the queue is full are dropped and -never sent to the Sentry server. -Depending on the environment (if the memory is sparse) it is important to be -able to control the size of that queue to avoid memory issues. +Some frames are replaced by the ``... N more`` line as they are the same frames +as in the enclosing exception. -It is possible to set a maximum with the option async.queuesize``: +To enable a similar behaviour in Sentry use the stacktrace.hidecommon`` option. :: - async.queuesize=100 - -This means that if the connection to the Sentry server is down, only the 100 -most recent events will be stored and processed as soon as the server is back up. - -The special value ``-1`` can be used to enable an unlimited queue. Beware -that network connectivity or Sentry server issues could mean your process -will run out of memory. - -Threads Count (Advanced) -~~~~~~~~~~~~~~~~~~~~~~~~ + stacktrace.hidecommon -By default the thread pool used by the async connection contains one thread per -processor available to the JVM. +Event Sampling +~~~~~~~~~~~~~~ -It's possible to manually set the number of threads (for example if you want -only one thread) with the option async.threads``: +Sentry can be configured to sample events with the sample.rate`` option: :: - async.threads=1 - -Threads Priority (Advanced) -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In most cases sending logs to Sentry isn't as important as an application -running smoothly, so the threads have a -`minimal priority `_. - -It is possible to customise this value to increase the priority of those threads -with the option async.priority``: - -:: + sample.rate=0.75 - async.priority=10 +This option takes a number from 0.0 to 1.0, representing the percent of +events to allow through to server (from 0% to 100%). By default all +events will be sent to the Sentry server. Buffering Events to Disk ------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~ Sentry can be configured to write events to a specified directory on disk anytime communication with the Sentry server fails with the buffer.dir`` @@ -366,7 +263,7 @@ buffer.flushtime`` option (in milliseconds): buffer.flushtime=10000 Graceful Shutdown (Advanced) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +```````````````````````````` In order to shutdown the buffer flushing thread gracefully, a ``ShutdownHook`` is created. By default, the buffer flushing thread is given 1 second @@ -393,68 +290,101 @@ by setting the buffer.gracefulshutdown`` option: buffer.gracefulshutdown=false -Event Sampling --------------- +Async Connection +~~~~~~~~~~~~~~~~ -Sentry can be configured to sample events with the sample.rate`` option: +In order to avoid performance issues due to a large amount of logs being +generated or a slow connection to the Sentry server, an asynchronous connection +is set up, using a low priority thread pool to submit events to Sentry. + +To disable the async mode, add async=false`` to your options: :: - sample.rate=0.75 + async=false -This option takes a number from 0.0 to 1.0, representing the percent of -events to allow through to server (from 0% to 100%). By default all -events will be sent to the Sentry server. +Graceful Shutdown (Advanced) +```````````````````````````` -"In Application" Stack Frames ------------------------------ +In order to shutdown the asynchronous connection gracefully, a ``ShutdownHook`` +is created. By default, the asynchronous connection is given 1 second +to shutdown gracefully, but this can be adjusted via +async.shutdowntimeout`` (represented in milliseconds): -Sentry differentiates stack frames that are directly related to your application -("in application") from stack frames that come from other packages such as the -standard library, frameworks, or other dependencies. The difference -is visible in the Sentry web interface where only the "in application" frames are -displayed by default. +:: -You can configure which package prefixes your application uses with the -stacktrace.app.packages`` option, which takes a comma separated list. + async.shutdowntimeout=5000 + +The special value ``-1`` can be used to disable the timeout and wait +indefinitely for the executor to terminate. + +The ``ShutdownHook`` could lead to memory leaks in an environment where +the life cycle of Sentry doesn't match the life cycle of the JVM. + +An example would be in a JEE environment where the application using Sentry +could be deployed and undeployed regularly. + +To avoid this behaviour, it is possible to disable the graceful shutdown. +This might lead to some log entries being lost if the log application +doesn't shut down the ``SentryClient`` instance nicely. + +The option to do so is async.gracefulshutdown``: :: - stacktrace.app.packages=com.mycompany,com.other.name + async.gracefulshutdown=false -Same Frame as Enclosing Exception -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Queue Size (Advanced) +````````````````````` -Sentry can use the "in application" system to hide frames in chained exceptions. Usually when a -StackTrace is printed, the result looks like this: +The default queue used to store unprocessed events is limited to 50 +items. Additional items added once the queue is full are dropped and +never sent to the Sentry server. +Depending on the environment (if the memory is sparse) it is important to be +able to control the size of that queue to avoid memory issues. + +It is possible to set a maximum with the option async.queuesize``: :: - HighLevelException: MidLevelException: LowLevelException - at Main.a(Main.java:13) - at Main.main(Main.java:4) - Caused by: MidLevelException: LowLevelException - at Main.c(Main.java:23) - at Main.b(Main.java:17) - at Main.a(Main.java:11) - ... 1 more - Caused by: LowLevelException - at Main.e(Main.java:30) - at Main.d(Main.java:27) - at Main.c(Main.java:21) - ... 3 more + async.queuesize=100 -Some frames are replaced by the ``... N more`` line as they are the same frames -as in the enclosing exception. +This means that if the connection to the Sentry server is down, only the 100 +most recent events will be stored and processed as soon as the server is back up. -To enable a similar behaviour in Sentry use the stacktrace.hidecommon`` option. +The special value ``-1`` can be used to enable an unlimited queue. Beware +that network connectivity or Sentry server issues could mean your process +will run out of memory. + +Threads Count (Advanced) +```````````````````````` + +By default the thread pool used by the async connection contains one thread per +processor available to the JVM. + +It's possible to manually set the number of threads (for example if you want +only one thread) with the option async.threads``: :: - stacktrace.hidecommon + async.threads=1 + +Threads Priority (Advanced) +``````````````````````````` + +In most cases sending logs to Sentry isn't as important as an application +running smoothly, so the threads have a +`minimal priority `_. + +It is possible to customise this value to increase the priority of those threads +with the option async.priority``: + +:: + + async.priority=10 Compression ------------ +~~~~~~~~~~~ By default the content sent to Sentry is compressed before being sent. However, compressing and encoding the data adds a small CPU and memory hit which @@ -472,7 +402,7 @@ compression`` compression=false Max Message Size ----------------- +~~~~~~~~~~~~~~~~ By default only the first 1000 characters of a message will be sent to the server. This can be changed with the maxmessagelength`` option. @@ -482,7 +412,7 @@ the server. This can be changed with the maxmessagelength`` option. maxmessagelength=1500 Timeout (Advanced) ------------------- +~~~~~~~~~~~~~~~~~~ A timeout is set to avoid blocking Sentry threads because establishing a connection is taking too long. @@ -494,15 +424,52 @@ It's possible to manually set the timeout length with timeout`` timeout=10000 +Using a Proxy +~~~~~~~~~~~~~ + +If your application needs to send outbound requests through an HTTP proxy, +you can configure the proxy information via JVM networking properties or +as a Sentry option. + +For example, using JVM networking properties (affects the entire JVM process), + +:: + + java \ + # if you are using the HTTP protocol \ + -Dhttp.proxyHost=proxy.example.com \ + -Dhttp.proxyPort=8080 \ + \ + # if you are using the HTTPS protocol \ + -Dhttps.proxyHost=proxy.example.com \ + -Dhttps.proxyPort=8080 \ + \ + # relevant to both HTTP and HTTPS + -Dhttp.nonProxyHosts=”localhost|host.example.com” \ + \ + MyApp + +See `Java Networking and +Proxies `_ +for more information about the proxy properties. + +Alternatively, using Sentry options (only affects the Sentry HTTP client, +useful inside shared application containers), + +:: + + http.proxy.host=proxy.example.com + http.proxy.port=8080 + Custom functionality -==================== +-------------------- -At times, you may require custom functionality that is not included in ``sentry-java`` +At times, you may require custom functionality that is not included in the Java SDK already. The most common way to do this is to create your own ``SentryClientFactory`` instance as seen in the example below. Implementation --------------- +~~~~~~~~~~~~~~ .. sourcecode:: java @@ -523,7 +490,7 @@ Implementation } Usage ------ +~~~~~ To use your custom ``SentryClientFactory`` implementation, use the ``factory`` option: diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 0ba09dd2c73..5c45d37a9fc 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -78,7 +78,7 @@ Example configuration using the ``logback.xml`` format: Next, **you'll need to configure your DSN** (client key) and optionally other values such as -``environment`` and ``release``. :ref:`See the configuration page ` for ways you can do this. +``environment`` and ``release``. :ref:`See the configuration page ` for ways you can do this. Additional Data --------------- diff --git a/docs/usage.rst b/docs/usage.rst index fc72d977657..b057e67651d 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -2,8 +2,8 @@ Manual Usage ============ **Note:** The following page provides examples on how to configure and use -Sentry directly. It is **highly recommended** that you use one of the provided -integrations instead if possible. +Sentry directly. It is **highly recommended** that you use one of the +:ref:`provided integrations ` instead if possible. Installation ------------ From 27f57e6165a2e69b3db4ee320c9e3496df267c9d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 8 Jun 2017 13:23:14 -0500 Subject: [PATCH 1620/2152] Note that config can be done in code. --- docs/config.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/config.rst b/docs/config.rst index 6a99e9a7871..5f8c44dcebd 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -37,6 +37,12 @@ Via a System Environment Variable: SENTRY_DSN=https://public:private@host:port/1 java -jar app.jar +In code: + +.. sourcecode:: java + + Sentry.init("https://public:private@host:port/1"); + .. _configuration_methods: Configuration methods @@ -86,6 +92,20 @@ them, and add a ``SENTRY_`` prefix. For example, to enable sampling: SENTRY_SAMPLE_RATE=0.75 java -jar app.jar +Configuration via code +~~~~~~~~~~~~~~~~~~~~~~ + +The DSN itself can also be configured directly in code: + +.. sourcecode:: java + + Sentry.init("https://public:private@host:port/1"); + +Note that Sentry will not be able to do anything with events until this line is run, so this +method is configuration is not recommended if you might have errors occur during startup. +In addition, by passing a hardcoded DSN you are no longer able to override the DSN at runtime +via Java System Properties or System Environment Variables. + Configuration via the DSN ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -99,6 +119,8 @@ if you are setting your DSN via the environment: SENTRY_DSN=https://public:private@host:port/1?sample.rate=0.75 java -jar app.jar +You can, of course, pass this DSN in using the other methods described above. + Options ------- From ecffa387182281006e4d9caf4a6ff01021d93841 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 8 Jun 2017 13:32:09 -0500 Subject: [PATCH 1621/2152] Add imports. --- docs/config.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 5f8c44dcebd..7eaeb940c43 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -41,7 +41,9 @@ In code: .. sourcecode:: java - Sentry.init("https://public:private@host:port/1"); + import io.sentry.Sentry; + + Sentry.init("https://public:private@host:port/1"); .. _configuration_methods: @@ -99,10 +101,12 @@ The DSN itself can also be configured directly in code: .. sourcecode:: java + import io.sentry.Sentry; + Sentry.init("https://public:private@host:port/1"); Note that Sentry will not be able to do anything with events until this line is run, so this -method is configuration is not recommended if you might have errors occur during startup. +method of configuration is not recommended if you might have errors occur during startup. In addition, by passing a hardcoded DSN you are no longer able to override the DSN at runtime via Java System Properties or System Environment Variables. From bf2a724ead31ad8c1d0bae5d6b9731ec2e6ce849 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 8 Jun 2017 14:39:07 -0500 Subject: [PATCH 1622/2152] Note in CHANGES. --- CHANGES | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES b/CHANGES index 68102c10c54..cd97dcc8899 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,9 @@ Version 1.0.1 Version 1.0.0 ------------- +1.0.0 is a major refactor, please see the official documentation to see how configuration and usage +has changed: https://docs.sentry.io/clients/java/ + - Move from ``com.getsentry.raven`` package to ``io.sentry``. - Rename from ``raven-java`` to ``sentry-java``. - Rename the ``Sentry`` class to ``SentryClient``. @@ -24,6 +27,9 @@ Version 1.0.0 - Logging integrations now use the new ``Sentry`` static API. - Configuration can now be provided by a ``sentry.properties`` file provided in resources. +raven-java changelog (before rename to sentry-java) +=================================================== + Version 8.0.2 ------------- From add65cc65fbea87713b071e0ab822e7cd7b6f35c Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 13 Jun 2017 11:26:03 +0200 Subject: [PATCH 1623/2152] Add DebugMetaInterface --- .../event/interfaces/DebugMetaInterface.java | 47 +++++++++++++++++++ .../io/sentry/event/EventBuilderTest.java | 17 +++++++ .../interfaces/DebugMetaInterfaceTest.java | 19 ++++++++ 3 files changed, 83 insertions(+) create mode 100644 sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java create mode 100644 sentry/src/test/java/io/sentry/event/interfaces/DebugMetaInterfaceTest.java diff --git a/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java b/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java new file mode 100644 index 00000000000..d4cf2c84e71 --- /dev/null +++ b/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java @@ -0,0 +1,47 @@ +package io.sentry.event.interfaces; + +import java.util.ArrayList; + +public class DebugMetaInterface implements SentryInterface { + + public static final String DEBUG_META_INTERFACE = "sentry.interfaces.DebugMeta"; + ArrayList debugImages = new ArrayList<>(); + + public void addDebugImage(DebugImage debugImage) { + debugImages.add(debugImage); + } + + @Override + public String getInterfaceName() { + return DEBUG_META_INTERFACE; + } + + @Override + public int hashCode() { + return debugImages.hashCode(); + } + + @Override + public String toString() { + return "DebugMetaInterface{" + + "images=" + debugImages.toString() + + '}'; + } + + public static class DebugImage { + private final String uuid; + private final String type = "proguard"; + + public DebugImage(String uuid) { + this.uuid = uuid; + } + + @Override + public String toString() { + return "DebugImage{" + + "uuid='" + uuid+ '\'' + + ", type='" + type+ '\'' + + '}'; + } + } +} diff --git a/sentry/src/test/java/io/sentry/event/EventBuilderTest.java b/sentry/src/test/java/io/sentry/event/EventBuilderTest.java index 741b3bd5188..047b4a269b8 100644 --- a/sentry/src/test/java/io/sentry/event/EventBuilderTest.java +++ b/sentry/src/test/java/io/sentry/event/EventBuilderTest.java @@ -1,6 +1,7 @@ package io.sentry.event; import io.sentry.BaseTest; +import io.sentry.event.interfaces.DebugMetaInterface; import mockit.Injectable; import mockit.NonStrictExpectations; import io.sentry.event.interfaces.SentryInterface; @@ -482,4 +483,20 @@ public void buildingTheEventTwiceFails() throws Exception { eventBuilder.build(); eventBuilder.build(); } + + @Test + public void builtEventWithDebugMeta() { + final EventBuilder eventBuilder = new EventBuilder(); + DebugMetaInterface.DebugImage image1 = new DebugMetaInterface.DebugImage("abcd-efgh"); + DebugMetaInterface.DebugImage image2 = new DebugMetaInterface.DebugImage("ijkl-mnop"); + final DebugMetaInterface debugInterface = new DebugMetaInterface(); + debugInterface.addDebugImage(image1); + debugInterface.addDebugImage(image2); + + eventBuilder.withSentryInterface(debugInterface); + + final Event event = eventBuilder.build(); + + assertThat(event.getSentryInterfaces(), hasKey(DebugMetaInterface.DEBUG_META_INTERFACE)); + } } diff --git a/sentry/src/test/java/io/sentry/event/interfaces/DebugMetaInterfaceTest.java b/sentry/src/test/java/io/sentry/event/interfaces/DebugMetaInterfaceTest.java new file mode 100644 index 00000000000..9341eb14106 --- /dev/null +++ b/sentry/src/test/java/io/sentry/event/interfaces/DebugMetaInterfaceTest.java @@ -0,0 +1,19 @@ +package io.sentry.event.interfaces; + +import io.sentry.BaseTest; +import org.testng.annotations.Test; + +import java.util.Objects; + +public class DebugMetaInterfaceTest extends BaseTest { + @Test + public void testDebugMeta() throws Exception { + DebugMetaInterface.DebugImage image1 = new DebugMetaInterface.DebugImage("abcd-efgh"); + DebugMetaInterface.DebugImage image2 = new DebugMetaInterface.DebugImage("ijkl-mnop"); + final DebugMetaInterface debugInterface = new DebugMetaInterface(); + debugInterface.addDebugImage(image1); + debugInterface.addDebugImage(image2); + + assert Objects.equals(debugInterface.toString(), "DebugMetaInterface{images=[DebugImage{uuid='abcd-efgh', type='proguard'}, DebugImage{uuid='ijkl-mnop', type='proguard'}]}"); + } +} From 17968f352113a6fb78b84b7e95ac2b18cef68ae8 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 13 Jun 2017 16:54:29 +0200 Subject: [PATCH 1624/2152] Add DebugMetaInterfaceBinding for json output --- .../event/interfaces/DebugMetaInterface.java | 16 ++++++++ .../json/DebugMetaInterfaceBinding.java | 33 ++++++++++++++++ .../json/DebugMetaInterfaceBindingTest.java | 39 +++++++++++++++++++ .../io/sentry/marshaller/json/Proguard.json | 10 +++++ 4 files changed, 98 insertions(+) create mode 100644 sentry/src/main/java/io/sentry/marshaller/json/DebugMetaInterfaceBinding.java create mode 100644 sentry/src/test/java/io/sentry/marshaller/json/DebugMetaInterfaceBindingTest.java create mode 100644 sentry/src/test/resources/io/sentry/marshaller/json/Proguard.json diff --git a/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java b/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java index d4cf2c84e71..dba78adae81 100644 --- a/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java @@ -2,9 +2,17 @@ import java.util.ArrayList; +/** + * The DebugMeta interface for Sentry allowing to add debug information about ProGuard. + */ public class DebugMetaInterface implements SentryInterface { public static final String DEBUG_META_INTERFACE = "sentry.interfaces.DebugMeta"; + + public ArrayList getDebugImages() { + return debugImages; + } + ArrayList debugImages = new ArrayList<>(); public void addDebugImage(DebugImage debugImage) { @@ -36,6 +44,14 @@ public DebugImage(String uuid) { this.uuid = uuid; } + public String getUuid() { + return uuid; + } + + public String getType() { + return type; + } + @Override public String toString() { return "DebugImage{" diff --git a/sentry/src/main/java/io/sentry/marshaller/json/DebugMetaInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/DebugMetaInterfaceBinding.java new file mode 100644 index 00000000000..bc9b4d42480 --- /dev/null +++ b/sentry/src/main/java/io/sentry/marshaller/json/DebugMetaInterfaceBinding.java @@ -0,0 +1,33 @@ +package io.sentry.marshaller.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import io.sentry.event.interfaces.DebugMetaInterface; + +import java.io.IOException; + +public class DebugMetaInterfaceBinding implements InterfaceBinding { + private static final String DEBUG_META = "debug_meta"; + private static final String IMAGES = "images"; + private static final String UUID = "uuid"; + private static final String TYPE = "type"; + + @Override + public void writeInterface(JsonGenerator generator, DebugMetaInterface debugMetaInterface) throws IOException { + generator.writeStartObject(); + generator.writeObjectFieldStart(DEBUG_META); + writeDebugImages(generator, debugMetaInterface); + generator.writeEndObject(); + generator.writeEndObject(); + } + + private void writeDebugImages(JsonGenerator generator, DebugMetaInterface debugMetaInterface) throws IOException { + generator.writeArrayFieldStart(IMAGES); + for (int i = 0; i < debugMetaInterface.getDebugImages().size(); i++) { + generator.writeStartObject(); + generator.writeStringField(UUID, debugMetaInterface.getDebugImages().get(i).getUuid()); + generator.writeStringField(TYPE, debugMetaInterface.getDebugImages().get(i).getType()); + generator.writeEndObject(); + } + generator.writeEndArray(); + } +} diff --git a/sentry/src/test/java/io/sentry/marshaller/json/DebugMetaInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/DebugMetaInterfaceBindingTest.java new file mode 100644 index 00000000000..3a123add6c7 --- /dev/null +++ b/sentry/src/test/java/io/sentry/marshaller/json/DebugMetaInterfaceBindingTest.java @@ -0,0 +1,39 @@ +package io.sentry.marshaller.json; + + +import io.sentry.BaseTest; +import io.sentry.event.interfaces.DebugMetaInterface; +import mockit.Injectable; +import mockit.NonStrictExpectations; +import mockit.Tested; +import org.testng.annotations.Test; + +import java.util.ArrayList; + +import static io.sentry.marshaller.json.JsonComparisonUtil.jsonResource; +import static io.sentry.marshaller.json.JsonComparisonUtil.newJsonGenerator; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class DebugMetaInterfaceBindingTest extends BaseTest { + @Tested + private DebugMetaInterfaceBinding debugMetaInterfaceBinding = null; + @Injectable + private DebugMetaInterface mockDebugMetaInterface = null; + + @Test + public void testSimpleDebugImage() throws Exception { + final JsonComparisonUtil.JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); + + final ArrayList images = new ArrayList<>(); + images.add(new DebugMetaInterface.DebugImage("abcd")); + + new NonStrictExpectations() {{ + mockDebugMetaInterface.getDebugImages(); + result = images; + }}; + debugMetaInterfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockDebugMetaInterface); + + assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/Proguard.json"))); + } +} diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/Proguard.json b/sentry/src/test/resources/io/sentry/marshaller/json/Proguard.json new file mode 100644 index 00000000000..a39a08171dd --- /dev/null +++ b/sentry/src/test/resources/io/sentry/marshaller/json/Proguard.json @@ -0,0 +1,10 @@ +{ + "debug_meta": { + "images": [ + { + "uuid": "abcd", + "type": "proguard" + } + ] + } +} From ce3882a0fb92537d50a7405eee37ef5e42f97625 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 13 Jun 2017 10:11:23 -0500 Subject: [PATCH 1625/2152] Checkstyle. --- .../event/interfaces/DebugMetaInterface.java | 44 +++++++++++++++---- .../json/DebugMetaInterfaceBinding.java | 10 +++-- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java b/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java index dba78adae81..f395b32c57b 100644 --- a/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java @@ -6,15 +6,21 @@ * The DebugMeta interface for Sentry allowing to add debug information about ProGuard. */ public class DebugMetaInterface implements SentryInterface { - + /** + * Name of the exception interface in Sentry. + */ public static final String DEBUG_META_INTERFACE = "sentry.interfaces.DebugMeta"; + private ArrayList debugImages = new ArrayList<>(); public ArrayList getDebugImages() { return debugImages; } - ArrayList debugImages = new ArrayList<>(); - + /** + * Adds a single {@link DebugImage} to the interface. + * + * @param debugImage {@link DebugImage} to add. + */ public void addDebugImage(DebugImage debugImage) { debugImages.add(debugImage); } @@ -32,16 +38,36 @@ public int hashCode() { @Override public String toString() { return "DebugMetaInterface{" - + "images=" + debugImages.toString() - + '}'; + + "debugImages=" + debugImages + + '}'; } + /** + * Object that represents a single debug image. + */ public static class DebugImage { + private static final String DEFAULT_TYPE = "proguard"; private final String uuid; - private final String type = "proguard"; + private final String type; + /** + * Construct a Proguard {@link DebugImage} with the provided UUID. + * + * @param uuid UUID of the image. + */ public DebugImage(String uuid) { + this(uuid, DEFAULT_TYPE); + } + + /** + * Construct a {@link DebugImage} with the provided UUID and type. + * + * @param uuid UUID of the image. + * @param type Type of the image. + */ + public DebugImage(String uuid, String type) { this.uuid = uuid; + this.type = type; } public String getUuid() { @@ -55,9 +81,9 @@ public String getType() { @Override public String toString() { return "DebugImage{" - + "uuid='" + uuid+ '\'' - + ", type='" + type+ '\'' - + '}'; + + "uuid='" + uuid + '\'' + + ", type='" + type + '\'' + + '}'; } } } diff --git a/sentry/src/main/java/io/sentry/marshaller/json/DebugMetaInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/DebugMetaInterfaceBinding.java index bc9b4d42480..564340a19c4 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/DebugMetaInterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/DebugMetaInterfaceBinding.java @@ -2,9 +2,13 @@ import com.fasterxml.jackson.core.JsonGenerator; import io.sentry.event.interfaces.DebugMetaInterface; +import io.sentry.event.interfaces.ExceptionInterface; import java.io.IOException; +/** + * Binding that converts a {@link DebugMetaInterface} to a JSON stream. + */ public class DebugMetaInterfaceBinding implements InterfaceBinding { private static final String DEBUG_META = "debug_meta"; private static final String IMAGES = "images"; @@ -22,10 +26,10 @@ public void writeInterface(JsonGenerator generator, DebugMetaInterface debugMeta private void writeDebugImages(JsonGenerator generator, DebugMetaInterface debugMetaInterface) throws IOException { generator.writeArrayFieldStart(IMAGES); - for (int i = 0; i < debugMetaInterface.getDebugImages().size(); i++) { + for (DebugMetaInterface.DebugImage debugImage : debugMetaInterface.getDebugImages()) { generator.writeStartObject(); - generator.writeStringField(UUID, debugMetaInterface.getDebugImages().get(i).getUuid()); - generator.writeStringField(TYPE, debugMetaInterface.getDebugImages().get(i).getType()); + generator.writeStringField(UUID, debugImage.getUuid()); + generator.writeStringField(TYPE, debugImage.getType()); generator.writeEndObject(); } generator.writeEndArray(); From f30ac16ec612d0ecd23aa8b8e5e9606efb1e01f1 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 13 Jun 2017 10:16:36 -0500 Subject: [PATCH 1626/2152] Fix test. --- .../io/sentry/marshaller/json/DebugMetaInterfaceBinding.java | 1 - .../io/sentry/event/interfaces/DebugMetaInterfaceTest.java | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sentry/src/main/java/io/sentry/marshaller/json/DebugMetaInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/DebugMetaInterfaceBinding.java index 564340a19c4..113097d4eb3 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/DebugMetaInterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/DebugMetaInterfaceBinding.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.core.JsonGenerator; import io.sentry.event.interfaces.DebugMetaInterface; -import io.sentry.event.interfaces.ExceptionInterface; import java.io.IOException; diff --git a/sentry/src/test/java/io/sentry/event/interfaces/DebugMetaInterfaceTest.java b/sentry/src/test/java/io/sentry/event/interfaces/DebugMetaInterfaceTest.java index 9341eb14106..c6e58c91210 100644 --- a/sentry/src/test/java/io/sentry/event/interfaces/DebugMetaInterfaceTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/DebugMetaInterfaceTest.java @@ -3,7 +3,8 @@ import io.sentry.BaseTest; import org.testng.annotations.Test; -import java.util.Objects; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; public class DebugMetaInterfaceTest extends BaseTest { @Test @@ -14,6 +15,6 @@ public void testDebugMeta() throws Exception { debugInterface.addDebugImage(image1); debugInterface.addDebugImage(image2); - assert Objects.equals(debugInterface.toString(), "DebugMetaInterface{images=[DebugImage{uuid='abcd-efgh', type='proguard'}, DebugImage{uuid='ijkl-mnop', type='proguard'}]}"); + assertThat(debugInterface.getDebugImages(), contains(image1, image2)); } } From 65a38d9964fe6447caae30ddf173d02d7136816f Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Wed, 14 Jun 2017 13:04:11 +0200 Subject: [PATCH 1627/2152] Fetch proguard uuids from android manifest --- .../helper/AndroidEventBuilderHelper.java | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java index 02c2dabd836..4e6c8fc9ce1 100644 --- a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java +++ b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java @@ -9,15 +9,13 @@ import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; -import android.os.BatteryManager; -import android.os.Build; -import android.os.Environment; -import android.os.StatFs; +import android.os.*; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; import io.sentry.event.EventBuilder; import io.sentry.event.helper.EventBuilderHelper; +import io.sentry.event.interfaces.DebugMetaInterface; import io.sentry.event.interfaces.UserInterface; import java.io.BufferedReader; @@ -71,6 +69,15 @@ public void helpBuildingEvent(EventBuilder eventBuilder) { eventBuilder.withSentryInterface(userInterface, false); } + String[] proGuardsUuids = getProGuardUuids(ctx); + if (proGuardsUuids != null && proGuardsUuids.length > 0) { + DebugMetaInterface debugMetaInterface = new DebugMetaInterface(); + for (int i = 0; i < proGuardsUuids.length; i++) { + debugMetaInterface.addDebugImage(new DebugMetaInterface.DebugImage(proGuardsUuids[i])); + } + eventBuilder.withSentryInterface(debugMetaInterface); + } + eventBuilder.withContexts(getContexts()); } @@ -138,6 +145,20 @@ private Map> getContexts() { return contexts; } + private static String[] getProGuardUuids(Context ctx) { + try { + ApplicationInfo ai = ctx.getPackageManager().getApplicationInfo(ctx.getPackageName(), PackageManager.GET_META_DATA); + Bundle bundle = ai.metaData; + String uuid = bundle.getString("io.sentry.ProguardUuids"); + return uuid.split("|"); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to load meta-data, NameNotFound: " + e.getMessage()); + } catch (NullPointerException e) { + Log.e(TAG, "Failed to load meta-data, NullPointer: " + e.getMessage()); + } + return null; + } + /** * Return the Application's PackageInfo if possible, or null. * From 777e2c958168b8efcf488f63a710ebfc207a2d74 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Wed, 14 Jun 2017 13:26:59 +0200 Subject: [PATCH 1628/2152] Fix checkstyle --- .../android/event/helper/AndroidEventBuilderHelper.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java index 4e6c8fc9ce1..a9b26e93008 100644 --- a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java +++ b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java @@ -147,10 +147,11 @@ private Map> getContexts() { private static String[] getProGuardUuids(Context ctx) { try { - ApplicationInfo ai = ctx.getPackageManager().getApplicationInfo(ctx.getPackageName(), PackageManager.GET_META_DATA); + PackageManager pm = ctx.getPackageManager(); + ApplicationInfo ai = pm.getApplicationInfo(ctx.getPackageName(), PackageManager.GET_META_DATA); Bundle bundle = ai.metaData; String uuid = bundle.getString("io.sentry.ProguardUuids"); - return uuid.split("|"); + return uuid.split("\\|"); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Failed to load meta-data, NameNotFound: " + e.getMessage()); } catch (NullPointerException e) { From 1983ef5c4294cf6f1346588e31c0f3ee024e4cdc Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 14 Jun 2017 08:16:05 -0500 Subject: [PATCH 1629/2152] Return null if proguard info isn't set, some style changes. --- .../helper/AndroidEventBuilderHelper.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java index a9b26e93008..b7d787571ad 100644 --- a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java +++ b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java @@ -17,6 +17,7 @@ import io.sentry.event.helper.EventBuilderHelper; import io.sentry.event.interfaces.DebugMetaInterface; import io.sentry.event.interfaces.UserInterface; +import io.sentry.util.Util; import java.io.BufferedReader; import java.io.File; @@ -72,8 +73,8 @@ public void helpBuildingEvent(EventBuilder eventBuilder) { String[] proGuardsUuids = getProGuardUuids(ctx); if (proGuardsUuids != null && proGuardsUuids.length > 0) { DebugMetaInterface debugMetaInterface = new DebugMetaInterface(); - for (int i = 0; i < proGuardsUuids.length; i++) { - debugMetaInterface.addDebugImage(new DebugMetaInterface.DebugImage(proGuardsUuids[i])); + for (String proGuardsUuid : proGuardsUuids) { + debugMetaInterface.addDebugImage(new DebugMetaInterface.DebugImage(proGuardsUuid)); } eventBuilder.withSentryInterface(debugMetaInterface); } @@ -147,15 +148,19 @@ private Map> getContexts() { private static String[] getProGuardUuids(Context ctx) { try { - PackageManager pm = ctx.getPackageManager(); - ApplicationInfo ai = pm.getApplicationInfo(ctx.getPackageName(), PackageManager.GET_META_DATA); - Bundle bundle = ai.metaData; + PackageManager pkgManager = ctx.getPackageManager(); + ApplicationInfo appInfo = pkgManager.getApplicationInfo( + ctx.getPackageName(), PackageManager.GET_META_DATA); + Bundle bundle = appInfo.metaData; + String uuid = bundle.getString("io.sentry.ProguardUuids"); + if (Util.isNullOrEmpty(uuid)) { + return null; + } + return uuid.split("\\|"); - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "Failed to load meta-data, NameNotFound: " + e.getMessage()); - } catch (NullPointerException e) { - Log.e(TAG, "Failed to load meta-data, NullPointer: " + e.getMessage()); + } catch (Exception e) { + Log.e(TAG, "Error getting Proguard UUIDs.", e); } return null; } From c07606ab5f11931c2471b312f10aaa69b833c9b3 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 15 Jun 2017 13:04:18 -0500 Subject: [PATCH 1630/2152] Add ability to set the culprit from a SentryStackTraceElement. (#413) --- CHANGES | 2 +- .../java/io/sentry/event/EventBuilder.java | 34 ++++++++++++++----- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index cd97dcc8899..c0910b7adb5 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.0.1 ------------- -- +- Add ability to set the culprit from a SentryStackTraceElement. Version 1.0.0 ------------- diff --git a/sentry/src/main/java/io/sentry/event/EventBuilder.java b/sentry/src/main/java/io/sentry/event/EventBuilder.java index a6c1144e81d..150950308a2 100644 --- a/sentry/src/main/java/io/sentry/event/EventBuilder.java +++ b/sentry/src/main/java/io/sentry/event/EventBuilder.java @@ -2,6 +2,7 @@ import io.sentry.environment.SentryEnvironment; import io.sentry.event.interfaces.SentryInterface; +import io.sentry.event.interfaces.SentryStackTraceElement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -218,6 +219,18 @@ public EventBuilder withSdkIntegration(String integration) { return this; } + /** + * Sets the culprit in the event based on a {@link SentryStackTraceElement}. + * + * @param frame stack frame during which the event was captured. + * @return the current {@code EventBuilder} for chained calls. + */ + public EventBuilder withCulprit(SentryStackTraceElement frame) { + return withCulprit(buildCulpritString(frame.getModule(), frame.getFunction(), + frame.getFileName(), frame.getLineno())); + } + + /** * Sets the culprit in the event based on a {@link StackTraceElement}. * @@ -225,21 +238,26 @@ public EventBuilder withSdkIntegration(String integration) { * @return the current {@code EventBuilder} for chained calls. */ public EventBuilder withCulprit(StackTraceElement frame) { + return withCulprit(buildCulpritString(frame.getClassName(), frame.getMethodName(), + frame.getFileName(), frame.getLineNumber())); + } + + private String buildCulpritString(String className, String methodName, String fileName, int lineNumber) { StringBuilder sb = new StringBuilder(); - sb.append(frame.getClassName()) - .append(".") - .append(frame.getMethodName()); + sb.append(className) + .append(".") + .append(methodName); - if (frame.getFileName() != null && !frame.getFileName().isEmpty()) { - sb.append("(").append(frame.getFileName()); - if (frame.getLineNumber() >= 0) { - sb.append(":").append(frame.getLineNumber()); + if (fileName != null && !fileName.isEmpty()) { + sb.append("(").append(fileName); + if (lineNumber >= 0) { + sb.append(":").append(lineNumber); } sb.append(")"); } - return withCulprit(sb.toString()); + return sb.toString(); } /** From 0735e923772f4686b7526490f3eb8fb6c8fa3c24 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 15 Jun 2017 17:56:13 -0500 Subject: [PATCH 1631/2152] Add local variables to stackframes if agent is available. (#401) --- agent/agent.cpp | 12 +-- agent/lib.cpp | 35 ++----- .../SentryAppenderEventBuildingTest.java | 4 +- .../SentryAppenderEventBuildingTest.java | 2 +- .../SentryAppenderEventBuildingTest.java | 4 +- .../event/interfaces/SentryException.java | 5 +- .../interfaces/SentryStackTraceElement.java | 45 ++++++++- .../event/interfaces/StackTraceInterface.java | 23 ++++- .../src/main/java/io/sentry/jvmti/Frame.java | 93 +++++++++++++++++++ .../main/java/io/sentry/jvmti/FrameCache.java | 47 ++++++++++ .../json/StackTraceInterfaceBinding.java | 15 +++ .../jul/SentryHandlerEventBuildingTest.java | 2 +- .../json/StackTraceInterfaceBindingTest.java | 8 +- 13 files changed, 243 insertions(+), 52 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/jvmti/Frame.java create mode 100644 sentry/src/main/java/io/sentry/jvmti/FrameCache.java diff --git a/agent/agent.cpp b/agent/agent.cpp index e9105ea45ad..cbaba70cba3 100644 --- a/agent/agent.cpp +++ b/agent/agent.cpp @@ -21,9 +21,9 @@ static void JNICALL ExceptionCallback(jvmtiEnv *jvmti, JNIEnv *env, jthread thre return; } - char *class_name = (char *) "io/sentry/jvmti/LocalsCache"; - char *method_name = (char *) "setCache"; - char *signature = (char *) "([Lio/sentry/jvmti/Frame;)V"; + char *class_name = (char *) "io/sentry/jvmti/FrameCache"; + char *method_name = (char *) "add"; + char *signature = (char *) "(Ljava/lang/Throwable;[Lio/sentry/jvmti/Frame;)V"; jclass callback_class = nullptr; jmethodID callback_method_id = nullptr; @@ -31,13 +31,13 @@ static void JNICALL ExceptionCallback(jvmtiEnv *jvmti, JNIEnv *env, jthread thre callback_class = env->FindClass(class_name); if (callback_class == nullptr) { env->ExceptionClear(); - log(TRACE, "Unable to locate callback class."); + log(TRACE, "Unable to locate FrameCache class."); return; } callback_method_id = env->GetStaticMethodID(callback_class, method_name, signature); if (callback_method_id == nullptr) { - log(TRACE, "Unable to locate static setCache method."); + log(TRACE, "Unable to locate static FrameCache.add method."); return; } @@ -45,7 +45,7 @@ static void JNICALL ExceptionCallback(jvmtiEnv *jvmti, JNIEnv *env, jthread thre jint start_depth = 0; frames = buildStackTraceFrames(jvmti, env, thread, start_depth); - env->CallStaticVoidMethod(callback_class, callback_method_id, frames); + env->CallStaticVoidMethod(callback_class, callback_method_id, exception, frames); log(TRACE, "ExceptionCallback exit."); } diff --git a/agent/lib.cpp b/agent/lib.cpp index e8725116e74..de7969c3969 100644 --- a/agent/lib.cpp +++ b/agent/lib.cpp @@ -1,6 +1,5 @@ #include #include "lib.h" -#include "jvmti.h" const std::string LEVEL_STRINGS[] = { "TRACE", @@ -43,6 +42,9 @@ static jobject getLocalValue(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint case '[': // Array case 'L': // Object jvmti_error = jvmti->GetLocalObject(thread, depth, table[index].slot, &result); + if (jvmti_error != JVMTI_ERROR_NONE || result == nullptr) { + return nullptr; + } obj_class = env->GetObjectClass(result); to_string_method = env->GetMethodID(obj_class, "toString", "()Ljava/lang/String;"); @@ -148,51 +150,28 @@ static void makeLocalVariable(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, env->SetObjectArrayElement(locals, index, local); } -static jobject makeFrameObject(jvmtiEnv* jvmti, JNIEnv *env, jmethodID method, - jobjectArray locals) { - jvmtiError jvmti_error; - jclass method_class; - jint modifiers; - jobject frame_method; +static jobject makeFrameObject(JNIEnv *env, jobjectArray locals) { jclass frame_class; jmethodID ctor; - jvmti_error = jvmti->GetMethodDeclaringClass(method, &method_class); - if (jvmti_error != JVMTI_ERROR_NONE) { - throwException(env, "java/lang/RuntimeException", "Could not get the declaring class of the method."); - return nullptr; - } - - jvmti_error = jvmti->GetMethodModifiers(method, &modifiers); - if (jvmti_error != JVMTI_ERROR_NONE) { - throwException(env, "java/lang/RuntimeException", "Could not get the modifiers of the method."); - return nullptr; - } - - frame_method = env->ToReflectedMethod(method_class, method, (jboolean) true); - if (frame_method == nullptr) { - return nullptr; // ToReflectedMethod raised an exception - } - frame_class = env->FindClass("io/sentry/jvmti/Frame"); if (frame_class == nullptr) { return nullptr; } ctor = env->GetMethodID(frame_class, "", - "(Ljava/lang/reflect/Method;[Lio/sentry/jvmti/Frame$LocalVariable;)V"); + "([Lio/sentry/jvmti/Frame$LocalVariable;)V"); if (ctor == nullptr) { return nullptr; } - return env->NewObject(frame_class, ctor, frame_method, locals); + return env->NewObject(frame_class, ctor, locals); } static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, jmethodID method, jlocation location) { jvmtiError jvmti_error; jvmtiLocalVariableEntry *local_var_table; - jvmtiLineNumberEntry* lineno_table; jint num_entries; jobject value_ptr; jobjectArray locals; @@ -238,7 +217,7 @@ static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint dep value_ptr = nullptr; } - return makeFrameObject(jvmti, env, method, locals); + return makeFrameObject(env, locals); } jobjectArray buildStackTraceFrames(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java index d862cea3b51..ea84d8a3345 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java @@ -118,7 +118,7 @@ public void testExceptionLogging() throws Exception { SentryException sentryException = exceptionInterface.getExceptions().getFirst(); assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); assertThat(sentryException.getStackTraceInterface().getStackTrace(), - is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace()))); + is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace(), null))); }}; assertNoErrorsInErrorHandler(); } @@ -185,7 +185,7 @@ public void testSourceUsedAsStacktrace(@Injectable final LocationInfo locationIn .get(StackTraceInterface.STACKTRACE_INTERFACE); assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); assertThat(stackTraceInterface.getStackTrace()[0], - is(new SentryStackTraceElement(className, methodName, fileName, line, null, null, null))); + is(new SentryStackTraceElement(className, methodName, fileName, line, null, null, null, null))); }}; assertNoErrorsInErrorHandler(); } diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java index 138bd12e15d..0c9e2f3b9f5 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java @@ -113,7 +113,7 @@ public void testExceptionLogging() throws Exception { SentryException sentryException = exceptionInterface.getExceptions().getFirst(); assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); assertThat(sentryException.getStackTraceInterface().getStackTrace(), - is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace()))); + is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace(), null))); }}; assertNoErrorsInErrorHandler(); } diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java index cadaac06aec..4468154b90f 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java @@ -125,7 +125,7 @@ public void testExceptionLogging() throws Exception { SentryException sentryException = exceptionInterface.getExceptions().getFirst(); assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); assertThat(sentryException.getStackTraceInterface().getStackTrace(), - is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace()))); + is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace(), null))); }}; assertNoErrorsInStatusManager(); } @@ -239,7 +239,7 @@ public void testSourceUsedAsStacktrace() throws Exception { Event event = eventBuilder.build(); StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() .get(StackTraceInterface.STACKTRACE_INTERFACE); - assertThat(stackTraceInterface.getStackTrace(), is(SentryStackTraceElement.fromStackTraceElements(location))); + assertThat(stackTraceInterface.getStackTrace(), is(SentryStackTraceElement.fromStackTraceElements(location, null))); }}; assertNoErrorsInStatusManager(); } diff --git a/sentry/src/main/java/io/sentry/event/interfaces/SentryException.java b/sentry/src/main/java/io/sentry/event/interfaces/SentryException.java index 80626666866..e2eea460a85 100644 --- a/sentry/src/main/java/io/sentry/event/interfaces/SentryException.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/SentryException.java @@ -1,5 +1,7 @@ package io.sentry.event.interfaces; +import io.sentry.jvmti.FrameCache; + import java.io.Serializable; import java.util.ArrayDeque; import java.util.Deque; @@ -33,7 +35,8 @@ public SentryException(Throwable throwable, StackTraceElement[] childExceptionSt this.exceptionClassName = throwable.getClass().getSimpleName(); Package exceptionPackage = throwable.getClass().getPackage(); this.exceptionPackageName = exceptionPackage != null ? exceptionPackage.getName() : null; - this.stackTraceInterface = new StackTraceInterface(throwable.getStackTrace(), childExceptionStackTrace); + this.stackTraceInterface = new StackTraceInterface(throwable.getStackTrace(), childExceptionStackTrace, + FrameCache.get(throwable)); } /** diff --git a/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java b/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java index fe00b903943..f68d9338784 100644 --- a/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java @@ -1,5 +1,8 @@ package io.sentry.event.interfaces; +import io.sentry.jvmti.Frame; + +import java.util.Map; import java.util.Objects; /** @@ -13,6 +16,7 @@ public class SentryStackTraceElement { private final Integer colno; private final String absPath; private final String platform; + private final Map locals; /** * Construct a SentryStackTraceElement. @@ -24,9 +28,11 @@ public class SentryStackTraceElement { * @param colno Column number. * @param absPath Absolute path. * @param platform Platform name. + * @param locals Local variables. */ + // CHECKSTYLE.OFF: ParameterNumber public SentryStackTraceElement(String module, String function, String fileName, int lineno, - Integer colno, String absPath, String platform) { + Integer colno, String absPath, String platform, Map locals) { this.module = module; this.function = function; this.fileName = fileName; @@ -34,7 +40,9 @@ public SentryStackTraceElement(String module, String function, String fileName, this.colno = colno; this.absPath = absPath; this.platform = platform; + this.locals = locals; } + // CHECKSTYLE.ON: ParameterNumber public String getModule() { return module; @@ -64,6 +72,10 @@ public String getPlatform() { return platform; } + public Map getLocals() { + return locals; + } + /** * Convert an array of {@link StackTraceElement}s to {@link SentryStackTraceElement}s. * @@ -71,10 +83,25 @@ public String getPlatform() { * @return Array of {@link SentryStackTraceElement}s. */ public static SentryStackTraceElement[] fromStackTraceElements(StackTraceElement[] stackTraceElements) { + return fromStackTraceElements(stackTraceElements, null); + } + + /** + * Convert an array of {@link StackTraceElement}s to {@link SentryStackTraceElement}s. + * + * @param stackTraceElements Array of {@link StackTraceElement}s to convert. + * @param cachedFrames Array of cached {@link Frame}s (from the Sentry agent) if available, + * or null. + * @return Array of {@link SentryStackTraceElement}s. + */ + public static SentryStackTraceElement[] fromStackTraceElements(StackTraceElement[] stackTraceElements, + Frame[] cachedFrames) { SentryStackTraceElement[] sentryStackTraceElements = new SentryStackTraceElement[stackTraceElements.length]; for (int i = 0; i < stackTraceElements.length; i++) { - sentryStackTraceElements[i] = fromStackTraceElement(stackTraceElements[i]); + sentryStackTraceElements[i] = fromStackTraceElement(stackTraceElements[i], + cachedFrames != null ? cachedFrames[i].getLocals() : null); } + return sentryStackTraceElements; } @@ -85,6 +112,11 @@ public static SentryStackTraceElement[] fromStackTraceElements(StackTraceElement * @return {@link SentryStackTraceElement} */ public static SentryStackTraceElement fromStackTraceElement(StackTraceElement stackTraceElement) { + return fromStackTraceElement(stackTraceElement, null); + } + + private static SentryStackTraceElement fromStackTraceElement(StackTraceElement stackTraceElement, + Map locals) { return new SentryStackTraceElement( stackTraceElement.getClassName(), stackTraceElement.getMethodName(), @@ -92,7 +124,8 @@ public static SentryStackTraceElement fromStackTraceElement(StackTraceElement st stackTraceElement.getLineNumber(), null, null, - null + null, + locals ); } @@ -111,12 +144,13 @@ public boolean equals(Object o) { && Objects.equals(fileName, that.fileName) && Objects.equals(colno, that.colno) && Objects.equals(absPath, that.absPath) - && Objects.equals(platform, that.platform); + && Objects.equals(platform, that.platform) + && Objects.equals(locals, that.locals); } @Override public int hashCode() { - return Objects.hash(module, function, fileName, lineno, colno, absPath, platform); + return Objects.hash(module, function, fileName, lineno, colno, absPath, platform, locals); } @Override @@ -129,6 +163,7 @@ public String toString() { + ", colno=" + colno + ", absPath='" + absPath + '\'' + ", platform='" + platform + '\'' + + ", locals='" + locals + '\'' + '}'; } } diff --git a/sentry/src/main/java/io/sentry/event/interfaces/StackTraceInterface.java b/sentry/src/main/java/io/sentry/event/interfaces/StackTraceInterface.java index 84f92adcb6a..a079fd75ea3 100644 --- a/sentry/src/main/java/io/sentry/event/interfaces/StackTraceInterface.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/StackTraceInterface.java @@ -1,5 +1,7 @@ package io.sentry.event.interfaces; +import io.sentry.jvmti.Frame; + import java.util.Arrays; /** @@ -19,7 +21,7 @@ public class StackTraceInterface implements SentryInterface { * @param stackTrace StackTrace to provide to Sentry. */ public StackTraceInterface(StackTraceElement[] stackTrace) { - this(stackTrace, new StackTraceElement[0]); + this(stackTrace, new StackTraceElement[0], null); } /** @@ -33,7 +35,24 @@ public StackTraceInterface(StackTraceElement[] stackTrace) { * are in common. */ public StackTraceInterface(StackTraceElement[] stackTrace, StackTraceElement[] enclosingStackTrace) { - this.stackTrace = SentryStackTraceElement.fromStackTraceElements(stackTrace); + this(stackTrace, enclosingStackTrace, null); + } + + /** + * Creates a StackTrace for an {@link io.sentry.event.Event}. + *

    + * With the help of the enclosing StackTrace, figure out which frames are in common with the parent exception + * to potentially hide them later in Sentry. + * + * @param stackTrace StackTrace to provide to Sentry. + * @param enclosingStackTrace StackTrace of the enclosing exception, to determine how many Stack frames + * are in common. + * @param cachedFrames Array of cached {@link Frame}s (from the Sentry agent) if available, + * or null. + */ + public StackTraceInterface(StackTraceElement[] stackTrace, StackTraceElement[] enclosingStackTrace, + Frame[] cachedFrames) { + this.stackTrace = SentryStackTraceElement.fromStackTraceElements(stackTrace, cachedFrames); int m = stackTrace.length - 1; int n = enclosingStackTrace.length - 1; diff --git a/sentry/src/main/java/io/sentry/jvmti/Frame.java b/sentry/src/main/java/io/sentry/jvmti/Frame.java new file mode 100644 index 00000000000..cbaf426cad2 --- /dev/null +++ b/sentry/src/main/java/io/sentry/jvmti/Frame.java @@ -0,0 +1,93 @@ +package io.sentry.jvmti; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Class representing a single call frame. + */ +public final class Frame { + /** + * Local variable information for this frame. + */ + private final LocalVariable[] locals; + + /** + * Construct a {@link Frame}. + * + * @param locals Local variable information for this frame. + */ + public Frame(LocalVariable[] locals) { + this.locals = locals; + } + + /** + * Converts the locals array to a Map of variable-name -> variable-value. + * + * @return Map of variable-name -> variable-value. + */ + public Map getLocals() { + if (locals == null || locals.length == 0) { + return Collections.emptyMap(); + } + + Map localsMap = new HashMap<>(); + for (Frame.LocalVariable localVariable : locals) { + if (localVariable != null) { + localsMap.put(localVariable.getName(), localVariable.getValue()); + } + } + + return localsMap; + } + + @Override + public String toString() { + return "Frame{" + + ", locals=" + Arrays.toString(locals) + + '}'; + } + + /** + * Class representing a single local variable. + */ + public static final class LocalVariable { + /** + * Variable name. + */ + final String name; + /** + * Variable value. + */ + final Object value; + + /** + * Construct a {@link LocalVariable} for a live object. + * + * @param name Variable name. + * @param value Variable value. + */ + private LocalVariable(String name, Object value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public Object getValue() { + return value; + } + + @Override + public String toString() { + return "LocalVariable{" + + "name='" + name + '\'' + + ", value=" + value + + '}'; + } + } +} diff --git a/sentry/src/main/java/io/sentry/jvmti/FrameCache.java b/sentry/src/main/java/io/sentry/jvmti/FrameCache.java new file mode 100644 index 00000000000..e416fd60a2f --- /dev/null +++ b/sentry/src/main/java/io/sentry/jvmti/FrameCache.java @@ -0,0 +1,47 @@ +package io.sentry.jvmti; + +import java.util.Map; +import java.util.WeakHashMap; + +/** + * Utility class used by the Sentry Java Agent to store per-frame local variable + * information for the last thrown exception. + */ +public final class FrameCache { + private static ThreadLocal> result = + new ThreadLocal>() { + @Override + protected WeakHashMap initialValue() { + return new WeakHashMap<>(); + } + }; + + /** + * Utility class, no public ctor. + */ + private FrameCache() { + + } + + /** + * Store the per-frame local variable information for the last exception thrown on this thread. + * + * @param throwable Throwable that the provided {@link Frame}s represent. + * @param frames Array of {@link Frame}s to store + */ + public static void add(Throwable throwable, Frame[] frames) { + Map weakMap = result.get(); + weakMap.put(throwable, frames); + } + + /** + * Retrieve the per-frame local variable information for the last exception thrown on this thread. + * + * @param throwable Throwable to look up cached {@link Frame}s for. + * @return Array of {@link Frame}s + */ + public static Frame[] get(Throwable throwable) { + Map weakMap = result.get(); + return weakMap.get(throwable); + } +} diff --git a/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java index 96b799a3e13..4b19033b364 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.util.Collection; import java.util.Collections; +import java.util.Map; /** * Binding allowing to convert a {@link StackTraceInterface} into a JSON stream. @@ -56,6 +57,20 @@ private void writeFrame(JsonGenerator generator, SentryStackTraceElement stackTr generator.writeStringField(ABSOLUTE_PATH_PARAMETER, stackTraceElement.getAbsPath()); } + if (stackTraceElement.getLocals() != null && !stackTraceElement.getLocals().isEmpty()) { + generator.writeObjectFieldStart(VARIABLES_PARAMETER); + for (Map.Entry varEntry : stackTraceElement.getLocals().entrySet()) { + String name = varEntry.getKey(); + Object value = varEntry.getValue(); + if (value == null) { + generator.writeNullField(name); + } else { + generator.writeObjectField(name, value); + } + } + generator.writeEndObject(); + } + generator.writeEndObject(); } diff --git a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java index c6b7088f7a7..359fc6806c3 100644 --- a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java +++ b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java @@ -115,7 +115,7 @@ public void testExceptionLogging() throws Exception { final SentryException sentryException = exceptionInterface.getExceptions().getFirst(); assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); assertThat(sentryException.getStackTraceInterface().getStackTrace(), - is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace()))); + is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace(), null))); }}; assertNoErrorsInErrorManager(); } diff --git a/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java index 245334b9223..ea466960401 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java @@ -23,7 +23,7 @@ public void testSingleSentryStackFrame() throws Exception { final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final SentryStackTraceElement sentryStackTraceElement = new SentryStackTraceElement( "", "throwError", "index.js", 100, 10, - "http://localhost","javascript"); + "http://localhost","javascript", null); new NonStrictExpectations() {{ mockStackTraceInterface.getStackTrace(); result = new SentryStackTraceElement[]{sentryStackTraceElement}; @@ -40,7 +40,7 @@ public void testSingleStackFrame() throws Exception { final String className = "31b26f01-9b97-442b-9f36-8a317f94ad76"; final int lineNumber = 1; final SentryStackTraceElement stackTraceElement = new SentryStackTraceElement(className, methodName, - "File.java", lineNumber, null, null, null); + "File.java", lineNumber, null, null, null, null); new NonStrictExpectations() {{ mockStackTraceInterface.getStackTrace(); result = new SentryStackTraceElement[]{stackTraceElement}; @@ -55,7 +55,7 @@ public void testSingleStackFrame() throws Exception { public void testFramesCommonWithEnclosing() throws Exception { final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final SentryStackTraceElement stackTraceElement = new SentryStackTraceElement("", "", - "File.java", 0, null, null, null); + "File.java", 0, null, null, null, null); new NonStrictExpectations() {{ mockStackTraceInterface.getStackTrace(); result = new SentryStackTraceElement[]{stackTraceElement, stackTraceElement}; @@ -73,7 +73,7 @@ public void testFramesCommonWithEnclosing() throws Exception { public void testFramesCommonWithEnclosingDisabled() throws Exception { final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final SentryStackTraceElement stackTraceElement = new SentryStackTraceElement("", "", - "File.java", 0, null, null, null); + "File.java", 0, null, null, null, null); new NonStrictExpectations() {{ mockStackTraceInterface.getStackTrace(); result = new SentryStackTraceElement[]{stackTraceElement, stackTraceElement}; From af3c69527de4de051fe5d415c470c2e7a89e0bd4 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 16 Jun 2017 09:44:51 -0500 Subject: [PATCH 1632/2152] Fix CHANGES and Makefile. --- CHANGES | 3 ++- Makefile | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index c0910b7adb5..e329ff483f8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,8 @@ -Version 1.0.1 +Version 1.1.0 ------------- - Add ability to set the culprit from a SentryStackTraceElement. +- (alpha) Add agent to collect local variable informatino when an exception occurs. Version 1.0.0 ------------- diff --git a/Makefile b/Makefile index 68e9b0038ec..1f0220692d5 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,8 @@ prepareDocs: @echo This release: $(RELEASE_VERSION) # Fix released version in documentation @echo Fixing documentation versions - find . \( -name '*.md' -or -name '*.rst' \) -exec $(SED) -i -e 's/$(PREVIOUS_RELEASE)/$(RELEASE_VERSION)/g' {} \; + $(eval PREVIOUS_ESCAPED=$(shell echo $(PREVIOUS_RELEASE) | $(SED) -e 's/\./\\\./g')) + find . \( -name '*.md' -or -name '*.rst' \) -exec $(SED) -i -e 's/$(PREVIOUS_ESCAPED)/$(RELEASE_VERSION)/g' {} \; # Commit documentation changes @echo Committing documentation version changes git commit -a -m 'Bump docs to $(RELEASE_VERSION)' @@ -58,6 +59,9 @@ prepareChanges: git add CHANGES git commit -m "Bump CHANGES to $(DEV_VERSION)" +change-version: + $(MVN) release:update-versions + # Prepare is broken into stages because otherwise `make` will run things out of order prepare: prepareDocs prepareMvn prepareChanges From 0d4da257a6033239449e47f000252c6a7a024799 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 16 Jun 2017 09:48:34 -0500 Subject: [PATCH 1633/2152] Change development version to 1.1.0-SNAPSHOT. --- pom.xml | 2 +- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry/pom.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 3318ce36d49..56a3ff842ed 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.0.1-SNAPSHOT + 1.1.0-SNAPSHOT pom Sentry-Java diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 1d3079e01dd..3ec29d48fb3 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.0.1-SNAPSHOT + 1.1.0-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 29facb34e81..d6e7bb0f74a 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.0.1-SNAPSHOT + 1.1.0-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index b4ae08af309..d857eb34e4d 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.0.1-SNAPSHOT + 1.1.0-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 85304583a03..41c7f7eb02c 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.0.1-SNAPSHOT + 1.1.0-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 12cf2c8bb6c..7d944cb4e28 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.0.1-SNAPSHOT + 1.1.0-SNAPSHOT sentry-logback diff --git a/sentry/pom.xml b/sentry/pom.xml index fd1ac6d5514..4b200d5b602 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.0.1-SNAPSHOT + 1.1.0-SNAPSHOT sentry From 023d22838c30c04cfccb78f2111f592516d433c2 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 16 Jun 2017 09:48:38 -0500 Subject: [PATCH 1634/2152] Bump docs to 1.1.0 --- docs/config.rst | 4 ++-- docs/modules/android.rst | 6 +++--- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 7eaeb940c43..5e62eeb036b 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -139,7 +139,7 @@ To set the application version that will be sent with each event, use the :: - release=1.0.0 + release=1.1.0 Distribution ```````````` @@ -149,7 +149,7 @@ To set the application distribution that will be sent with each event, use the :: - release=1.0.0 + release=1.1.0 dist=x86 Note that the distribution is only useful (and used) if the ``release`` is also diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 8262147998f..bb45623fdb0 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.0.0`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.1.0`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.0.0' + compile 'io.sentry:sentry-android:1.1.0' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 491c009b3c1..9cc4fbe2617 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.0.0 + 1.1.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.0.0' + compile 'io.sentry:sentry-appengine:1.1.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.0.0" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.1.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 7d77233e00c..7a4c34bbec8 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -24,22 +24,22 @@ Using Maven: io.sentry sentry - 1.0.0 + 1.1.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.0.0' + compile 'io.sentry:sentry:1.1.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.0.0" + libraryDependencies += "io.sentry" % "sentry" % "1.1.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index f65c609a9f7..98fc3ad91f6 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -24,22 +24,22 @@ Using Maven: io.sentry sentry-log4j - 1.0.0 + 1.1.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.0.0' + compile 'io.sentry:sentry-log4j:1.1.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.0.0" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.1.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index bdef1bfd146..373eab16f08 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -24,22 +24,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.0.0 + 1.1.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.0.0' + compile 'io.sentry:sentry-log4j2:1.1.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.0.0" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.1.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 5c45d37a9fc..686e8253e7a 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -24,22 +24,22 @@ Using Maven: io.sentry sentry-logback - 1.0.0 + 1.1.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.0.0' + compile 'io.sentry:sentry-logback:1.1.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.0.0" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.1.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index b057e67651d..d98c04a9171 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -15,22 +15,22 @@ Using Maven: io.sentry sentry - 1.0.0 + 1.1.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.0.0' + compile 'io.sentry:sentry:1.1.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.0.0" + libraryDependencies += "io.sentry" % "sentry" % "1.1.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From df0321a0a51d04eafa2306eaace272dbc5f582b2 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 16 Jun 2017 09:49:56 -0500 Subject: [PATCH 1635/2152] [maven-release-plugin] prepare release v1.1.0 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 56a3ff842ed..d7c8a94bbbe 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.1.0-SNAPSHOT + 1.1.0 pom Sentry-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.1.0 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 3ec29d48fb3..2b286dec5f1 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.1.0-SNAPSHOT + 1.1.0 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index d6e7bb0f74a..5ba8b637e03 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.1.0-SNAPSHOT + 1.1.0 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index d857eb34e4d..945041fa73a 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.1.0-SNAPSHOT + 1.1.0 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 41c7f7eb02c..a8341534d49 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.1.0-SNAPSHOT + 1.1.0 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 7d944cb4e28..fb97b21c08d 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.1.0-SNAPSHOT + 1.1.0 sentry-logback diff --git a/sentry/pom.xml b/sentry/pom.xml index 4b200d5b602..c1941b9f854 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.1.0-SNAPSHOT + 1.1.0 sentry From 247528bcdb3e842b54736931bbbbf014ac979256 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 16 Jun 2017 09:49:56 -0500 Subject: [PATCH 1636/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index d7c8a94bbbe..1da49633c30 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.1.0 + 1.1.1-SNAPSHOT pom Sentry-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.1.0 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 2b286dec5f1..039cef51441 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.1.0 + 1.1.1-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 5ba8b637e03..76fe32c8c19 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.1.0 + 1.1.1-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 945041fa73a..1003930b747 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.1.0 + 1.1.1-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index a8341534d49..4070a377c36 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.1.0 + 1.1.1-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index fb97b21c08d..c69fca15637 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.1.0 + 1.1.1-SNAPSHOT sentry-logback diff --git a/sentry/pom.xml b/sentry/pom.xml index c1941b9f854..0715b93e307 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.1.0 + 1.1.1-SNAPSHOT sentry From ca2cb8cf9bd9d4fb9187d6db567dd788c012b86d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 16 Jun 2017 09:49:59 -0500 Subject: [PATCH 1637/2152] Bump CHANGES to 1.1.1 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index e329ff483f8..d3247fa7781 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.1.1 +------------- + +- + Version 1.1.0 ------------- From e25160dfa17408fb2c07a2321772bf0ad9e7fc0d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 16 Jun 2017 09:59:47 -0500 Subject: [PATCH 1638/2152] Fix docs. --- docs/config.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 5e62eeb036b..0d8c372b2c5 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -139,7 +139,7 @@ To set the application version that will be sent with each event, use the :: - release=1.1.0 + release=1.0.0 Distribution ```````````` @@ -149,7 +149,7 @@ To set the application distribution that will be sent with each event, use the :: - release=1.1.0 + release=1.0.0 dist=x86 Note that the distribution is only useful (and used) if the ``release`` is also @@ -525,4 +525,4 @@ To use your custom ``SentryClientFactory`` implementation, use the ``factory`` o factory=my.company.SentryClientFactory Your factory class will need to be available on your classpath with a zero argument constructor -or an error will be thrown. \ No newline at end of file +or an error will be thrown. From 578a255885dc839059488a0a86fbfbb1fce269d2 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 19 Jun 2017 10:57:10 -0500 Subject: [PATCH 1639/2152] Add back the old SentryStackTraceElement ctor. --- .../interfaces/SentryStackTraceElement.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java b/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java index f68d9338784..c46fa261fba 100644 --- a/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java @@ -18,6 +18,24 @@ public class SentryStackTraceElement { private final String platform; private final Map locals; + /** + * Construct a SentryStackTraceElement. + * + * @param module Module (class) name. + * @param function Function (method) name. + * @param fileName Filename. + * @param lineno Line number. + * @param colno Column number. + * @param absPath Absolute path. + * @param platform Platform name. + */ + // CHECKSTYLE.OFF: ParameterNumber + public SentryStackTraceElement(String module, String function, String fileName, int lineno, + Integer colno, String absPath, String platform) { + this(module, function, fileName, lineno, colno, absPath, platform, null); + } + // CHECKSTYLE.ON: ParameterNumber + /** * Construct a SentryStackTraceElement. * From d446f6b9cb1e23adb4a4e370152ddc9bdcf33ee4 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 19 Jun 2017 13:16:31 -0500 Subject: [PATCH 1640/2152] Allow overriding the location of the properties file. (#419) --- CHANGES | 2 +- docs/config.rst | 19 +++++----- .../java/io/sentry/SentryClientFactory.java | 15 ++++---- .../main/java/io/sentry/config/Lookup.java | 35 ++++++++++++++++--- 4 files changed, 51 insertions(+), 20 deletions(-) diff --git a/CHANGES b/CHANGES index d3247fa7781..adba779499d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.1.1 ------------- -- +- Allow overriding the location of the properties file. Version 1.1.0 ------------- diff --git a/docs/config.rst b/docs/config.rst index 0d8c372b2c5..53a57ec9f39 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -15,11 +15,11 @@ Setting the DSN (Data Source Name) ---------------------------------- The DSN is the first and most important thing to configure because it tells the SDK where -to send events. You can find a basic DSN in the "Client Keys" section of your "Project Settings" +to send events. You can find your project's DSN in the "Client Keys" section of your "Project Settings" in Sentry. It can be configured in multiple ways. Explanations of the :ref:`configuration methods are detailed below `. -In your ``sentry.properties``: +In a properties file on your filesystem or classpath (defaults to ``sentry.properties``): .. sourcecode:: properties @@ -53,18 +53,19 @@ Configuration methods Configuration via properties file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The Java SDK can be configured via a ``sentry.properties`` file placed at the root of -your classpath, which is typically achieved by adding a ``src/main/resources/sentry.properties`` file -to your project. This file follows the standard `.properties file format `_ -and thus contains one option per line. +The Java SDK can be configured via a `.properties file `_ +that is located on the filesystem or on your application's classpath. By default the SDK will look for +a ``sentry.properties`` file in the current directory or in the root of your classpath. You can override +the location of the properties file by using either the ``sentry.properties.file`` Java System Property +or the ``SENTRY_PROPERTIES_FILE`` System Environment Variable. -Because this file is bundled with your application, the values cannot be changed easily at -runtime. For this reason, the properties file is useful for setting defaults or options +Because this file is often bundled with your application, the values cannot be changed easily once your +application has been packaged. For this reason, the properties file is useful for setting defaults or options that you don't expect to change often. The properties file is the last place checked for each option value, so runtime configuration (described below) will override it if available. Option names in the property file exactly match the examples given below. For example, to enable -sampling, in your ``sentry.properties`` file: +sampling, in your properties file: .. sourcecode:: properties diff --git a/sentry/src/main/java/io/sentry/SentryClientFactory.java b/sentry/src/main/java/io/sentry/SentryClientFactory.java index b393006f580..c187313bd2b 100644 --- a/sentry/src/main/java/io/sentry/SentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/SentryClientFactory.java @@ -56,13 +56,16 @@ public static SentryClient sentryClient(String dsn, SentryClientFactory sentryCl } private static Dsn resolveDsn(String dsn) { - Dsn realDsn; - if (!Util.isNullOrEmpty(dsn)) { - realDsn = new Dsn(dsn); - } else { - realDsn = new Dsn(Dsn.dsnLookup()); + try { + if (Util.isNullOrEmpty(dsn)) { + dsn = Dsn.dsnLookup(); + } + + return new Dsn(dsn); + } catch (Exception e) { + logger.error("Error creating valid DSN from: '{}'.", dsn, e); + throw e; } - return realDsn; } /** diff --git a/sentry/src/main/java/io/sentry/config/Lookup.java b/sentry/src/main/java/io/sentry/config/Lookup.java index 9d8f0eb4362..0215b1cb53f 100644 --- a/sentry/src/main/java/io/sentry/config/Lookup.java +++ b/sentry/src/main/java/io/sentry/config/Lookup.java @@ -4,6 +4,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.InputStream; import java.util.Properties; @@ -24,18 +27,18 @@ public final class Lookup { private static Properties configProps; static { + String filePath = getConfigFilePath(); try { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - InputStream input = classLoader.getResourceAsStream(CONFIG_FILE_NAME); + InputStream input = getInputStream(filePath); if (input != null) { configProps = new Properties(); configProps.load(input); } else { - logger.debug("Sentry configuration file '{}' not found.", CONFIG_FILE_NAME); + logger.debug("Sentry configuration file not found in filesystem or classpath: '{}'.", filePath); } } catch (Exception e) { - logger.error("Error loading Sentry configuration file '{}': ", CONFIG_FILE_NAME, e); + logger.error("Error loading Sentry configuration file '{}': ", filePath, e); } } @@ -46,6 +49,30 @@ private Lookup() { } + private static String getConfigFilePath() { + String filePath = System.getProperty("sentry.properties.file"); + + if (filePath == null) { + filePath = System.getenv("SENTRY_PROPERTIES_FILE"); + } + + if (filePath == null) { + filePath = CONFIG_FILE_NAME; + } + + return filePath; + } + + private static InputStream getInputStream(String filePath) throws FileNotFoundException { + File file = new File(filePath); + if (file.isFile() && file.canRead()) { + return new FileInputStream(file); + } + + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + return classLoader.getResourceAsStream(filePath); + } + /** * Attempt to lookup a configuration key, without checking any DSN options. * From 33d48b605d00ba1aa81490091ca0727170fa3fdb Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 20 Jun 2017 16:50:36 +0200 Subject: [PATCH 1641/2152] Proguard support for Gradle (#418) * Added groovy plugin for proguard * Added proguard support for sentry-java --- CHANGES | 1 + gradle/.gitignore | 4 + gradle/build.gradle | 38 ++++ gradle/download-sentry-cli.sh | 18 ++ gradle/gradle.properties | 2 + gradle/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + gradle/gradlew | 164 ++++++++++++++ .../sentry/android/gradle/SentryPlugin.groovy | 200 ++++++++++++++++++ .../gradle/SentryPluginExtension.groovy | 5 + .../gradle/SentryProguardConfigTask.groovy | 29 +++ .../io.sentry.android.gradle.properties | 1 + gradle/src/main/resources/bin/.gitignore | 1 + .../helper/AndroidEventBuilderHelper.java | 36 ++-- .../io/sentry/DefaultSentryClientFactory.java | 1 + .../event/interfaces/DebugMetaInterface.java | 2 +- .../json/DebugMetaInterfaceBinding.java | 3 - .../io/sentry/marshaller/json/Proguard.json | 14 +- 18 files changed, 498 insertions(+), 27 deletions(-) create mode 100644 gradle/.gitignore create mode 100644 gradle/build.gradle create mode 100755 gradle/download-sentry-cli.sh create mode 100644 gradle/gradle.properties create mode 100644 gradle/gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/gradle/wrapper/gradle-wrapper.properties create mode 100755 gradle/gradlew create mode 100644 gradle/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy create mode 100644 gradle/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy create mode 100644 gradle/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy create mode 100644 gradle/src/main/resources/META-INF/gradle-plugins/io.sentry.android.gradle.properties create mode 100644 gradle/src/main/resources/bin/.gitignore diff --git a/CHANGES b/CHANGES index adba779499d..297a905046c 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 1.1.1 ------------- - Allow overriding the location of the properties file. +- Add support for handling and uploading Proguard files to Sentry (for Android applications). Version 1.1.0 ------------- diff --git a/gradle/.gitignore b/gradle/.gitignore new file mode 100644 index 00000000000..26cc88223fb --- /dev/null +++ b/gradle/.gitignore @@ -0,0 +1,4 @@ +.gradle/ +.idea/ +build/ +out/ diff --git a/gradle/build.gradle b/gradle/build.gradle new file mode 100644 index 00000000000..e192b545325 --- /dev/null +++ b/gradle/build.gradle @@ -0,0 +1,38 @@ +plugins { + id 'groovy' + id 'com.bmuschko.nexus' version '2.3.1' +} +apply plugin: 'maven-publish' + + +repositories { + jcenter() +} + +compileGroovy { + sourceCompatibility = '1.6' + targetCompatibility = '1.6' +} + +dependencies { + compile gradleApi() + compile localGroovy() + compile 'com.android.tools.build:gradle:2.2.0' +} + +publishing { + publications { + Publication(MavenPublication) { + artifact jar + groupId 'io.sentry' + artifactId 'sentry-android-gradle-plugin' + version project.version + } + } + + repositories { + maven { + url "$buildDir/repo" + } + } +} diff --git a/gradle/download-sentry-cli.sh b/gradle/download-sentry-cli.sh new file mode 100755 index 00000000000..4855b597bad --- /dev/null +++ b/gradle/download-sentry-cli.sh @@ -0,0 +1,18 @@ +#!/bin/bash +cd $(dirname "$0") +REPO=getsentry/sentry-cli +VERSION=1.14.0 +PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" + +rm -f src/main/resources/bin/sentry-cli-* +for plat in $PLATFORMS; do + suffix='' + if [[ $plat == *"Windows"* ]]; then + suffix='.exe' + fi + echo "${plat}" + download_url=https://github.com/$REPO/releases/download/$VERSION/sentry-cli-${plat}${suffix} + fn="src/main/resources/bin/sentry-cli-${plat}${suffix}" + curl -SL --progress-bar "$download_url" -o "$fn" + chmod +x "$fn" +done diff --git a/gradle/gradle.properties b/gradle/gradle.properties new file mode 100644 index 00000000000..a438c32a572 --- /dev/null +++ b/gradle/gradle.properties @@ -0,0 +1,2 @@ +group = io.sentry +version = 1.0.0 diff --git a/gradle/gradle/wrapper/gradle-wrapper.jar b/gradle/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..8c0fb64a8698b08ecc4158d828ca593c4928e9dd GIT binary patch literal 49896 zcmagFb986H(k`5d^NVfUwr$(C?M#x1ZQHiZiEVpg+jrjgoQrerx!>1o_ul)D>ebz~ zs=Mmxr&>W81QY-S1PKWQ%N-;H^tS;2*XwVA`dej1RRn1z<;3VgfE4~kaG`A%QSPsR z#ovnZe+tS9%1MfeDyz`RirvdjPRK~p(#^q2(^5@O&NM19EHdvN-A&StN>0g6QA^VN z0Gx%Gq#PD$QMRFzmK+utjS^Y1F0e8&u&^=w5K<;4Rz|i3A=o|IKLY+g`iK6vfr9?+ z-`>gmU&i?FGSL5&F?TXFu`&Js6h;15QFkXp2M1H9|Eq~bpov-GU(uz%mH0n55wUl- zv#~ccAz`F5wlQ>e_KlJS3@{)B?^v*EQM=IxLa&76^y51a((wq|2-`qON>+4dLc{Oo z51}}o^Zen(oAjxDK7b++9_Yg`67p$bPo3~BCpGM7uAWmvIhWc5Gi+gQZ|Pwa-Gll@<1xmcPy z|NZmu6m)g5Ftu~BG&Xdxclw7Cij{xbBMBn-LMII#Slp`AElb&2^Hw+w>(3crLH!;I zN+Vk$D+wP1#^!MDCiad@vM>H#6+`Ct#~6VHL4lzmy;lSdk>`z6)=>Wh15Q2)dQtGqvn0vJU@+(B5{MUc*qs4!T+V=q=wy)<6$~ z!G>e_4dN@lGeF_$q9`Ju6Ncb*x?O7=l{anm7Eahuj_6lA{*#Gv*TaJclevPVbbVYu z(NY?5q+xxbO6%g1xF0r@Ix8fJ~u)VRUp`S%&rN$&e!Od`~s+64J z5*)*WSi*i{k%JjMSIN#X;jC{HG$-^iX+5f5BGOIHWAl*%15Z#!xntpk($-EGKCzKa zT7{siZ9;4TICsWQ$pu&wKZQTCvpI$Xvzwxoi+XkkpeE&&kFb!B?h2hi%^YlXt|-@5 zHJ~%AN!g_^tmn1?HSm^|gCE#!GRtK2(L{9pL#hp0xh zME}|DB>(5)`iE7CM)&_+S}-Bslc#@B5W4_+k4Cp$l>iVyg$KP>CN?SVGZ(&02>iZK zB<^HP$g$Lq*L$BWd?2(F?-MUbNWTJVQdW7$#8a|k_30#vHAD1Z{c#p;bETk0VnU5A zBgLe2HFJ3032$G<`m*OB!KM$*sdM20jm)It5OSru@tXpK5LT>#8)N!*skNu1$TpIw zufjjdp#lyH5bZ%|Iuo|iu9vG1HrIVWLH>278xo>aVBkPN3V$~!=KnlXQ4eDqS7%E% zQ!z^$Q$b^6Q)g#cLpwur(|<0gWHo6A6jc;n`t(V9T;LzTAU{IAu*uEQ%Ort1k+Kn+f_N`9|bxYC+~Z1 zCC1UCWv*Orx$_@ydv9mIe(liLfOr7mhbV@tKw{6)q^1DH1nmvZ0cj215R<~&I<4S| zgnr;9Cdjqpz#o8i0CQjtl`}{c*P)aSdH|abxGdrR)-3z+02-eX(k*B)Uqv6~^nh** z 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradle/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/gradle/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy new file mode 100644 index 00000000000..d547be57874 --- /dev/null +++ b/gradle/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -0,0 +1,200 @@ +package io.sentry.android.gradle + +import com.android.build.gradle.AppPlugin +import com.android.build.gradle.api.ApplicationVariant +import org.apache.commons.compress.utils.IOUtils +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.Task; +import org.gradle.api.tasks.Exec +import org.apache.tools.ant.taskdefs.condition.Os + +class SentryPlugin implements Plugin { + static final String GROUP_NAME = 'Sentry' + + /** + * Return the correct sentry-cli executable path to use for the given project. This + * will look for a sentry-cli executable in a local node_modules in case it was put + * there by sentry-react-native or others before falling back to the global installation. + * + * @param project + * @return + */ + static String getSentryCli(Project project) { + // if a path is provided explicitly use that first + def propertiesFile = "${project.rootDir.toPath()}/sentry.properties" + Properties sentryProps = new Properties() + try { + sentryProps.load(new FileInputStream(propertiesFile)) + } catch (FileNotFoundException e) { + // it's okay, we can ignore it. + } + + def rv = sentryProps.getProperty("cli.executable"); + if (rv != null) { + return rv + } + + // in case there is a version from npm right around the corner use that one. This + // is the case for react-native-sentry for instance + def exePath = "${project.rootDir.toPath()}/../node_modules/sentry-cli-binary/bin/sentry-cli" + if ((new File(exePath)).exists()) { + return exePath; + } + if ((new File(exePath + ".exe")).exists()) { + return exePath + ".exe"; + } + + // next up try a packaged version of sentry-cli + def cliSuffix + def osName = System.getProperty("os.name").toLowerCase() + if (osName.indexOf("mac") >= 0) { + cliSuffix = "Darwin-x86_64" + } else if (osName.indexOf("linux") >= 0) { + cliSuffix = "Linux-" + System.getProperty("os.arch") + } else if (osName.indexOf("win") >= 0) { + cliSuffix = "Windows-i686.exe" + } + + if (cliSuffix != null) { + def resPath = "/bin/sentry-cli-${cliSuffix}"; + def fsPath = SentryPlugin.class.getResource(resPath).getFile() + + // if we are not in a jar, we can use the file directly + if ((new File(fsPath)).exists()) { + return fsPath; + } + + // otherwise we need to unpack into a file + def resStream = SentryPlugin.class.getResourceAsStream(resPath) + File tempFile = File.createTempFile(".sentry-cli", ".exe") + tempFile.deleteOnExit() + def out = new FileOutputStream(tempFile); + try { + IOUtils.copy(resStream, out) + } finally { + out.close() + } + tempFile.setExecutable(true) + return tempFile.getAbsolutePath() + } + + return "sentry-cli"; + } + + /** + * Returns the proguard task for the given project and variant. + * + * @param project + * @param variant + * @return + */ + static Task getProguardTask(Project project, ApplicationVariant variant) { + def name = variant.name.capitalize() + def rv = project.tasks.findByName("transformClassesAndResourcesWithProguardFor${name}") + if (rv != null) { + return rv + } + return project.tasks.findByName("proguard${name}") + } + + /** + * Returns the dex task for the given project and variant. + * + * @param project + * @param variant + * @return + */ + static Task getDexTask(Project project, ApplicationVariant variant) { + def name = variant.name.capitalize() + def rv = project.tasks.findByName("transformClassesWithDexFor${name}") + if (rv != null) { + return rv + } + return project.tasks.findByName("dex${name}") + } + + /** + * Returns the path to the debug meta properties file for the given variant. + * + * @param project + * @param variant + * @return + */ + static String getDebugMetaPropPath(Project project, ApplicationVariant variant) { + return "${project.rootDir.toPath()}/app/build/intermediates/assets/${variant.dirName}/sentry-debug-meta.properties" + } + + void apply(Project project) { + project.extensions.create("sentry", SentryPluginExtension) + + project.afterEvaluate { + if(!project.plugins.hasPlugin(AppPlugin)) { + throw new IllegalStateException('Must apply \'com.android.application\' first!') + } + + project.android.applicationVariants.all { ApplicationVariant variant -> + def variantOutput = variant.outputs.first() + def manifestPath = variantOutput.processManifest.manifestOutputFile + def mappingFile = variant.getMappingFile() + def proguardTask = getProguardTask(project, variant) + def dexTask = getDexTask(project, variant) + + if (proguardTask == null) { + return; + } + + // create a task to configure proguard automatically unless the user disabled it. + if (project.sentry.autoProguardConfig) { + SentryProguardConfigTask proguardConfigTask = project.tasks.create( + "addSentryProguardSettingsFor${variant.name.capitalize()}", + SentryProguardConfigTask) + proguardConfigTask.group = GROUP_NAME + proguardConfigTask.applicationVariant = variant + proguardTask.dependsOn proguardConfigTask + } + + def cli = getSentryCli(project) + + // create a task that persists our proguard uuid as android asset + def persistIdsTask = project.tasks.create( + name: "persistSentryProguardUuidsFor${variant.name.capitalize()}", + type: Exec) { + description "Write references to proguard UUIDs to the android assets." + workingDir project.rootDir + environment("SENTRY_PROPERTIES", "${project.rootDir.toPath()}/sentry.properties") + + def args = [ + cli, + "upload-proguard", + "--android-manifest", + manifestPath, + "--write-properties", + getDebugMetaPropPath(project, variant), + mappingFile + ] + + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + commandLine("cmd", "/c", *args) + } else { + commandLine(*args) + } + + enabled true + } + + // and run before dex transformation. If we managed to find the dex task + // we set ourselves as dependency, otherwise we just hack outselves into + // the proguard task's doLast. + if (dexTask != null) { + dexTask.dependsOn persistIdsTask + } else { + proguardTask.doLast { + persistIdsTask.execute() + } + } + persistIdsTask.dependsOn proguardTask + } + } + } +} diff --git a/gradle/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy b/gradle/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy new file mode 100644 index 00000000000..16f552cdb18 --- /dev/null +++ b/gradle/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy @@ -0,0 +1,5 @@ +package io.sentry.android.gradle + +class SentryPluginExtension { + def boolean autoProguardConfig = true; +} diff --git a/gradle/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy b/gradle/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy new file mode 100644 index 00000000000..3beca229c10 --- /dev/null +++ b/gradle/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy @@ -0,0 +1,29 @@ +package io.sentry.android.gradle + +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.TaskAction + +import com.android.build.gradle.api.ApplicationVariant + +class SentryProguardConfigTask extends DefaultTask { + + ApplicationVariant applicationVariant + + SentryProguardConfigTask() { + super() + this.description = "Adds the Sentry recommended proguard settings to your project." + } + + @TaskAction + def createProguardConfig() { + def file = project.file("build/intermediates/sentry/sentry.pro") + file.getParentFile().mkdirs() + FileWriter f = new FileWriter(file.path) + f.write("-keepattributes LineNumberTable,SourceFile\n" + + "-dontwarn com.facebook.fbui.**\n" + + "-dontwarn org.slf4j.**\n" + + "-dontwarn javax.**\n") + f.close() + applicationVariant.getBuildType().buildType.proguardFiles(file) + } +} diff --git a/gradle/src/main/resources/META-INF/gradle-plugins/io.sentry.android.gradle.properties b/gradle/src/main/resources/META-INF/gradle-plugins/io.sentry.android.gradle.properties new file mode 100644 index 00000000000..bf34fdd1a69 --- /dev/null +++ b/gradle/src/main/resources/META-INF/gradle-plugins/io.sentry.android.gradle.properties @@ -0,0 +1 @@ +implementation-class=io.sentry.android.gradle.SentryPlugin diff --git a/gradle/src/main/resources/bin/.gitignore b/gradle/src/main/resources/bin/.gitignore new file mode 100644 index 00000000000..f65d58f9047 --- /dev/null +++ b/gradle/src/main/resources/bin/.gitignore @@ -0,0 +1 @@ +sentry-cli-* diff --git a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java index b7d787571ad..015878a0ac4 100644 --- a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java +++ b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java @@ -7,6 +7,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.res.AssetManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.*; @@ -19,14 +20,12 @@ import io.sentry.event.interfaces.UserInterface; import io.sentry.util.Util; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; +import java.io.*; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.Properties; import static android.content.Context.ACTIVITY_SERVICE; @@ -42,6 +41,7 @@ public class AndroidEventBuilderHelper implements EventBuilderHelper { private static final Boolean IS_EMULATOR = isEmulator(); private static final String KERNEL_VERSION = getKernelVersion(); + private static String[] cachedProGuardUuids = null; private Context ctx; @@ -147,22 +147,28 @@ private Map> getContexts() { } private static String[] getProGuardUuids(Context ctx) { - try { - PackageManager pkgManager = ctx.getPackageManager(); - ApplicationInfo appInfo = pkgManager.getApplicationInfo( - ctx.getPackageName(), PackageManager.GET_META_DATA); - Bundle bundle = appInfo.metaData; + if (cachedProGuardUuids != null) { + return cachedProGuardUuids; + } - String uuid = bundle.getString("io.sentry.ProguardUuids"); - if (Util.isNullOrEmpty(uuid)) { - return null; + String[] retVal = new String[0]; + try { + AssetManager assets = ctx.getAssets(); + InputStream is = assets.open("sentry-debug-meta.properties"); + Properties properties = new Properties(); + properties.load(is); + is.close(); + + String uuid = properties.getProperty("io.sentry.ProguardUuids"); + if (!Util.isNullOrEmpty(uuid)) { + retVal = uuid.split("\\|"); } - - return uuid.split("\\|"); } catch (Exception e) { Log.e(TAG, "Error getting Proguard UUIDs.", e); } - return null; + + cachedProGuardUuids = retVal; + return retVal; } /** diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index c901084915a..04825d0eafd 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -438,6 +438,7 @@ protected Marshaller createMarshaller(Dsn dsn) { marshaller.addInterfaceBinding(ExceptionInterface.class, new ExceptionInterfaceBinding(stackTraceBinding)); marshaller.addInterfaceBinding(MessageInterface.class, new MessageInterfaceBinding(maxMessageLength)); marshaller.addInterfaceBinding(UserInterface.class, new UserInterfaceBinding()); + marshaller.addInterfaceBinding(DebugMetaInterface.class, new DebugMetaInterfaceBinding()); HttpInterfaceBinding httpBinding = new HttpInterfaceBinding(); //TODO: Add a way to clean the HttpRequest //httpBinding. diff --git a/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java b/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java index f395b32c57b..c5ebffda907 100644 --- a/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java @@ -9,7 +9,7 @@ public class DebugMetaInterface implements SentryInterface { /** * Name of the exception interface in Sentry. */ - public static final String DEBUG_META_INTERFACE = "sentry.interfaces.DebugMeta"; + public static final String DEBUG_META_INTERFACE = "debug_meta"; private ArrayList debugImages = new ArrayList<>(); public ArrayList getDebugImages() { diff --git a/sentry/src/main/java/io/sentry/marshaller/json/DebugMetaInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/DebugMetaInterfaceBinding.java index 113097d4eb3..8ed53a7af79 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/DebugMetaInterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/DebugMetaInterfaceBinding.java @@ -9,7 +9,6 @@ * Binding that converts a {@link DebugMetaInterface} to a JSON stream. */ public class DebugMetaInterfaceBinding implements InterfaceBinding { - private static final String DEBUG_META = "debug_meta"; private static final String IMAGES = "images"; private static final String UUID = "uuid"; private static final String TYPE = "type"; @@ -17,10 +16,8 @@ public class DebugMetaInterfaceBinding implements InterfaceBinding Date: Wed, 21 Jun 2017 14:17:22 +0200 Subject: [PATCH 1642/2152] Added proguard docs --- docs/modules/android.rst | 138 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index bb45623fdb0..f77638edf1e 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -112,3 +112,141 @@ Now you can use ``Sentry`` to capture events anywhere in your application: } } +ProGuard +-------- + +If you want to use ProGuard with Sentry Android you will need to upload +proguard mapping files to Sentry with :doc:`sentry-cli +` or by using our Gradle integration. + +Gradle Integration +`````````````````` + +Using Gradle (Android Studio) in your ``app/build.gradle`` add: + +.. sourcecode:: groovy + + apply plugin: 'io.sentry.android.gradle' + +And declare a dependency in your toplevel ``build.gradle``: + +.. sourcecode:: groovy + + buildscript { + dependencies { + classpath 'io.sentry:sentry-android-gradle-plugin:1.0.0' + } + } + +This will then automatically generate appropriate ProGuard mapping files +and upload them when you run ``gradlew assembleRelease``. The credentials +for the upload step are loaded from the ``sentry.properties`` file in +your project root. At the very minimum you will need something like this +in there:: + + defaults.project=___PROJECT_NAME___ + defaults.org=___ORG_NAME___ + auth.token=YOUR_AUTH_TOKEN + +The auth token you can find at `sentry.io/api `. +For more information about the available config keys see +:doc:`/learn/cli/configuration`. + +Gradle Configuration +```````````````````` + +Additionally we expose a few configuration values directly in Gradle: + +.. sourcecode:: groovy + + sentry { + // disables or enables the automatic configuration of proguard + // for sentry. This injects a default config for proguard so + // you don't need to do it manually. + autoProguardConfig true + + // this enables or disables the automatic upload of mapping files + // during the build. If you do not want to do that you can + // disable that here and later use sentry-cli to manually upload + // it. + autoUpload true + } + +Proguard Requirements +````````````````````` + +If you want to manually configure ProGuard it's not much more complex. +You just need to follow some minium requirements in your ProGuard rules +file:: + + -keepattributes LineNumberTable,SourceFile + -dontwarn org.slf4j.** + -dontwarn javax.** + +ProGuard UUIDs +`````````````` + +After ProGuard files are generated you will need to embed the UUIDs of the +ProGuard mapping file in a file named ``sentry-debug-meta.properties`` in +the assets folder. Sentry-Java will look for the UUIDs there to map them +against the correct mapping. + +.. admonition:: Note + + Sentry calculates UUIDs for proguard files. For more information + about how this works see :ref:`proguard-uuids`. + +``sentry-cli`` can do that for you:: + + sentry-cli upload-proguard \ + --android-manifest app/build/intermediates/manifests/full/release/AndroidManifest.xml \ + --write-properties app/build/intermediates/assets/release/sentry-debug-meta.properties \ + --no-upload \ + app/build/outputs/mapping/release/mapping.txt + +Note that this will need to end up in your APK so it needs to run before +the APK is packaged. You can do that by creating a gradle task that runs +before the dex packaging. However it's *strongly* recommended to use the +gradle plugin which will do that for you. + +You can for example add a gradle task after the proguard step and before +the dex one which executes ``sentry-cli`` to just validate and process +the mapping files and to write the UUIDs into the properties file: + +.. sourcecode:: groovy + + gradle.projectsEvaluated { + android.applicationVariants.each { variant -> + def variantName = variant.name.capitalize(); + def proguardTask = project.tasks.findByName( + "transformClassesAndResourcesWithProguardFor${variantName}") + def dexTask = project.tasks.findByName( + "transformClassesWithDexFor${variantName}") + def task = project.tasks.create( + name: "processSentryProguardFor${variantName}", + type: Exec) { + workingDir project.rootDir + commandLine *[ + "sentry-cli", + "upload-proguard", + "--write-properties", + "${project.rootDir.toPath()}/app/build/intermediates/assets" + + "/${variant.dirName}/sentry-debug-meta.properties", + variant.getMappingFile(), + "--no-upload" + ] + } + dexTask.dependsOn task + task.dependsOn proguardTask + } + } + +Uploading ProGuard Files +```````````````````````` + +You can then manually upload ProGuard files with ``sentry-cli`` as +follows:: + + sentry-cli upload-proguard \ + --android-manifest app/build/intermediates/manifests/full/release/AndroidManifest.xml \ + app/build/outputs/mapping/release/mapping.txt From 94985a512ace244622664b5d4eb108bf4ca8e804 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 21 Jun 2017 14:19:24 -0500 Subject: [PATCH 1643/2152] =?UTF-8?q?Change=20the=20``EventBuilder``=20hos?= =?UTF-8?q?tname=20lookup=20code=20to=20only=20run=20one=20thre=E2=80=A6?= =?UTF-8?q?=20(#423)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES | 1 + .../java/io/sentry/event/EventBuilder.java | 49 ++++++++++++------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/CHANGES b/CHANGES index 297a905046c..6bc922adbcd 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ Version 1.1.1 - Allow overriding the location of the properties file. - Add support for handling and uploading Proguard files to Sentry (for Android applications). +- Change the ``EventBuilder`` hostname lookup code to only run one thread at a time. Version 1.1.0 ------------- diff --git a/sentry/src/main/java/io/sentry/event/EventBuilder.java b/sentry/src/main/java/io/sentry/event/EventBuilder.java index 150950308a2..3e259ab3bf1 100644 --- a/sentry/src/main/java/io/sentry/event/EventBuilder.java +++ b/sentry/src/main/java/io/sentry/event/EventBuilder.java @@ -12,6 +12,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.CRC32; import java.util.zip.Checksum; @@ -463,11 +464,15 @@ private static final class HostnameCache { /** * Current value for hostname (might change over time). */ - private String hostname = DEFAULT_HOSTNAME; + private volatile String hostname = DEFAULT_HOSTNAME; /** * Time at which the cache should expire. */ - private long expirationTimestamp; + private volatile long expirationTimestamp; + /** + * Whether a cache update thread is currently running or not. + */ + private AtomicBoolean updateRunning = new AtomicBoolean(false); /** * Sets up a cache for the hostname. @@ -486,7 +491,8 @@ private HostnameCache(long cacheDuration) { * @return the hostname of the current machine. */ public String getHostname() { - if (expirationTimestamp < System.currentTimeMillis()) { + if (expirationTimestamp < System.currentTimeMillis() + && updateRunning.compareAndSet(false, true)) { updateCache(); } @@ -497,26 +503,31 @@ public String getHostname() { * Force an update of the cache to get the current value of the hostname. */ public void updateCache() { - FutureTask futureTask = new FutureTask<>(new HostRetriever()); + Callable hostRetriever = new Callable() { + @Override + public Void call() throws Exception { + try { + hostname = InetAddress.getLocalHost().getCanonicalHostName(); + expirationTimestamp = System.currentTimeMillis() + cacheDuration; + } finally { + updateRunning.set(false); + } + + return null; + } + }; + try { - new Thread(futureTask).start(); logger.debug("Updating the hostname cache"); - hostname = futureTask.get(GET_HOSTNAME_TIMEOUT, TimeUnit.MILLISECONDS); - expirationTimestamp = System.currentTimeMillis() + cacheDuration; + FutureTask futureTask = new FutureTask<>(hostRetriever); + new Thread(futureTask).start(); + futureTask.get(GET_HOSTNAME_TIMEOUT, TimeUnit.MILLISECONDS); } catch (Exception e) { - futureTask.cancel(true); expirationTimestamp = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(1); - logger.warn("Localhost hostname lookup failed, keeping the value '{}'", hostname, e); - } - } - - /** - * Task retrieving the current hostname. - */ - private static final class HostRetriever implements Callable { - @Override - public String call() throws Exception { - return InetAddress.getLocalHost().getCanonicalHostName(); + logger.warn("Localhost hostname lookup failed, keeping the value '{}'." + + " If this persists it may mean your DNS is incorrectly configured and" + + " you may want to hardcode your server name: https://docs.sentry.io/clients/java/config/", + hostname, e); } } } From ad6e928157ca0d29cbb7d1c21c6069a90f898e3f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 21 Jun 2017 14:20:32 -0500 Subject: [PATCH 1644/2152] =?UTF-8?q?Cleanup=20thread=20local=20context=20?= =?UTF-8?q?in=20``ThreadLocalContextManager``=20after=20e=E2=80=A6=20(#422?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES | 1 + sentry/src/main/java/io/sentry/Sentry.java | 2 +- sentry/src/main/java/io/sentry/SentryClient.java | 7 +++++++ sentry/src/main/java/io/sentry/context/ContextManager.java | 5 +++++ .../java/io/sentry/context/SingletonContextManager.java | 5 +++++ .../java/io/sentry/context/ThreadLocalContextManager.java | 5 +++++ .../io/sentry/servlet/SentryServletRequestListener.java | 2 +- sentry/src/test/java/io/sentry/SentryClientTest.java | 2 +- sentry/src/test/java/io/sentry/SentryTest.java | 2 +- sentry/src/test/java/io/sentry/event/BreadcrumbTest.java | 2 +- sentry/src/test/java/io/sentry/event/UserTest.java | 2 +- 11 files changed, 29 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 6bc922adbcd..5e30d7a245f 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ Version 1.1.1 - Allow overriding the location of the properties file. - Add support for handling and uploading Proguard files to Sentry (for Android applications). +- Cleanup thread local context in ``ThreadLocalContextManager`` after each servlet request finishes. - Change the ``EventBuilder`` hostname lookup code to only run one thread at a time. Version 1.1.0 diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 652eca7eb19..2f5c937dd24 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -182,7 +182,7 @@ public static void setUser(User user) { * Clears the current context. */ public static void clearContext() { - getStoredClient().getContext().clear(); + getStoredClient().clearContext(); } /** diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 2420f22c1e4..b402fc8ee4b 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -215,6 +215,13 @@ public void closeConnection() { } } + /** + * Clears the {@link ContextManager} data. + */ + public void clearContext() { + contextManager.clear(); + } + public Context getContext() { return contextManager.getContext(); } diff --git a/sentry/src/main/java/io/sentry/context/ContextManager.java b/sentry/src/main/java/io/sentry/context/ContextManager.java index f180a087a47..22a9b36e69b 100644 --- a/sentry/src/main/java/io/sentry/context/ContextManager.java +++ b/sentry/src/main/java/io/sentry/context/ContextManager.java @@ -17,4 +17,9 @@ public interface ContextManager { */ Context getContext(); + /** + * Clear the underlying context data. + */ + void clear(); + } diff --git a/sentry/src/main/java/io/sentry/context/SingletonContextManager.java b/sentry/src/main/java/io/sentry/context/SingletonContextManager.java index ce43d4b362d..1bfed5e13eb 100644 --- a/sentry/src/main/java/io/sentry/context/SingletonContextManager.java +++ b/sentry/src/main/java/io/sentry/context/SingletonContextManager.java @@ -16,4 +16,9 @@ public class SingletonContextManager implements ContextManager { public Context getContext() { return context; } + + @Override + public void clear() { + context.clear(); + } } diff --git a/sentry/src/main/java/io/sentry/context/ThreadLocalContextManager.java b/sentry/src/main/java/io/sentry/context/ThreadLocalContextManager.java index 2c505a14ebd..583177e7304 100644 --- a/sentry/src/main/java/io/sentry/context/ThreadLocalContextManager.java +++ b/sentry/src/main/java/io/sentry/context/ThreadLocalContextManager.java @@ -21,4 +21,9 @@ public Context getContext() { return context.get(); } + @Override + public void clear() { + context.remove(); + } + } diff --git a/sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java b/sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java index b017641b2a6..92a7b8bd098 100644 --- a/sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java +++ b/sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java @@ -31,7 +31,7 @@ public void requestDestroyed(ServletRequestEvent servletRequestEvent) { try { SentryClient sentryClient = Sentry.getStoredClient(); if (sentryClient != null) { - sentryClient.getContext().clear(); + sentryClient.clearContext(); } } catch (Exception e) { logger.error("Error clearing Context state.", e); diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.java b/sentry/src/test/java/io/sentry/SentryClientTest.java index 720c07cf03f..303fd555974 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.java +++ b/sentry/src/test/java/io/sentry/SentryClientTest.java @@ -35,7 +35,7 @@ public class SentryClientTest extends BaseTest { @BeforeMethod public void setup() { - contextManager.getContext().clear(); + contextManager.clear(); } @Test diff --git a/sentry/src/test/java/io/sentry/SentryTest.java b/sentry/src/test/java/io/sentry/SentryTest.java index ad66017a548..462c6edf48e 100644 --- a/sentry/src/test/java/io/sentry/SentryTest.java +++ b/sentry/src/test/java/io/sentry/SentryTest.java @@ -34,7 +34,7 @@ public class SentryTest extends BaseTest { @BeforeMethod public void setup() { - contextManager.getContext().clear(); + contextManager.clear(); } @Test diff --git a/sentry/src/test/java/io/sentry/event/BreadcrumbTest.java b/sentry/src/test/java/io/sentry/event/BreadcrumbTest.java index b7665df6f1b..95ee298f8e6 100644 --- a/sentry/src/test/java/io/sentry/event/BreadcrumbTest.java +++ b/sentry/src/test/java/io/sentry/event/BreadcrumbTest.java @@ -28,7 +28,7 @@ public class BreadcrumbTest extends BaseTest { @BeforeMethod public void setup() { - contextManager.getContext().clear(); + contextManager.clear(); } @Test diff --git a/sentry/src/test/java/io/sentry/event/UserTest.java b/sentry/src/test/java/io/sentry/event/UserTest.java index 0683762ef8b..59818416d53 100644 --- a/sentry/src/test/java/io/sentry/event/UserTest.java +++ b/sentry/src/test/java/io/sentry/event/UserTest.java @@ -26,7 +26,7 @@ public class UserTest extends BaseTest { @BeforeMethod public void setup() { - contextManager.getContext().clear(); + contextManager.clear(); } @Test From c3a216b44c8b827dcd40f532c050e6231d461452 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 22 Jun 2017 10:25:37 +0200 Subject: [PATCH 1645/2152] Added a note on UUIDs to the docs --- docs/modules/android.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index f77638edf1e..f15135fe5ec 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -241,6 +241,10 @@ the mapping files and to write the UUIDs into the properties file: } } +Alternatively you can generate a UUID upfront yourself and then force +Sentry to honor that UUID after upload. However this is strongly +discouraged! + Uploading ProGuard Files ```````````````````````` From 70fad06b81ddf57d221b5701cc68b1cc3f088d77 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 22 Jun 2017 15:55:02 +0200 Subject: [PATCH 1646/2152] Add a flag to disable proguard uploads (#424) --- gradle/download-sentry-cli.sh | 2 +- .../main/groovy/io/sentry/android/gradle/SentryPlugin.groovy | 4 ++++ .../io/sentry/android/gradle/SentryPluginExtension.groovy | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/gradle/download-sentry-cli.sh b/gradle/download-sentry-cli.sh index 4855b597bad..99f13b67a37 100755 --- a/gradle/download-sentry-cli.sh +++ b/gradle/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.14.0 +VERSION=1.15.0 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* diff --git a/gradle/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/gradle/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index d547be57874..2015450ba3c 100644 --- a/gradle/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/gradle/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -174,6 +174,10 @@ class SentryPlugin implements Plugin { mappingFile ] + if (!project.sentry.autoUpload) { + args.push("--no-upload") + } + if (Os.isFamily(Os.FAMILY_WINDOWS)) { commandLine("cmd", "/c", *args) } else { diff --git a/gradle/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy b/gradle/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy index 16f552cdb18..11100be887e 100644 --- a/gradle/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy +++ b/gradle/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy @@ -2,4 +2,5 @@ package io.sentry.android.gradle class SentryPluginExtension { def boolean autoProguardConfig = true; + def boolean autoUpload = true; } From abb40252b5a4cdee8c296741377fbdda0ea2d00e Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 22 Jun 2017 10:31:07 -0500 Subject: [PATCH 1647/2152] Touch up Proguard docs. --- docs/modules/android.rst | 64 ++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index f15135fe5ec..daab75cf400 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -115,12 +115,12 @@ Now you can use ``Sentry`` to capture events anywhere in your application: ProGuard -------- -If you want to use ProGuard with Sentry Android you will need to upload -proguard mapping files to Sentry with :doc:`sentry-cli -` or by using our Gradle integration. +In order to use ProGuard with Sentry you will need to upload +the proguard mapping files to Sentry by using our Gradle integration +(recommended) or manually by using :doc:`sentry-cli ` Gradle Integration -`````````````````` +~~~~~~~~~~~~~~~~~~ Using Gradle (Android Studio) in your ``app/build.gradle`` add: @@ -138,9 +138,9 @@ And declare a dependency in your toplevel ``build.gradle``: } } -This will then automatically generate appropriate ProGuard mapping files -and upload them when you run ``gradlew assembleRelease``. The credentials -for the upload step are loaded from the ``sentry.properties`` file in +The plugin will then automatically generate appropriate ProGuard mapping files +and upload them when you run ``gradle assembleRelease``. The credentials +for the upload step are loaded from a ``sentry.properties`` file in your project root. At the very minimum you will need something like this in there:: @@ -148,9 +148,9 @@ in there:: defaults.org=___ORG_NAME___ auth.token=YOUR_AUTH_TOKEN -The auth token you can find at `sentry.io/api `. -For more information about the available config keys see -:doc:`/learn/cli/configuration`. +You can find your authentication token `on the Sentry API page `_. +For more information about the available configuration options see +`/learn/cli/configuration`. Gradle Configuration ```````````````````` @@ -160,24 +160,25 @@ Additionally we expose a few configuration values directly in Gradle: .. sourcecode:: groovy sentry { - // disables or enables the automatic configuration of proguard - // for sentry. This injects a default config for proguard so + // Disables or enables the automatic configuration of proguard + // for Sentry. This injects a default config for proguard so // you don't need to do it manually. autoProguardConfig true - // this enables or disables the automatic upload of mapping files - // during the build. If you do not want to do that you can - // disable that here and later use sentry-cli to manually upload - // it. + // Enables or disables the automatic upload of mapping files + // during a build. If you disable this you'll need to manually + // upload the mapping files with sentry-cli when you do a release. autoUpload true } -Proguard Requirements -````````````````````` +Manual Integration +~~~~~~~~~~~~~~~~~~ -If you want to manually configure ProGuard it's not much more complex. -You just need to follow some minium requirements in your ProGuard rules -file:: +If you choose not to use the Gradle integration, you may handle the processing +and upload steps manually. However, it is highly recommended that you use the +Gradle integration if at all possible. + +First, you need to add the following to your ProGuard rules file:: -keepattributes LineNumberTable,SourceFile -dontwarn org.slf4j.** @@ -187,16 +188,16 @@ ProGuard UUIDs `````````````` After ProGuard files are generated you will need to embed the UUIDs of the -ProGuard mapping file in a file named ``sentry-debug-meta.properties`` in -the assets folder. Sentry-Java will look for the UUIDs there to map them -against the correct mapping. +ProGuard mapping files in a properties file named ``sentry-debug-meta.properties`` in +the assets folder. The Java SDK will look for the UUIDs there to link events to +the correct mapping files on the server side. .. admonition:: Note Sentry calculates UUIDs for proguard files. For more information about how this works see :ref:`proguard-uuids`. -``sentry-cli`` can do that for you:: +``sentry-cli`` can write the ``sentry-debug-meta.properties`` file for you:: sentry-cli upload-proguard \ --android-manifest app/build/intermediates/manifests/full/release/AndroidManifest.xml \ @@ -204,13 +205,12 @@ against the correct mapping. --no-upload \ app/build/outputs/mapping/release/mapping.txt -Note that this will need to end up in your APK so it needs to run before +Note that this file needs to be in your APK, so this needs to be run before the APK is packaged. You can do that by creating a gradle task that runs -before the dex packaging. However it's *strongly* recommended to use the -gradle plugin which will do that for you. +before the dex packaging. You can for example add a gradle task after the proguard step and before -the dex one which executes ``sentry-cli`` to just validate and process +the dex one which executes ``sentry-cli`` to validate and process the mapping files and to write the UUIDs into the properties file: .. sourcecode:: groovy @@ -223,8 +223,8 @@ the mapping files and to write the UUIDs into the properties file: def dexTask = project.tasks.findByName( "transformClassesWithDexFor${variantName}") def task = project.tasks.create( - name: "processSentryProguardFor${variantName}", - type: Exec) { + name: "processSentryProguardFor${variantName}", + type: Exec) { workingDir project.rootDir commandLine *[ "sentry-cli", @@ -248,7 +248,7 @@ discouraged! Uploading ProGuard Files ```````````````````````` -You can then manually upload ProGuard files with ``sentry-cli`` as +Finally, you need manually upload ProGuard files with ``sentry-cli`` as follows:: sentry-cli upload-proguard \ From d4842d1fd3da111138df8500d292c24de07ea4a7 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 22 Jun 2017 20:27:41 +0200 Subject: [PATCH 1648/2152] Add ``ShouldSendEventCallback``, which can be added to each ``SentryClient`` instance. (#428) --- CHANGES | 1 + .../src/main/java/io/sentry/SentryClient.java | 25 ++++++++++-- .../event/helper/ShouldSendEventCallback.java | 17 ++++++++ .../test/java/io/sentry/SentryClientTest.java | 40 +++++++++++++++++++ 4 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/event/helper/ShouldSendEventCallback.java diff --git a/CHANGES b/CHANGES index 5e30d7a245f..2a043d69846 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,7 @@ Version 1.1.1 - Add support for handling and uploading Proguard files to Sentry (for Android applications). - Cleanup thread local context in ``ThreadLocalContextManager`` after each servlet request finishes. - Change the ``EventBuilder`` hostname lookup code to only run one thread at a time. +- Add ``ShouldSendEventCallback``, which can be added to each ``SentryClient`` instance. Version 1.1.0 ------------- diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index b402fc8ee4b..950b9c68c74 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -8,6 +8,7 @@ import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.helper.EventBuilderHelper; +import io.sentry.event.helper.ShouldSendEventCallback; import io.sentry.event.interfaces.ExceptionInterface; import io.sentry.util.Util; import org.slf4j.Logger; @@ -28,7 +29,6 @@ public class SentryClient { // CHECKSTYLE.OFF: ConstantName private static final Logger lockdownLogger = LoggerFactory.getLogger(SentryClient.class.getName() + ".lockdown"); // CHECKSTYLE.ON: ConstantName - /** * Identifies the version of the application. *

    @@ -66,6 +66,10 @@ public class SentryClient { * the {@link #extraTags} set will be extracted and set as tags on the {@link Event}. */ protected Set extraTags = new HashSet<>(); + /** + * Set of callbacks that are checked before each {@link Event} is sent to Sentry. + */ + private final Set shouldSendEventCallbacks = new HashSet<>(); /** * The underlying {@link Connection} to use for sending events to Sentry. */ @@ -112,6 +116,13 @@ public void runBuilderHelpers(EventBuilder eventBuilder) { * @param event event to send to Sentry. */ public void sendEvent(Event event) { + for (ShouldSendEventCallback shouldSendEventCallback : shouldSendEventCallbacks) { + if (!shouldSendEventCallback.shouldSend(event)) { + logger.trace("Not sending Event because of ShouldSendEventCallback: {}", shouldSendEventCallback); + return; + } + } + try { connection.send(event); } catch (LockedDownException e) { @@ -318,8 +329,7 @@ public void addExtraTag(String extraName) { } /** - * Add a callback that is called when an exception occurs while attempting to - * send events to the Sentry server. + * Add a callback that is called after an {@link Event} is sent to Sentry. * * @param eventSendCallback callback instance */ @@ -327,6 +337,15 @@ void addEventSendCallback(EventSendCallback eventSendCallback) { connection.addEventSendCallback(eventSendCallback); } + /** + * Add a callback that is called before an {@link Event} is sent to Sentry. + * + * @param shouldSendEventCallback callback instance + */ + void addShouldSendEventCallback(ShouldSendEventCallback shouldSendEventCallback) { + shouldSendEventCallbacks.add(shouldSendEventCallback); + } + @Override public String toString() { return "SentryClient{" diff --git a/sentry/src/main/java/io/sentry/event/helper/ShouldSendEventCallback.java b/sentry/src/main/java/io/sentry/event/helper/ShouldSendEventCallback.java new file mode 100644 index 00000000000..72b96de0bc2 --- /dev/null +++ b/sentry/src/main/java/io/sentry/event/helper/ShouldSendEventCallback.java @@ -0,0 +1,17 @@ +package io.sentry.event.helper; + +import io.sentry.event.Event; + +/** + * Callback that decides whether or not an {@link Event} should be sent to Sentry. + */ +public interface ShouldSendEventCallback { + + /** + * Callback that decides whether or not an {@link Event} should be sent to Sentry. + * + * @param event {@link Event} that may be sent. + * @return true if the {@link Event} should be sent, false otherwise. + */ + boolean shouldSend(Event event); +} diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.java b/sentry/src/test/java/io/sentry/SentryClientTest.java index 303fd555974..2f80ed7c49f 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.java +++ b/sentry/src/test/java/io/sentry/SentryClientTest.java @@ -2,6 +2,7 @@ import io.sentry.context.ContextManager; import io.sentry.context.SingletonContextManager; +import io.sentry.event.helper.ShouldSendEventCallback; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; @@ -17,6 +18,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -189,4 +191,42 @@ public void testFields() throws Exception { assertThat(event.getTags(), equalTo(tags)); }}; } + + @Test + public void testShouldNotSendEvent() throws Exception { + final AtomicBoolean called = new AtomicBoolean(false); + sentryClient.addShouldSendEventCallback(new ShouldSendEventCallback() { + @Override + public boolean shouldSend(Event event) { + called.set(true); + return false; + } + }); + + sentryClient.sendEvent(mockEvent); + + new Verifications() {{ + mockConnection.send(mockEvent); times = 0; + assertThat(called.get(), is(true)); + }}; + } + + @Test + public void testShouldSendEvent() throws Exception { + final AtomicBoolean called = new AtomicBoolean(false); + sentryClient.addShouldSendEventCallback(new ShouldSendEventCallback() { + @Override + public boolean shouldSend(Event event) { + called.set(true); + return true; + } + }); + + sentryClient.sendEvent(mockEvent); + + new Verifications() {{ + mockConnection.send(mockEvent); + assertThat(called.get(), is(true)); + }}; + } } From 0be0a9e2b035e39ab5fa76adb9ab8aad03553a69 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 22 Jun 2017 14:25:16 -0500 Subject: [PATCH 1649/2152] Add ability to set extra data on the ``UserInterface``. (#429) --- CHANGES | 1 + .../src/main/java/io/sentry/event/User.java | 22 ++++++++- .../java/io/sentry/event/UserBuilder.java | 33 ++++++++++++- .../event/helper/ContextBuilderHelper.java | 3 +- .../event/interfaces/UserInterface.java | 48 ++++++++++++++----- .../marshaller/json/UserInterfaceBinding.java | 17 +++++++ .../test/java/io/sentry/event/UserTest.java | 13 ++++- .../json/UserInterfaceBindingTest.java | 10 ++++ .../io/sentry/marshaller/json/User1.json | 7 ++- 9 files changed, 137 insertions(+), 17 deletions(-) diff --git a/CHANGES b/CHANGES index 2a043d69846..511935a0178 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,7 @@ Version 1.1.1 - Cleanup thread local context in ``ThreadLocalContextManager`` after each servlet request finishes. - Change the ``EventBuilder`` hostname lookup code to only run one thread at a time. - Add ``ShouldSendEventCallback``, which can be added to each ``SentryClient`` instance. +- Add ability to set extra data on the ``UserInterface``. Version 1.1.0 ------------- diff --git a/sentry/src/main/java/io/sentry/event/User.java b/sentry/src/main/java/io/sentry/event/User.java index 73d322cbeca..ea331034423 100644 --- a/sentry/src/main/java/io/sentry/event/User.java +++ b/sentry/src/main/java/io/sentry/event/User.java @@ -2,6 +2,7 @@ import java.io.Serializable; +import java.util.Map; /** * An object that represents a user. Typically used to represent @@ -14,6 +15,7 @@ public class User implements Serializable { private final String username; private final String ipAddress; private final String email; + private final Map data; /** * Create an immutable User object. @@ -22,12 +24,26 @@ public class User implements Serializable { * @param username String (optional) * @param ipAddress String (optional) * @param email String (optional) + * @param data Extra user data (optional) */ - public User(String id, String username, String ipAddress, String email) { + public User(String id, String username, String ipAddress, String email, Map data) { this.id = id; this.username = username; this.ipAddress = ipAddress; this.email = email; + this.data = data; + } + + /** + * Create an immutable User object. + * + * @param id String (optional) + * @param username String (optional) + * @param ipAddress String (optional) + * @param email String (optional) + */ + public User(String id, String username, String ipAddress, String email) { + this(id, username, ipAddress, email, null); } public String getId() { @@ -46,4 +62,8 @@ public String getEmail() { return email; } + public Map getData() { + return data; + } + } diff --git a/sentry/src/main/java/io/sentry/event/UserBuilder.java b/sentry/src/main/java/io/sentry/event/UserBuilder.java index 01177d4fedf..3694e5ff76b 100644 --- a/sentry/src/main/java/io/sentry/event/UserBuilder.java +++ b/sentry/src/main/java/io/sentry/event/UserBuilder.java @@ -1,5 +1,8 @@ package io.sentry.event; +import java.util.HashMap; +import java.util.Map; + /** * Builder to assist with the creation of {@link User}s. */ @@ -8,6 +11,7 @@ public class UserBuilder { private String username; private String ipAddress; private String email; + private Map data; /** * Sets the Id for the user. @@ -53,12 +57,39 @@ public UserBuilder setEmail(String value) { return this; } + /** + * Sets the extra data for the user. + * + * @param value Map of extra data + * @return current instance of UserBuilder + */ + public UserBuilder setData(Map value) { + this.data = value; + return this; + } + + /** + * Adds to the extra data for the user. + * + * @param name Name of the data + * @param value Value of the data + * @return current instance of UserBuilder + */ + public UserBuilder withData(String name, Object value) { + if (this.data == null) { + this.data = new HashMap<>(); + } + + this.data.put(name, value); + return this; + } + /** * Build and return the {@link User} object. * * @return User */ public User build() { - return new User(id, username, ipAddress, email); + return new User(id, username, ipAddress, email, data); } } diff --git a/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java b/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java index feb9684fe3c..ab449dfc793 100644 --- a/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java +++ b/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java @@ -56,7 +56,8 @@ public void helpBuildingEvent(EventBuilder eventBuilder) { * @return UserInterface */ private UserInterface fromUser(User user) { - return new UserInterface(user.getId(), user.getUsername(), user.getIpAddress(), user.getEmail()); + return new UserInterface(user.getId(), user.getUsername(), user.getIpAddress(), + user.getEmail(), user.getData()); } } diff --git a/sentry/src/main/java/io/sentry/event/interfaces/UserInterface.java b/sentry/src/main/java/io/sentry/event/interfaces/UserInterface.java index e8240d788d3..5f2b623d5ce 100644 --- a/sentry/src/main/java/io/sentry/event/interfaces/UserInterface.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/UserInterface.java @@ -1,5 +1,8 @@ package io.sentry.event.interfaces; +import java.util.Map; +import java.util.Objects; + /** * The User interface for Sentry allows to send details about the User currently using the application. */ @@ -12,6 +15,7 @@ public class UserInterface implements SentryInterface { private final String username; private final String ipAddress; private final String email; + private final Map data; /** * Creates a user. @@ -19,13 +23,27 @@ public class UserInterface implements SentryInterface { * @param id Id of the user in the system, as a String. * @param username Name of the user. * @param ipAddress IP address used to connect to the application. - * @param email user email address. + * @param email User's email address. + * @param data Extra data about the user. */ - public UserInterface(String id, String username, String ipAddress, String email) { + public UserInterface(String id, String username, String ipAddress, String email, Map data) { this.id = id; this.username = username; this.ipAddress = ipAddress; this.email = email; + this.data = data; + } + + /** + * Creates a user. + * + * @param id Id of the user in the system, as a String. + * @param username Name of the user. + * @param ipAddress IP address used to connect to the application. + * @param email User's email address. + */ + public UserInterface(String id, String username, String ipAddress, String email) { + this(id, username, ipAddress, email, null); } @Override @@ -49,6 +67,10 @@ public String getEmail() { return email; } + public Map getData() { + return data; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -57,25 +79,27 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - UserInterface that = (UserInterface) o; - - return !(id != null ? !id.equals(that.id) : that.id != null); - + return Objects.equals(id, that.id) + && Objects.equals(username, that.username) + && Objects.equals(ipAddress, that.ipAddress) + && Objects.equals(email, that.email) + && Objects.equals(data, that.data); } @Override public int hashCode() { - return id != null ? id.hashCode() : 0; + return Objects.hash(id, username, ipAddress, email, data); } @Override public String toString() { return "UserInterface{" - + "id='" + id + '\'' - + ", username='" + username + '\'' - + ", ipAddress='" + ipAddress + '\'' - + ", email='" + email + '\'' - + '}'; + + "id='" + id + '\'' + + ", username='" + username + '\'' + + ", ipAddress='" + ipAddress + '\'' + + ", email='" + email + '\'' + + ", data=" + data + + '}'; } } diff --git a/sentry/src/main/java/io/sentry/marshaller/json/UserInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/UserInterfaceBinding.java index 4748fc46074..c6cb9ec0c0b 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/UserInterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/UserInterfaceBinding.java @@ -4,6 +4,7 @@ import io.sentry.event.interfaces.UserInterface; import java.io.IOException; +import java.util.Map; /** * Binding allowing to transform a {@link UserInterface} into a JSON stream. @@ -13,6 +14,7 @@ public class UserInterfaceBinding implements InterfaceBinding { private static final String USERNAME = "username"; private static final String EMAIL = "email"; private static final String IP_ADDRESS = "ip_address"; + private static final String DATA = "data"; @Override public void writeInterface(JsonGenerator generator, UserInterface userInterface) throws IOException { @@ -21,6 +23,21 @@ public void writeInterface(JsonGenerator generator, UserInterface userInterface) generator.writeStringField(USERNAME, userInterface.getUsername()); generator.writeStringField(EMAIL, userInterface.getEmail()); generator.writeStringField(IP_ADDRESS, userInterface.getIpAddress()); + + if (userInterface.getData() != null && !userInterface.getData().isEmpty()) { + generator.writeObjectFieldStart(DATA); + for (Map.Entry entry : userInterface.getData().entrySet()) { + String name = entry.getKey(); + Object value = entry.getValue(); + if (value == null) { + generator.writeNullField(name); + } else { + generator.writeObjectField(name, value); + } + } + generator.writeEndObject(); + } + generator.writeEndObject(); } } diff --git a/sentry/src/test/java/io/sentry/event/UserTest.java b/sentry/src/test/java/io/sentry/event/UserTest.java index 59818416d53..7d1253817c0 100644 --- a/sentry/src/test/java/io/sentry/event/UserTest.java +++ b/sentry/src/test/java/io/sentry/event/UserTest.java @@ -13,6 +13,9 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import java.util.HashMap; +import java.util.Map; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsEqual.equalTo; @@ -37,13 +40,20 @@ public void testUserPropagation() { .setEmail("test@example.com") .setId("1234") .setIpAddress("192.168.0.1") - .setUsername("testUser_123").build(); + .setUsername("testUser_123") + .withData("foo", "bar") + .withData("baz", 2) + .build(); sentryClient.getContext().setUser(user); sentryClient.sendEvent(new EventBuilder() .withMessage("Some random message") .withLevel(Event.Level.INFO)); + final Map map = new HashMap<>(); + map.put("foo", "bar"); + map.put("baz", 2); + new Verifications() {{ Event event; mockConnection.send(event = withCapture()); @@ -52,6 +62,7 @@ public void testUserPropagation() { assertThat(userInterface.getEmail(), equalTo(user.getEmail())); assertThat(userInterface.getIpAddress(), equalTo(user.getIpAddress())); assertThat(userInterface.getUsername(), equalTo(user.getUsername())); + assertThat(userInterface.getData(), equalTo(map)); }}; } diff --git a/sentry/src/test/java/io/sentry/marshaller/json/UserInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/UserInterfaceBindingTest.java index 1d4d1228a0e..098b2bcda18 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/UserInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/UserInterfaceBindingTest.java @@ -8,6 +8,9 @@ import io.sentry.marshaller.json.JsonComparisonUtil.JsonGeneratorParser; import org.testng.annotations.Test; +import java.util.HashMap; +import java.util.Map; + import static io.sentry.marshaller.json.JsonComparisonUtil.jsonResource; import static io.sentry.marshaller.json.JsonComparisonUtil.newJsonGenerator; import static org.hamcrest.MatcherAssert.assertThat; @@ -26,6 +29,11 @@ public void testSimpleMessage() throws Exception { final String username = "3eaa555a-e813-4778-9852-7c1880bf0fd7"; final String email = "9bcade34-a58c-4616-9de7-bc8b456c96de"; final String ipAddress = "9a1a658b-6f74-43ae-9e45-0f89f4c5fcb4"; + final Map data = new HashMap<>(); + data.put("foo", "bar"); + data.put("baz", 2); + data.put("qux", null); + new NonStrictExpectations() {{ mockUserInterface.getId(); result = id; @@ -35,6 +43,8 @@ public void testSimpleMessage() throws Exception { result = email; mockUserInterface.getIpAddress(); result = ipAddress; + mockUserInterface.getData(); + result = data; }}; userInterfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockUserInterface); diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/User1.json b/sentry/src/test/resources/io/sentry/marshaller/json/User1.json index 94795cbe238..f17890284ba 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/User1.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/User1.json @@ -2,5 +2,10 @@ "id": "970e9df6-6e0b-4a27-b2ee-0faf0f368354", "username": "3eaa555a-e813-4778-9852-7c1880bf0fd7", "email": "9bcade34-a58c-4616-9de7-bc8b456c96de", - "ip_address": "9a1a658b-6f74-43ae-9e45-0f89f4c5fcb4" + "ip_address": "9a1a658b-6f74-43ae-9e45-0f89f4c5fcb4", + "data": { + "foo": "bar", + "baz": 2, + "qux": null + } } From 98e87d95ef4f61fe2e4c44a2e68313aa0500f223 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 22 Jun 2017 15:14:19 -0500 Subject: [PATCH 1650/2152] Rename gradle -> sentry-android-gradle-plugin. --- gradle/gradle.properties | 2 -- {gradle => sentry-android-gradle-plugin}/.gitignore | 0 .../build.gradle | 0 .../download-sentry-cli.sh | 2 +- sentry-android-gradle-plugin/gradle.properties | 3 +++ .../gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 {gradle => sentry-android-gradle-plugin}/gradlew | 0 .../io/sentry/android/gradle/SentryPlugin.groovy | 0 .../android/gradle/SentryPluginExtension.groovy | 0 .../android/gradle/SentryProguardConfigTask.groovy | 0 .../io.sentry.android.gradle.properties | 0 .../src/main/resources/bin/.gitignore | 0 13 files changed, 4 insertions(+), 3 deletions(-) delete mode 100644 gradle/gradle.properties rename {gradle => sentry-android-gradle-plugin}/.gitignore (100%) rename {gradle => sentry-android-gradle-plugin}/build.gradle (100%) rename {gradle => sentry-android-gradle-plugin}/download-sentry-cli.sh (97%) create mode 100644 sentry-android-gradle-plugin/gradle.properties rename {gradle => sentry-android-gradle-plugin}/gradle/wrapper/gradle-wrapper.jar (100%) rename {gradle => sentry-android-gradle-plugin}/gradle/wrapper/gradle-wrapper.properties (100%) rename {gradle => sentry-android-gradle-plugin}/gradlew (100%) rename {gradle => sentry-android-gradle-plugin}/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy (100%) rename {gradle => sentry-android-gradle-plugin}/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy (100%) rename {gradle => sentry-android-gradle-plugin}/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy (100%) rename {gradle => sentry-android-gradle-plugin}/src/main/resources/META-INF/gradle-plugins/io.sentry.android.gradle.properties (100%) rename {gradle => sentry-android-gradle-plugin}/src/main/resources/bin/.gitignore (100%) diff --git a/gradle/gradle.properties b/gradle/gradle.properties deleted file mode 100644 index a438c32a572..00000000000 --- a/gradle/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -group = io.sentry -version = 1.0.0 diff --git a/gradle/.gitignore b/sentry-android-gradle-plugin/.gitignore similarity index 100% rename from gradle/.gitignore rename to sentry-android-gradle-plugin/.gitignore diff --git a/gradle/build.gradle b/sentry-android-gradle-plugin/build.gradle similarity index 100% rename from gradle/build.gradle rename to sentry-android-gradle-plugin/build.gradle diff --git a/gradle/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh similarity index 97% rename from gradle/download-sentry-cli.sh rename to sentry-android-gradle-plugin/download-sentry-cli.sh index 99f13b67a37..40fd2c49761 100755 --- a/gradle/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.15.0 +VERSION=1.16.0 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties new file mode 100644 index 00000000000..a6668bf4251 --- /dev/null +++ b/sentry-android-gradle-plugin/gradle.properties @@ -0,0 +1,3 @@ +name = sentry-android-gradle-plugin +group = io.sentry +version = 1.0.0-beta diff --git a/gradle/gradle/wrapper/gradle-wrapper.jar b/sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from gradle/gradle/wrapper/gradle-wrapper.jar rename to sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/gradle/wrapper/gradle-wrapper.properties b/sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from gradle/gradle/wrapper/gradle-wrapper.properties rename to sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.properties diff --git a/gradle/gradlew b/sentry-android-gradle-plugin/gradlew similarity index 100% rename from gradle/gradlew rename to sentry-android-gradle-plugin/gradlew diff --git a/gradle/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy similarity index 100% rename from gradle/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy rename to sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy diff --git a/gradle/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy similarity index 100% rename from gradle/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy rename to sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy diff --git a/gradle/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy similarity index 100% rename from gradle/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy rename to sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy diff --git a/gradle/src/main/resources/META-INF/gradle-plugins/io.sentry.android.gradle.properties b/sentry-android-gradle-plugin/src/main/resources/META-INF/gradle-plugins/io.sentry.android.gradle.properties similarity index 100% rename from gradle/src/main/resources/META-INF/gradle-plugins/io.sentry.android.gradle.properties rename to sentry-android-gradle-plugin/src/main/resources/META-INF/gradle-plugins/io.sentry.android.gradle.properties diff --git a/gradle/src/main/resources/bin/.gitignore b/sentry-android-gradle-plugin/src/main/resources/bin/.gitignore similarity index 100% rename from gradle/src/main/resources/bin/.gitignore rename to sentry-android-gradle-plugin/src/main/resources/bin/.gitignore From 34364e444d838e53aabf6fffa09c7a9a652d64d6 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 22 Jun 2017 15:17:25 -0500 Subject: [PATCH 1651/2152] Change version to 1.2.0. --- pom.xml | 2 +- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry/pom.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 1da49633c30..9db5978d83c 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.1.1-SNAPSHOT + 1.2.0-SNAPSHOT pom Sentry-Java diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 039cef51441..82b6a17b1d9 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.1.1-SNAPSHOT + 1.2.0-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 76fe32c8c19..a6349c6d791 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.1.1-SNAPSHOT + 1.2.0-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 1003930b747..4164fa7ffd5 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.1.1-SNAPSHOT + 1.2.0-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 4070a377c36..1e8b1a286c5 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.1.1-SNAPSHOT + 1.2.0-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index c69fca15637..f66bd35a6ec 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.1.1-SNAPSHOT + 1.2.0-SNAPSHOT sentry-logback diff --git a/sentry/pom.xml b/sentry/pom.xml index 0715b93e307..4c58172b182 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.1.1-SNAPSHOT + 1.2.0-SNAPSHOT sentry From 42ccab4b58d15a643c5059b314eaf5b77d36861b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 22 Jun 2017 15:17:30 -0500 Subject: [PATCH 1652/2152] Bump docs to 1.2.0 --- docs/modules/android.rst | 6 +++--- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index daab75cf400..1ee0bbabbd3 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.1.0`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.2.0`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.1.0' + compile 'io.sentry:sentry-android:1.2.0' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 9cc4fbe2617..0dcbef33c66 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.1.0 + 1.2.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.1.0' + compile 'io.sentry:sentry-appengine:1.2.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.1.0" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.2.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 7a4c34bbec8..74b41634d20 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -24,22 +24,22 @@ Using Maven: io.sentry sentry - 1.1.0 + 1.2.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.1.0' + compile 'io.sentry:sentry:1.2.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.1.0" + libraryDependencies += "io.sentry" % "sentry" % "1.2.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 98fc3ad91f6..921176cec5c 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -24,22 +24,22 @@ Using Maven: io.sentry sentry-log4j - 1.1.0 + 1.2.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.1.0' + compile 'io.sentry:sentry-log4j:1.2.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.1.0" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.2.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 373eab16f08..63726d08174 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -24,22 +24,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.1.0 + 1.2.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.1.0' + compile 'io.sentry:sentry-log4j2:1.2.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.1.0" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.2.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 686e8253e7a..517e5a4eb0e 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -24,22 +24,22 @@ Using Maven: io.sentry sentry-logback - 1.1.0 + 1.2.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.1.0' + compile 'io.sentry:sentry-logback:1.2.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.1.0" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.2.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index d98c04a9171..454dc1420aa 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -15,22 +15,22 @@ Using Maven: io.sentry sentry - 1.1.0 + 1.2.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.1.0' + compile 'io.sentry:sentry:1.2.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.1.0" + libraryDependencies += "io.sentry" % "sentry" % "1.2.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From cf24605d5d6d6e133df8f324e151328bb7b71aec Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 22 Jun 2017 15:18:40 -0500 Subject: [PATCH 1653/2152] [maven-release-plugin] prepare release v1.2.0 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 9db5978d83c..005541855bc 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.2.0-SNAPSHOT + 1.2.0 pom Sentry-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.2.0 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 82b6a17b1d9..460d2f2d609 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.0-SNAPSHOT + 1.2.0 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index a6349c6d791..f8810244c6b 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.2.0-SNAPSHOT + 1.2.0 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 4164fa7ffd5..4b16ff49d24 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.0-SNAPSHOT + 1.2.0 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 1e8b1a286c5..afe64275b8b 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.0-SNAPSHOT + 1.2.0 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index f66bd35a6ec..730319f765b 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.0-SNAPSHOT + 1.2.0 sentry-logback diff --git a/sentry/pom.xml b/sentry/pom.xml index 4c58172b182..68e45eae111 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.0-SNAPSHOT + 1.2.0 sentry From 0df703e9ab20c6843e6da3cc248309ee9e8d06e3 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 22 Jun 2017 15:18:40 -0500 Subject: [PATCH 1654/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 005541855bc..89c44efad86 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.2.0 + 1.2.1-SNAPSHOT pom Sentry-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.2.0 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 460d2f2d609..572fcf43d76 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.0 + 1.2.1-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index f8810244c6b..ba22ad0c92a 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.2.0 + 1.2.1-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 4b16ff49d24..2a8f8fd1bf6 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.0 + 1.2.1-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index afe64275b8b..3b658f03df6 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.0 + 1.2.1-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 730319f765b..b2207047564 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.0 + 1.2.1-SNAPSHOT sentry-logback diff --git a/sentry/pom.xml b/sentry/pom.xml index 68e45eae111..7f819839178 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.0 + 1.2.1-SNAPSHOT sentry From a33e373c2fe3abfad069b22365fd347caffd4e9b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 22 Jun 2017 15:18:42 -0500 Subject: [PATCH 1655/2152] Bump CHANGES to 1.2.1 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 511935a0178..8bde215f013 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.2.1 +------------- + +- + Version 1.1.1 ------------- From b5fd441bde7845b582981c922b1276e711ce9e79 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 22 Jun 2017 15:25:36 -0500 Subject: [PATCH 1656/2152] Fix CHANGES. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 8bde215f013..bd112da4d6e 100644 --- a/CHANGES +++ b/CHANGES @@ -3,7 +3,7 @@ Version 1.2.1 - -Version 1.1.1 +Version 1.2.0 ------------- - Allow overriding the location of the properties file. From 5737442a885058fa8a463aaf866b265e2047df0b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 22 Jun 2017 16:12:17 -0500 Subject: [PATCH 1657/2152] Fix Gradle plugin version in docs. --- docs/modules/android.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 1ee0bbabbd3..c10cce3ddd4 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -134,7 +134,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.0.0' + classpath 'io.sentry:sentry-android-gradle-plugin:1.0.0-beta' } } From e80b11f90dc7149af029c610edbc78f067a10074 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 22 Jun 2017 17:39:34 -0500 Subject: [PATCH 1658/2152] Add required POM stuff for Maven Central release. --- sentry-android-gradle-plugin/Makefile | 5 ++++ sentry-android-gradle-plugin/build.gradle | 30 +++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 sentry-android-gradle-plugin/Makefile diff --git a/sentry-android-gradle-plugin/Makefile b/sentry-android-gradle-plugin/Makefile new file mode 100644 index 00000000000..d9621700c62 --- /dev/null +++ b/sentry-android-gradle-plugin/Makefile @@ -0,0 +1,5 @@ +.PHONY: release + +release: + download-sentry-cli.sh + ./gradlew uploadArchives diff --git a/sentry-android-gradle-plugin/build.gradle b/sentry-android-gradle-plugin/build.gradle index e192b545325..4dc24f6eddd 100644 --- a/sentry-android-gradle-plugin/build.gradle +++ b/sentry-android-gradle-plugin/build.gradle @@ -36,3 +36,33 @@ publishing { } } } + +modifyPom { + project { + name 'Sentry Android Gradle Plugin' + description 'Sentry Android Gradle Plugin' + url 'https://github.com/getsentry/sentry-java' + + scm { + url 'https://github.com/getsentry/sentry-java' + connection 'scm:https://github.com/getsentry/sentry-java.git' + developerConnection 'scm:git@github.com:getsentry/sentry-java.git' + } + + licenses { + license { + name 'BSD' + url 'https://github.com/getsentry/sentry-java/blob/master/LICENSE' + distribution 'repo' + } + } + + developers { + developer { + id 'bretthoerner' + name 'Brett Hoerner' + email 'brett@sentry.io' + } + } + } +} \ No newline at end of file From c19f532c91ce69f1021034d2953da523a56eac70 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 23 Jun 2017 11:05:38 -0500 Subject: [PATCH 1659/2152] Documentation tweaks. --- docs/config.rst | 24 ++++++++++++++---------- docs/modules/android.rst | 12 ++++++++++-- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 53a57ec9f39..4a03deebeac 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -50,6 +50,10 @@ In code: Configuration methods --------------------- +There are multiple ways to configure the Java SDK, but all of them take the same options. +See below for how to use each configuration method and how the option names might +differ between them. + Configuration via properties file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -289,8 +293,8 @@ buffer.flushtime`` option (in milliseconds): buffer.flushtime=10000 -Graceful Shutdown (Advanced) -```````````````````````````` +Graceful Shutdown of Buffering (Advanced) +````````````````````````````````````````` In order to shutdown the buffer flushing thread gracefully, a ``ShutdownHook`` is created. By default, the buffer flushing thread is given 1 second @@ -330,8 +334,8 @@ To disable the async mode, add async=false`` to your options: async=false -Graceful Shutdown (Advanced) -```````````````````````````` +Graceful Shutdown of Async (Advanced) +````````````````````````````````````` In order to shutdown the asynchronous connection gracefully, a ``ShutdownHook`` is created. By default, the asynchronous connection is given 1 second @@ -361,8 +365,8 @@ The option to do so is async.gracefulshutdown``: async.gracefulshutdown=false -Queue Size (Advanced) -````````````````````` +Async Queue Size (Advanced) +``````````````````````````` The default queue used to store unprocessed events is limited to 50 items. Additional items added once the queue is full are dropped and @@ -383,8 +387,8 @@ The special value ``-1`` can be used to enable an unlimited queue. Beware that network connectivity or Sentry server issues could mean your process will run out of memory. -Threads Count (Advanced) -```````````````````````` +Async Threads Count (Advanced) +`````````````````````````````` By default the thread pool used by the async connection contains one thread per processor available to the JVM. @@ -396,8 +400,8 @@ only one thread) with the option async.threads``: async.threads=1 -Threads Priority (Advanced) -``````````````````````````` +Async Threads Priority (Advanced) +````````````````````````````````` In most cases sending logs to Sentry isn't as important as an application running smoothly, so the threads have a diff --git a/docs/modules/android.rst b/docs/modules/android.rst index c10cce3ddd4..75fc925cd50 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -12,7 +12,7 @@ Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ (in the application's cache directory) by default. This allows events to be sent at a later time if the device does not have connectivity when an event is created. This can -be disabled by setting the DSN option ``buffer.enabled`` to ``false``. +be disabled by :ref:`setting the option ` ``buffer.enabled`` to ``false``. An ``UncaughtExceptionHandler`` is configured so that crash events will be stored to disk and sent the next time the application is run. @@ -56,13 +56,21 @@ Then initialize the Sentry client in your application's main ``onCreate`` method public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + Context ctx = this.getApplicationContext(); + // Use the Sentry DSN (client key) from the Project Settings page on Sentry String sentryDsn = "https://publicKey:secretKey@host:port/1?options"; - Context ctx = this.getApplicationContext(); Sentry.init(sentryDsn, new AndroidSentryClientFactory(ctx)); + + // Alternatively, if you configured your DSN in a `sentry.properties` + // file (see the configuration documentation). + Sentry.init(new AndroidSentryClientFactory(ctx)); } } +You can optionally configure other values such as ``environment`` and ``release``. +:ref:`See the configuration page ` for ways you can do this. + Usage ----- From 7f79ef8951e3239546bae641035964e06a259106 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 27 Jun 2017 17:26:37 -0500 Subject: [PATCH 1660/2152] Make SentryStackTraceElement Serializable. (#432) --- CHANGES | 2 +- .../io/sentry/event/interfaces/SentryStackTraceElement.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index bd112da4d6e..172a728581e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.2.1 ------------- -- +- Fix ``SentryStackTraceElement`` to implement ``Serializable``. Version 1.2.0 ------------- diff --git a/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java b/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java index c46fa261fba..4c20ff0ade0 100644 --- a/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java @@ -2,13 +2,14 @@ import io.sentry.jvmti.Frame; +import java.io.Serializable; import java.util.Map; import java.util.Objects; /** * Richer StackTraceElement class. */ -public class SentryStackTraceElement { +public class SentryStackTraceElement implements Serializable { private final String module; private final String function; private final String fileName; From 716b27d24972fa9faed68198d2b672b165580006 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 27 Jun 2017 17:26:54 -0500 Subject: [PATCH 1661/2152] Bump docs to 1.2.1 --- docs/modules/android.rst | 6 +++--- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 75fc925cd50..5291f7934ba 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.2.0`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.2.1`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.2.0' + compile 'io.sentry:sentry-android:1.2.1' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 0dcbef33c66..9346904af85 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.2.0 + 1.2.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.2.0' + compile 'io.sentry:sentry-appengine:1.2.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.2.0" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.2.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 74b41634d20..7a77b027fe3 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -24,22 +24,22 @@ Using Maven: io.sentry sentry - 1.2.0 + 1.2.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.2.0' + compile 'io.sentry:sentry:1.2.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.2.0" + libraryDependencies += "io.sentry" % "sentry" % "1.2.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 921176cec5c..c3a7b277527 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -24,22 +24,22 @@ Using Maven: io.sentry sentry-log4j - 1.2.0 + 1.2.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.2.0' + compile 'io.sentry:sentry-log4j:1.2.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.2.0" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.2.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 63726d08174..ff718799618 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -24,22 +24,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.2.0 + 1.2.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.2.0' + compile 'io.sentry:sentry-log4j2:1.2.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.2.0" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.2.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 517e5a4eb0e..3c44e5163cd 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -24,22 +24,22 @@ Using Maven: io.sentry sentry-logback - 1.2.0 + 1.2.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.2.0' + compile 'io.sentry:sentry-logback:1.2.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.2.0" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.2.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index 454dc1420aa..c8a47b3cbfe 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -15,22 +15,22 @@ Using Maven: io.sentry sentry - 1.2.0 + 1.2.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.2.0' + compile 'io.sentry:sentry:1.2.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.2.0" + libraryDependencies += "io.sentry" % "sentry" % "1.2.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From a6502275061a0e9e7a7ffba5489fae550e10bbf8 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 27 Jun 2017 17:28:18 -0500 Subject: [PATCH 1662/2152] [maven-release-plugin] prepare release v1.2.1 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 89c44efad86..8f580d8cf0d 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.2.1-SNAPSHOT + 1.2.1 pom Sentry-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.2.1 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 572fcf43d76..f6ec2d9f516 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.1-SNAPSHOT + 1.2.1 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index ba22ad0c92a..17191873f4b 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.2.1-SNAPSHOT + 1.2.1 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 2a8f8fd1bf6..476d3b5106c 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.1-SNAPSHOT + 1.2.1 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 3b658f03df6..fa1e7999de9 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.1-SNAPSHOT + 1.2.1 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index b2207047564..f1000cd7956 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.1-SNAPSHOT + 1.2.1 sentry-logback diff --git a/sentry/pom.xml b/sentry/pom.xml index 7f819839178..0dd71829141 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.1-SNAPSHOT + 1.2.1 sentry From cebe623c82081d578da40a091d552c92402c43ed Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 27 Jun 2017 17:28:19 -0500 Subject: [PATCH 1663/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 8f580d8cf0d..104b031ef16 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.2.1 + 1.2.2-SNAPSHOT pom Sentry-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.2.1 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index f6ec2d9f516..a648893fbb1 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.1 + 1.2.2-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 17191873f4b..03063c8a6ef 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.2.1 + 1.2.2-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 476d3b5106c..120521e43f1 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.1 + 1.2.2-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index fa1e7999de9..b2bb0ecdc94 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.1 + 1.2.2-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index f1000cd7956..1100b911b9a 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.1 + 1.2.2-SNAPSHOT sentry-logback diff --git a/sentry/pom.xml b/sentry/pom.xml index 0dd71829141..abd5fbe93ec 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.1 + 1.2.2-SNAPSHOT sentry From c8ca822be2b007973d25ec115bacf99067f7db73 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 27 Jun 2017 17:28:21 -0500 Subject: [PATCH 1664/2152] Bump CHANGES to 1.2.2 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 172a728581e..c5a7517bda1 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.2.2 +------------- + +- + Version 1.2.1 ------------- From 1d28b52d6c4e8516bb5b4bd479357e0e61c6748c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 29 Jun 2017 08:58:50 -0500 Subject: [PATCH 1665/2152] Make operations on Context threadsafe. (#435) --- .../main/java/io/sentry/context/Context.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/sentry/src/main/java/io/sentry/context/Context.java b/sentry/src/main/java/io/sentry/context/Context.java index b13f8ea4908..20c5847db91 100644 --- a/sentry/src/main/java/io/sentry/context/Context.java +++ b/sentry/src/main/java/io/sentry/context/Context.java @@ -5,8 +5,7 @@ import io.sentry.util.CircularFifoQueue; import java.io.Serializable; -import java.util.Iterator; -import java.util.UUID; +import java.util.*; /** * Context is used to hold context data (such as {@link Breadcrumb}s) @@ -22,7 +21,7 @@ public class Context implements Serializable { /** * UUID of the last event sent to the Sentry server, if any. */ - private UUID lastEventId; + private volatile UUID lastEventId; /** * Ring buffer of {@link Breadcrumb} objects. @@ -32,7 +31,7 @@ public class Context implements Serializable { /** * User active in the current context, if any. */ - private User user; + private volatile User user; /** * Create a new (empty) Context object with the default Breadcrumb limit. @@ -53,7 +52,7 @@ public Context(int breadcrumbLimit) { /** * Clear state from this context. */ - public void clear() { + public synchronized void clear() { breadcrumbs.clear(); lastEventId = null; user = null; @@ -64,8 +63,10 @@ public void clear() { * * @return Iterator of {@link Breadcrumb}s. */ - public Iterator getBreadcrumbs() { - return breadcrumbs.iterator(); + public synchronized Iterator getBreadcrumbs() { + List copyList = new ArrayList<>(breadcrumbs.size()); + copyList.addAll(breadcrumbs); + return copyList.iterator(); } /** @@ -73,7 +74,7 @@ public Iterator getBreadcrumbs() { * * @param breadcrumb Breadcrumb object to record */ - public void recordBreadcrumb(Breadcrumb breadcrumb) { + public synchronized void recordBreadcrumb(Breadcrumb breadcrumb) { breadcrumbs.add(breadcrumb); } From 76880b3af307b4d771e7508f4978588adc595bfc Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 30 Jun 2017 12:30:03 -0500 Subject: [PATCH 1666/2152] =?UTF-8?q?Add=20support=20for=20buildTypes=20an?= =?UTF-8?q?d=20flavors=20in=20the=20Android=20Gradle=20plugin.=20=E2=80=A6?= =?UTF-8?q?=20(#440)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add support for buildTypes and flavors in the Android Gradle plugin. #437 * Remove print. --- .../sentry/android/gradle/SentryPlugin.groovy | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 2015450ba3c..c0b5a0d372a 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -162,7 +162,30 @@ class SentryPlugin implements Plugin { type: Exec) { description "Write references to proguard UUIDs to the android assets." workingDir project.rootDir - environment("SENTRY_PROPERTIES", "${project.rootDir.toPath()}/sentry.properties") + + def variantName = variant.buildType.name + def flavorName = variant.flavorName + def propName = "sentry.properties" + def possibleProps = [ + "${project.rootDir.toPath()}/app/src/${variantName}/${propName}", + "${project.rootDir.toPath()}/app/src/${variantName}/${flavorName}/${propName}", + "${project.rootDir.toPath()}/app/src/${flavorName}/${variantName}/${propName}", + "${project.rootDir.toPath()}/src/${variantName}/${propName}", + "${project.rootDir.toPath()}/src/${variantName}/${flavorName}/${propName}", + "${project.rootDir.toPath()}/src/${flavorName}/${variantName}/${propName}", + "${project.rootDir.toPath()}/${propName}" + ] + + def propsFile = null + possibleProps.each { + if (propsFile == null && new File(it).isFile()) { + propsFile = it + } + } + + if (propsFile != null) { + environment("SENTRY_PROPERTIES", propsFile) + } def args = [ cli, From 9fd5109634be27519e298484527df5328f1c57d2 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 30 Jun 2017 12:37:46 -0500 Subject: [PATCH 1667/2152] Bump plugin to 1.0.0. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index a6668bf4251..a8523cb2c0e 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.0.0-beta +version = 1.0.0 From dd91457830f5bc24ea008d120b2fb48ee5430a05 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 30 Jun 2017 12:45:07 -0500 Subject: [PATCH 1668/2152] Update Gradle plugin in docs. --- docs/modules/android.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 5291f7934ba..9e75c803f70 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -142,7 +142,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.0.0-beta' + classpath 'io.sentry:sentry-android-gradle-plugin:1.0.0' } } From 168dd12f15007eff7bdce074e21a5e9ac4014960 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 30 Jun 2017 12:45:50 -0500 Subject: [PATCH 1669/2152] Fix CHANGES for 1.2.2. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index c5a7517bda1..cb3ccf5bb11 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.2.2 ------------- -- +- Make operations on Context threadsafe. Version 1.2.1 ------------- From 75931420382a8cf72c613fa2da56313637e6d621 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 30 Jun 2017 13:25:11 -0500 Subject: [PATCH 1670/2152] Bump docs to 1.2.2 --- docs/modules/android.rst | 6 +++--- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 9e75c803f70..6a296834a8e 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.2.1`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.2.2`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.2.1' + compile 'io.sentry:sentry-android:1.2.2' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 9346904af85..baaa1395078 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.2.1 + 1.2.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.2.1' + compile 'io.sentry:sentry-appengine:1.2.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.2.1" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.2.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 7a77b027fe3..f47b00c1dc8 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -24,22 +24,22 @@ Using Maven: io.sentry sentry - 1.2.1 + 1.2.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.2.1' + compile 'io.sentry:sentry:1.2.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.2.1" + libraryDependencies += "io.sentry" % "sentry" % "1.2.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index c3a7b277527..5908ff2c9f0 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -24,22 +24,22 @@ Using Maven: io.sentry sentry-log4j - 1.2.1 + 1.2.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.2.1' + compile 'io.sentry:sentry-log4j:1.2.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.2.1" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.2.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index ff718799618..0c4ea685971 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -24,22 +24,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.2.1 + 1.2.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.2.1' + compile 'io.sentry:sentry-log4j2:1.2.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.2.1" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.2.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 3c44e5163cd..6025ac7154f 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -24,22 +24,22 @@ Using Maven: io.sentry sentry-logback - 1.2.1 + 1.2.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.2.1' + compile 'io.sentry:sentry-logback:1.2.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.2.1" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.2.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index c8a47b3cbfe..09fda6dd2bf 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -15,22 +15,22 @@ Using Maven: io.sentry sentry - 1.2.1 + 1.2.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.2.1' + compile 'io.sentry:sentry:1.2.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.2.1" + libraryDependencies += "io.sentry" % "sentry" % "1.2.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From a7cd552edd03868cdf55e37594513dddf6d4c9ce Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 30 Jun 2017 13:27:07 -0500 Subject: [PATCH 1671/2152] [maven-release-plugin] prepare release v1.2.2 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 104b031ef16..b57f49f89e9 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.2.2-SNAPSHOT + 1.2.2 pom Sentry-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.2.2 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index a648893fbb1..290c56a8051 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.2-SNAPSHOT + 1.2.2 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 03063c8a6ef..af519e87ba2 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.2.2-SNAPSHOT + 1.2.2 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 120521e43f1..51568a25901 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.2-SNAPSHOT + 1.2.2 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index b2bb0ecdc94..5a476002709 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.2-SNAPSHOT + 1.2.2 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 1100b911b9a..372fbf6655e 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.2-SNAPSHOT + 1.2.2 sentry-logback diff --git a/sentry/pom.xml b/sentry/pom.xml index abd5fbe93ec..a38e63ab1f7 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.2-SNAPSHOT + 1.2.2 sentry From ee7bb41cced7a0f07742e19cd34dfbbaafc09724 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 30 Jun 2017 13:27:07 -0500 Subject: [PATCH 1672/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index b57f49f89e9..dbfc09855c0 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.2.2 + 1.2.3-SNAPSHOT pom Sentry-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.2.2 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 290c56a8051..e1a9bd74ede 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.2 + 1.2.3-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index af519e87ba2..bc63a390f5f 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.2.2 + 1.2.3-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 51568a25901..63c548b9520 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.2 + 1.2.3-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 5a476002709..00f672ea32a 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.2 + 1.2.3-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 372fbf6655e..677f0ba329b 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.2 + 1.2.3-SNAPSHOT sentry-logback diff --git a/sentry/pom.xml b/sentry/pom.xml index a38e63ab1f7..24013ff607a 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.2 + 1.2.3-SNAPSHOT sentry From a2daf9d9798a76d78763164e500a4ea91bab6cd7 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 30 Jun 2017 13:27:14 -0500 Subject: [PATCH 1673/2152] Bump CHANGES to 1.2.3 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index cb3ccf5bb11..c0242f4141b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.2.3 +------------- + +- + Version 1.2.2 ------------- From 00a6c404d03066beb16093e76bd1f0148efc693c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 3 Jul 2017 13:43:47 -0500 Subject: [PATCH 1674/2152] Add ability to set tags and extra data on the Context. --- .../main/java/io/sentry/context/Context.java | 85 +++++++++++++++++-- .../event/helper/ContextBuilderHelper.java | 27 ++++-- 2 files changed, 100 insertions(+), 12 deletions(-) diff --git a/sentry/src/main/java/io/sentry/context/Context.java b/sentry/src/main/java/io/sentry/context/Context.java index 20c5847db91..9bfa2146f25 100644 --- a/sentry/src/main/java/io/sentry/context/Context.java +++ b/sentry/src/main/java/io/sentry/context/Context.java @@ -17,21 +17,30 @@ public class Context implements Serializable { * The number of {@link Breadcrumb}s to keep in the ring buffer by default. */ private static final int DEFAULT_BREADCRUMB_LIMIT = 100; - + /** + * The number of {@link Breadcrumb}s to keep in the ring buffer. + */ + private final int breadcrumbLimit; /** * UUID of the last event sent to the Sentry server, if any. */ private volatile UUID lastEventId; - /** * Ring buffer of {@link Breadcrumb} objects. */ - private CircularFifoQueue breadcrumbs; - + private volatile CircularFifoQueue breadcrumbs; /** * User active in the current context, if any. */ private volatile User user; + /** + * Tags to add to any future {@link io.sentry.event.Event}s. + */ + private volatile Map tags; + /** + * Extra data to add to any future {@link io.sentry.event.Event}s. + */ + private volatile Map extra; /** * Create a new (empty) Context object with the default Breadcrumb limit. @@ -46,16 +55,18 @@ public Context() { * @param breadcrumbLimit Number of Breadcrumb objects to retain in ring buffer. */ public Context(int breadcrumbLimit) { - breadcrumbs = new CircularFifoQueue<>(breadcrumbLimit); + this.breadcrumbLimit = breadcrumbLimit; } /** * Clear state from this context. */ public synchronized void clear() { - breadcrumbs.clear(); + breadcrumbs = null; lastEventId = null; user = null; + tags = null; + extra = null; } /** @@ -64,17 +75,79 @@ public synchronized void clear() { * @return Iterator of {@link Breadcrumb}s. */ public synchronized Iterator getBreadcrumbs() { + if (breadcrumbs == null || breadcrumbs.isEmpty()) { + return Collections.emptyIterator(); + } + List copyList = new ArrayList<>(breadcrumbs.size()); copyList.addAll(breadcrumbs); return copyList.iterator(); } + /** + * Return tags attached to this Context. + * + * @return tags attached to this Context. + */ + public synchronized Map getTags() { + if (tags == null || tags.isEmpty()) { + return Collections.emptyMap(); + } + + return Collections.unmodifiableMap(tags); + } + + /** + * Return extra data attached to this Context. + * + * @return extra data attached to this Context. + */ + public synchronized Map getExtra() { + if (extra == null || extra.isEmpty()) { + return Collections.emptyMap(); + } + + return Collections.unmodifiableMap(extra); + } + + /** + * Add tag to the current Context. + * + * @param name tag name + * @param value tag value + */ + public synchronized void addTag(String name, String value) { + if (tags == null) { + tags = new HashMap<>(); + } + + tags.put(name, value); + } + + /** + * Add extra data to the current Context. + * + * @param name extra name + * @param value extra value + */ + public synchronized void addExtra(String name, Object value) { + if (extra == null) { + extra = new HashMap<>(); + } + + extra.put(name, value); + } + /** * Record a single {@link Breadcrumb} into this context. * * @param breadcrumb Breadcrumb object to record */ public synchronized void recordBreadcrumb(Breadcrumb breadcrumb) { + if (breadcrumbs == null) { + breadcrumbs = new CircularFifoQueue<>(breadcrumbLimit); + } + breadcrumbs.add(breadcrumb); } diff --git a/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java b/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java index ab449dfc793..3f9a9bb838b 100644 --- a/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java +++ b/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Map; /** * {@link EventBuilderHelper} that extracts and sends any data attached to the @@ -33,21 +34,35 @@ public ContextBuilderHelper(SentryClient sentryClient) { @Override public void helpBuildingEvent(EventBuilder eventBuilder) { - List breadcrumbs = new ArrayList<>(); Context context = sentryClient.getContext(); Iterator breadcrumbIterator = context.getBreadcrumbs(); - while (breadcrumbIterator.hasNext()) { - breadcrumbs.add(breadcrumbIterator.next()); - } - - if (!breadcrumbs.isEmpty()) { + if (breadcrumbIterator.hasNext()) { + List breadcrumbs = new ArrayList<>(); + while (breadcrumbIterator.hasNext()) { + breadcrumbs.add(breadcrumbIterator.next()); + } eventBuilder.withBreadcrumbs(breadcrumbs); } + if (context.getUser() != null) { eventBuilder.withSentryInterface(fromUser(context.getUser())); } + + Map tags = context.getTags(); + if (!tags.isEmpty()) { + for (Map.Entry entry : tags.entrySet()) { + eventBuilder.withTag(entry.getKey(), entry.getValue()); + } + } + + Map extra = context.getExtra(); + if (!extra.isEmpty()) { + for (Map.Entry entry : extra.entrySet()) { + eventBuilder.withExtra(entry.getKey(), entry.getValue()); + } + } } /** From ec610c6260ca6dab7b3dfb41b881c171d85712ff Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 5 Jul 2017 11:49:44 -0500 Subject: [PATCH 1675/2152] Add ability to set ``extra`` data via options and on ``SentryClient`` instances. --- CHANGES | 2 +- docs/config.rst | 12 +++++ .../io/sentry/DefaultSentryClientFactory.java | 22 +++++++- .../src/main/java/io/sentry/SentryClient.java | 54 ++++++++++++++++--- .../src/main/java/io/sentry/event/Event.java | 1 - sentry/src/main/java/io/sentry/util/Util.java | 36 +++++++++---- .../DefaultSentryClientFactoryTest.java | 11 +++- .../test/java/io/sentry/SentryClientTest.java | 4 ++ 8 files changed, 118 insertions(+), 24 deletions(-) diff --git a/CHANGES b/CHANGES index c0242f4141b..30ea9a4551f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.2.3 ------------- -- +- Add ability to set ``extra`` data via options and on ``SentryClient`` instances. Version 1.2.2 ------------- diff --git a/docs/config.rst b/docs/config.rst index 4a03deebeac..fbdec6320f2 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -204,6 +204,18 @@ Note that how these extra tags are used depends on which integration you are using. For example: when using a logging integration any SLF4J MDC keys that are in the extra tags set will be extracted and set as tags on events. +Extra Data +~~~~~~~~~~ + +To set extra data that will be sent with each event (but not as tags), use the +``extra`` option with comma separated pairs of keys and values that are joined +by a colon: + +:: + + extra=key1:value1,key2:value2 + + "In Application" Stack Frames ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index 04825d0eafd..f869bca3e3d 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -32,7 +32,6 @@ * In most cases this is the implementation to use or extend for additional features. */ public class DefaultSentryClientFactory extends SentryClientFactory { - //TODO: Add support for tags set by default /** * Protocol setting to disable security checks over an SSL connection. */ @@ -206,6 +205,10 @@ public class DefaultSentryClientFactory extends SentryClientFactory { * Option to set extras to extract and send as tags, where applicable. */ public static final String EXTRATAGS_OPTION = "extratags"; + /** + * Option to set extra data to be sent to Sentry. + */ + public static final String EXTRA_OPTION = "extra"; private static final Logger logger = LoggerFactory.getLogger(DefaultSentryClientFactory.class); private static final String FALSE = Boolean.FALSE.toString(); @@ -276,6 +279,13 @@ protected SentryClient configureSentryClient(SentryClient sentryClient, Dsn dsn) } } + Map extra = getExtra(dsn); + if (!extra.isEmpty()) { + for (Map.Entry extraEntry : extra.entrySet()) { + sentryClient.addExtra(extraEntry.getKey(), extraEntry.getValue()); + } + } + return sentryClient; } @@ -728,6 +738,16 @@ protected Set getExtraTags(Dsn dsn) { return Util.parseExtraTags(Lookup.lookup(EXTRATAGS_OPTION, dsn)); } + /** + * Extra data to send with {@link io.sentry.event.Event}s. + * + * @param dsn Sentry server DSN which may contain options. + * @return Extra data to send with {@link io.sentry.event.Event}s. + */ + protected Map getExtra(Dsn dsn) { + return Util.parseExtra(Lookup.lookup(EXTRA_OPTION, dsn)); + } + /** * Whether to compress requests sent to the Sentry Server. * diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 950b9c68c74..c554585f00f 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -56,7 +56,7 @@ public class SentryClient { /** * Additional tags to be sent to sentry. *

    - * Might be empty in which case no tags are sent. + * Might be empty in which case no tags are added to the events. */ protected Map tags = new HashMap<>(); /** @@ -66,6 +66,12 @@ public class SentryClient { * the {@link #extraTags} set will be extracted and set as tags on the {@link Event}. */ protected Set extraTags = new HashSet<>(); + /** + * Extra data to be sent to sentry. + *

    + * Might be empty in which case no extra data is added to the events. + */ + protected Map extra = new HashMap<>(); /** * Set of callbacks that are checked before each {@link Event} is sent to Sentry. */ @@ -159,6 +165,10 @@ public void sendEvent(EventBuilder eventBuilder) { eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); } + for (Map.Entry extraEntry : extra.entrySet()) { + eventBuilder.withExtra(extraEntry.getKey(), extraEntry.getValue()); + } + runBuilderHelpers(eventBuilder); Event event = eventBuilder.build(); sendEvent(event); @@ -261,6 +271,10 @@ public Set getExtraTags() { return Collections.unmodifiableSet(extraTags); } + public Map getExtra() { + return extra; + } + public void setRelease(String release) { this.release = release; } @@ -300,6 +314,18 @@ public void setTags(Map tags) { } } + /** + * Add an extra to extract and send as tags on all future {@link Event}s, where applicable. + *

    + * For example: when using a logging integration any {@link org.slf4j.MDC} keys that are in + * the {@link #extraTags} set will be extracted and set as tags on the {@link Event}. + * + * @param extraName Extra name + */ + public void addExtraTag(String extraName) { + this.extraTags.add(extraName); + } + /** * Set the extras to extract and send as tags on all future {@link Event}s, where applicable. *

    @@ -317,15 +343,26 @@ public void setExtraTags(Set extraTags) { } /** - * Add an extra to extract and send as tags on all future {@link Event}s, where applicable. - *

    - * For example: when using a logging integration any {@link org.slf4j.MDC} keys that are in - * the {@link #extraTags} set will be extracted and set as tags on the {@link Event}. + * Add to the extra data that will be sent with all future {@link Event}s. * - * @param extraName Extra name + * @param name Data name + * @param value Data value */ - public void addExtraTag(String extraName) { - this.extraTags.add(extraName); + public void addExtra(String name, Object value) { + this.extra.put(name, value); + } + + /** + * Set the extra data that will be sent with all future {@link Event}s. + * + * @param extra Map of extra data + */ + public void setExtra(Map extra) { + if (extra == null) { + this.extra = new HashMap<>(); + } else { + this.extra = extra; + } } /** @@ -355,6 +392,7 @@ public String toString() { + ", serverName='" + serverName + '\'' + ", tags=" + tags + ", extraTags=" + extraTags + + ", extra=" + extra + ", connection=" + connection + ", builderHelpers=" + builderHelpers + ", contextManager=" + contextManager diff --git a/sentry/src/main/java/io/sentry/event/Event.java b/sentry/src/main/java/io/sentry/event/Event.java index 5a9feb97ba9..16430759ce3 100644 --- a/sentry/src/main/java/io/sentry/event/Event.java +++ b/sentry/src/main/java/io/sentry/event/Event.java @@ -102,7 +102,6 @@ public class Event implements Serializable { * by {@link #readObject(ObjectInputStream)} and {@link #writeObject(ObjectOutputStream)}. */ private transient Map extra = new HashMap<>(); - /** * Event fingerprint, a list of strings used to dictate the deduplicating for this event. */ diff --git a/sentry/src/main/java/io/sentry/util/Util.java b/sentry/src/main/java/io/sentry/util/Util.java index 6e866af1139..ff8efe876ba 100644 --- a/sentry/src/main/java/io/sentry/util/Util.java +++ b/sentry/src/main/java/io/sentry/util/Util.java @@ -27,29 +27,43 @@ public static boolean isNullOrEmpty(String string) { return string == null || string.length() == 0; // string.isEmpty() in Java 6 } - /** - * Parses the provided tags string into a Map of String -> String. - * - * @param tagsString comma-delimited key-value pairs, e.g. "tag1:value1,tag2:value2". - * @return Map of tags e.g. (tag1 -> value1, tag2 -> value2) - */ - public static Map parseTags(String tagsString) { - if (isNullOrEmpty(tagsString)) { + private static Map parseCsv(String inputString, String typeName) { + if (isNullOrEmpty(inputString)) { return Collections.emptyMap(); } - String[] entries = tagsString.split(","); - Map map = new LinkedHashMap(); + String[] entries = inputString.split(","); + Map map = new LinkedHashMap<>(); for (String entry : entries) { String[] split = entry.split(":"); if (split.length != 2) { - throw new IllegalArgumentException("Invalid tags entry: " + entry); + throw new IllegalArgumentException("Invalid " + typeName + " entry: " + entry); } map.put(split[0], split[1]); } return map; } + /** + * Parses the provided tags string into a Map of String -> String. + * + * @param tagsString comma-delimited key-value pairs, e.g. "tag1:value1,tag2:value2". + * @return Map of tags e.g. (tag1 -> value1, tag2 -> value2) + */ + public static Map parseTags(String tagsString) { + return parseCsv(tagsString, "tags"); + } + + /** + * Parses the provided extras string into a Map of String -> String. + * + * @param extrasString comma-delimited key-value pairs, e.g. "extra1:value1,extra2:value2". + * @return Map of extras e.g. (extra1 -> value1, extra2 -> value2) + */ + public static Map parseExtra(String extrasString) { + return parseCsv(extrasString, "extras"); + } + /** * Parses the provided extraTags string into a Set of Strings. * diff --git a/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java b/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java index adaef981fe4..d7cef05c5db 100644 --- a/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java +++ b/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java @@ -18,10 +18,11 @@ public void testFieldsFromDsn() throws Exception { String serverName = "serv"; String tags = "foo:bar,qux:baz"; String extraTags = "aaa,bbb"; + String extras = "red:blue,green:yellow"; String dsn = String.format("https://user:pass@example.com/1?" + - "release=%s&dist=%s&environment=%s&servername=%s&tags=%s&extratags=%s", - release, dist, environment, serverName, tags, extraTags); + "release=%s&dist=%s&environment=%s&servername=%s&tags=%s&extratags=%s&extra=%s", + release, dist, environment, serverName, tags, extraTags, extras); SentryClient sentryClient = DefaultSentryClientFactory.sentryClient(dsn); assertThat(sentryClient.getRelease(), is(release)); @@ -39,5 +40,11 @@ public void testFieldsFromDsn() throws Exception { extraTagsSet.add("bbb"); assertThat(sentryClient.getExtraTags(), is(extraTagsSet)); + Map extrasMap = new HashMap<>(); + extrasMap.put("red", "blue"); + extrasMap.put("green", "yellow"); + assertThat(sentryClient.getExtra(), is(extrasMap)); + + } } diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.java b/sentry/src/test/java/io/sentry/SentryClientTest.java index 2f80ed7c49f..6f51adc7b7b 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.java +++ b/sentry/src/test/java/io/sentry/SentryClientTest.java @@ -176,6 +176,9 @@ public void testFields() throws Exception { final Map tags = new HashMap<>(); tags.put("name", "value"); sentryClient.setTags(tags); + final Map extras = new HashMap<>(); + extras.put("name", "value"); + sentryClient.setExtra(extras); sentryClient.sendMessage("message"); @@ -189,6 +192,7 @@ public void testFields() throws Exception { assertThat(event.getDist(), equalTo(dist)); assertThat(event.getRelease(), equalTo(release)); assertThat(event.getTags(), equalTo(tags)); + assertThat(event.getExtra(), equalTo(extras)); }}; } From 328f8b0bbcd54ec60105931f1748fbe169c1862d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 5 Jul 2017 12:20:47 -0500 Subject: [PATCH 1676/2152] Deprecate ``extratags`` in favor of the more clear ``mdctags``. (#447) --- CHANGES | 1 + docs/config.rst | 24 +++++--- docs/modules/log4j.rst | 2 +- docs/modules/log4j2.rst | 2 +- docs/modules/logback.rst | 2 +- .../java/io/sentry/log4j/SentryAppender.java | 2 +- .../SentryAppenderEventBuildingTest.java | 5 +- .../java/io/sentry/log4j2/SentryAppender.java | 2 +- .../SentryAppenderEventBuildingTest.java | 3 +- .../io/sentry/logback/SentryAppender.java | 2 +- .../SentryAppenderEventBuildingTest.java | 3 +- .../io/sentry/DefaultSentryClientFactory.java | 47 ++++++++++---- .../src/main/java/io/sentry/SentryClient.java | 61 +++++++++++-------- .../java/io/sentry/jul/SentryHandler.java | 2 +- sentry/src/main/java/io/sentry/util/Util.java | 17 +++++- .../DefaultSentryClientFactoryTest.java | 3 +- 16 files changed, 116 insertions(+), 62 deletions(-) diff --git a/CHANGES b/CHANGES index 30ea9a4551f..747ca79133f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ Version 1.2.3 ------------- +- Deprecate ``extratags`` in favor of the more clear ``mdctags``. - Add ability to set ``extra`` data via options and on ``SentryClient`` instances. Version 1.2.2 diff --git a/docs/config.rst b/docs/config.rst index fbdec6320f2..48a967d14f5 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -190,19 +190,27 @@ comma separated pairs of keys and values that are joined by a colon: tags=tag1:value1,tag2:value2 -Extra Tags -~~~~~~~~~~ +MDC Tags +~~~~~~~~ -To set extras that are extracted and used as additional tags, use the -``extratags`` option with comma separated key names. +To set tag names that are extracted from the SLF4J MDC system, use the +``mdctags`` option with comma separated key names. Note that this option +is only useful when are you using one of the logging integrations. :: - extratags=foo,bar + mdctags=foo,bar + +.. sourcecode:: java + + import org.slf4j.MDC; + + MDC.put("foo", "value1"); + MDC.put("bar", "value2"); + + // This sends an event where the 'foo' and 'bar' MDC values are set as additional tags + logger.error("This is a test"); -Note that how these extra tags are used depends on which integration you are -using. For example: when using a logging integration any SLF4J MDC keys that -are in the extra tags set will be extracted and set as tags on events. Extra Data ~~~~~~~~~~ diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 5908ff2c9f0..d3dab6902f3 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -115,7 +115,7 @@ Mapped Tags ~~~~~~~~~~~ By default all MDC parameters are stored under the "Additional Data" tab in Sentry. By -specifying the ``extratags`` option in your configuration you can +specifying the ``mdctags`` option in your configuration you can choose which MDC keys to send as tags instead, which allows them to be used as filters within the Sentry UI. diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 0c4ea685971..0856db7bf46 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -87,7 +87,7 @@ Mapped Tags ~~~~~~~~~~~ By default all MDC parameters are stored under the "Additional Data" tab in Sentry. By -specifying the ``extratags`` option in your configuration you can +specifying the ``mdctags`` option in your configuration you can choose which MDC keys to send as tags instead, which allows them to be used as filters within the Sentry UI. diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 6025ac7154f..6fb5e4d4476 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -90,7 +90,7 @@ Mapped Tags ~~~~~~~~~~~ By default all MDC parameters are stored under the "Additional Data" tab in Sentry. By -specifying the ``extratags`` option in your configuration you can +specifying the ``mdctags`` option in your configuration you can choose which MDC keys to send as tags instead, which allows them to be used as filters within the Sentry UI. diff --git a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java index c8466a6cf74..6e79e3ffb61 100644 --- a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java +++ b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java @@ -135,7 +135,7 @@ protected EventBuilder createEventBuilder(LoggingEvent loggingEvent) { eventBuilder.withExtra(LOG4J_NDC, loggingEvent.getNDC()); } - Set extraTags = Sentry.getStoredClient().getExtraTags(); + Set extraTags = Sentry.getStoredClient().getMdcTags(); @SuppressWarnings("unchecked") Map properties = (Map) loggingEvent.getProperties(); for (Map.Entry mdcEntry : properties.entrySet()) { diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java index ea84d8a3345..e2c1d3b2d56 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java @@ -3,7 +3,6 @@ import io.sentry.BaseTest; import io.sentry.Sentry; import io.sentry.SentryClient; -import io.sentry.environment.SentryEnvironment; import io.sentry.event.interfaces.SentryStackTraceElement; import mockit.*; import io.sentry.event.Event; @@ -246,7 +245,7 @@ public void testExtraTagObtainedFromMdc() throws Exception { properties.put("other_property", "10ebc4f6-a915-46d0-bb60-75bc9bd71371"); new NonStrictExpectations() {{ - mockSentryClient.getExtraTags(); + mockSentryClient.getMdcTags(); result = extraTags; }}; @@ -270,7 +269,7 @@ public void testExtraTagObtainedFromMdcConvertedToString(@Injectable final Objec new NonStrictExpectations() {{ extraTagValue.toString(); result = "3c8981b4-01ad-47ec-8a3a-77a0bbcb42e2"; - mockSentryClient.getExtraTags(); + mockSentryClient.getMdcTags(); result = extraTags; }}; diff --git a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java index 3b3fc68ecc6..eb5b47a2b5a 100644 --- a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -183,7 +183,7 @@ protected EventBuilder createEventBuilder(LogEvent event) { if (event.getContextMap() != null) { for (Map.Entry contextEntry : event.getContextMap().entrySet()) { - if (Sentry.getStoredClient().getExtraTags().contains(contextEntry.getKey())) { + if (Sentry.getStoredClient().getMdcTags().contains(contextEntry.getKey())) { eventBuilder.withTag(contextEntry.getKey(), contextEntry.getValue()); } else { eventBuilder.withExtra(contextEntry.getKey(), contextEntry.getValue()); diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java index 0c9e2f3b9f5..d1a10de27ae 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java @@ -3,7 +3,6 @@ import io.sentry.BaseTest; import io.sentry.Sentry; import io.sentry.SentryClient; -import io.sentry.environment.SentryEnvironment; import io.sentry.event.interfaces.*; import mockit.Injectable; import mockit.NonStrictExpectations; @@ -251,7 +250,7 @@ public void testExtraTagObtainedFromMdc() throws Exception { mdc.put("other_property", "395856e8-fa1d-474f-8fa9-c062b4886527"); new NonStrictExpectations() {{ - mockSentryClient.getExtraTags(); + mockSentryClient.getMdcTags(); result = extraTags; }}; diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index 4552b73aac0..79f7e4955fd 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -153,7 +153,7 @@ protected EventBuilder createEventBuilder(ILoggingEvent iLoggingEvent) { } for (Map.Entry mdcEntry : iLoggingEvent.getMDCPropertyMap().entrySet()) { - if (Sentry.getStoredClient().getExtraTags().contains(mdcEntry.getKey())) { + if (Sentry.getStoredClient().getMdcTags().contains(mdcEntry.getKey())) { eventBuilder.withTag(mdcEntry.getKey(), mdcEntry.getValue()); } else { eventBuilder.withExtra(mdcEntry.getKey(), mdcEntry.getValue()); diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java index 4468154b90f..7e5e3bf6711 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java @@ -8,7 +8,6 @@ import io.sentry.BaseTest; import io.sentry.Sentry; import io.sentry.SentryClient; -import io.sentry.environment.SentryEnvironment; import io.sentry.event.interfaces.*; import mockit.Injectable; import mockit.NonStrictExpectations; @@ -283,7 +282,7 @@ public void testExtraTagObtainedFromMdc() throws Exception { mdcPropertyMap.put("other_property", "cb9c92a1-0182-4e9c-866f-b06b271cd196"); new NonStrictExpectations() {{ - mockSentryClient.getExtraTags(); + mockSentryClient.getMdcTags(); result = extraTags; }}; diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index f869bca3e3d..9a666c28262 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -7,7 +7,6 @@ import io.sentry.context.ContextManager; import io.sentry.context.ThreadLocalContextManager; import io.sentry.dsn.Dsn; -import io.sentry.event.Event; import io.sentry.event.helper.ContextBuilderHelper; import io.sentry.event.helper.HttpEventBuilderHelper; import io.sentry.event.interfaces.*; @@ -202,9 +201,15 @@ public class DefaultSentryClientFactory extends SentryClientFactory { */ public static final String TAGS_OPTION = "tags"; /** - * Option to set extras to extract and send as tags, where applicable. + * Option to set tags that are extracted from the MDC system, where applicable. + * @deprecated prefer {@link DefaultSentryClientFactory#MDCTAGS_OPTION} */ + @Deprecated public static final String EXTRATAGS_OPTION = "extratags"; + /** + * Option to set tags that are extracted from the MDC system, where applicable. + */ + public static final String MDCTAGS_OPTION = "mdctags"; /** * Option to set extra data to be sent to Sentry. */ @@ -272,10 +277,10 @@ protected SentryClient configureSentryClient(SentryClient sentryClient, Dsn dsn) } } - Set extraTags = getExtraTags(dsn); - if (!extraTags.isEmpty()) { - for (String extraTag : extraTags) { - sentryClient.addExtraTag(extraTag); + Set mdcTags = getMdcTags(dsn); + if (!mdcTags.isEmpty()) { + for (String mdcTag : mdcTags) { + sentryClient.addMdcTag(mdcTag); } } @@ -726,16 +731,34 @@ protected Map getTags(Dsn dsn) { } /** - * Extras to extract and send as tags on {@link io.sentry.event.Event}s, where applicable. - *

    - * For example: when using a logging integration any {@link org.slf4j.MDC} keys that are in - * the {@link #getExtraTags(Dsn)} set will be extracted and set as tags on the {@link Event}. + * Tags to extract from the MDC system and set on {@link io.sentry.event.Event}s, where applicable. * * @param dsn Sentry server DSN which may contain options. - * @return Extras to use as tags on {@link io.sentry.event.Event}s, where applicable. + * @return Tags to extract from the MDC system and set on {@link io.sentry.event.Event}s, where applicable. + * @deprecated prefer {@link DefaultSentryClientFactory#getMdcTags(Dsn)} */ + @Deprecated protected Set getExtraTags(Dsn dsn) { - return Util.parseExtraTags(Lookup.lookup(EXTRATAGS_OPTION, dsn)); + return getMdcTags(dsn); + } + + /** + * Tags to extract from the MDC system and set on {@link io.sentry.event.Event}s, where applicable. + * + * @param dsn Sentry server DSN which may contain options. + * @return Tags to extract from the MDC system and set on {@link io.sentry.event.Event}s, where applicable. + */ + protected Set getMdcTags(Dsn dsn) { + String val = Lookup.lookup(MDCTAGS_OPTION, dsn); + if (Util.isNullOrEmpty(val)) { + val = Lookup.lookup(EXTRATAGS_OPTION, dsn); + if (!Util.isNullOrEmpty(val)) { + logger.warn("The '" + EXTRATAGS_OPTION + "' option is deprecated, please use" + + " the '" + MDCTAGS_OPTION + "' option instead."); + } + } + + return Util.parseMdcTags(val); } /** diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index c554585f00f..23303bead64 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -60,12 +60,9 @@ public class SentryClient { */ protected Map tags = new HashMap<>(); /** - * Extras to extract and use as tags, where applicable. - *

    - * For example: when using a logging integration any {@link org.slf4j.MDC} keys that are in - * the {@link #extraTags} set will be extracted and set as tags on the {@link Event}. + * Tags to extract from the MDC system and set on {@link io.sentry.event.Event}s, where applicable. */ - protected Set extraTags = new HashSet<>(); + protected Set mdcTags = new HashSet<>(); /** * Extra data to be sent to sentry. *

    @@ -267,8 +264,8 @@ public Map getTags() { return Collections.unmodifiableMap(tags); } - public Set getExtraTags() { - return Collections.unmodifiableSet(extraTags); + public Set getMdcTags() { + return Collections.unmodifiableSet(mdcTags); } public Map getExtra() { @@ -315,33 +312,49 @@ public void setTags(Map tags) { } /** - * Add an extra to extract and send as tags on all future {@link Event}s, where applicable. - *

    - * For example: when using a logging integration any {@link org.slf4j.MDC} keys that are in - * the {@link #extraTags} set will be extracted and set as tags on the {@link Event}. + * Set the tags to extract from the MDC system and set on {@link io.sentry.event.Event}s, where applicable. * - * @param extraName Extra name + * @param extraTags Set of tags to extract from the MDC system + * @deprecated prefer {@link SentryClient#setMdcTags(Set)} */ - public void addExtraTag(String extraName) { - this.extraTags.add(extraName); + @Deprecated + public void setExtraTags(Set extraTags) { + setMdcTags(extraTags); } /** - * Set the extras to extract and send as tags on all future {@link Event}s, where applicable. - *

    - * For example: when using a logging integration any {@link org.slf4j.MDC} keys that are in - * the {@link #extraTags} set will be extracted and set as tags on the {@link Event}. + * Set the tags to extract from the MDC system and set on {@link io.sentry.event.Event}s, where applicable. * - * @param extraTags Set of extras + * @param mdcTags Set of tags to extract from the MDC system */ - public void setExtraTags(Set extraTags) { - if (extraTags == null) { - this.extraTags = new HashSet<>(); + public void setMdcTags(Set mdcTags) { + if (mdcTags == null) { + this.mdcTags = new HashSet<>(); } else { - this.extraTags = extraTags; + this.mdcTags = mdcTags; } } + /** + * Add a tag to extract from the MDC system and set on {@link io.sentry.event.Event}s, where applicable. + * + * @param extraName Tag name to extract from the MDC system + * @deprecated prefer {@link SentryClient#addMdcTag(String)} + */ + @Deprecated + public void addExtraTag(String extraName) { + addMdcTag(extraName); + } + + /** + * Add a tag to extract from the MDC system and set on {@link io.sentry.event.Event}s, where applicable. + * + * @param tagName Tag name to extract from the MDC system + */ + public void addMdcTag(String tagName) { + this.mdcTags.add(tagName); + } + /** * Add to the extra data that will be sent with all future {@link Event}s. * @@ -391,7 +404,7 @@ public String toString() { + ", environment='" + environment + '\'' + ", serverName='" + serverName + '\'' + ", tags=" + tags - + ", extraTags=" + extraTags + + ", mdcTags=" + mdcTags + ", extra=" + extra + ", connection=" + connection + ", builderHelpers=" + builderHelpers diff --git a/sentry/src/main/java/io/sentry/jul/SentryHandler.java b/sentry/src/main/java/io/sentry/jul/SentryHandler.java index e784f2c351d..f6f922211bd 100644 --- a/sentry/src/main/java/io/sentry/jul/SentryHandler.java +++ b/sentry/src/main/java/io/sentry/jul/SentryHandler.java @@ -152,7 +152,7 @@ protected EventBuilder createEventBuilder(LogRecord record) { Map mdc = MDC.getMDCAdapter().getCopyOfContextMap(); if (mdc != null) { for (Map.Entry mdcEntry : mdc.entrySet()) { - if (Sentry.getStoredClient().getExtraTags().contains(mdcEntry.getKey())) { + if (Sentry.getStoredClient().getMdcTags().contains(mdcEntry.getKey())) { eventBuilder.withTag(mdcEntry.getKey(), mdcEntry.getValue()); } else { eventBuilder.withExtra(mdcEntry.getKey(), mdcEntry.getValue()); diff --git a/sentry/src/main/java/io/sentry/util/Util.java b/sentry/src/main/java/io/sentry/util/Util.java index ff8efe876ba..ed4d7a73ac2 100644 --- a/sentry/src/main/java/io/sentry/util/Util.java +++ b/sentry/src/main/java/io/sentry/util/Util.java @@ -69,15 +69,28 @@ public static Map parseExtra(String extrasString) { * * @param extraTagsString comma-delimited tags * @return Set of Strings representing extra tags + * @deprecated prefer {@link Util#parseMdcTags(String)} */ + @Deprecated public static Set parseExtraTags(String extraTagsString) { - if (isNullOrEmpty(extraTagsString)) { + return parseMdcTags(extraTagsString); + } + + /** + * Parses the provided Strings into a Set of Strings. + * + * @param mdcTagsString comma-delimited tags + * @return Set of Strings representing mdc tags + */ + public static Set parseMdcTags(String mdcTagsString) { + if (isNullOrEmpty(mdcTagsString)) { return Collections.emptySet(); } - return new HashSet<>(Arrays.asList(extraTagsString.split(","))); + return new HashSet<>(Arrays.asList(mdcTagsString.split(","))); } + /** * Parses the provided string value into an integer value. *

    If the string is null or empty this returns the default value.

    diff --git a/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java b/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java index d7cef05c5db..f6817a7ee6b 100644 --- a/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java +++ b/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java @@ -6,7 +6,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; public class DefaultSentryClientFactoryTest extends BaseTest { @@ -38,7 +37,7 @@ public void testFieldsFromDsn() throws Exception { Set extraTagsSet = new HashSet<>(); extraTagsSet.add("aaa"); extraTagsSet.add("bbb"); - assertThat(sentryClient.getExtraTags(), is(extraTagsSet)); + assertThat(sentryClient.getMdcTags(), is(extraTagsSet)); Map extrasMap = new HashMap<>(); extrasMap.put("red", "blue"); From dd8c717f5064d950dcfb6d26a35f549f5ff8ba1b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 5 Jul 2017 14:32:08 -0500 Subject: [PATCH 1677/2152] Handle os.arch value of 'amd64' in the Android Gradle Plugin. (#455) --- .../groovy/io/sentry/android/gradle/SentryPlugin.groovy | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index c0b5a0d372a..b8f088ec4c8 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -51,7 +51,11 @@ class SentryPlugin implements Plugin { if (osName.indexOf("mac") >= 0) { cliSuffix = "Darwin-x86_64" } else if (osName.indexOf("linux") >= 0) { - cliSuffix = "Linux-" + System.getProperty("os.arch") + def arch = System.getProperty("os.arch") + if (arch == "amd64") { + arch = "x86_64" + } + cliSuffix = "Linux-" + arch } else if (osName.indexOf("win") >= 0) { cliSuffix = "Windows-i686.exe" } From 2c69d875b93d34fd25aaf5931c8b6487aaeba34e Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 5 Jul 2017 14:33:19 -0500 Subject: [PATCH 1678/2152] Bump Android Gradle Plugin version. --- docs/modules/android.rst | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 6a296834a8e..9de5917566a 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -142,7 +142,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.0.0' + classpath 'io.sentry:sentry-android-gradle-plugin:1.0.1' } } diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index a8523cb2c0e..61470910452 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.0.0 +version = 1.0.1 From da0cbf06088e3d213b0ee01659031ab6988a735e Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 5 Jul 2017 16:55:48 -0500 Subject: [PATCH 1679/2152] Deprecate context-specific operations directly on the static Sentry class (for clarity). (#446) --- docs/context.rst | 6 +++-- docs/modules/android.rst | 8 +++++-- docs/usage.rst | 8 +++++-- sentry/src/main/java/io/sentry/Sentry.java | 28 ++++++++++++++++------ 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/docs/context.rst b/docs/context.rst index 0b654943339..360cfa7d13f 100644 --- a/docs/context.rst +++ b/docs/context.rst @@ -53,10 +53,12 @@ the current context. Sentry.init(); // Set the current user in the context. - Sentry.setUser(new UserBuilder().setUsername("user1").build()); + Sentry.getContext().setUser( + new UserBuilder().setUsername("user1").build() + ); // Record a breadcrumb without having to look up the context instance manually. - Sentry.record( + Sentry.getContext().recordBreadcrumb( new BreadcrumbBuilder().setMessage("User did something specific again!").build() ); diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 9de5917566a..43f1c11cb46 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -99,10 +99,14 @@ Now you can use ``Sentry`` to capture events anywhere in your application: Record a breadcrumb in the current context which will be sent with the next event(s). By default the last 100 breadcrumbs are kept. */ - Sentry.record(new BreadcrumbBuilder().setMessage("User made an action").build()); + Sentry.getContext().recordBreadcrumb( + new BreadcrumbBuilder().setMessage("User made an action").build() + ); // Set the user in the current context. - Sentry.setUser(new UserBuilder().setEmail("hello@sentry.io").build()); + Sentry.getContext().setUser( + new UserBuilder().setEmail("hello@sentry.io").build() + ); /* This sends a simple event to Sentry using the statically stored instance diff --git a/docs/usage.rst b/docs/usage.rst index 09fda6dd2bf..5d5939bf393 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -91,10 +91,14 @@ your own ``SentryClient`` instance. An example of each style is shown below: Record a breadcrumb in the current context which will be sent with the next event(s). By default the last 100 breadcrumbs are kept. */ - Sentry.record(new BreadcrumbBuilder().setMessage("User made an action").build()); + Sentry.getContext().recordBreadcrumb( + new BreadcrumbBuilder().setMessage("User made an action").build() + ); // Set the user in the current context. - Sentry.setUser(new UserBuilder().setEmail("hello@sentry.io").build()); + Sentry.getContext().setUser( + new UserBuilder().setEmail("hello@sentry.io").build() + ); /* This sends a simple event to Sentry using the statically stored instance diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 2f5c937dd24..9629dbe1e3c 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -1,5 +1,6 @@ package io.sentry; +import io.sentry.context.Context; import io.sentry.dsn.Dsn; import io.sentry.event.Breadcrumb; import io.sentry.event.Event; @@ -105,6 +106,22 @@ public static SentryClient getStoredClient() { return storedClient; } + /** + * Returns the {@link Context} on the statically stored {@link SentryClient}. + * + * @return the {@link Context} on the statically stored {@link SentryClient}. + */ + public static Context getContext() { + return getStoredClient().getContext(); + } + + /** + * Clears the current context. + */ + public static void clearContext() { + getStoredClient().clearContext(); + } + /** * Set the statically stored {@link SentryClient} instance. * @@ -164,7 +181,9 @@ public static void capture(EventBuilder eventBuilder) { * Record a {@link Breadcrumb}. * * @param breadcrumb Breadcrumb to record. + * @deprecated use {@link Sentry#getContext()} and then {@link Context#recordBreadcrumb(Breadcrumb)}. */ + @Deprecated public static void record(Breadcrumb breadcrumb) { getStoredClient().getContext().recordBreadcrumb(breadcrumb); } @@ -173,18 +192,13 @@ public static void record(Breadcrumb breadcrumb) { * Set the {@link User} in the current context. * * @param user User to store. + * @deprecated use {@link Sentry#getContext()} and then {@link Context#setUser(User)}. */ + @Deprecated public static void setUser(User user) { getStoredClient().getContext().setUser(user); } - /** - * Clears the current context. - */ - public static void clearContext() { - getStoredClient().clearContext(); - } - /** * Close the stored {@link SentryClient}'s connections and remove it from static storage. */ From 1eef1a46ee03cbf05c5c4c59859d8d189bd59617 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 10 Jul 2017 14:48:09 -0500 Subject: [PATCH 1680/2152] Fix gradle plugin for users of the Android Studio beta (com.android.tools.build:gradle:3.0.0+) --- .../gradle.properties | 2 +- .../sentry/android/gradle/SentryPlugin.groovy | 29 ++++++++++++------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 61470910452..8aed1564ad8 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.0.1 +version = 1.0.2 diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index b8f088ec4c8..e1c1e2def08 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -5,7 +5,7 @@ import com.android.build.gradle.api.ApplicationVariant import org.apache.commons.compress.utils.IOUtils import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.Task; +import org.gradle.api.Task import org.gradle.api.tasks.Exec import org.apache.tools.ant.taskdefs.condition.Os @@ -30,7 +30,7 @@ class SentryPlugin implements Plugin { // it's okay, we can ignore it. } - def rv = sentryProps.getProperty("cli.executable"); + def rv = sentryProps.getProperty("cli.executable") if (rv != null) { return rv } @@ -39,10 +39,10 @@ class SentryPlugin implements Plugin { // is the case for react-native-sentry for instance def exePath = "${project.rootDir.toPath()}/../node_modules/sentry-cli-binary/bin/sentry-cli" if ((new File(exePath)).exists()) { - return exePath; + return exePath } if ((new File(exePath + ".exe")).exists()) { - return exePath + ".exe"; + return exePath + ".exe" } // next up try a packaged version of sentry-cli @@ -61,19 +61,19 @@ class SentryPlugin implements Plugin { } if (cliSuffix != null) { - def resPath = "/bin/sentry-cli-${cliSuffix}"; + def resPath = "/bin/sentry-cli-${cliSuffix}" def fsPath = SentryPlugin.class.getResource(resPath).getFile() // if we are not in a jar, we can use the file directly if ((new File(fsPath)).exists()) { - return fsPath; + return fsPath } // otherwise we need to unpack into a file def resStream = SentryPlugin.class.getResourceAsStream(resPath) File tempFile = File.createTempFile(".sentry-cli", ".exe") tempFile.deleteOnExit() - def out = new FileOutputStream(tempFile); + def out = new FileOutputStream(tempFile) try { IOUtils.copy(resStream, out) } finally { @@ -83,7 +83,7 @@ class SentryPlugin implements Plugin { return tempFile.getAbsolutePath() } - return "sentry-cli"; + return "sentry-cli" } /** @@ -139,13 +139,22 @@ class SentryPlugin implements Plugin { project.android.applicationVariants.all { ApplicationVariant variant -> def variantOutput = variant.outputs.first() - def manifestPath = variantOutput.processManifest.manifestOutputFile + + def manifestPath + try { + // Android Gradle Plugin < 3.0.0 + manifestPath = variantOutput.processManifest.manifestOutputFile + } catch (Exception ignored) { + // Android Gradle Plugin >= 3.0.0 + manifestPath = new File(variantOutput.processManifest.manifestOutputDirectory, "AndroidManifest.xml") + } + def mappingFile = variant.getMappingFile() def proguardTask = getProguardTask(project, variant) def dexTask = getDexTask(project, variant) if (proguardTask == null) { - return; + return } // create a task to configure proguard automatically unless the user disabled it. From 3f2d8cdca98b15fc9d2053b1dc29cb12c1e78dc3 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 10 Jul 2017 15:02:14 -0500 Subject: [PATCH 1681/2152] Bump Android Gradle Plugin version. --- docs/modules/android.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 43f1c11cb46..15075e99b53 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.0.1' + classpath 'io.sentry:sentry-android-gradle-plugin:1.0.2' } } From 82bd43edcb657a51a957a81106667d36e733a1d4 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 10 Jul 2017 15:48:33 -0500 Subject: [PATCH 1682/2152] Improve docs. --- docs/context.rst | 20 ++++++++++- docs/usage.rst | 34 +++++++++++-------- .../main/java/io/sentry/context/Context.java | 8 ++--- .../event/helper/ContextBuilderHelper.java | 11 ++---- .../src/test/java/io/sentry/ContextTest.java | 16 ++------- 5 files changed, 46 insertions(+), 43 deletions(-) diff --git a/docs/context.rst b/docs/context.rst index 360cfa7d13f..b1b936ea7cb 100644 --- a/docs/context.rst +++ b/docs/context.rst @@ -52,16 +52,25 @@ the current context. // initialize on the first use of the static API, so this isn't strictly necessary. Sentry.init(); + // Note that all fields set on the context are optional. Context data is copied onto + // all future events in the current context (until the context is cleared). + // Set the current user in the context. Sentry.getContext().setUser( new UserBuilder().setUsername("user1").build() ); - // Record a breadcrumb without having to look up the context instance manually. + // Record a breadcrumb in the context. Sentry.getContext().recordBreadcrumb( new BreadcrumbBuilder().setMessage("User did something specific again!").build() ); + // Add extra data to future events in this context. + Sentry.getContext().addExtra("extra", "thing"); + + // Add an additional tag to future events in this context. + Sentry.getContext().addTag("tagName", "tagValue"); + // Send an event with the context data attached. Sentry.capture("New event message"); @@ -80,6 +89,9 @@ the current context. // Get the current context instance. Context context = sentryClient.getContext(); + // Note that all fields set on the context are optional. Context data is copied onto + // all future events in the current context (until the context is cleared). + // Set the current user in the context. context.setUser( new UserBuilder().setUsername("user1").build() @@ -90,6 +102,12 @@ the current context. new BreadcrumbBuilder().setMessage("User did something specific!").build() ); + // Add extra data to future events in this context. + context.addExtra("extra", "thing"); + + // Add an additional tag to future events in this context. + context.addTag("tagName", "tagValue"); + // Send an event with the context data attached. sentryClient.sendMessage("New event message"); diff --git a/docs/usage.rst b/docs/usage.rst index 5d5939bf393..98cfc4493c6 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -3,7 +3,9 @@ Manual Usage **Note:** The following page provides examples on how to configure and use Sentry directly. It is **highly recommended** that you use one of the -:ref:`provided integrations ` instead if possible. +:ref:`provided integrations ` if possible. You can use Sentry's +static API, as shown below, in addition to an integration in order to do things +like record breadcrumbs or set the current user. Installation ------------ @@ -51,9 +53,11 @@ your own ``SentryClient`` instance. An example of each style is shown below: public static void main(String... args) { /* It is recommended that you use the DSN detection system, which - will check the environment variable "SENTRY_DSN" and the Java - System Property "sentry.dsn". This makes it easier to provide - and adjust your DSN without needing to change your code. + will check the environment variable "SENTRY_DSN", the Java + System Property "sentry.dsn", or the "sentry.properties" file + in your classpath. This makes it easier to provide and adjust + your DSN without needing to change your code. See the configuration + page for more information. */ Sentry.init(); @@ -82,15 +86,12 @@ your own ``SentryClient`` instance. An example of each style is shown below: /** * Examples using the (recommended) static API. - * - * Note that the ``Sentry.init`` method must be called before the static API - * is used, otherwise a ``NullPointerException`` will be thrown. */ void logWithStaticAPI() { - /* - Record a breadcrumb in the current context which will be sent - with the next event(s). By default the last 100 breadcrumbs are kept. - */ + // Note that all fields set on the context are optional. Context data is copied onto + // all future events in the current context (until the context is cleared). + + // Record a breadcrumb in the current context. By default the last 100 breadcrumbs are kept. Sentry.getContext().recordBreadcrumb( new BreadcrumbBuilder().setMessage("User made an action").build() ); @@ -100,6 +101,12 @@ your own ``SentryClient`` instance. An example of each style is shown below: new UserBuilder().setEmail("hello@sentry.io").build() ); + // Add extra data to future events in this context. + Sentry.getContext().addExtra("extra", "thing"); + + // Add an additional tag to future events in this context. + Sentry.getContext().addTag("tagName", "tagValue"); + /* This sends a simple event to Sentry using the statically stored instance that was created in the ``main`` method. @@ -122,10 +129,7 @@ your own ``SentryClient`` instance. An example of each style is shown below: // Retrieve the current context. Context context = sentry.getContext(); - /* - Record a breadcrumb in the current context which will be sent - with the next event(s). By default the last 100 breadcrumbs are kept. - */ + // Record a breadcrumb in the current context. By default the last 100 breadcrumbs are kept. context.recordBreadcrumb(new BreadcrumbBuilder().setMessage("User made an action").build()); // Set the user in the current context. diff --git a/sentry/src/main/java/io/sentry/context/Context.java b/sentry/src/main/java/io/sentry/context/Context.java index 9bfa2146f25..babd0a26cde 100644 --- a/sentry/src/main/java/io/sentry/context/Context.java +++ b/sentry/src/main/java/io/sentry/context/Context.java @@ -72,16 +72,16 @@ public synchronized void clear() { /** * Return {@link Breadcrumb}s attached to this Context. * - * @return Iterator of {@link Breadcrumb}s. + * @return List of {@link Breadcrumb}s. */ - public synchronized Iterator getBreadcrumbs() { + public synchronized List getBreadcrumbs() { if (breadcrumbs == null || breadcrumbs.isEmpty()) { - return Collections.emptyIterator(); + return Collections.emptyList(); } List copyList = new ArrayList<>(breadcrumbs.size()); copyList.addAll(breadcrumbs); - return copyList.iterator(); + return copyList; } /** diff --git a/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java b/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java index 3f9a9bb838b..fc654f5e4e5 100644 --- a/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java +++ b/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java @@ -7,8 +7,6 @@ import io.sentry.event.User; import io.sentry.event.interfaces.UserInterface; -import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.Map; @@ -36,16 +34,11 @@ public ContextBuilderHelper(SentryClient sentryClient) { public void helpBuildingEvent(EventBuilder eventBuilder) { Context context = sentryClient.getContext(); - Iterator breadcrumbIterator = context.getBreadcrumbs(); - if (breadcrumbIterator.hasNext()) { - List breadcrumbs = new ArrayList<>(); - while (breadcrumbIterator.hasNext()) { - breadcrumbs.add(breadcrumbIterator.next()); - } + List breadcrumbs = context.getBreadcrumbs(); + if (!breadcrumbs.isEmpty()) { eventBuilder.withBreadcrumbs(breadcrumbs); } - if (context.getUser() != null) { eventBuilder.withSentryInterface(fromUser(context.getUser())); } diff --git a/sentry/src/test/java/io/sentry/ContextTest.java b/sentry/src/test/java/io/sentry/ContextTest.java index 2a232c5782f..fac28c3dbfd 100644 --- a/sentry/src/test/java/io/sentry/ContextTest.java +++ b/sentry/src/test/java/io/sentry/ContextTest.java @@ -30,13 +30,7 @@ public void testBreadcrumbs() { List breadcrumbMatch = new ArrayList<>(); breadcrumbMatch.add(breadcrumb); - List breadcrumbs = new ArrayList<>(); - Iterator iter = context.getBreadcrumbs(); - while (iter.hasNext()) { - breadcrumbs.add(iter.next()); - } - - assertThat(breadcrumbs, equalTo(breadcrumbMatch)); + assertThat(context.getBreadcrumbs(), equalTo(breadcrumbMatch)); } @Test @@ -60,13 +54,7 @@ public void breadcrumbLimit() { List breadcrumbMatch = new ArrayList<>(); breadcrumbMatch.add(breadcrumb2); - List breadcrumbs = new ArrayList<>(); - Iterator iter = context.getBreadcrumbs(); - while (iter.hasNext()) { - breadcrumbs.add(iter.next()); - } - - assertThat(breadcrumbs, equalTo(breadcrumbMatch)); + assertThat(context.getBreadcrumbs(), equalTo(breadcrumbMatch)); } From 25ec9a07cced1632d6b2c22a5a3a60625582a931 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 10 Jul 2017 16:19:19 -0500 Subject: [PATCH 1683/2152] Add tests. --- .../java/io/sentry/SentryClientFactory.java | 9 +++ .../main/java/io/sentry/event/Breadcrumb.java | 23 ++++++ .../helper/ContextBuilderHelperTest.java | 71 +++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 sentry/src/test/java/io/sentry/event/helper/ContextBuilderHelperTest.java diff --git a/sentry/src/main/java/io/sentry/SentryClientFactory.java b/sentry/src/main/java/io/sentry/SentryClientFactory.java index c187313bd2b..1716bc7804b 100644 --- a/sentry/src/main/java/io/sentry/SentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/SentryClientFactory.java @@ -12,6 +12,15 @@ public abstract class SentryClientFactory { private static final Logger logger = LoggerFactory.getLogger(SentryClientFactory.class); + /** + * Creates an instance of Sentry by discovering the DSN. + * + * @return an instance of Sentry. + */ + public static SentryClient sentryClient() { + return sentryClient(null, null); + } + /** * Creates an instance of Sentry using the provided DSN. * diff --git a/sentry/src/main/java/io/sentry/event/Breadcrumb.java b/sentry/src/main/java/io/sentry/event/Breadcrumb.java index 0105db2d580..83fe69d5731 100644 --- a/sentry/src/main/java/io/sentry/event/Breadcrumb.java +++ b/sentry/src/main/java/io/sentry/event/Breadcrumb.java @@ -3,6 +3,7 @@ import java.io.Serializable; import java.util.Date; import java.util.Map; +import java.util.Objects; /** * An object that represents a single breadcrumb. Events may include a list @@ -174,4 +175,26 @@ public Map getData() { return data; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Breadcrumb that = (Breadcrumb) o; + return type == that.type + && Objects.equals(timestamp, that.timestamp) + && level == that.level + && Objects.equals(message, that.message) + && Objects.equals(category, that.category) + && Objects.equals(data, that.data); + } + + @Override + public int hashCode() { + return Objects.hash(type, timestamp, level, message, category, data); + } + } diff --git a/sentry/src/test/java/io/sentry/event/helper/ContextBuilderHelperTest.java b/sentry/src/test/java/io/sentry/event/helper/ContextBuilderHelperTest.java new file mode 100644 index 00000000000..04c438494b7 --- /dev/null +++ b/sentry/src/test/java/io/sentry/event/helper/ContextBuilderHelperTest.java @@ -0,0 +1,71 @@ +package io.sentry.event.helper; + +import io.sentry.SentryClient; +import io.sentry.connection.Connection; +import io.sentry.context.Context; +import io.sentry.context.ContextManager; +import io.sentry.context.SingletonContextManager; +import io.sentry.event.*; +import io.sentry.event.interfaces.UserInterface; +import mockit.Injectable; +import mockit.Tested; +import mockit.Verifications; +import org.testng.annotations.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class ContextBuilderHelperTest { + @Tested + private SentryClient client = null; + @Injectable + private Connection mockConnection = null; + @Injectable + private ContextManager contextManager = new SingletonContextManager(); + + @Test + public void testHelper() { + Context context = client.getContext(); + + final Breadcrumb breadcrumb1 = new BreadcrumbBuilder().setMessage("message1").build(); + client.getContext().recordBreadcrumb(breadcrumb1); + final Breadcrumb breadcrumb2 = new BreadcrumbBuilder().setMessage("message2").build(); + client.getContext().recordBreadcrumb(breadcrumb2); + + final User user = new UserBuilder().setEmail("email").build(); + context.setUser(user); + + context.addExtra("extra1", "value1"); + context.addExtra("extra2", 2); + + context.addTag("tag1", "value1"); + context.addTag("tag2", "value2"); + + ContextBuilderHelper contextBuilderHelper = new ContextBuilderHelper(client); + EventBuilder eventBuilder = new EventBuilder() + .withMessage("event message") + .withLevel(Event.Level.INFO); + contextBuilderHelper.helpBuildingEvent(eventBuilder); + + final Event event = eventBuilder.getEvent(); + final UserInterface userInterface = (UserInterface) event.getSentryInterfaces().get(UserInterface.USER_INTERFACE); + + final Map extra = new HashMap<>(); + extra.put("extra1", "value1"); + extra.put("extra2", 2); + + final Map tags = new HashMap<>(); + tags.put("tag1", "value1"); + tags.put("tag2", "value2"); + + new Verifications() {{ + assertThat(event.getBreadcrumbs(), contains(breadcrumb1, breadcrumb2)); + assertThat(userInterface.getEmail(), equalTo(user.getEmail())); + assertThat(event.getExtra().entrySet(), everyItem(isIn(extra.entrySet()))); + assertThat(event.getTags().entrySet(), everyItem(isIn(tags.entrySet()))); + }}; + } +} From 51fc239fe4aa504609dc1b48dc89e47a1ec704a2 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 11 Jul 2017 11:34:01 -0500 Subject: [PATCH 1684/2152] Don't throw exception when Proguard UUID file doesn't exist. --- .../sentry/android/event/helper/AndroidEventBuilderHelper.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java index 015878a0ac4..bf2ab71857c 100644 --- a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java +++ b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java @@ -163,6 +163,8 @@ private static String[] getProGuardUuids(Context ctx) { if (!Util.isNullOrEmpty(uuid)) { retVal = uuid.split("\\|"); } + } catch (FileNotFoundException e) { + Log.d(TAG, "Proguard UUIDs file not found."); } catch (Exception e) { Log.e(TAG, "Error getting Proguard UUIDs.", e); } From 6adba246707f370f5f70b95a92e1897d6caf7aad Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 11 Jul 2017 13:59:15 -0500 Subject: [PATCH 1685/2152] Add note about sentry-cli configuration to docs. #436 --- docs/modules/android.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 15075e99b53..3710b082e93 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -153,8 +153,9 @@ And declare a dependency in your toplevel ``build.gradle``: The plugin will then automatically generate appropriate ProGuard mapping files and upload them when you run ``gradle assembleRelease``. The credentials for the upload step are loaded from a ``sentry.properties`` file in -your project root. At the very minimum you will need something like this -in there:: +your project root *or* via environment variables, for more information +`see the sentry-cli documentation `_. +At the very minimum you will need something like this:: defaults.project=___PROJECT_NAME___ defaults.org=___ORG_NAME___ From fc66b83de4b62bdea026e3b33ddaa1629822d8a4 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 11 Jul 2017 14:30:32 -0500 Subject: [PATCH 1686/2152] Clarify that logging integrations and manual API can be used simultaneously. #456 --- docs/index.rst | 2 +- docs/modules/jul.rst | 7 +++++-- docs/modules/log4j.rst | 7 +++++-- docs/modules/log4j2.rst | 7 +++++-- docs/modules/logback.rst | 7 +++++-- docs/usage.rst | 2 ++ 6 files changed, 23 insertions(+), 9 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 28c9e8bcb6f..e7332d3d468 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,7 +14,7 @@ Sentry for Java (``sentry-java``) is the official Java SDK for Sentry. At its co a raw client for sending events to Sentry, but it is highly recommended that you use one of the included library or framework integrations listed below if at all possible. -**Note:** ``raven-java`` is no longer maintained. It is highly recommended that +**Note:** The old ``raven-java`` library is no longer maintained. It is highly recommended that you migrate to ``sentry-java`` (which this documentation covers). If you are still using ``raven-java`` you can `find the old documentation here `_. diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index f47b00c1dc8..b0df00679b1 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -3,12 +3,15 @@ java.util.logging The ``sentry`` library provides a `java.util.logging Handler `_ -that sends logged exceptions to Sentry. +that sends logged exceptions to Sentry. Once this integration is configured +you can *also* use Sentry's static API, :ref:`as shown on the usage page `, +in order to do things like record breadcrumbs, set the current user, or manually send +events. The source for ``sentry`` can be found `on Github `_. -**Note:** ``raven`` is no longer maintained. It is highly recommended that +**Note:** The old ``raven`` library is no longer maintained. It is highly recommended that you migrate to ``sentry`` (which this documentation covers). If you are still using ``raven`` you can `find the old documentation here `_. diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index d3dab6902f3..bc562bd4a15 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -4,12 +4,15 @@ Log4j 1.x The ``sentry-log4j`` library provides `Log4j 1.x `_ support for Sentry via an `Appender `_ -that sends logged exceptions to Sentry. +that sends logged exceptions to Sentry. Once this integration is configured +you can *also* use Sentry's static API, :ref:`as shown on the usage page `, +in order to do things like record breadcrumbs, set the current user, or manually send +events. The source can be found `on Github `_. -**Note:** ``raven-log4j`` is no longer maintained. It is highly recommended that +**Note:** The old ``raven-log4j`` library is no longer maintained. It is highly recommended that you migrate to ``sentry-log4j`` (which this documentation covers). If you are still using ``raven-log4j`` you can `find the old documentation here `_. diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 0856db7bf46..1b1f50c07c7 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -4,12 +4,15 @@ Log4j 2.x The ``sentry-log4j2`` library provides `Log4j 2.x `_ support for Sentry via an `Appender `_ -that sends logged exceptions to Sentry. +that sends logged exceptions to Sentry. Once this integration is configured +you can *also* use Sentry's static API, :ref:`as shown on the usage page `, +in order to do things like record breadcrumbs, set the current user, or manually send +events. The source can be found `on Github `_. -**Note:** ``raven-log4j2`` is no longer maintained. It is highly recommended that +**Note:** The old ``raven-log4j2`` library is no longer maintained. It is highly recommended that you migrate to ``sentry-log4j2`` (which this documentation covers). If you are still using ``raven-log4j2`` you can `find the old documentation here `_. diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 6fb5e4d4476..87861b3ee81 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -4,12 +4,15 @@ Logback The ``sentry-logback`` library provides `Logback `_ support for Sentry via an `Appender `_ -that sends logged exceptions to Sentry. +that sends logged exceptions to Sentry. Once this integration is configured +you can *also* use Sentry's static API, :ref:`as shown on the usage page `, +in order to do things like record breadcrumbs, set the current user, or manually send +events. The source can be found `on Github `_. -**Note:** ``raven-logback`` is no longer maintained. It is highly recommended that +**Note:** The old ``raven-logback`` library is no longer maintained. It is highly recommended that you migrate to ``sentry-logback`` (which this documentation covers). If you are still using ``raven-logback`` you can `find the old documentation here `_. diff --git a/docs/usage.rst b/docs/usage.rst index 5d5939bf393..de4148f81b5 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -35,6 +35,8 @@ For other dependency managers see the `central Maven repository Date: Tue, 11 Jul 2017 14:35:40 -0500 Subject: [PATCH 1687/2152] Update usage. --- docs/usage.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index 5b1fd4fadd4..5c583ba7c58 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -3,9 +3,10 @@ Manual Usage **Note:** The following page provides examples on how to configure and use Sentry directly. It is **highly recommended** that you use one of the -:ref:`provided integrations ` if possible. You can use Sentry's -static API, as shown below, in addition to an integration in order to do things -like record breadcrumbs or set the current user. +:ref:`provided integrations ` if possible. Once the integration +is configured you can *also* use Sentry's static API, as shown below, +in order to do things like record breadcrumbs, set the current user, or manually +send events. Installation ------------ From ec530bbb27038dadece669bdfd9c5d166f3e801a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 11 Jul 2017 14:36:56 -0500 Subject: [PATCH 1688/2152] Sync Android gradle plugin version up with sentry-java version. #460 --- docs/modules/android.rst | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 3710b082e93..84179f25b7e 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.0.2' + classpath 'io.sentry:sentry-android-gradle-plugin:1.2.2' } } diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 8aed1564ad8..03dc581e190 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.0.2 +version = 1.2.2-SNAPSHOT From 6eb56f98285ba2a1a7778e361e4995286e97b561 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 11 Jul 2017 14:39:55 -0500 Subject: [PATCH 1689/2152] Fix Android gradle plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 03dc581e190..6b287d44d34 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.2.2-SNAPSHOT +version = 1.2.3-SNAPSHOT From 62ea5b022dd78de2c7dee718cd99d9f1c28f6f66 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 11 Jul 2017 15:01:09 -0500 Subject: [PATCH 1690/2152] Document the usage of EventBuilderHelpers. #439 --- docs/usage.rst | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/usage.rst b/docs/usage.rst index de4148f81b5..e5e9a16d22f 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -196,3 +196,37 @@ For more complex messages, you'll need to build an ``Event`` with the } } } + +Automatically Enhancing Events +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also implement an ``EventBuilderHelper`` that is able to automatically +enhance outgoing events. + +.. sourcecode:: java + + import io.sentry.Sentry; + import io.sentry.SentryClient; + import io.sentry.event.EventBuilder; + import io.sentry.event.helper.EventBuilderHelper; + + public class MyClass { + public void myMethod() { + SentryClient client = Sentry.getStoredClient(); + + EventBuilderHelper myEventBuilderHelper = new EventBuilderHelper() { + @Override + public void helpBuildingEvent(EventBuilder eventBuilder) { + eventBuilder.withMessage("Overwritten by myEventBuilderHelper!"); + } + }; + + // Add an ``EventBuilderHelper`` to the current client instance. Note that + // this helper will process *all* future events. + client.addBuilderHelper(myEventBuilderHelper); + + // Send an event to Sentry. During construction of the event the message + // body will be overwritten by ``myEventBuilderHelper``. + Sentry.capture("Hello, world!"); + } + } From 237f7a1b947c8cee7c0dd1a27685630b1be4d250 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 11 Jul 2017 15:59:05 -0500 Subject: [PATCH 1691/2152] No longer throw an exception for a noop DSN on Android. (#458) --- .../android/AndroidSentryClientFactory.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java index cd6a71285a0..2212e733dc0 100644 --- a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java +++ b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java @@ -53,17 +53,20 @@ public SentryClient createSentryClient(Dsn dsn) { Log.d(TAG, "Sentry init with ctx='" + ctx.toString() + "' and dsn='" + dsn + "'"); String protocol = dsn.getProtocol(); - if (!(protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https"))) { + if (protocol.equalsIgnoreCase("noop")) { + Log.w(TAG, "*** Couldn't find a suitable DSN, Sentry operations will do nothing!" + + " See documentation: https://docs.sentry.io/clients/java/modules/android/ ***"); + } else if (!(protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https"))) { + String async = Lookup.lookup(DefaultSentryClientFactory.ASYNC_OPTION, dsn); + if (async != null && async.equalsIgnoreCase("false")) { + throw new IllegalArgumentException("Sentry Android cannot use synchronous connections, remove '" + + DefaultSentryClientFactory.ASYNC_OPTION + "=false' from your options."); + } + throw new IllegalArgumentException("Only 'http' or 'https' connections are supported in" + " Sentry Android, but received: " + protocol); } - String async = Lookup.lookup(DefaultSentryClientFactory.ASYNC_OPTION, dsn); - if (async != null && async.equalsIgnoreCase("false")) { - throw new IllegalArgumentException("Sentry Android cannot use synchronous connections, remove '" - + DefaultSentryClientFactory.ASYNC_OPTION + "=false' from your DSN."); - } - SentryClient sentryClient = super.createSentryClient(dsn); sentryClient.addBuilderHelper(new AndroidEventBuilderHelper(ctx)); SentryUncaughtExceptionHandler.setup(); From bc9ffa63f2491c8d9e01766d5c0287d1559bbf80 Mon Sep 17 00:00:00 2001 From: Frieder Bluemle Date: Wed, 28 Jun 2017 13:31:27 +0800 Subject: [PATCH 1692/2152] Update Gradle wrapper to 4.0.1 --- .../gradle/wrapper/gradle-wrapper.jar | Bin 49896 -> 54712 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- sentry-android-gradle-plugin/gradlew | 78 ++++++++-------- sentry-android-gradle-plugin/gradlew.bat | 84 ++++++++++++++++++ 4 files changed, 129 insertions(+), 37 deletions(-) create mode 100644 sentry-android-gradle-plugin/gradlew.bat diff --git a/sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.jar index 8c0fb64a8698b08ecc4158d828ca593c4928e9dd..48e7610e2b59fbe5b4430887b710a26d50b5d98a 100644 GIT binary patch delta 48653 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%0Alk0k!JB4fW=&r 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 aA}~dPb^B!s1H%ISv4EZ^v8ufN>;C}Z7qY1U diff --git a/sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.properties index fd27bdfd0a4..58852583d37 100644 --- a/sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 15:27:10 PDT 2013 +#Wed Jul 12 09:52:05 SGT 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.0.1-all.zip diff --git a/sentry-android-gradle-plugin/gradlew b/sentry-android-gradle-plugin/gradlew index 91a7e269e19..cccdd3d517f 100755 --- a/sentry-android-gradle-plugin/gradlew +++ b/sentry-android-gradle-plugin/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,20 +6,38 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` @@ -154,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/sentry-android-gradle-plugin/gradlew.bat b/sentry-android-gradle-plugin/gradlew.bat new file mode 100644 index 00000000000..e95643d6a2c --- /dev/null +++ b/sentry-android-gradle-plugin/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From 1f5cf708145334c8f1580a4cad27bb4f8b67459e Mon Sep 17 00:00:00 2001 From: Frieder Bluemle Date: Wed, 28 Jun 2017 13:36:20 +0800 Subject: [PATCH 1693/2152] Update Android Gradle plugin to 2.3.3 --- sentry-android-gradle-plugin/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-android-gradle-plugin/build.gradle b/sentry-android-gradle-plugin/build.gradle index 4dc24f6eddd..4a805baea1a 100644 --- a/sentry-android-gradle-plugin/build.gradle +++ b/sentry-android-gradle-plugin/build.gradle @@ -17,7 +17,7 @@ compileGroovy { dependencies { compile gradleApi() compile localGroovy() - compile 'com.android.tools.build:gradle:2.2.0' + compile 'com.android.tools.build:gradle:2.3.3' } publishing { @@ -65,4 +65,4 @@ modifyPom { } } } -} \ No newline at end of file +} From fa0bf4a8d05984ba8ee37a1e8ac072809b1cfdca Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 12 Jul 2017 11:58:20 -0500 Subject: [PATCH 1694/2152] Deprecate minLevel in Logback SentryAppender. --- .../java/io/sentry/logback/SentryAppender.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index 79f7e4955fd..cd447a741cf 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -39,10 +39,11 @@ public class SentryAppender extends AppenderBase { */ public static final String THREAD_NAME = "Sentry-Threadname"; /** - * If set, only events with level = minLevel and up will be recorded. (This - * configuration parameter is deprecated in favor of using Logback - * Filters.) + * If set, only events with level = minLevel and up will be recorded. + * + * @deprecated use logback filters. */ + @Deprecated protected Level minLevel; /** @@ -264,6 +265,13 @@ protected StackTraceElement[] toStackTraceElements(IThrowableProxy throwableProx return stackTraceElements; } + /** + * Set minimum level to log. + * + * @param minLevel minimum level to log. + * @deprecated use logback filters. + */ + @Deprecated public void setMinLevel(String minLevel) { this.minLevel = minLevel != null ? Level.toLevel(minLevel) : null; } From 547f59388ec8fa1af143b12e1789370614c8a433 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 12 Jul 2017 13:23:11 -0500 Subject: [PATCH 1695/2152] Update CHANGES --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 747ca79133f..c200c6b8325 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ Version 1.2.3 - Deprecate ``extratags`` in favor of the more clear ``mdctags``. - Add ability to set ``extra`` data via options and on ``SentryClient`` instances. +- Add ability to set ``extra`` data and ``tags`` in the current context. Version 1.2.2 ------------- From 036ceef57e5f69f5f3eb47cff858cfaf0bcc2193 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 12 Jul 2017 15:51:29 -0500 Subject: [PATCH 1696/2152] Add migration guide from raven. --- docs/index.rst | 12 ++- docs/migration.rst | 201 +++++++++++++++++++++++++++++++++++++++ docs/modules/jul.rst | 6 +- docs/modules/log4j.rst | 5 +- docs/modules/log4j2.rst | 5 +- docs/modules/logback.rst | 5 +- 6 files changed, 220 insertions(+), 14 deletions(-) create mode 100644 docs/migration.rst diff --git a/docs/index.rst b/docs/index.rst index e7332d3d468..97da9b19193 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,14 +10,15 @@ Java ==== -Sentry for Java (``sentry-java``) is the official Java SDK for Sentry. At its core it provides +Sentry for Java is the official Java SDK for Sentry. At its core it provides a raw client for sending events to Sentry, but it is highly recommended that you use one of the included library or framework integrations listed below if at all possible. -**Note:** The old ``raven-java`` library is no longer maintained. It is highly recommended that -you migrate to ``sentry-java`` (which this documentation covers). If you are still -using ``raven-java`` you can -`find the old documentation here `_. +**Note:** The old ``raven`` library is no longer maintained. It is highly recommended that +you `migrate `_ to ``sentry`` (which this +documentation covers). `Check out the migration guide `_ +for more information. If you are still using ``raven`` you can +`find the old documentation here `_. .. toctree:: :maxdepth: 2 @@ -27,6 +28,7 @@ using ``raven-java`` you can context usage modules/index + migration Resources: diff --git a/docs/migration.rst b/docs/migration.rst new file mode 100644 index 00000000000..958e7cae64e --- /dev/null +++ b/docs/migration.rst @@ -0,0 +1,201 @@ +Migration from Raven Java +========================= + +The old ``raven-java`` library has been overhauled and renamed to ``sentry-java``. The focus +of the new release was to improve APIs, make the underlying client completely independent +of logging integrations, and to rename (from ``raven-*``) for clarity. + +What follows is a small guide explaining the major changes. + +New Artifacts +------------- + +.. describe:: Before (raven-java) + + Artifact named ``raven`` (and others: ``raven-*``) under the ``com.getsentry.raven`` group. + Final minor release was version ``8.0.x``. + +.. describe:: Now (sentry-java) + + Artifact named ``sentry`` (and others: ``sentry-*``) under the ``io.sentry`` group. + Started over with version ``1.0.0`` (but please use the latest version!). + +New Packages +------------ + +.. describe:: Before (raven-java) + + Package root was ``com.getsentry.raven``. + + For example, the ``logback`` appender used to be referenced in configuration by using + ``com.getsentry.raven.logback.SentryAppender``. + +.. describe:: Now (sentry-java) + + Package root is ``io.sentry``. + + For example, the ``logback`` appender is now referenced in configuration by using + ``io.sentry.logback.SentryAppender``. + +Raven Class Changes +------------------- + +rename, static vs not +Raven->SentryClient + +.. describe:: Before (raven-java) + + The ``Raven`` class was both the core client class and also had the ability to + statically store a client and send events without keeping track of the instance + yourself. + +.. describe:: Now (sentry-java) + + The core client class is now called ``SentryClient`` and there is now separate + ``Sentry`` class that you may use to handle initializing Sentry statically and + sending events. + + For example: + + .. sourcecode:: java + + // The static SentryClient can be lazily initialized from anywhere in your application. + // Your DSN needs to be provided somehow, check the configuration documentation! + Sentry.capture("Hello, world!) + +Classes Renamed +--------------- + +.. describe:: Before (raven-java) + + Many classes contained the word ``Raven``. For example ``RavenServletRequestListener``. + +.. describe:: Now (sentry-java) + + All instances of ``Raven`` have been renamed ``Sentry``. For example ``SentryServletRequestListener``. + + In addition, as noted above, the underlying client class ``Raven`` became ``SentryClient``, and + so ``RavenFactory`` also became ``SentryClientFactory`` and ``DefaultRavenFactory`` became + ``DefaultSentryClientFactory``. + +Logging Integration Configuration +--------------------------------- + +.. describe:: Before (raven-java) + + Most (or all) configuration would be done inside of the logging appender itself. For example: + + .. sourcecode:: xml + + + + WARN + + https://host:port/1?options + 1.0.0 + + +.. describe:: Now (sentry-java) + + While setting up the ``SentryAppender`` itself is still required for logging integrations, + **configuration** of Sentry is no longer done in the same place. + + This is because appenders are initialized only when the first message (above their threshold) + is sent to them, which means Sentry has no idea how to initialize and configure itself until + the first event is sent. This may seem OK, except it prevented users from being able to do + things like record breadcrumbs, set the current user, programmatically configure the Sentry + client, and more. + + For this reason, all configuration is now done "outside" of the logging integration itself. + You may configure Sentry using a properties file (default: ``sentry.properties``) if you + preferred the old style, :ref:`more information can be found on the configuration page `. + + For example: + + .. sourcecode:: xml + + + + + WARN + + + + .. sourcecode:: properties + + # sentry.properties + dsn=https://host:port/1?options + release=1.0.0 + + .. sourcecode:: java + + // you can now record breadcrumbs *before* the first event is even sent + Sentry.getContext().recordBreadcrumb( + new BreadcrumbBuilder().setMessage("Made a call to the database.").build() + ); + +Configuration via DSN +--------------------- + +.. describe:: Before (raven-java) + + Options were prefixed with ``raven.``, for example: ``raven.async``. + +.. describe:: Now (sentry-java) + + Options are no longer prefixed, for example: ``async``. + +Configuration via Java System Properties +---------------------------------------- + +.. describe:: Before (raven-java) + + Only certain options could be set, and only in the logging integrations. For example: + ``sentry.release`` was allowed but ``sentry.async`` did nothing. + +.. describe:: Now (sentry-java) + + All options can be configured via Java System Properties, for example: ``sentry.async=false`` + is respected. + +Configuration via Environment Variables +--------------------------------------- + +.. describe:: Before (raven-java) + + Only certain options could be set, and only in the logging integrations. For example: + ``SENTRY_RELEASE`` was allowed but ``SENTRY_ASYNC`` did nothing. + +.. describe:: Now (sentry-java) + + All options can be configured via Environment Variables, for example: ``SENTRY_ASYNC=false`` + is respected. + +Custom Factories +---------------- + +.. describe:: Before (raven-java) + + To do customization users would typically create a ``DefaultRavenFactory`` subclass + and register it in one of multiple (painful) ways. + +.. describe:: Now (sentry-java) + + To do customization users subclass ``DefaultSentryClientFactory`` and then call out + that class with the ``factory`` option, like ``factory=my.company.MySentryClientFactory``. + :ref:`See the configuration page ` for more information. + +Android +------- + +.. describe:: Before (raven-java) + + There used to be a ``Raven`` wrapper called ``com.getsentry.raven.android.Raven`` that + was a second class interface for interacting with Sentry on Android. + +.. describe:: Now (sentry-java) + + Android users now use the same ``Sentry`` and ``SentryClient`` classes as everyone, + they just need to initialize it with their application context and the + ``AndroidSentryClientFactory``. For an example, `see the Android documentation + `_. \ No newline at end of file diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index b0df00679b1..7dc8941fb99 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -12,10 +12,10 @@ The source for ``sentry`` can be found `on Github `_. **Note:** The old ``raven`` library is no longer maintained. It is highly recommended that -you migrate to ``sentry`` (which this documentation covers). If you are still -using ``raven`` you can +you `migrate `_ to ``sentry`` (which this +documentation covers). `Check out the migration guide `_ +for more information. If you are still using ``raven`` you can `find the old documentation here `_. -\ Installation ------------ diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index bc562bd4a15..3f73901d4ed 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -13,8 +13,9 @@ The source can be found `on Github `_. **Note:** The old ``raven-log4j`` library is no longer maintained. It is highly recommended that -you migrate to ``sentry-log4j`` (which this documentation covers). If you are still -using ``raven-log4j`` you can +you `migrate `_ to ``sentry-log4j`` (which this +documentation covers). `Check out the migration guide `_ +for more information. If you are still using ``raven-log4j`` you can `find the old documentation here `_. Installation diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 1b1f50c07c7..019264a333f 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -13,8 +13,9 @@ The source can be found `on Github `_. **Note:** The old ``raven-log4j2`` library is no longer maintained. It is highly recommended that -you migrate to ``sentry-log4j2`` (which this documentation covers). If you are still -using ``raven-log4j2`` you can +you `migrate `_ to ``sentry-log4j2`` (which this +documentation covers). `Check out the migration guide `_ +for more information. If you are still using ``raven-log4j2`` you can `find the old documentation here `_. Installation diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 87861b3ee81..83237484f70 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -13,8 +13,9 @@ The source can be found `on Github `_. **Note:** The old ``raven-logback`` library is no longer maintained. It is highly recommended that -you migrate to ``sentry-logback`` (which this documentation covers). If you are still -using ``raven-logback`` you can +you `migrate `_ to ``sentry-logback`` (which this +documentation covers). `Check out the migration guide `_ +for more information. If you are still using ``raven-logback`` you can `find the old documentation here `_. Installation From aca80cff8b6e1e36ec18bb6759503268865c9995 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 12 Jul 2017 16:18:26 -0500 Subject: [PATCH 1697/2152] Fix java.util.logging wizard. --- docs/sentry-doc-config.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json index 1b12c2eb210..2cbb23ccfc5 100644 --- a/docs/sentry-doc-config.json +++ b/docs/sentry-doc-config.json @@ -13,10 +13,10 @@ "java.logging": { "name": "java.util.logging", "type": "framework", - "doc_link": "modules/sentry/", + "doc_link": "modules/jul/", "wizard": [ - "modules/sentry#installation", - "modules/sentry#usage" + "modules/jul#installation", + "modules/jul#usage" ] }, "java.log4j": { From 6f4842194268190dd4ec02381ce03ab57de486a6 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 12 Jul 2017 16:23:55 -0500 Subject: [PATCH 1698/2152] Remove leftover text. --- docs/migration.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/migration.rst b/docs/migration.rst index 958e7cae64e..df16c075d61 100644 --- a/docs/migration.rst +++ b/docs/migration.rst @@ -40,9 +40,6 @@ New Packages Raven Class Changes ------------------- -rename, static vs not -Raven->SentryClient - .. describe:: Before (raven-java) The ``Raven`` class was both the core client class and also had the ability to From 17dcbd30a48c0ae48ad7a13e474e1737ccd18bc3 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 13 Jul 2017 10:03:32 -0500 Subject: [PATCH 1699/2152] Change version to 1.3.0. --- CHANGES | 2 +- pom.xml | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index c200c6b8325..d538a13df83 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,4 @@ -Version 1.2.3 +Version 1.3.0 ------------- - Deprecate ``extratags`` in favor of the more clear ``mdctags``. diff --git a/pom.xml b/pom.xml index dbfc09855c0..42c3bda8911 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.2.3-SNAPSHOT + 1.3.0-SNAPSHOT pom Sentry-Java diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 6b287d44d34..4515d00a10e 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.2.3-SNAPSHOT +version = 1.3.0-SNAPSHOT diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index e1a9bd74ede..0b26ef75059 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.3-SNAPSHOT + 1.3.0-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index bc63a390f5f..fe7bf33ed72 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.2.3-SNAPSHOT + 1.3.0-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 63c548b9520..4789750ca12 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.3-SNAPSHOT + 1.3.0-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 00f672ea32a..cc84afe854a 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.3-SNAPSHOT + 1.3.0-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 677f0ba329b..496cfaf5a18 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.3-SNAPSHOT + 1.3.0-SNAPSHOT sentry-logback diff --git a/sentry/pom.xml b/sentry/pom.xml index 24013ff607a..1fd4c02b542 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.2.3-SNAPSHOT + 1.3.0-SNAPSHOT sentry From a03afe2ab56cd2bb0062d026325635e84c043465 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 13 Jul 2017 10:04:22 -0500 Subject: [PATCH 1700/2152] Bump docs to 1.3.0 --- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 84179f25b7e..86d964fde55 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.2.2`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.3.0`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.2.2' + compile 'io.sentry:sentry-android:1.3.0' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.2.2' + classpath 'io.sentry:sentry-android-gradle-plugin:1.3.0' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index baaa1395078..093428fd655 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.2.2 + 1.3.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.2.2' + compile 'io.sentry:sentry-appengine:1.3.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.2.2" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.3.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 7dc8941fb99..1c17f7be59f 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.2.2 + 1.3.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.2.2' + compile 'io.sentry:sentry:1.3.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.2.2" + libraryDependencies += "io.sentry" % "sentry" % "1.3.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 3f73901d4ed..b2461143734 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.2.2 + 1.3.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.2.2' + compile 'io.sentry:sentry-log4j:1.3.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.2.2" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.3.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 019264a333f..d7c60ce12ae 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.2.2 + 1.3.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.2.2' + compile 'io.sentry:sentry-log4j2:1.3.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.2.2" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.3.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 83237484f70..ecf8a034578 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.2.2 + 1.3.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.2.2' + compile 'io.sentry:sentry-logback:1.3.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.2.2" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.3.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index 7534f4e9916..c10076c328c 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.2.2 + 1.3.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.2.2' + compile 'io.sentry:sentry:1.3.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.2.2" + libraryDependencies += "io.sentry" % "sentry" % "1.3.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 412869e527088b09e7e6c3c7384addb571927d4e Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 13 Jul 2017 10:05:42 -0500 Subject: [PATCH 1701/2152] [maven-release-plugin] prepare release v1.3.0 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 42c3bda8911..573051f81c1 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.3.0-SNAPSHOT + 1.3.0 pom Sentry-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.3.0 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 0b26ef75059..153365b2cf0 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.0-SNAPSHOT + 1.3.0 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index fe7bf33ed72..9882fc5ffe2 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.3.0-SNAPSHOT + 1.3.0 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 4789750ca12..cf12b332924 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.0-SNAPSHOT + 1.3.0 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index cc84afe854a..7ecf5b9ebaf 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.0-SNAPSHOT + 1.3.0 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 496cfaf5a18..917ef8c2325 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.0-SNAPSHOT + 1.3.0 sentry-logback diff --git a/sentry/pom.xml b/sentry/pom.xml index 1fd4c02b542..c5e40d0fabb 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.0-SNAPSHOT + 1.3.0 sentry From c284507df35427304ee67c77f3c891d10340204c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 13 Jul 2017 10:05:42 -0500 Subject: [PATCH 1702/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 573051f81c1..7ec1dcdf151 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.3.0 + 1.3.1-SNAPSHOT pom Sentry-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.3.0 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 153365b2cf0..4c9afbdfaf7 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.0 + 1.3.1-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 9882fc5ffe2..9daba82974a 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.3.0 + 1.3.1-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index cf12b332924..bb3b514a010 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.0 + 1.3.1-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 7ecf5b9ebaf..e60431ffff4 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.0 + 1.3.1-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 917ef8c2325..bbe7ca7ed41 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.0 + 1.3.1-SNAPSHOT sentry-logback diff --git a/sentry/pom.xml b/sentry/pom.xml index c5e40d0fabb..8f1a47cb93d 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.0 + 1.3.1-SNAPSHOT sentry From cd898fae35aac688134c6fa9bdd3ce1212db0ad9 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 13 Jul 2017 10:05:45 -0500 Subject: [PATCH 1703/2152] Bump CHANGES to 1.3.1 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index d538a13df83..dd36244d5f1 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.3.1 +------------- + +- + Version 1.3.0 ------------- From 7d548fb588729ec22d19d2a765a89506745e6c2f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 13 Jul 2017 10:21:11 -0500 Subject: [PATCH 1704/2152] Update Android Gradle plugin version and deploy script. --- sentry-android-gradle-plugin/Makefile | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-android-gradle-plugin/Makefile b/sentry-android-gradle-plugin/Makefile index d9621700c62..2600031c489 100644 --- a/sentry-android-gradle-plugin/Makefile +++ b/sentry-android-gradle-plugin/Makefile @@ -2,4 +2,4 @@ release: download-sentry-cli.sh - ./gradlew uploadArchives + ./gradlew uploadArchives --no-daemon diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 4515d00a10e..43097bc60b5 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.3.0-SNAPSHOT +version = 1.3.1-SNAPSHOT From 8596bae34a52b3bf9edbc18d6c1f2cb6e73e34c3 Mon Sep 17 00:00:00 2001 From: Oleg Golberg Date: Tue, 18 Jul 2017 15:30:38 -0400 Subject: [PATCH 1705/2152] Ensure FileInputStream is closed (#464) --- sentry/src/main/java/io/sentry/buffer/DiskBuffer.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java b/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java index 68b6e29432c..d36f1628f12 100644 --- a/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java +++ b/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java @@ -111,12 +111,10 @@ public void discard(Event event) { * @return Event from the File, or null */ private Event fileToEvent(File eventFile) { - FileInputStream fileInputStream; Object eventObj; - try { - fileInputStream = new FileInputStream(new File(eventFile.getAbsolutePath())); - ObjectInputStream ois = new ObjectInputStream(fileInputStream); + try (FileInputStream fileInputStream = new FileInputStream(new File(eventFile.getAbsolutePath())); + ObjectInputStream ois = new ObjectInputStream(fileInputStream)) { eventObj = ois.readObject(); } catch (Exception e) { logger.error("Error reading Event file: " + eventFile.getAbsolutePath(), e); From 9191885a6ffa686c9e5e88a5d161624febd6430f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 20 Jul 2017 12:36:25 -0500 Subject: [PATCH 1706/2152] Add examples link. --- docs/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.rst b/docs/index.rst index 97da9b19193..c2e25e5ef87 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -33,6 +33,7 @@ for more information. If you are still using ``raven`` you can Resources: * `Documentation `_ +* `Examples `_ * `Bug Tracker `_ * `Code `_ * `Mailing List `_ From b8e9fc915ae9abc67f6219be7d94d300c4c44d1d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 25 Jul 2017 19:03:21 -0500 Subject: [PATCH 1707/2152] Bump sentry-cli to 1.18.0 --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 40fd2c49761..75238475a6c 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.16.0 +VERSION=1.18.0 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* From 18d234e0ea5734fa77b696cc9984c2db1887d57d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 26 Jul 2017 08:54:21 -0500 Subject: [PATCH 1708/2152] Reduce startup and shutdown logging noise by moving a lot of INFO level logs to DEBUG. --- CHANGES | 2 +- docs/modules/logback.rst | 4 ++-- .../connection/AppEngineAsyncConnection.java | 2 +- .../java/io/sentry/DefaultSentryClientFactory.java | 6 +++--- .../java/io/sentry/connection/AsyncConnection.java | 13 ++++++------- .../io/sentry/connection/BufferedConnection.java | 12 ++++++------ 6 files changed, 19 insertions(+), 20 deletions(-) diff --git a/CHANGES b/CHANGES index dd36244d5f1..06c392e5d6d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.3.1 ------------- -- +- Reduce startup and shutdown logging noise by moving a lot of INFO level logs to DEBUG. Version 1.3.0 ------------- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index ecf8a034578..074fc30ec31 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -141,14 +141,14 @@ In Practice void logWithTag() { // This sends an event with a tag named 'logback-Marker' to Sentry - logger.info(MARKER, "This is a test"); + logger.error(MARKER, "This is a test"); } void logWithExtras() { // MDC extras MDC.put("extra_key", "extra_value"); // This sends an event with extra data to Sentry - logger.info("This is a test"); + logger.error("This is a test"); } void logException() { diff --git a/sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java b/sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java index 7bb94c6fdc3..5708f93b941 100644 --- a/sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java +++ b/sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java @@ -89,7 +89,7 @@ public void addEventSendCallback(EventSendCallback eventSendCallback) { */ @Override public void close() throws IOException { - logger.info("Gracefully stopping sentry tasks."); + logger.debug("Gracefully stopping Sentry tasks."); closed = true; actualConnection.close(); APP_ENGINE_ASYNC_CONNECTIONS.remove(id); diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index 9a666c28262..19f540d9231 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -305,13 +305,13 @@ protected Connection createConnection(Dsn dsn) { Connection connection; if (protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https")) { - logger.info("Using an {} connection to Sentry.", protocol.toUpperCase()); + logger.debug("Using an {} connection to Sentry.", protocol.toUpperCase()); connection = createHttpConnection(dsn); } else if (protocol.equalsIgnoreCase("out")) { - logger.info("Using StdOut to send events."); + logger.debug("Using StdOut to send events."); connection = createStdOutConnection(dsn); } else if (protocol.equalsIgnoreCase("noop")) { - logger.info("Using noop to send events."); + logger.debug("Using noop to send events."); connection = new NoopConnection(); } else { throw new IllegalStateException("Couldn't create a connection for the protocol '" + protocol + "'"); diff --git a/sentry/src/main/java/io/sentry/connection/AsyncConnection.java b/sentry/src/main/java/io/sentry/connection/AsyncConnection.java index 4c179abd2b7..2f01b6ff53b 100644 --- a/sentry/src/main/java/io/sentry/connection/AsyncConnection.java +++ b/sentry/src/main/java/io/sentry/connection/AsyncConnection.java @@ -123,7 +123,7 @@ public void close() throws IOException { */ @SuppressWarnings("checkstyle:magicnumber") private void doClose() throws IOException { - logger.info("Gracefully shutdown sentry threads."); + logger.debug("Gracefully shutting down Sentry async threads."); closed = true; executorService.shutdown(); try { @@ -134,19 +134,19 @@ private void doClose() throws IOException { if (executorService.awaitTermination(waitBetweenLoggingMs, TimeUnit.MILLISECONDS)) { break; } - logger.info("Still waiting on async executor to terminate."); + logger.debug("Still waiting on async executor to terminate."); } } else if (!executorService.awaitTermination(shutdownTimeout, TimeUnit.MILLISECONDS)) { logger.warn("Graceful shutdown took too much time, forcing the shutdown."); List tasks = executorService.shutdownNow(); - logger.info("{} tasks failed to execute before the shutdown.", tasks.size()); + logger.warn("{} tasks failed to execute before shutdown.", tasks.size()); } - logger.info("Shutdown finished."); + logger.debug("Shutdown finished."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - logger.error("Graceful shutdown interrupted, forcing the shutdown."); + logger.warn("Graceful shutdown interrupted, forcing the shutdown."); List tasks = executorService.shutdownNow(); - logger.info("{} tasks failed to execute before the shutdown.", tasks.size()); + logger.warn("{} tasks failed to execute before shutdown.", tasks.size()); } finally { actualConnection.close(); } @@ -195,7 +195,6 @@ public void run() { SentryEnvironment.startManagingThread(); try { // The current thread is managed by sentry - logger.info("Automatic shutdown of the async connection"); AsyncConnection.this.doClose(); } catch (Exception e) { logger.error("An exception occurred while closing the connection.", e); diff --git a/sentry/src/main/java/io/sentry/connection/BufferedConnection.java b/sentry/src/main/java/io/sentry/connection/BufferedConnection.java index 888eec28acf..918e04f21ac 100644 --- a/sentry/src/main/java/io/sentry/connection/BufferedConnection.java +++ b/sentry/src/main/java/io/sentry/connection/BufferedConnection.java @@ -117,6 +117,7 @@ public void close() throws IOException { shutDownHook.enabled = false; } + logger.debug("Gracefully shutting down Sentry buffer threads."); closed = true; executorService.shutdown(); try { @@ -127,19 +128,19 @@ public void close() throws IOException { if (executorService.awaitTermination(waitBetweenLoggingMs, TimeUnit.MILLISECONDS)) { break; } - logger.info("Still waiting on buffer flusher executor to terminate."); + logger.debug("Still waiting on buffer flusher executor to terminate."); } } else if (!executorService.awaitTermination(shutdownTimeout, TimeUnit.MILLISECONDS)) { logger.warn("Graceful shutdown took too much time, forcing the shutdown."); List tasks = executorService.shutdownNow(); - logger.info("{} tasks failed to execute before the shutdown.", tasks.size()); + logger.warn("{} tasks failed to execute before the shutdown.", tasks.size()); } - logger.info("Shutdown finished."); + logger.debug("Shutdown finished."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - logger.error("Graceful shutdown interrupted, forcing the shutdown."); + logger.warn("Graceful shutdown interrupted, forcing the shutdown."); List tasks = executorService.shutdownNow(); - logger.info("{} tasks failed to execute before the shutdown.", tasks.size()); + logger.warn("{} tasks failed to execute before the shutdown.", tasks.size()); } finally { actualConnection.close(); } @@ -260,7 +261,6 @@ public void run() { SentryEnvironment.startManagingThread(); try { // The current thread is managed by sentry - logger.info("Automatic shutdown of the buffered connection"); BufferedConnection.this.close(); } catch (Exception e) { logger.error("An exception occurred while closing the connection.", e); From 2fbabd55d4bb927cbcca2b6a647b87720e7c21be Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 26 Jul 2017 09:14:28 -0500 Subject: [PATCH 1709/2152] Force Travis to use Ubuntu Precise to fix tests. (#476) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index aeacc99dc6c..539e4cf805c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +dist: precise language: java script: mvn verify jdk: From d8ed9e74a0f2bf087b23a666cd11c17c92346e9b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 27 Jul 2017 11:06:55 -0500 Subject: [PATCH 1710/2152] Fix example of custom DefaultSentryClientFactory. --- docs/config.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 48a967d14f5..f671bddf6de 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -527,16 +527,17 @@ Implementation public class MySentryClientFactory extends DefaultSentryClientFactory { @Override public SentryClient createSentryClient(Dsn dsn) { - SentryClient sentry = new SentryClient(createConnection(dsn)); + SentryClient sentryClient = new SentryClient(createConnection(dsn), getContextManager(dsn)); /* Create and use the ForwardedAddressResolver, which will use the X-FORWARDED-FOR header for the remote address if it exists. */ ForwardedAddressResolver forwardedAddressResolver = new ForwardedAddressResolver(); - sentry.addBuilderHelper(new HttpEventBuilderHelper(forwardedAddressResolver)); + sentryClient.addBuilderHelper(new HttpEventBuilderHelper(forwardedAddressResolver)); - return sentry; + sentryClient.addBuilderHelper(new ContextBuilderHelper(sentryClient)); + return configureSentryClient(sentryClient, dsn); } } From 1b62d0c903d173660c6a74c4ff0490a6588a7f13 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 27 Jul 2017 11:29:41 -0500 Subject: [PATCH 1711/2152] Add basic CONTRIBUTING.md --- CONTRIBUTING.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000000..013abbd1533 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,17 @@ +# Contributing to sentry-java + +We love pull requests from everyone. + +The test suite currently requires you run JDK version `1.7.0`. +See [#487](https://github.com/getsentry/sentry-java/issues/478) +for more information. + +To run the tests (and checkstyle): + +```shell +make test +``` + +Tests are automatically run against branches and pull requests +via TravisCI, so you can also depend on that if you'd rather not +deal with installing an older JDK. \ No newline at end of file From 065347c057e8c3db4874b5d036eb1ec8dbbdf208 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 28 Jul 2017 08:39:03 -0500 Subject: [PATCH 1712/2152] Check that /proc/version can be read on Android. (#474) --- .../event/helper/AndroidEventBuilderHelper.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java index bf2ab71857c..b66e123724b 100644 --- a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java +++ b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java @@ -322,15 +322,23 @@ private static Boolean isCharging(Context ctx) { } /** - * Get the device's current kernel version, as a string (from /proc/version). + * Get the device's current kernel version, as a string. Attempts to read + * /proc/version, and falls back to the 'os.version' System Property. * - * @return the device's current kernel version, as a string (from /proc/version) + * @return the device's current kernel version, as a string */ private static String getKernelVersion() { String errorMsg = "Exception while attempting to read kernel information"; + String defaultVersion = System.getProperty("os.version"); + BufferedReader br = null; try { - br = new BufferedReader(new FileReader("/proc/version")); + File file = new File("/proc/version"); + if (!file.canRead()) { + return defaultVersion; + } + + br = new BufferedReader(new FileReader(file)); return br.readLine(); } catch (Exception e) { Log.e(TAG, errorMsg, e); @@ -344,7 +352,7 @@ private static String getKernelVersion() { } } - return null; + return defaultVersion; } /** From 662d1f9cb18889a303295a7ff2b75de24cc6f113 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 28 Jul 2017 08:41:38 -0500 Subject: [PATCH 1713/2152] Update CHANGES. --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 06c392e5d6d..12464473a2e 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 1.3.1 ------------- - Reduce startup and shutdown logging noise by moving a lot of INFO level logs to DEBUG. +- Android: fallback to `os.version` property for kernel version is `/proc/version` is unreadable. Version 1.3.0 ------------- From ce6228795441ec9216a1aaa6dcb8daefa0251cab Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 28 Jul 2017 08:41:47 -0500 Subject: [PATCH 1714/2152] Bump docs to 1.3.1 --- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 86d964fde55..a4b3ff52b19 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.3.0`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.3.1`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.3.0' + compile 'io.sentry:sentry-android:1.3.1' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.3.0' + classpath 'io.sentry:sentry-android-gradle-plugin:1.3.1' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 093428fd655..e70b2f78042 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.3.0 + 1.3.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.3.0' + compile 'io.sentry:sentry-appengine:1.3.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.3.0" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.3.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 1c17f7be59f..5ba3487e867 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.3.0 + 1.3.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.3.0' + compile 'io.sentry:sentry:1.3.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.3.0" + libraryDependencies += "io.sentry" % "sentry" % "1.3.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index b2461143734..7fcfde740fc 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.3.0 + 1.3.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.3.0' + compile 'io.sentry:sentry-log4j:1.3.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.3.0" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.3.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index d7c60ce12ae..6d8f6532f4d 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.3.0 + 1.3.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.3.0' + compile 'io.sentry:sentry-log4j2:1.3.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.3.0" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.3.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 074fc30ec31..e4dedb7cdca 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.3.0 + 1.3.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.3.0' + compile 'io.sentry:sentry-logback:1.3.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.3.0" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.3.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index c10076c328c..f67930e9c49 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.3.0 + 1.3.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.3.0' + compile 'io.sentry:sentry:1.3.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.3.0" + libraryDependencies += "io.sentry" % "sentry" % "1.3.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 2c981fdcf41b63d49399ada238f0e4ddefb5f711 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 28 Jul 2017 08:43:01 -0500 Subject: [PATCH 1715/2152] [maven-release-plugin] prepare release v1.3.1 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 7ec1dcdf151..cb736928101 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.3.1-SNAPSHOT + 1.3.1 pom Sentry-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.3.1 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 4c9afbdfaf7..426215d7a3a 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.1-SNAPSHOT + 1.3.1 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 9daba82974a..4a560533e88 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.3.1-SNAPSHOT + 1.3.1 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index bb3b514a010..f7d4efed7f9 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.1-SNAPSHOT + 1.3.1 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index e60431ffff4..6fb6dc25920 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.1-SNAPSHOT + 1.3.1 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index bbe7ca7ed41..4b8abf962e9 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.1-SNAPSHOT + 1.3.1 sentry-logback diff --git a/sentry/pom.xml b/sentry/pom.xml index 8f1a47cb93d..44b9ebd18e1 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.1-SNAPSHOT + 1.3.1 sentry From dd427cbd641461fd3a39d73c94ef871d16333411 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 28 Jul 2017 08:43:02 -0500 Subject: [PATCH 1716/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry/pom.xml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index cb736928101..6dff3f4c9e2 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.3.1 + 1.3.2-SNAPSHOT pom Sentry-Java @@ -85,7 +85,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.3.1 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 426215d7a3a..b08bdec2c0f 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.1 + 1.3.2-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 4a560533e88..544f99360ce 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.3.1 + 1.3.2-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index f7d4efed7f9..c43cc2e9f8d 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.1 + 1.3.2-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 6fb6dc25920..15a8d2ac118 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.1 + 1.3.2-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 4b8abf962e9..60b3df66a77 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.1 + 1.3.2-SNAPSHOT sentry-logback diff --git a/sentry/pom.xml b/sentry/pom.xml index 44b9ebd18e1..abd390af653 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.1 + 1.3.2-SNAPSHOT sentry From f4a3678c5353cfa52b79b32b630558e20ac04dd6 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 28 Jul 2017 08:43:05 -0500 Subject: [PATCH 1717/2152] Bump CHANGES to 1.3.2 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 12464473a2e..f2b29939c0d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.3.2 +------------- + +- + Version 1.3.1 ------------- From 62f0c1945b6e54f1536d5445f8ee0564cbac9b8a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 28 Jul 2017 09:29:18 -0500 Subject: [PATCH 1718/2152] Update Android Gradle plugin version. --- sentry-android-gradle-plugin/Makefile | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-android-gradle-plugin/Makefile b/sentry-android-gradle-plugin/Makefile index 2600031c489..56d3cca154e 100644 --- a/sentry-android-gradle-plugin/Makefile +++ b/sentry-android-gradle-plugin/Makefile @@ -1,5 +1,5 @@ .PHONY: release release: - download-sentry-cli.sh + ./download-sentry-cli.sh ./gradlew uploadArchives --no-daemon diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 43097bc60b5..77a7441b304 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.3.1-SNAPSHOT +version = 1.3.2-SNAPSHOT From 57559fa3a7d66fb418212f63305c3ab1e593aff0 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 28 Jul 2017 15:22:18 -0500 Subject: [PATCH 1719/2152] Build & Deploy Java Agent on Travis-CI. (#479) --- .ci/agent-build.sh | 15 ++++++ .ci/agent-deploy.sh | 12 +++++ .ci/agent-install.sh | 6 +++ .ci/agent-upload-release.py | 93 +++++++++++++++++++++++++++++++++++++ .travis.yml | 52 +++++++++++++++++---- agent/CMakeLists.txt | 9 +++- agent/agent.cpp | 1 + 7 files changed, 177 insertions(+), 11 deletions(-) create mode 100755 .ci/agent-build.sh create mode 100755 .ci/agent-deploy.sh create mode 100755 .ci/agent-install.sh create mode 100644 .ci/agent-upload-release.py diff --git a/.ci/agent-build.sh b/.ci/agent-build.sh new file mode 100755 index 00000000000..b88658dba3c --- /dev/null +++ b/.ci/agent-build.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -ex + +pushd agent + +DEST=libsentry_agent_linux-$TARGET.so + +cmake CMakeLists.txt +make + +mv libsentry_agent.so $DEST +file $DEST + +popd \ No newline at end of file diff --git a/.ci/agent-deploy.sh b/.ci/agent-deploy.sh new file mode 100755 index 00000000000..c36507b150e --- /dev/null +++ b/.ci/agent-deploy.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -ex + +[[ $TRAVIS_LANGUAGE == "cpp" ]] || (echo "Not a C++ run, exiting." && exit 0;) + +pushd agent + +pip install --user requests==2.18.2 +python ../.ci/agent-upload-release.py + +popd \ No newline at end of file diff --git a/.ci/agent-install.sh b/.ci/agent-install.sh new file mode 100755 index 00000000000..f00fd4a9363 --- /dev/null +++ b/.ci/agent-install.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +set -ex + +sudo apt-get -qq update +sudo apt-get install -y g++-multilib diff --git a/.ci/agent-upload-release.py b/.ci/agent-upload-release.py new file mode 100644 index 00000000000..0018b40deaf --- /dev/null +++ b/.ci/agent-upload-release.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python + +import os +import sys +import urlparse +import requests + +try: + from requests.packages import urllib3 + urllib3.disable_warnings() +except ImportError: + pass + + +AUTH_USERNAME = 'getsentry-bot' +AUTH_TOKEN = os.environ['GITHUB_AUTH_TOKEN'] +AUTH = (AUTH_USERNAME, AUTH_TOKEN) +TAG = os.environ.get('TRAVIS_TAG') +TARGET = os.environ.get('TARGET') +LIB = 'libsentry_agent_linux-%(target)s.%(ext)s' +EXT = 'so' +BIN_TYPE = os.environ.get('BIN_TYPE', 'release') +REPO = 'getsentry/sentry-java' + + +def log(message, *args): + if args: + message = message % args + print >> sys.stderr, message + + +def api_request(method, path, **kwargs): + url = urlparse.urljoin('https://api.github.com/', path.lstrip('/')) + # default travis python does not have SNI + return requests.request(method, url, auth=AUTH, verify=False, **kwargs) + + +def find_executable(): + path = LIB % {'target': TARGET, 'ext': EXT} + log("Checking for executable: " + path) + if os.path.isfile(path): + return path + + +def ensure_release(): + resp = api_request('GET', 'repos/%s/releases' % REPO) + resp.raise_for_status() + for release in resp.json(): + if release['tag_name'] == TAG: + log('Found already existing release %s' % release['id']) + return release + resp = api_request('POST', 'repos/%s/releases' % REPO, json={ + 'tag_name': TAG, + 'name': 'sentry-java-agent %s' % TAG, + 'draft': True, + }) + resp.raise_for_status() + release = resp.json() + log('Created new release %s' % release['id']) + return release + + +def upload_asset(release, executable, target_name): + resp = api_request('GET', release['assets_url']) + resp.raise_for_status() + for asset in resp.json(): + if asset['name'] == target_name: + log('Already have release asset %s. Skipping' % target_name) + return + + upload_url = release['upload_url'].split('{')[0] + with open(executable, 'rb') as f: + log('Creating new release asset %s.' % target_name) + resp = api_request('POST', upload_url, + params={'name': target_name}, + headers={'Content-Type': 'application/octet-stream'}, + data=f) + resp.raise_for_status() + + +def main(): + if not TAG: + return log('No tag specified. Doing nothing.') + executable = find_executable() + if executable is None: + return log('Could not locate executable. Doing nothing.') + + release = ensure_release() + upload_asset(release, executable, executable) + + +if __name__ == '__main__': + main() diff --git a/.travis.yml b/.travis.yml index 539e4cf805c..941bfbfa77f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,47 @@ -dist: precise -language: java -script: mvn verify -jdk: - - oraclejdk7 - - oraclejdk8 -cache: - directories: - - $HOME/.m2 env: global: - secure: "Xs3g5ZOMymPp0EvxF0T0tk5xMaV3htMPii5uDS17z9wDWYg7hj78mrP2cFea0ZjIqIjENG7YuxIjw34yb7507pvIxv8v+QvSkwlzVz4XJNzh2w1gbB2C+oVvbmbFd0A2Pmpf3jvkey6Tk5jhW7lxNVAZ7QzONuAf66iWdi6bwSg=" - secure: "PBz5Jjoaf33a+Iqv5eJsFHbCYP1ZdeiQvrQBH7hJK5/Kdrbn4GCrDx+RkA58ZC8i8ZHfsW2MkMuD7RphcrgHuB3UwcvHwUgrEZaV1/ypIOe5O8CQ4Z0gkHCj2Ei/dsL3fpDU8WuPJSLa13tsMoQTdGaOhluCuSXpuArx3B3gX28=" + after_success: - - "{ [[ $TRAVIS_BRANCH == 'master' ]] || [[ $TRAVIS_BRANCH == raven-*.x ]]; } && { python .travis/addServer.py; mvn clean deploy --settings $HOME/.m2/mySettings.xml; };" + - "[[ $TRAVIS_BRANCH == 'master' ]] && { python .travis/addServer.py; mvn clean deploy --settings $HOME/.m2/mySettings.xml; };" + +matrix: + include: + - language: java + dist: precise + jdk: oraclejdk7 + script: env | sort && mvn verify + cache: + directories: + - $HOME/.m2 + - language: java + dist: precise + jdk: oraclejdk8 + script: env | sort && mvn verify + cache: + directories: + - $HOME/.m2 + - language: cpp + dist: trusty + jdk: oraclejdk7 + env: TARGET=x86_64 + before_install: .ci/agent-install.sh + script: .ci/agent-build.sh + cache: false + after_success: true + - language: cpp + dist: trusty + jdk: oraclejdk7 + env: TARGET=i686 + before_install: .ci/agent-install.sh + script: .ci/agent-build.sh + cache: false + after_success: true + +deploy: + provider: script + script: .ci/agent-deploy.sh + skip_cleanup: true + on: + tags: true \ No newline at end of file diff --git a/agent/CMakeLists.txt b/agent/CMakeLists.txt index 4da94877df2..0b8268a4011 100644 --- a/agent/CMakeLists.txt +++ b/agent/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.7) +cmake_minimum_required(VERSION 3.2) project(sentry_java_agent) set(CMAKE_CXX_STANDARD 14) @@ -6,8 +6,15 @@ set(CMAKE_CXX_STANDARD 14) set(SOURCE_FILES agent.cpp lib.cpp lib.h) add_library(sentry_agent SHARED ${SOURCE_FILES}) +if($ENV{TARGET} MATCHES "i686") + message("Setting target to 32-bit.") + set_target_properties(sentry_agent PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32") +endif() + include_directories($ENV{JAVA_HOME}/include) if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") include_directories($ENV{JAVA_HOME}/include/darwin) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + include_directories($ENV{JAVA_HOME}/include/linux) endif() diff --git a/agent/agent.cpp b/agent/agent.cpp index cbaba70cba3..fef82031e49 100644 --- a/agent/agent.cpp +++ b/agent/agent.cpp @@ -6,6 +6,7 @@ #include "jvmti.h" #include +#include #include "lib.h" extern Level LOG_LEVEL; From 094a49779a789ee66d90d4428dcbcbedf8d97622 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 31 Jul 2017 13:12:54 -0500 Subject: [PATCH 1720/2152] Automatically set ``stacktrace.app.packages`` on Android if it is not provided by the user. (#466) --- CHANGES | 2 +- .../android/AndroidSentryClientFactory.java | 27 +++++++++++++++++++ .../io/sentry/DefaultSentryClientFactory.java | 2 +- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index f2b29939c0d..6111c5f1f52 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.3.2 ------------- -- +- Automatically set ``stacktrace.app.packages`` on Android if it is not provided by the user. Version 1.3.1 ------------- diff --git a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java index 2212e733dc0..9b03e76dd18 100644 --- a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java +++ b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java @@ -2,6 +2,7 @@ import android.Manifest; import android.content.Context; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.util.Log; import io.sentry.*; @@ -12,8 +13,12 @@ import io.sentry.context.ContextManager; import io.sentry.context.SingletonContextManager; import io.sentry.dsn.Dsn; +import io.sentry.util.Util; import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; /** * SentryClientFactory that handles Android-specific construction, like taking advantage @@ -73,6 +78,28 @@ public SentryClient createSentryClient(Dsn dsn) { return sentryClient; } + @Override + protected Collection getInAppFrames(Dsn dsn) { + Collection inAppFrames = super.getInAppFrames(dsn); + + if (inAppFrames.isEmpty()) { + PackageInfo info = null; + try { + info = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Error getting package information.", e); + } + + if (info != null && !Util.isNullOrEmpty(info.packageName)) { + List newPackages = new ArrayList<>(1); + newPackages.add(info.packageName); + return newPackages; + } + } + + return inAppFrames; + } + @Override protected Buffer getBuffer(Dsn dsn) { File bufferDir; diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index 19f540d9231..bb9d202e7dc 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -493,7 +493,7 @@ protected Collection getInAppFrames(Dsn dsn) { return Collections.emptyList(); } - ArrayList inAppPackages = new ArrayList<>(); + List inAppPackages = new ArrayList<>(); for (String inAppPackage : inAppFramesOption.split(",")) { if (!inAppPackage.trim().equals("")) { inAppPackages.add(inAppPackage); From 5b342b485ee69769059e66b75f241402506deb8a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 31 Jul 2017 13:27:38 -0500 Subject: [PATCH 1721/2152] * Enable UncaughtExceptionHandler by default, add ``uncaught.handler.enabled`` option. --- CHANGES | 1 + docs/config.rst | 15 ++++- .../android/AndroidSentryClientFactory.java | 2 +- .../io/sentry/DefaultSentryClientFactory.java | 18 ++++++ .../src/main/java/io/sentry/SentryClient.java | 21 ++++++- .../SentryUncaughtExceptionHandler.java | 60 ++++++++++++++----- .../SentryUncaughtExceptionHandlerTest.java | 55 +++++++++++++++++ 7 files changed, 152 insertions(+), 20 deletions(-) create mode 100644 sentry/src/test/java/io/sentry/SentryUncaughtExceptionHandlerTest.java diff --git a/CHANGES b/CHANGES index 6111c5f1f52..834a411361d 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 1.3.2 ------------- - Automatically set ``stacktrace.app.packages`` on Android if it is not provided by the user. +- Enable UncaughtExceptionHandler by default, add ``uncaught.handler.enabled`` option. Version 1.3.1 ------------- diff --git a/docs/config.rst b/docs/config.rst index f671bddf6de..f9197758fc0 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -265,11 +265,22 @@ StackTrace is printed, the result looks like this: Some frames are replaced by the ``... N more`` line as they are the same frames as in the enclosing exception. -To enable a similar behaviour in Sentry use the stacktrace.hidecommon`` option. +Similar behaviour is enabled by default in Sentry. To disable it, use the +``stacktrace.hidecommon`` option. :: - stacktrace.hidecommon + stacktrace.hidecommon=false + +Uncaught Exception Handler +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, an ``UncaughtExceptionHandler`` is configured that will send exceptions +to Sentry. To disable it, use the ``uncaught.handler.enabled`` option. + +:: + + uncaught.handler.enabled=false Event Sampling ~~~~~~~~~~~~~~ diff --git a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java index 9b03e76dd18..af6fa1348f5 100644 --- a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java +++ b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java @@ -74,7 +74,7 @@ public SentryClient createSentryClient(Dsn dsn) { SentryClient sentryClient = super.createSentryClient(dsn); sentryClient.addBuilderHelper(new AndroidEventBuilderHelper(ctx)); - SentryUncaughtExceptionHandler.setup(); + return sentryClient; } diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index bb9d202e7dc..2fc8f1e8fba 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -214,6 +214,10 @@ public class DefaultSentryClientFactory extends SentryClientFactory { * Option to set extra data to be sent to Sentry. */ public static final String EXTRA_OPTION = "extra"; + /** + * Option for whether to enable an uncaught exception handler, defaults to 'true'. + */ + public static final String UNCAUGHT_HANDLER_ENABLED_OPTION = "uncaught.handler.enabled"; private static final Logger logger = LoggerFactory.getLogger(DefaultSentryClientFactory.class); private static final String FALSE = Boolean.FALSE.toString(); @@ -291,6 +295,10 @@ protected SentryClient configureSentryClient(SentryClient sentryClient, Dsn dsn) } } + if (getUncaughtHandlerEnabled(dsn)) { + sentryClient.setupUncaughtExceptionHandler(); + } + return sentryClient; } @@ -850,6 +858,16 @@ protected int getBufferSize(Dsn dsn) { return Util.parseInteger(Lookup.lookup(BUFFER_SIZE_OPTION, dsn), BUFFER_SIZE_DEFAULT); } + /** + * Whether or not to enable a {@link SentryUncaughtExceptionHandler}. + * + * @param dsn Sentry server DSN which may contain options. + * @return Whether or not to enable a {@link SentryUncaughtExceptionHandler}. + */ + protected boolean getUncaughtHandlerEnabled(Dsn dsn) { + return !FALSE.equalsIgnoreCase(Lookup.lookup(UNCAUGHT_HANDLER_ENABLED_OPTION, dsn)); + } + /** * Thread factory generating daemon threads with a custom priority. *

    diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 23303bead64..6eabe40cdea 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -86,6 +86,10 @@ public class SentryClient { * such as {@link io.sentry.event.Breadcrumb}s. */ private final ContextManager contextManager; + /** + * Reference to the {@link SentryUncaughtExceptionHandler} if one was enabled, or null. + */ + private SentryUncaughtExceptionHandler uncaughtExceptionHandler; /** * Constructs a {@link SentryClient} instance using the provided connection. @@ -226,6 +230,10 @@ public List getBuilderHelpers() { * Closes the connection for the {@link SentryClient} instance. */ public void closeConnection() { + if (uncaughtExceptionHandler != null) { + uncaughtExceptionHandler.disable(); + } + try { connection.close(); } catch (IOException e) { @@ -383,7 +391,7 @@ public void setExtra(Map extra) { * * @param eventSendCallback callback instance */ - void addEventSendCallback(EventSendCallback eventSendCallback) { + public void addEventSendCallback(EventSendCallback eventSendCallback) { connection.addEventSendCallback(eventSendCallback); } @@ -392,10 +400,18 @@ void addEventSendCallback(EventSendCallback eventSendCallback) { * * @param shouldSendEventCallback callback instance */ - void addShouldSendEventCallback(ShouldSendEventCallback shouldSendEventCallback) { + public void addShouldSendEventCallback(ShouldSendEventCallback shouldSendEventCallback) { shouldSendEventCallbacks.add(shouldSendEventCallback); } + /** + * Setup and store the {@link SentryUncaughtExceptionHandler} so that it can be + * disabled on {@link SentryClient#closeConnection()}. + */ + protected void setupUncaughtExceptionHandler() { + uncaughtExceptionHandler = SentryUncaughtExceptionHandler.setup(); + } + @Override public String toString() { return "SentryClient{" @@ -409,6 +425,7 @@ public String toString() { + ", connection=" + connection + ", builderHelpers=" + builderHelpers + ", contextManager=" + contextManager + + ", uncaughtExceptionHandler=" + uncaughtExceptionHandler + '}'; } } diff --git a/sentry/src/main/java/io/sentry/SentryUncaughtExceptionHandler.java b/sentry/src/main/java/io/sentry/SentryUncaughtExceptionHandler.java index 813d60b4be4..298d1352a79 100644 --- a/sentry/src/main/java/io/sentry/SentryUncaughtExceptionHandler.java +++ b/sentry/src/main/java/io/sentry/SentryUncaughtExceptionHandler.java @@ -17,6 +17,12 @@ public class SentryUncaughtExceptionHandler implements Thread.UncaughtExceptionH * Reference to the pre-existing uncaught exception handler. */ private Thread.UncaughtExceptionHandler defaultExceptionHandler; + /** + * Whether or not this instance is enabled. If it has been wrapped by another + * handler (and is therefore not the {@link Thread#getDefaultUncaughtExceptionHandler()}), + * this boolean is the only way we can disable it. + */ + private volatile Boolean enabled = true; /** * Construct the {@link SentryUncaughtExceptionHandler}, storing the pre-existing uncaught exception @@ -37,17 +43,19 @@ public SentryUncaughtExceptionHandler(Thread.UncaughtExceptionHandler defaultExc */ @Override public void uncaughtException(Thread thread, Throwable thrown) { - logger.trace("Uncaught exception received."); + if (enabled) { + logger.trace("Uncaught exception received."); - EventBuilder eventBuilder = new EventBuilder() - .withMessage(thrown.getMessage()) - .withLevel(Event.Level.FATAL) - .withSentryInterface(new ExceptionInterface(thrown)); + EventBuilder eventBuilder = new EventBuilder() + .withMessage(thrown.getMessage()) + .withLevel(Event.Level.FATAL) + .withSentryInterface(new ExceptionInterface(thrown)); - try { - Sentry.capture(eventBuilder); - } catch (Exception e) { - logger.error("Error sending uncaught exception to Sentry.", e); + try { + Sentry.capture(eventBuilder); + } catch (Exception e) { + logger.error("Error sending uncaught exception to Sentry.", e); + } } if (defaultExceptionHandler != null) { @@ -59,19 +67,41 @@ public void uncaughtException(Thread thread, Throwable thrown) { /** * Configures an uncaught exception handler which sends events to * Sentry, then calls the preexisting uncaught exception handler. + * + * @return {@link SentryUncaughtExceptionHandler} that was setup. */ - public static void setup() { + public static SentryUncaughtExceptionHandler setup() { + logger.debug("Configuring uncaught exception handler."); + Thread.UncaughtExceptionHandler currentHandler = Thread.getDefaultUncaughtExceptionHandler(); if (currentHandler != null) { logger.debug("default UncaughtExceptionHandler class='" + currentHandler.getClass().getName() + "'"); } - // don't double register - if (!(currentHandler instanceof SentryUncaughtExceptionHandler)) { - // register as default exception handler - Thread.setDefaultUncaughtExceptionHandler( - new SentryUncaughtExceptionHandler(currentHandler)); + SentryUncaughtExceptionHandler handler = new SentryUncaughtExceptionHandler(currentHandler); + Thread.setDefaultUncaughtExceptionHandler(handler); + return handler; + } + + /** + * Disable this instance and attempt to remove it as the default {@link Thread.UncaughtExceptionHandler}. + */ + public void disable() { + enabled = false; + + // It's possible that another uncaught exception handler was installed 'over' us. + // Whether or not it wrapped us, we have no control over what other classes do, and + // so we can only remove ourselves if we are still the ('top most') default handler. + // The 'enabled' boolean exists to ensure we no longer handle uncaught exceptions + // even in the scenario where we are wrapped. + Thread.UncaughtExceptionHandler currentHandler = Thread.getDefaultUncaughtExceptionHandler(); + if (currentHandler == this) { + Thread.setDefaultUncaughtExceptionHandler(defaultExceptionHandler); } } + public Boolean isEnabled() { + return enabled; + } + } diff --git a/sentry/src/test/java/io/sentry/SentryUncaughtExceptionHandlerTest.java b/sentry/src/test/java/io/sentry/SentryUncaughtExceptionHandlerTest.java new file mode 100644 index 00000000000..a21c48a71ee --- /dev/null +++ b/sentry/src/test/java/io/sentry/SentryUncaughtExceptionHandlerTest.java @@ -0,0 +1,55 @@ +package io.sentry; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class SentryUncaughtExceptionHandlerTest { + private Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler; + + @BeforeMethod + public void setup() { + defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); + } + + @AfterMethod + public void teardown() { + Thread.setDefaultUncaughtExceptionHandler(defaultUncaughtExceptionHandler); + } + + @Test + public void testUnwrapped() { + Thread.setDefaultUncaughtExceptionHandler(null); + + SentryUncaughtExceptionHandler handler = SentryUncaughtExceptionHandler.setup(); + assertThat(handler, is(sameInstance(Thread.getDefaultUncaughtExceptionHandler()))); + + handler.disable(); + assertThat(Thread.getDefaultUncaughtExceptionHandler(), is(nullValue())); + assertThat(handler.isEnabled(), is(false)); + } + + @Test + public void testWrapped() { + Thread.setDefaultUncaughtExceptionHandler(null); + + final SentryUncaughtExceptionHandler handler = SentryUncaughtExceptionHandler.setup(); + assertThat(handler, is(sameInstance(Thread.getDefaultUncaughtExceptionHandler()))); + + Thread.UncaughtExceptionHandler wrappingHandler = new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + handler.uncaughtException(t, e); + } + }; + + Thread.setDefaultUncaughtExceptionHandler(wrappingHandler); + + handler.disable(); + assertThat(handler, is(not(Thread.getDefaultUncaughtExceptionHandler()))); + assertThat(handler.isEnabled(), is(false)); + } +} From 777149d715d12117747cc12f75b94e7487a42770 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 1 Aug 2017 07:33:06 -0500 Subject: [PATCH 1722/2152] Add Spring exception reporter and Spring Boot servlet initializer. (#471) --- CHANGES | 1 + docs/modules/index.rst | 1 + docs/modules/spring.rst | 105 ++++++++++++++++++ pom.xml | 1 + sentry-spring/README.md | 3 + sentry-spring/pom.xml | 97 ++++++++++++++++ .../spring/SentryExceptionResolver.java | 35 ++++++ .../SentryServletContextInitializer.java | 21 ++++ 8 files changed, 264 insertions(+) create mode 100644 docs/modules/spring.rst create mode 100644 sentry-spring/README.md create mode 100644 sentry-spring/pom.xml create mode 100644 sentry-spring/src/main/java/io/sentry/spring/SentryExceptionResolver.java create mode 100644 sentry-spring/src/main/java/io/sentry/spring/SentryServletContextInitializer.java diff --git a/CHANGES b/CHANGES index 834a411361d..34a77173647 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ Version 1.3.2 - Automatically set ``stacktrace.app.packages`` on Android if it is not provided by the user. - Enable UncaughtExceptionHandler by default, add ``uncaught.handler.enabled`` option. +- Add Spring exception reporter and Spring Boot servlet initializer. Version 1.3.1 ------------- diff --git a/docs/modules/index.rst b/docs/modules/index.rst index 46ea6c31305..f2332aeab06 100644 --- a/docs/modules/index.rst +++ b/docs/modules/index.rst @@ -15,3 +15,4 @@ you don't have to capture and send errors manually. log4j log4j2 logback + spring diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst new file mode 100644 index 00000000000..e538e78e736 --- /dev/null +++ b/docs/modules/spring.rst @@ -0,0 +1,105 @@ +Spring +====== + +The ``sentry-spring`` library provides `Spring `_ +support for Sentry via a `HandlerExceptionResolver +`_ +that sends exceptions to Sentry. Once this integration is configured +you can *also* use Sentry's static API, :ref:`as shown on the usage page `, +in order to do things like record breadcrumbs, set the current user, or manually send +events. + +The source can be found `on Github +`_. + +Installation +------------ + +Using Maven: + +.. sourcecode:: xml + + + io.sentry + sentry-spring + 1.3.1 + + +Using Gradle: + +.. sourcecode:: groovy + + compile 'io.sentry:sentry-spring:1.3.1' + +Using SBT: + +.. sourcecode:: scala + + libraryDependencies += "io.sentry" % "sentry-spring" % "1.3.1" + +For other dependency managers see the `central Maven repository `_. + +Usage +----- + +The ``sentry-spring`` library provides two classes that can be enabled by +registering them as Beans in your Spring application. + +Recording Exceptions +~~~~~~~~~~~~~~~~~~~~ + +In order to record all exceptions thrown by your controllers, you can register +``io.sentry.spring.SentryExceptionResolver`` as a Bean in your application. Once +registered, all exceptions will be sent to Sentry and then passed on to the default +exception handlers. + +**Note** that you should **not** configure the ``SentryExceptionResolver`` +alongside a logging integration (such as ``sentry-logback``), or you will most +likely double-report exceptions. You should use one or the other depending on +your needs. A logging integration is more general and will capture errors (and +possibly warnings, depending on your configuration) that occur inside *or outside* +of a Spring controller. + +Configuration via ``web.xml``: + +.. sourcecode:: xml + + + +Or via a configuration class: + +.. sourcecode:: java + + @Bean + public HandlerExceptionResolver sentryExceptionResolver() { + return new io.sentry.spring.SentryExceptionResolver(); + } + +Next, **you'll need to configure your DSN** (client key) and optionally other values such as +``environment`` and ``release``. :ref:`See the configuration page ` for ways you can do this. + +Spring Boot HTTP Data +~~~~~~~~~~~~~~~~~~~~~ + +Spring Boot doesn't automatically load ``javax.servlet.ServletContainerInitializer``s, +which means the Sentry SDK doesn't have an opportunity to hook into the request cycle +to collect information about the HTTP request. In order to add HTTP request data to +your Sentry events in Spring Boot, you need to register the +``io.sentry.spring.SentryServletContextInitializer`` class as a Bean in your application. + +Configuration via ``web.xml``: + +.. sourcecode:: xml + + + +Or via a configuration class: + +.. sourcecode:: java + + @Bean + public ServletContextInitializer sentryServletContextInitializer() { + return new io.sentry.spring.SentryServletContextInitializer(); + } + +After that, your Sentry events should contain information such as HTTP request headers. \ No newline at end of file diff --git a/pom.xml b/pom.xml index 6dff3f4c9e2..3a152bbee5a 100644 --- a/pom.xml +++ b/pom.xml @@ -79,6 +79,7 @@ sentry-log4j sentry-logback sentry-log4j2 + sentry-spring diff --git a/sentry-spring/README.md b/sentry-spring/README.md new file mode 100644 index 00000000000..12535cd4801 --- /dev/null +++ b/sentry-spring/README.md @@ -0,0 +1,3 @@ +# sentry-spring + +See the [Sentry documentation](https://docs.sentry.io/clients/java/modules/spring/) for more information. diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml new file mode 100644 index 00000000000..dd0f2ef1af8 --- /dev/null +++ b/sentry-spring/pom.xml @@ -0,0 +1,97 @@ + + + 4.0.0 + + + io.sentry + sentry-all + 1.3.1-SNAPSHOT + + + sentry-spring + jar + + Sentry-Java for Spring + Spring exception handler and servlet initializer for sending exceptions to Sentry. + + + 4.3.10.RELEASE + 1.5.4.RELEASE + + + + + ${project.groupId} + sentry + + + org.springframework + spring-webmvc + ${spring.version} + provided + + + org.springframework.boot + spring-boot + ${spring-boot.version} + provided + + + javax.servlet + javax.servlet-api + 3.0.1 + provided + + + + + org.jmockit + jmockit + test + + + org.testng + testng + test + + + org.hamcrest + hamcrest-core + test + + + org.hamcrest + hamcrest-library + test + + + junit + junit + test + + + com.github.tomakehurst + wiremock-standalone + test + + + + + + ${project.groupId} + sentry + test-jar + test + + + com.fasterxml.jackson.core + jackson-databind + test + + + com.fasterxml.jackson.core + jackson-annotations + test + + + diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryExceptionResolver.java b/sentry-spring/src/main/java/io/sentry/spring/SentryExceptionResolver.java new file mode 100644 index 00000000000..57f5ba54e53 --- /dev/null +++ b/sentry-spring/src/main/java/io/sentry/spring/SentryExceptionResolver.java @@ -0,0 +1,35 @@ +package io.sentry.spring; + +import io.sentry.Sentry; +import org.springframework.core.Ordered; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * {@link HandlerExceptionResolver} implementation that will record any exception that a + * Spring {@link org.springframework.web.servlet.mvc.Controller} throws to Sentry. It then + * returns null, which will let the other (default or custom) exception resolvers handle + * the actual error. + */ +public class SentryExceptionResolver implements HandlerExceptionResolver, Ordered { + @Override + public ModelAndView resolveException(HttpServletRequest request, + HttpServletResponse response, + Object handler, + Exception ex) { + + Sentry.capture(ex); + + // null = run other HandlerExceptionResolvers to actually handle the exception + return null; + } + + @Override + public int getOrder() { + // ensure this resolver runs first so that all exceptions are reported + return Integer.MIN_VALUE; + } +} diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryServletContextInitializer.java b/sentry-spring/src/main/java/io/sentry/spring/SentryServletContextInitializer.java new file mode 100644 index 00000000000..3ce2c17eb6e --- /dev/null +++ b/sentry-spring/src/main/java/io/sentry/spring/SentryServletContextInitializer.java @@ -0,0 +1,21 @@ +package io.sentry.spring; + +import io.sentry.servlet.SentryServletRequestListener; +import org.springframework.boot.web.servlet.ServletContextInitializer; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +/** + * {@link ServletContextInitializer} implementation that enables data collection for + * Sentry's {@link io.sentry.event.helper.HttpEventBuilderHelper}. Spring Boot + * doesn't automatically activate {@link javax.servlet.ServletContainerInitializer}s, + * so this exists so that it can be manually configured as a + * {@link org.springframework.context.annotation.Bean} in the user's application. + */ +public class SentryServletContextInitializer implements ServletContextInitializer { + @Override + public void onStartup(ServletContext ctx) throws ServletException { + ctx.addListener(SentryServletRequestListener.class); + } +} From 91bf0f064b0c7d74f3be05713df2a9ad61d445f7 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 1 Aug 2017 07:35:27 -0500 Subject: [PATCH 1723/2152] Change version to 1.4.0. --- CHANGES | 2 +- pom.xml | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index 34a77173647..eb2c3a030c9 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,4 @@ -Version 1.3.2 +Version 1.4.0 ------------- - Automatically set ``stacktrace.app.packages`` on Android if it is not provided by the user. diff --git a/pom.xml b/pom.xml index 3a152bbee5a..c76325057e5 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.3.2-SNAPSHOT + 1.4.0-SNAPSHOT pom Sentry-Java diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 77a7441b304..064cb69a718 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.3.2-SNAPSHOT +version = 1.4.0-SNAPSHOT diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index b08bdec2c0f..d7a933349c1 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.2-SNAPSHOT + 1.4.0-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 544f99360ce..ae46188a6c8 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.3.2-SNAPSHOT + 1.4.0-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index c43cc2e9f8d..e5629e3da1a 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.2-SNAPSHOT + 1.4.0-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 15a8d2ac118..afdbb258997 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.2-SNAPSHOT + 1.4.0-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 60b3df66a77..31260683bd8 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.2-SNAPSHOT + 1.4.0-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index dd0f2ef1af8..11409b7983c 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.1-SNAPSHOT + 1.4.0-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index abd390af653..217f71901c6 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.3.2-SNAPSHOT + 1.4.0-SNAPSHOT sentry From ceffda399c43da1905b78813f09de6ec2f5ecaa4 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 1 Aug 2017 07:36:51 -0500 Subject: [PATCH 1724/2152] Bump docs to 1.4.0 --- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index a4b3ff52b19..a79a08d8653 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.3.1`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.4.0`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.3.1' + compile 'io.sentry:sentry-android:1.4.0' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.3.1' + classpath 'io.sentry:sentry-android-gradle-plugin:1.4.0' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index e70b2f78042..cdb023cc633 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.3.1 + 1.4.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.3.1' + compile 'io.sentry:sentry-appengine:1.4.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.3.1" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.4.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 5ba3487e867..300aacd92ed 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.3.1 + 1.4.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.3.1' + compile 'io.sentry:sentry:1.4.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.3.1" + libraryDependencies += "io.sentry" % "sentry" % "1.4.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 7fcfde740fc..d8e61d627e3 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.3.1 + 1.4.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.3.1' + compile 'io.sentry:sentry-log4j:1.4.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.3.1" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.4.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 6d8f6532f4d..29083de8627 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.3.1 + 1.4.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.3.1' + compile 'io.sentry:sentry-log4j2:1.4.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.3.1" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.4.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index e4dedb7cdca..cc7af9045a1 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.3.1 + 1.4.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.3.1' + compile 'io.sentry:sentry-logback:1.4.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.3.1" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.4.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index e538e78e736..209cd5ad366 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.3.1 + 1.4.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.3.1' + compile 'io.sentry:sentry-spring:1.4.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.3.1" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.4.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index f67930e9c49..9d87dc65fe7 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.3.1 + 1.4.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.3.1' + compile 'io.sentry:sentry:1.4.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.3.1" + libraryDependencies += "io.sentry" % "sentry" % "1.4.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 3ded038248a4bb88658ebacd43338ce8d765b474 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 1 Aug 2017 07:38:18 -0500 Subject: [PATCH 1725/2152] [maven-release-plugin] prepare release v1.4.0 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index c76325057e5..4c4149c4984 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.4.0-SNAPSHOT + 1.4.0 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.4.0 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index d7a933349c1..205940e1129 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.4.0-SNAPSHOT + 1.4.0 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index ae46188a6c8..1d4ce9d82f7 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.4.0-SNAPSHOT + 1.4.0 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index e5629e3da1a..5352c143440 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.4.0-SNAPSHOT + 1.4.0 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index afdbb258997..aba229ae4b4 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.4.0-SNAPSHOT + 1.4.0 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 31260683bd8..d656caabfef 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.4.0-SNAPSHOT + 1.4.0 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 11409b7983c..85787b72ba9 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.4.0-SNAPSHOT + 1.4.0 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 217f71901c6..3a46c0e3567 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.4.0-SNAPSHOT + 1.4.0 sentry From 04c982fee2ab20df5cd36ac3aa883f0427d23829 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 1 Aug 2017 07:38:18 -0500 Subject: [PATCH 1726/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 4c4149c4984..e7f0efa4991 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.4.0 + 1.4.1-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.4.0 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 205940e1129..730eff9cfa9 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.4.0 + 1.4.1-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 1d4ce9d82f7..8e66add3ae4 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.4.0 + 1.4.1-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 5352c143440..e2c4a5b2f60 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.4.0 + 1.4.1-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index aba229ae4b4..4ce2258c50e 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.4.0 + 1.4.1-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index d656caabfef..311378c5870 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.4.0 + 1.4.1-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 85787b72ba9..7e85c5251e3 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.4.0 + 1.4.1-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 3a46c0e3567..a2573a25b20 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.4.0 + 1.4.1-SNAPSHOT sentry From bf4d78d6abc3e2d9fc9885435f26c77683d35829 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 1 Aug 2017 07:38:22 -0500 Subject: [PATCH 1727/2152] Bump CHANGES to 1.4.1 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index eb2c3a030c9..4373764413b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.4.1 +------------- + +- + Version 1.4.0 ------------- From ed00bc0b6811a363992670d51d13fd427e085c8a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 1 Aug 2017 07:56:59 -0500 Subject: [PATCH 1728/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 064cb69a718..db06f2808af 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.4.0-SNAPSHOT +version = 1.4.1-SNAPSHOT From dfe1185b58036289dc7181776e44443e892a1221 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 1 Aug 2017 09:11:55 -0500 Subject: [PATCH 1729/2152] Set JDK in Makefile. --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index 1f0220692d5..97ac72b72b8 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,11 @@ # export GPG_TTY=`tty` # eval $(gpg-agent --daemon) +# the test suite currently only works with 1.7.0_80 +# https://github.com/getsentry/sentry-java/issues/478 +export JAVA_HOME := $(shell /usr/libexec/java_home -v 1.7.0_80) +export PATH := $(JAVA_HOME)/bin:$(PATH) + .PHONY: checkstyle compile test install clean prepare prepareDocs prepareMvn prepareChanges perform verify # TODO: Fix to work between macOS and Linux From 62867b732fb5e4c0c1ad0b568993f5aca01d9d62 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 1 Aug 2017 10:41:05 -0500 Subject: [PATCH 1730/2152] ReST syntax fix. --- docs/modules/spring.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index 209cd5ad366..b322fb7870c 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -81,7 +81,7 @@ Next, **you'll need to configure your DSN** (client key) and optionally other va Spring Boot HTTP Data ~~~~~~~~~~~~~~~~~~~~~ -Spring Boot doesn't automatically load ``javax.servlet.ServletContainerInitializer``s, +Spring Boot doesn't automatically load any ``javax.servlet.ServletContainerInitializer``, which means the Sentry SDK doesn't have an opportunity to hook into the request cycle to collect information about the HTTP request. In order to add HTTP request data to your Sentry events in Spring Boot, you need to register the From 73338a5e79ea2c374b4261435ca8a71e5dded9ff Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 1 Aug 2017 13:21:55 -0500 Subject: [PATCH 1731/2152] Warn if users don't set in-app packages. (#480) --- .../src/main/java/io/sentry/DefaultSentryClientFactory.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index 2fc8f1e8fba..b6645a3276e 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -498,6 +498,10 @@ protected ContextManager getContextManager(Dsn dsn) { protected Collection getInAppFrames(Dsn dsn) { String inAppFramesOption = Lookup.lookup(IN_APP_FRAMES_OPTION, dsn); if (Util.isNullOrEmpty(inAppFramesOption)) { + + logger.warn("No '" + IN_APP_FRAMES_OPTION + "' was configured, this option is highly recommended " + + "as it affects stacktrace grouping and display on Sentry. See documentation: " + + "https://docs.sentry.io/clients/java/config/#in-application-stack-frames"); return Collections.emptyList(); } @@ -507,6 +511,7 @@ protected Collection getInAppFrames(Dsn dsn) { inAppPackages.add(inAppPackage); } } + return inAppPackages; } From fee4f1e8661005adea30e0d7f2cb51c45ad718a0 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 3 Aug 2017 11:50:50 -0500 Subject: [PATCH 1732/2152] Store object references instead of Strings in the Agent. --- agent/agent.cpp | 1 - agent/lib.cpp | 12 ------------ 2 files changed, 13 deletions(-) diff --git a/agent/agent.cpp b/agent/agent.cpp index fef82031e49..39623ec6153 100644 --- a/agent/agent.cpp +++ b/agent/agent.cpp @@ -1,5 +1,4 @@ // TODO: cache all FindClass/Find* calls globally -// TODO: trim object string length // TODO: better error messages with (void) jvmti->GetErrorName(errnum, &errnum_str); // TODO: do we need any locking? jrawMonitorID lock; jvmti->RawMonitorEnter(lock); jvmti->RawMonitorExit(lock); // TODO: use *options instead of env for log level diff --git a/agent/lib.cpp b/agent/lib.cpp index de7969c3969..f654854f052 100644 --- a/agent/lib.cpp +++ b/agent/lib.cpp @@ -34,10 +34,6 @@ static jobject getLocalValue(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint jclass reflect_class; jmethodID value_of; - jclass obj_class; - jmethodID to_string_method; - jobject stringified_result = nullptr; - switch (table[index].signature[0]) { case '[': // Array case 'L': // Object @@ -46,10 +42,6 @@ static jobject getLocalValue(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint return nullptr; } - obj_class = env->GetObjectClass(result); - to_string_method = env->GetMethodID(obj_class, "toString", "()Ljava/lang/String;"); - stringified_result = (jstring) env->CallObjectMethod(result, to_string_method); - break; case 'J': // long jvmti_error = jvmti->GetLocalLong(thread, depth, table[index].slot, &j_val); @@ -76,10 +68,6 @@ static jobject getLocalValue(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint return nullptr; } - if (stringified_result != nullptr) { - return stringified_result; - } - switch (table[index].signature[0]) { case 'J': // long reflect_class = env->FindClass("java/lang/Long"); From 918195bda29e8c373874315d921daef6650fcbfd Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 3 Aug 2017 12:17:27 -0500 Subject: [PATCH 1733/2152] Migration guide tweaks for clarity. --- docs/migration.rst | 83 +++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/docs/migration.rst b/docs/migration.rst index df16c075d61..46956301944 100644 --- a/docs/migration.rst +++ b/docs/migration.rst @@ -37,44 +37,6 @@ New Packages For example, the ``logback`` appender is now referenced in configuration by using ``io.sentry.logback.SentryAppender``. -Raven Class Changes -------------------- - -.. describe:: Before (raven-java) - - The ``Raven`` class was both the core client class and also had the ability to - statically store a client and send events without keeping track of the instance - yourself. - -.. describe:: Now (sentry-java) - - The core client class is now called ``SentryClient`` and there is now separate - ``Sentry`` class that you may use to handle initializing Sentry statically and - sending events. - - For example: - - .. sourcecode:: java - - // The static SentryClient can be lazily initialized from anywhere in your application. - // Your DSN needs to be provided somehow, check the configuration documentation! - Sentry.capture("Hello, world!) - -Classes Renamed ---------------- - -.. describe:: Before (raven-java) - - Many classes contained the word ``Raven``. For example ``RavenServletRequestListener``. - -.. describe:: Now (sentry-java) - - All instances of ``Raven`` have been renamed ``Sentry``. For example ``SentryServletRequestListener``. - - In addition, as noted above, the underlying client class ``Raven`` became ``SentryClient``, and - so ``RavenFactory`` also became ``SentryClientFactory`` and ``DefaultRavenFactory`` became - ``DefaultSentryClientFactory``. - Logging Integration Configuration --------------------------------- @@ -97,11 +59,10 @@ Logging Integration Configuration While setting up the ``SentryAppender`` itself is still required for logging integrations, **configuration** of Sentry is no longer done in the same place. - This is because appenders are initialized only when the first message (above their threshold) + This is because appenders are initialized only when the first message (at or above the threshold) is sent to them, which means Sentry has no idea how to initialize and configure itself until - the first event is sent. This may seem OK, except it prevented users from being able to do - things like record breadcrumbs, set the current user, programmatically configure the Sentry - client, and more. + the first event is sent. This may seem OK, but it prevented users from being able to do + things before an error was sent, such as: record breadcrumbs, set the current user, and more. For this reason, all configuration is now done "outside" of the logging integration itself. You may configure Sentry using a properties file (default: ``sentry.properties``) if you @@ -131,6 +92,29 @@ Logging Integration Configuration new BreadcrumbBuilder().setMessage("Made a call to the database.").build() ); +Raven Class Changes +------------------- + +.. describe:: Before (raven-java) + + The ``Raven`` class was both the core client class and also had the ability to + statically store a client and send events without keeping track of the instance + yourself. + +.. describe:: Now (sentry-java) + + The core client class is now called ``SentryClient`` and there is now separate + ``Sentry`` class that you may use to handle initializing Sentry statically and + sending events. + + For example: + + .. sourcecode:: java + + // The static SentryClient can be lazily initialized from anywhere in your application. + // Your DSN needs to be provided somehow, check the configuration documentation! + Sentry.capture("Hello, world!) + Configuration via DSN --------------------- @@ -168,6 +152,21 @@ Configuration via Environment Variables All options can be configured via Environment Variables, for example: ``SENTRY_ASYNC=false`` is respected. +Classes Renamed +--------------- + +.. describe:: Before (raven-java) + + Many classes contained the word ``Raven``. For example ``RavenServletRequestListener``. + +.. describe:: Now (sentry-java) + + All instances of ``Raven`` have been renamed ``Sentry``. For example ``SentryServletRequestListener``. + + In addition, as noted above, the underlying client class ``Raven`` became ``SentryClient``, and + so ``RavenFactory`` also became ``SentryClientFactory`` and ``DefaultRavenFactory`` became + ``DefaultSentryClientFactory``. + Custom Factories ---------------- From 8d0c80f0000c9e98aff317907b5640427d25c7c5 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 14 Aug 2017 09:54:14 -0500 Subject: [PATCH 1734/2152] Only check for JNDI once, don't print scary stacktrace if class isn't found. --- .../main/java/io/sentry/config/Lookup.java | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/sentry/src/main/java/io/sentry/config/Lookup.java b/sentry/src/main/java/io/sentry/config/Lookup.java index 0215b1cb53f..981f71ce3c5 100644 --- a/sentry/src/main/java/io/sentry/config/Lookup.java +++ b/sentry/src/main/java/io/sentry/config/Lookup.java @@ -25,6 +25,11 @@ public final class Lookup { * found or it failed to parse. */ private static Properties configProps; + /** + * Whether or not to check the JNDI system. Exists so that JNDI checks can be disabled + * the first time the class is not found. + */ + private static boolean checkJndi = true; static { String filePath = getConfigFilePath(); @@ -99,17 +104,19 @@ public static String lookup(String key) { public static String lookup(String key, Dsn dsn) { String value = null; - // Try to obtain from JNDI - try { - // Check that JNDI is available (not available on Android) by loading InitialContext - Class.forName("javax.naming.InitialContext", false, Dsn.class.getClassLoader()); - value = JndiLookup.jndiLookup(key); - if (value != null) { - logger.debug("Found {}={} in JNDI.", key, value); + if (checkJndi) { + // Try to obtain from JNDI + try { + // Check that JNDI is available (not available on Android) by loading InitialContext + Class.forName("javax.naming.InitialContext", false, Dsn.class.getClassLoader()); + value = JndiLookup.jndiLookup(key); + if (value != null) { + logger.debug("Found {}={} in JNDI.", key, value); + } + } catch (ClassNotFoundException | NoClassDefFoundError e) { + logger.trace("JNDI is not available: " + e.getMessage()); + checkJndi = false; } - - } catch (ClassNotFoundException | NoClassDefFoundError e) { - logger.trace("JNDI not available", e); } // Try to obtain from a Java System Property From 3e6ea1aa8674e2e9254984b3eb1adbcdd1f5acd4 Mon Sep 17 00:00:00 2001 From: Mark Woon Date: Tue, 15 Aug 2017 06:54:45 -0700 Subject: [PATCH 1735/2152] Add OSGI support. (#484) * Add OSGI support. * Make javax.servlet optional. * Update to bnd-maven-plugin 3.4.0. * OSGI support for sentry-logback. * OSGI support for sentry-log4j and sentry-log4j2. * OSGI support for sentry-spring. * OSGI support for sentry-android. --- sentry-android/bnd.bnd | 6 ++++++ sentry-android/pom.xml | 21 +++++++++++++++++++++ sentry-log4j/bnd.bnd | 5 +++++ sentry-log4j/pom.xml | 21 +++++++++++++++++++++ sentry-log4j2/bnd.bnd | 5 +++++ sentry-log4j2/pom.xml | 21 +++++++++++++++++++++ sentry-logback/bnd.bnd | 5 +++++ sentry-logback/pom.xml | 21 +++++++++++++++++++++ sentry-spring/bnd.bnd | 5 +++++ sentry-spring/pom.xml | 26 ++++++++++++++++++++++++++ sentry/bnd.bnd | 27 +++++++++++++++++++++++++++ sentry/pom.xml | 17 +++++++++++++++++ 12 files changed, 180 insertions(+) create mode 100644 sentry-android/bnd.bnd create mode 100644 sentry-log4j/bnd.bnd create mode 100644 sentry-log4j2/bnd.bnd create mode 100644 sentry-logback/bnd.bnd create mode 100644 sentry-spring/bnd.bnd create mode 100644 sentry/bnd.bnd diff --git a/sentry-android/bnd.bnd b/sentry-android/bnd.bnd new file mode 100644 index 00000000000..b7a89210ab6 --- /dev/null +++ b/sentry-android/bnd.bnd @@ -0,0 +1,6 @@ +Bundle-Name: ${project.name} +Bundle-Description: ${project.description} + +-exportcontents:\ + io.sentry.android,\ + io.sentry.android.event.helper diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 730eff9cfa9..42f8aa24741 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -90,6 +90,27 @@ + + biz.aQute.bnd + bnd-maven-plugin + 3.4.0 + + + + bnd-process + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + org.apache.maven.plugins maven-failsafe-plugin diff --git a/sentry-log4j/bnd.bnd b/sentry-log4j/bnd.bnd new file mode 100644 index 00000000000..bfaf08b0548 --- /dev/null +++ b/sentry-log4j/bnd.bnd @@ -0,0 +1,5 @@ +Bundle-Name: ${project.name} +Bundle-Description: ${project.description} + +-exportcontents:\ + io.sentry.log4j diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index e2c4a5b2f60..f2bc75c38fa 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -94,6 +94,27 @@ + + biz.aQute.bnd + bnd-maven-plugin + 3.4.0 + + + + bnd-process + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + org.apache.maven.plugins maven-surefire-plugin diff --git a/sentry-log4j2/bnd.bnd b/sentry-log4j2/bnd.bnd new file mode 100644 index 00000000000..42c64c4dfc1 --- /dev/null +++ b/sentry-log4j2/bnd.bnd @@ -0,0 +1,5 @@ +Bundle-Name: ${project.name} +Bundle-Description: ${project.description} + +-exportcontents:\ + io.sentry.log4j2 diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 4ce2258c50e..e28a4e85759 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -99,6 +99,27 @@ + + biz.aQute.bnd + bnd-maven-plugin + 3.4.0 + + + + bnd-process + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + org.apache.maven.plugins maven-failsafe-plugin diff --git a/sentry-logback/bnd.bnd b/sentry-logback/bnd.bnd new file mode 100644 index 00000000000..af741bc9c79 --- /dev/null +++ b/sentry-logback/bnd.bnd @@ -0,0 +1,5 @@ +Bundle-Name: ${project.name} +Bundle-Description: ${project.description} + +-exportcontents:\ + io.sentry.logback diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 311378c5870..5bf29266db8 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -92,6 +92,27 @@ + + biz.aQute.bnd + bnd-maven-plugin + 3.4.0 + + + + bnd-process + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + org.apache.maven.plugins maven-failsafe-plugin diff --git a/sentry-spring/bnd.bnd b/sentry-spring/bnd.bnd new file mode 100644 index 00000000000..4cf0756cd3a --- /dev/null +++ b/sentry-spring/bnd.bnd @@ -0,0 +1,5 @@ +Bundle-Name: ${project.name} +Bundle-Description: ${project.description} + +-exportcontents:\ + io.sentry.spring diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 7e85c5251e3..a30f77a7542 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -94,4 +94,30 @@ test + + + + + biz.aQute.bnd + bnd-maven-plugin + 3.4.0 + + + + bnd-process + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + diff --git a/sentry/bnd.bnd b/sentry/bnd.bnd new file mode 100644 index 00000000000..c12faf77d5e --- /dev/null +++ b/sentry/bnd.bnd @@ -0,0 +1,27 @@ +Bundle-Name: ${project.name} +Bundle-Description: ${project.description} + +-exportcontents:\ + io.sentry,\ + io.sentry.buffer,\ + io.sentry.config,\ + io.sentry.connection,\ + io.sentry.context,\ + io.sentry.dsn,\ + io.sentry.environment,\ + io.sentry.event,\ + io.sentry.event.helper,\ + io.sentry.event.interfaces,\ + io.sentry.jul,\ + io.sentry.jvmti,\ + io.sentry.marshaller,\ + io.sentry.marshaller.json,\ + io.sentry.servlet,\ + io.sentry.time,\ + io.sentry.util + +Import-Package: \ +javax.servlet;resolution:=optional,\ +javax.servlet.http;resolution:=optional,\ +* + diff --git a/sentry/pom.xml b/sentry/pom.xml index a2573a25b20..848db9926f6 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -120,9 +120,26 @@ 5 + + biz.aQute.bnd + bnd-maven-plugin + 3.4.0 + + + + bnd-process + + + + org.apache.maven.plugins maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + From 454baf66963883adf6814b9640acb71d5bb97844 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 15 Aug 2017 08:56:03 -0500 Subject: [PATCH 1736/2152] Update CHANGES. --- CHANGES | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 4373764413b..25f825a1b1c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,8 @@ Version 1.4.1 ------------- -- +- Reduce logging noise if JNDI is missing. +- Add OSGi support. (thanks markwoon) Version 1.4.0 ------------- From e4c8ebfc61c4d1c5f1e02ff4263010895cd4c316 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 15 Aug 2017 10:30:57 -0500 Subject: [PATCH 1737/2152] Check for common/simple DSN typo. --- sentry/src/main/java/io/sentry/dsn/Dsn.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sentry/src/main/java/io/sentry/dsn/Dsn.java b/sentry/src/main/java/io/sentry/dsn/Dsn.java index 5d6b8bb0686..ac5dbdbc82b 100644 --- a/sentry/src/main/java/io/sentry/dsn/Dsn.java +++ b/sentry/src/main/java/io/sentry/dsn/Dsn.java @@ -79,6 +79,11 @@ public Dsn(URI dsn) throws InvalidDsnException { public static String dsnLookup() { String dsn = Lookup.lookup("dsn"); + if (dsn == null) { + // check if the user accidentally set "dns" instead of "dsn" + dsn = Lookup.lookup("dns"); + } + if (dsn == null) { logger.warn("*** Couldn't find a suitable DSN, Sentry operations will do nothing!" + " See documentation: https://docs.sentry.io/clients/java/ ***"); From 2f05385fa67b6d47516e02c96cf4beb65dd2bdd3 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 17 Aug 2017 09:09:02 -0500 Subject: [PATCH 1738/2152] Improve Object JSON serializer for local variables. (#483) --- .../marshaller/json/JsonMarshaller.java | 44 +- .../marshaller/json/SentryJsonGenerator.java | 436 ++++++++++++++++++ .../json/StackTraceInterfaceBinding.java | 9 +- sentry/src/main/java/io/sentry/util/Util.java | 1 - .../json/SentryJsonGeneratorTest.java | 208 +++++++++ .../jsonobjectmarshallertest/testByte.json | 1 + .../testByteArray.json | 1 + .../jsonobjectmarshallertest/testCycle.json | 1 + .../testIntArray.json | 1 + .../testIterable.json | 1 + .../jsonobjectmarshallertest/testMap.json | 1 + .../jsonobjectmarshallertest/testNull.json | 1 + .../testObjectArray.json | 1 + .../jsonobjectmarshallertest/testPath.json | 1 + 14 files changed, 659 insertions(+), 48 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/marshaller/json/SentryJsonGenerator.java create mode 100644 sentry/src/test/java/io/sentry/marshaller/json/SentryJsonGeneratorTest.java create mode 100644 sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testByte.json create mode 100644 sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testByteArray.json create mode 100644 sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testCycle.json create mode 100644 sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testIntArray.json create mode 100644 sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testIterable.json create mode 100644 sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testMap.json create mode 100644 sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testNull.json create mode 100644 sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testObjectArray.json create mode 100644 sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testPath.json diff --git a/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java b/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java index 454c9c48fc7..3b959f3dab8 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java @@ -132,11 +132,11 @@ protected DateFormat initialValue() { * Create instance of JsonMarshaller with default message length. */ public JsonMarshaller() { - maxMessageLength = DEFAULT_MAX_MESSAGE_LENGTH; + this(DEFAULT_MAX_MESSAGE_LENGTH); } /** - * Create instance of JsonMarshaller with provided the maximum length of the messages. + * Create instance of JsonMarshaller with provided maximum length of the messages. * * @param maxMessageLength the maximum message length */ @@ -153,7 +153,7 @@ public void marshall(Event event, OutputStream destination) throws IOException { destination = new GZIPOutputStream(destination); } - try (JsonGenerator generator = jsonFactory.createGenerator(destination)) { + try (SentryJsonGenerator generator = new SentryJsonGenerator(jsonFactory.createGenerator(destination))) { writeContent(generator, event); } catch (IOException e) { logger.error("An exception occurred while serialising the event.", e); @@ -230,7 +230,7 @@ private void writeExtras(JsonGenerator generator, Map extras) th generator.writeObjectFieldStart(EXTRA); for (Map.Entry extra : extras.entrySet()) { generator.writeFieldName(extra.getKey()); - safelyWriteObject(generator, extra.getValue()); + generator.writeObject(extra.getValue()); } generator.writeEndObject(); } @@ -245,42 +245,6 @@ private void writeCollection(JsonGenerator generator, String name, Collection) value) { - safelyWriteObject(generator, subValue); - } - generator.writeEndArray(); - } else if (value instanceof Map) { - generator.writeStartObject(); - for (Map.Entry entry : ((Map) value).entrySet()) { - if (entry.getKey() == null) { - generator.writeFieldName("null"); - } else { - generator.writeFieldName(entry.getKey().toString()); - } - safelyWriteObject(generator, entry.getValue()); - } - generator.writeEndObject(); - } else if (value == null) { - generator.writeNull(); - } else { - try { - /** @see com.fasterxml.jackson.core.JsonGenerator#_writeSimpleObject(Object) */ - generator.writeObject(value); - } catch (IllegalStateException e) { - logger.debug("Couldn't marshal '{}' of type '{}', had to be converted into a String", - value, value.getClass()); - generator.writeString(value.toString()); - } - } - } - private void writeSdk(JsonGenerator generator, Sdk sdk) throws IOException { generator.writeObjectFieldStart(SDK); generator.writeStringField("name", sdk.getName()); diff --git a/sentry/src/main/java/io/sentry/marshaller/json/SentryJsonGenerator.java b/sentry/src/main/java/io/sentry/marshaller/json/SentryJsonGenerator.java new file mode 100644 index 00000000000..569682af771 --- /dev/null +++ b/sentry/src/main/java/io/sentry/marshaller/json/SentryJsonGenerator.java @@ -0,0 +1,436 @@ +package io.sentry.marshaller.json; + +import com.fasterxml.jackson.core.*; +import io.sentry.util.Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.file.Path; +import java.util.Map; + +/** + * JsonGenerator that makes an attempt at serializing any Java POJO to the "best" + * JSON representation. For example, iterables should become JSON arrays, Maps should + * become JSON objects, etc. As a fallback we use {@link Object#toString()}. + * + * Every method except {@link JsonGenerator#writeObject(Object)} is proxied to an + * underlying {@link JsonGenerator}. + */ +public class SentryJsonGenerator extends JsonGenerator { + private static final Logger logger = LoggerFactory.getLogger(Util.class); + + private static final String RECURSION_LIMIT_HIT = ""; + private static final int MAX_LENGTH_LIST = 10; + private static final int MAX_SIZE_MAP = 50; + private static final int MAX_LENGTH_STRING = 400; + private static final int MAX_NESTING = 3; + private static final String ELIDED = "..."; + + private int maxLengthList; + private int maxLengthString; + private int maxSizeMap; + private int maxNesting; + private JsonGenerator generator; + + /** + * Construct a JsonObjectMarshaller with the default configuration. + * + * @param generator Underlying JsonGenerator used for actual writing + */ + public SentryJsonGenerator(JsonGenerator generator) { + this.generator = generator; + + this.maxLengthList = MAX_LENGTH_LIST; + this.maxLengthString = MAX_LENGTH_STRING; + this.maxSizeMap = MAX_SIZE_MAP; + this.maxNesting = MAX_NESTING; + } + + /** + * Serialize any object to JSON. Large collections are elided. + * + * @param value Value to write out. + * @throws IOException On Jackson error (unserializable object). + */ + public void writeObject(Object value) throws IOException { + writeObject(value, 0); + } + + private void writeObject(Object value, int recursionLevel) throws IOException { + if (recursionLevel >= maxNesting) { + generator.writeString(RECURSION_LIMIT_HIT); + return; + } + + if (value == null) { + generator.writeNull(); + } else if (value.getClass().isArray()) { + generator.writeStartArray(); + writeArray(value, recursionLevel); + generator.writeEndArray(); + } else if (value instanceof Path) { + // Path is weird because it implements Iterable, and then the iterator returns + // more Paths, which are iterable... which would cause a stack overflow below. + generator.writeString(Util.trimString(value.toString(), maxLengthString)); + } else if (value instanceof Iterable) { + generator.writeStartArray(); + int i = 0; + for (Object subValue : (Iterable) value) { + if (i >= maxLengthList) { + writeElided(); + break; + } + + writeObject(subValue, recursionLevel + 1); + + i++; + } + generator.writeEndArray(); + } else if (value instanceof Map) { + generator.writeStartObject(); + int i = 0; + for (Map.Entry entry : ((Map) value).entrySet()) { + if (i >= maxSizeMap) { + break; + } + + if (entry.getKey() == null) { + generator.writeFieldName("null"); + } else { + generator.writeFieldName(Util.trimString(entry.getKey().toString(), maxLengthString)); + } + writeObject(entry.getValue(), recursionLevel + 1); + + i++; + } + generator.writeEndObject(); + } else if (value instanceof String) { + generator.writeString(Util.trimString((String) value, maxLengthString)); + } else { + try { + /** @see com.fasterxml.jackson.core.JsonGenerator#_writeSimpleObject(Object) */ + generator.writeObject(value); + } catch (IllegalStateException e) { + logger.debug("Couldn't marshal '{}' of type '{}', had to be converted into a String", + value, value.getClass()); + generator.writeString(Util.trimString(value.toString(), maxLengthString)); + } + } + } + + private void writeArray(Object value, + int recursionLevel) throws IOException { + if (value instanceof byte[]) { + byte[] byteArray = (byte[]) value; + for (int i = 0; i < byteArray.length && i < maxLengthList; i++) { + generator.writeNumber((int) byteArray[i]); + } + if (byteArray.length > maxLengthList) { + writeElided(); + } + } else if (value instanceof short[]) { + short[] shortArray = (short[]) value; + for (int i = 0; i < shortArray.length && i < maxLengthList; i++) { + generator.writeNumber((int) shortArray[i]); + } + if (shortArray.length > maxLengthList) { + writeElided(); + } + } else if (value instanceof int[]) { + int[] intArray = (int[]) value; + for (int i = 0; i < intArray.length && i < maxLengthList; i++) { + generator.writeNumber(intArray[i]); + } + if (intArray.length > maxLengthList) { + writeElided(); + } + } else if (value instanceof long[]) { + long[] longArray = (long[]) value; + for (int i = 0; i < longArray.length && i < maxLengthList; i++) { + generator.writeNumber(longArray[i]); + } + if (longArray.length > maxLengthList) { + writeElided(); + } + } else if (value instanceof float[]) { + float[] floatArray = (float[]) value; + for (int i = 0; i < floatArray.length && i < maxLengthList; i++) { + generator.writeNumber(floatArray[i]); + } + if (floatArray.length > maxLengthList) { + writeElided(); + } + } else if (value instanceof double[]) { + double[] doubleArray = (double[]) value; + for (int i = 0; i < doubleArray.length && i < maxLengthList; i++) { + generator.writeNumber(doubleArray[i]); + } + if (doubleArray.length > maxLengthList) { + writeElided(); + } + } else if (value instanceof char[]) { + char[] charArray = (char[]) value; + for (int i = 0; i < charArray.length && i < maxLengthList; i++) { + generator.writeString(String.valueOf(charArray[i])); + } + if (charArray.length > maxLengthList) { + writeElided(); + } + } else if (value instanceof boolean[]) { + boolean[] boolArray = (boolean[]) value; + for (int i = 0; i < boolArray.length && i < maxLengthList; i++) { + generator.writeBoolean(boolArray[i]); + } + if (boolArray.length > maxLengthList) { + writeElided(); + } + } else { + Object[] objArray = (Object[]) value; + for (int i = 0; i < objArray.length && i < maxLengthList; i++) { + writeObject(objArray[i], recursionLevel + 1); + } + if (objArray.length > maxLengthList) { + writeElided(); + } + } + } + + private void writeElided() throws IOException { + generator.writeString(ELIDED); + } + + public void setMaxLengthList(int maxLengthList) { + this.maxLengthList = maxLengthList; + } + + public void setMaxLengthString(int maxLengthString) { + this.maxLengthString = maxLengthString; + } + + public void setMaxSizeMap(int maxSizeMap) { + this.maxSizeMap = maxSizeMap; + } + + public void setMaxNesting(int maxNesting) { + this.maxNesting = maxNesting; + } + + @Override + public JsonGenerator setCodec(ObjectCodec oc) { + return generator.setCodec(oc); + } + + @Override + public ObjectCodec getCodec() { + return generator.getCodec(); + } + + @Override + public Version version() { + return generator.version(); + } + + @Override + public JsonGenerator enable(Feature f) { + return generator.enable(f); + } + + @Override + public JsonGenerator disable(Feature f) { + return generator.disable(f); + } + + @Override + public boolean isEnabled(Feature f) { + return generator.isEnabled(f); + } + + @Override + public int getFeatureMask() { + return generator.getFeatureMask(); + } + + @Override + public JsonGenerator setFeatureMask(int values) { + return generator.setFeatureMask(values); + } + + @Override + public JsonGenerator useDefaultPrettyPrinter() { + return generator.useDefaultPrettyPrinter(); + } + + @Override + public void writeStartArray() throws IOException { + generator.writeStartArray(); + } + + @Override + public void writeEndArray() throws IOException { + generator.writeEndArray(); + } + + @Override + public void writeStartObject() throws IOException { + generator.writeStartObject(); + } + + @Override + public void writeEndObject() throws IOException { + generator.writeEndObject(); + } + + @Override + public void writeFieldName(String name) throws IOException { + generator.writeFieldName(name); + } + + @Override + public void writeFieldName(SerializableString name) throws IOException { + generator.writeFieldName(name); + } + + @Override + public void writeString(String text) throws IOException { + generator.writeString(text); + } + + @Override + public void writeString(char[] text, int offset, int len) throws IOException { + generator.writeString(text, offset, len); + } + + @Override + public void writeString(SerializableString text) throws IOException { + generator.writeString(text); + } + + @Override + public void writeRawUTF8String(byte[] text, int offset, int length) throws IOException { + generator.writeRawUTF8String(text, offset, length); + } + + @Override + public void writeUTF8String(byte[] text, int offset, int length) throws IOException { + generator.writeUTF8String(text, offset, length); + } + + @Override + public void writeRaw(String text) throws IOException { + generator.writeRaw(text); + } + + @Override + public void writeRaw(String text, int offset, int len) throws IOException { + generator.writeRaw(text, offset, len); + } + + @Override + public void writeRaw(char[] text, int offset, int len) throws IOException { + generator.writeRaw(text, offset, len); + } + + @Override + public void writeRaw(char c) throws IOException { + generator.writeRaw(c); + } + + @Override + public void writeRawValue(String text) throws IOException { + generator.writeRawValue(text); + } + + @Override + public void writeRawValue(String text, int offset, int len) throws IOException { + generator.writeRawValue(text, offset, len); + } + + @Override + public void writeRawValue(char[] text, int offset, int len) throws IOException { + generator.writeRawValue(text, offset, len); + } + + @Override + public void writeBinary(Base64Variant bv, byte[] data, int offset, int len) throws IOException { + generator.writeBinary(bv, data, offset, len); + } + + @Override + public int writeBinary(Base64Variant bv, InputStream data, int dataLength) throws IOException { + return generator.writeBinary(bv, data, dataLength); + } + + @Override + public void writeNumber(int v) throws IOException { + generator.writeNumber(v); + } + + @Override + public void writeNumber(long v) throws IOException { + generator.writeNumber(v); + } + + @Override + public void writeNumber(BigInteger v) throws IOException { + generator.writeNumber(v); + } + + @Override + public void writeNumber(double v) throws IOException { + generator.writeNumber(v); + } + + @Override + public void writeNumber(float v) throws IOException { + generator.writeNumber(v); + } + + @Override + public void writeNumber(BigDecimal v) throws IOException { + generator.writeNumber(v); + } + + @Override + public void writeNumber(String encodedValue) throws IOException { + generator.writeNumber(encodedValue); + } + + @Override + public void writeBoolean(boolean state) throws IOException { + generator.writeBoolean(state); + } + + @Override + public void writeNull() throws IOException { + generator.writeNull(); + } + + @Override + public void writeTree(TreeNode rootNode) throws IOException { + generator.writeTree(rootNode); + } + + @Override + public JsonStreamContext getOutputContext() { + return generator.getOutputContext(); + } + + @Override + public void flush() throws IOException { + generator.flush(); + } + + @Override + public boolean isClosed() { + return generator.isClosed(); + } + + @Override + public void close() throws IOException { + generator.close(); + } +} diff --git a/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java index 4b19033b364..e90fa7597d9 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java @@ -60,13 +60,8 @@ private void writeFrame(JsonGenerator generator, SentryStackTraceElement stackTr if (stackTraceElement.getLocals() != null && !stackTraceElement.getLocals().isEmpty()) { generator.writeObjectFieldStart(VARIABLES_PARAMETER); for (Map.Entry varEntry : stackTraceElement.getLocals().entrySet()) { - String name = varEntry.getKey(); - Object value = varEntry.getValue(); - if (value == null) { - generator.writeNullField(name); - } else { - generator.writeObjectField(name, value); - } + generator.writeFieldName(varEntry.getKey()); + generator.writeObject(varEntry.getValue()); } generator.writeEndObject(); } diff --git a/sentry/src/main/java/io/sentry/util/Util.java b/sentry/src/main/java/io/sentry/util/Util.java index ed4d7a73ac2..3487693baaa 100644 --- a/sentry/src/main/java/io/sentry/util/Util.java +++ b/sentry/src/main/java/io/sentry/util/Util.java @@ -11,7 +11,6 @@ * Sentry static Utility class. */ public final class Util { - // Hide the constructor. private Util() { diff --git a/sentry/src/test/java/io/sentry/marshaller/json/SentryJsonGeneratorTest.java b/sentry/src/test/java/io/sentry/marshaller/json/SentryJsonGeneratorTest.java new file mode 100644 index 00000000000..14ce7b82052 --- /dev/null +++ b/sentry/src/test/java/io/sentry/marshaller/json/SentryJsonGeneratorTest.java @@ -0,0 +1,208 @@ +package io.sentry.marshaller.json; + +import com.fasterxml.jackson.core.JsonFactory; +import io.sentry.BaseTest; +import org.testng.annotations.Test; + +import java.io.OutputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + +import static io.sentry.marshaller.json.JsonComparisonUtil.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import static io.sentry.marshaller.json.JsonComparisonUtil.newJsonOutputStream; + +public class SentryJsonGeneratorTest extends BaseTest { + private void configureGenerator(SentryJsonGenerator generator) throws Exception { + generator.setMaxLengthList(2); + generator.setMaxLengthString(10); + generator.setMaxSizeMap(2); + generator.setMaxNesting(3); + } + + @Test + public void testNull() throws Exception { + final JsonComparisonUtil.JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); + + write(jsonOutputStreamParser.outputStream, null); + + assertThat(jsonOutputStreamParser.value(), + is(jsonResource("/io/sentry/marshaller/json/jsonobjectmarshallertest/testNull.json"))); + } + + @Test + public void testPath() throws Exception { + final JsonComparisonUtil.JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); + + Path path = Paths.get("/home", "user", "tmp"); + write(jsonOutputStreamParser.outputStream, path); + + assertThat(jsonOutputStreamParser.value(), + is(jsonResource("/io/sentry/marshaller/json/jsonobjectmarshallertest/testPath.json"))); + } + + @Test + public void testIterable() throws Exception { + final JsonComparisonUtil.JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); + + List> listList = new ArrayList<>(); + List list1 = new ArrayList<>(); + list1.add("1"); + list1.add("2"); + list1.add("3"); + listList.add(list1); + List list2 = new ArrayList<>(); + list2.add("4"); + list2.add("5"); + list2.add("6"); + listList.add(list2); + List list3 = new ArrayList<>(); + list3.add("7"); + list3.add("8"); + list3.add("9"); + listList.add(list3); + + write(jsonOutputStreamParser.outputStream, listList); + + assertThat(jsonOutputStreamParser.value(), + is(jsonResource("/io/sentry/marshaller/json/jsonobjectmarshallertest/testIterable.json"))); + } + + @Test + public void testMap() throws Exception { + final JsonComparisonUtil.JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); + + Map> mapMap = new LinkedHashMap<>(); + Map map1 = new LinkedHashMap<>(); + map1.put("one very long key that will be elided", 1); + map1.put("two very long key that will be elided", 2); + map1.put("three very long key that will be elided", 3); + mapMap.put("map1", map1); + Map map2 = new LinkedHashMap<>(); + map2.put("four very long key that will be elided", 4); + map2.put("five very long key that will be elided", 5); + map2.put("six very long key that will be elided", 6); + mapMap.put("map2", map2); + Map map3 = new LinkedHashMap<>(); + map3.put("seven very long key that will be elided", 7); + map3.put("eight very long key that will be elided", 8); + map3.put("nine very long key that will be elided", 9); + mapMap.put("map3", map3); + + write(jsonOutputStreamParser.outputStream, mapMap); + + assertThat(jsonOutputStreamParser.value(), + is(jsonResource("/io/sentry/marshaller/json/jsonobjectmarshallertest/testMap.json"))); + } + + @Test + public void testCycle() throws Exception { + final JsonComparisonUtil.JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); + + Map cycleMap = new HashMap<>(); + cycleMap.put("cycle!", cycleMap); + + write(jsonOutputStreamParser.outputStream, cycleMap); + + assertThat(jsonOutputStreamParser.value(), + is(jsonResource("/io/sentry/marshaller/json/jsonobjectmarshallertest/testCycle.json"))); + } + + @Test + public void testByte() throws Exception { + final JsonComparisonUtil.JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); + + byte b = 127; + write(jsonOutputStreamParser.outputStream, b); + + assertThat(jsonOutputStreamParser.value(), + is(jsonResource("/io/sentry/marshaller/json/jsonobjectmarshallertest/testByte.json"))); + } + + @Test + public void testIntArray() throws Exception { + final JsonComparisonUtil.JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); + + int[] ints = {1, 2, 3}; + write(jsonOutputStreamParser.outputStream, ints); + + assertThat(jsonOutputStreamParser.value(), + is(jsonResource("/io/sentry/marshaller/json/jsonobjectmarshallertest/testIntArray.json"))); + } + + @Test + public void testIntegerArray() throws Exception { + final JsonComparisonUtil.JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); + + Integer[] ints = {1, 2, 3}; + write(jsonOutputStreamParser.outputStream, ints); + + assertThat(jsonOutputStreamParser.value(), + is(jsonResource("/io/sentry/marshaller/json/jsonobjectmarshallertest/testIntArray.json"))); + + } + + @Test + public void testByteArray() throws Exception { + final JsonComparisonUtil.JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); + + byte[] bytes = {3, 4, 5}; + write(jsonOutputStreamParser.outputStream, bytes); + + assertThat(jsonOutputStreamParser.value(), + is(jsonResource("/io/sentry/marshaller/json/jsonobjectmarshallertest/testByteArray.json"))); + + } + + @Test + public void testObjectArray() throws Exception { + final JsonComparisonUtil.JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); + + Object o1 = new Object() { + @Override + public String toString() { + return "obj1"; + } + }; + + Object o2 = new Object() { + @Override + public String toString() { + return "obj2"; + } + }; + + Object o3 = new Object() { + @Override + public String toString() { + return "obj3"; + } + }; + + Object[] objs = {o1, o2, o3}; + write(jsonOutputStreamParser.outputStream, objs); + + assertThat(jsonOutputStreamParser.value(), + is(jsonResource("/io/sentry/marshaller/json/jsonobjectmarshallertest/testObjectArray.json"))); + + } + + private void write(OutputStream destination, Object object) throws Exception { + final JsonFactory jsonFactory = new JsonFactory(); + + try (SentryJsonGenerator generator = new SentryJsonGenerator(jsonFactory.createGenerator(destination))) { + configureGenerator(generator); + + generator.writeStartObject(); + generator.writeFieldName("output"); + generator.writeObject(object); + generator.writeEndObject(); + } finally { + destination.close(); + } + } + +} diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testByte.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testByte.json new file mode 100644 index 00000000000..26b66c9b416 --- /dev/null +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testByte.json @@ -0,0 +1 @@ +{"output": 127} \ No newline at end of file diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testByteArray.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testByteArray.json new file mode 100644 index 00000000000..30a24e7c750 --- /dev/null +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testByteArray.json @@ -0,0 +1 @@ +{"output": [3,4,"..."]} \ No newline at end of file diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testCycle.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testCycle.json new file mode 100644 index 00000000000..b7e6f62f2b7 --- /dev/null +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testCycle.json @@ -0,0 +1 @@ +{"output":{"cycle!":{"cycle!":{"cycle!":""}}}} \ No newline at end of file diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testIntArray.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testIntArray.json new file mode 100644 index 00000000000..66d31fb59b1 --- /dev/null +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testIntArray.json @@ -0,0 +1 @@ +{"output": [1,2,"..."]} \ No newline at end of file diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testIterable.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testIterable.json new file mode 100644 index 00000000000..5f44373683a --- /dev/null +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testIterable.json @@ -0,0 +1 @@ +{"output":[["1","2","..."],["4","5","..."],"..."]} \ No newline at end of file diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testMap.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testMap.json new file mode 100644 index 00000000000..8c05321fb8c --- /dev/null +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testMap.json @@ -0,0 +1 @@ +{"output":{"map1":{"one ver...":1,"two ver...":2},"map2":{"four ve...":4,"five ve...":5}}} \ No newline at end of file diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testNull.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testNull.json new file mode 100644 index 00000000000..06cbf7ad610 --- /dev/null +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testNull.json @@ -0,0 +1 @@ +{"output": null} \ No newline at end of file diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testObjectArray.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testObjectArray.json new file mode 100644 index 00000000000..b73bca3724b --- /dev/null +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testObjectArray.json @@ -0,0 +1 @@ +{"output": ["obj1", "obj2","..."]} \ No newline at end of file diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testPath.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testPath.json new file mode 100644 index 00000000000..b70d2d96ac6 --- /dev/null +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testPath.json @@ -0,0 +1 @@ +{"output": "/home/u..."} \ No newline at end of file From 5ab2a708416fbec9b65b64ce49f5fb4afe6cc96a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 18 Aug 2017 10:51:33 -0500 Subject: [PATCH 1739/2152] Local Variable caching work. (#490) --- agent/agent.cpp | 45 +++-- agent/lib.cpp | 31 +-- agent/lib.h | 2 +- .../io/sentry/DefaultSentryClientFactory.java | 5 + .../event/interfaces/SentryException.java | 4 +- .../interfaces/SentryStackTraceElement.java | 42 +++- .../src/main/java/io/sentry/jvmti/Frame.java | 15 +- .../main/java/io/sentry/jvmti/FrameCache.java | 63 +++++- .../SentryStackTraceElementTest.java | 183 ++++++++++++++++++ .../java/io/sentry/jvmti/FrameCacheTest.java | 65 +++++++ 10 files changed, 417 insertions(+), 38 deletions(-) create mode 100644 sentry/src/test/java/io/sentry/event/interfaces/SentryStackTraceElementTest.java create mode 100644 sentry/src/test/java/io/sentry/jvmti/FrameCacheTest.java diff --git a/agent/agent.cpp b/agent/agent.cpp index 39623ec6153..b6a9b8d0cb5 100644 --- a/agent/agent.cpp +++ b/agent/agent.cpp @@ -21,31 +21,48 @@ static void JNICALL ExceptionCallback(jvmtiEnv *jvmti, JNIEnv *env, jthread thre return; } - char *class_name = (char *) "io/sentry/jvmti/FrameCache"; - char *method_name = (char *) "add"; - char *signature = (char *) "(Ljava/lang/Throwable;[Lio/sentry/jvmti/Frame;)V"; - - jclass callback_class = nullptr; - jmethodID callback_method_id = nullptr; - - callback_class = env->FindClass(class_name); - if (callback_class == nullptr) { + jclass framecache_class = env->FindClass("io/sentry/jvmti/FrameCache"); + if (framecache_class == nullptr) { env->ExceptionClear(); log(TRACE, "Unable to locate FrameCache class."); return; } - callback_method_id = env->GetStaticMethodID(callback_class, method_name, signature); - if (callback_method_id == nullptr) { + jmethodID should_cache_method = env->GetStaticMethodID(framecache_class, + "shouldCacheThrowable", + "(Ljava/lang/Throwable;I)Z"); + if (should_cache_method == nullptr) { + log(TRACE, "Unable to locate static FrameCache.shouldCacheThrowable method."); + return; + } + + jint num_frames; + jvmtiError jvmti_error = jvmti->GetFrameCount(thread, &num_frames); + if (jvmti_error != JVMTI_ERROR_NONE) { + log(ERROR, "Could not get the frame count."); + return; + } + + jboolean shouldCache = env->CallStaticBooleanMethod(framecache_class, + should_cache_method, + exception, + num_frames); + if (!((bool) shouldCache)) { + return; + } + + jmethodID framecache_add_method = env->GetStaticMethodID(framecache_class, + "add", + "(Ljava/lang/Throwable;[Lio/sentry/jvmti/Frame;)V"); + if (framecache_add_method == nullptr) { log(TRACE, "Unable to locate static FrameCache.add method."); return; } - jobjectArray frames; jint start_depth = 0; - frames = buildStackTraceFrames(jvmti, env, thread, start_depth); + jobjectArray frames = buildStackTraceFrames(jvmti, env, thread, start_depth, num_frames); - env->CallStaticVoidMethod(callback_class, callback_method_id, exception, frames); + env->CallStaticVoidMethod(framecache_class, framecache_add_method, exception, frames); log(TRACE, "ExceptionCallback exit."); } diff --git a/agent/lib.cpp b/agent/lib.cpp index f654854f052..967af13739c 100644 --- a/agent/lib.cpp +++ b/agent/lib.cpp @@ -138,22 +138,36 @@ static void makeLocalVariable(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, env->SetObjectArrayElement(locals, index, local); } -static jobject makeFrameObject(JNIEnv *env, jobjectArray locals) { +static jobject makeFrameObject(jvmtiEnv* jvmti, JNIEnv *env, jmethodID method, jobjectArray locals) { + jvmtiError jvmti_error; + jclass method_class; + jobject frame_method; jclass frame_class; jmethodID ctor; + jvmti_error = jvmti->GetMethodDeclaringClass(method, &method_class); + if (jvmti_error != JVMTI_ERROR_NONE) { + throwException(env, "java/lang/RuntimeException", "Could not get the declaring class of the method."); + return nullptr; + } + + frame_method = env->ToReflectedMethod(method_class, method, (jboolean) true); + if (frame_method == nullptr) { + return nullptr; // ToReflectedMethod raised an exception + } + frame_class = env->FindClass("io/sentry/jvmti/Frame"); if (frame_class == nullptr) { return nullptr; } ctor = env->GetMethodID(frame_class, "", - "([Lio/sentry/jvmti/Frame$LocalVariable;)V"); + "(Ljava/lang/reflect/Method;[Lio/sentry/jvmti/Frame$LocalVariable;)V"); if (ctor == nullptr) { return nullptr; } - return env->NewObject(frame_class, ctor, locals); + return env->NewObject(frame_class, ctor, frame_method, locals); } static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, @@ -205,14 +219,13 @@ static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint dep value_ptr = nullptr; } - return makeFrameObject(env, locals); + return makeFrameObject(jvmti, env, method, locals); } jobjectArray buildStackTraceFrames(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, - jint start_depth) { + jint start_depth, jint num_frames) { log(TRACE, "buildStackTraceFrames called."); - jint num_frames; jvmtiFrameInfo* frames; jclass result_class; jint num_frames_returned; @@ -220,12 +233,6 @@ jobjectArray buildStackTraceFrames(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jobjectArray result; jobject frame; - jvmti_error = jvmti->GetFrameCount(thread, &num_frames); - if (jvmti_error != JVMTI_ERROR_NONE) { - throwException(env, "java/lang/RuntimeException", "Could not get the frame count."); - return nullptr; - } - jvmti_error = jvmti->Allocate(num_frames * (int)sizeof(jvmtiFrameInfo), (unsigned char **) &frames); if (jvmti_error != JVMTI_ERROR_NONE) { throwException(env, "java/lang/RuntimeException", "Could not allocate frame buffer."); diff --git a/agent/lib.h b/agent/lib.h index 2de82887502..19ce0f71597 100644 --- a/agent/lib.h +++ b/agent/lib.h @@ -15,6 +15,6 @@ enum Level { void log(Level level, std::string message); jobjectArray buildStackTraceFrames(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, - jint start_depth); + jint start_depth, jint num_frames); #endif //SENTRY_JAVA_AGENT_LIB_H diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index b6645a3276e..9a14a90b8f5 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -10,6 +10,7 @@ import io.sentry.event.helper.ContextBuilderHelper; import io.sentry.event.helper.HttpEventBuilderHelper; import io.sentry.event.interfaces.*; +import io.sentry.jvmti.FrameCache; import io.sentry.marshaller.Marshaller; import io.sentry.marshaller.json.*; import io.sentry.util.Util; @@ -299,6 +300,10 @@ protected SentryClient configureSentryClient(SentryClient sentryClient, Dsn dsn) sentryClient.setupUncaughtExceptionHandler(); } + for (String inAppPackage : getInAppFrames(dsn)) { + FrameCache.addAppPackage(inAppPackage); + } + return sentryClient; } diff --git a/sentry/src/main/java/io/sentry/event/interfaces/SentryException.java b/sentry/src/main/java/io/sentry/event/interfaces/SentryException.java index e2eea460a85..f23ceb0832a 100644 --- a/sentry/src/main/java/io/sentry/event/interfaces/SentryException.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/SentryException.java @@ -35,7 +35,9 @@ public SentryException(Throwable throwable, StackTraceElement[] childExceptionSt this.exceptionClassName = throwable.getClass().getSimpleName(); Package exceptionPackage = throwable.getClass().getPackage(); this.exceptionPackageName = exceptionPackage != null ? exceptionPackage.getName() : null; - this.stackTraceInterface = new StackTraceInterface(throwable.getStackTrace(), childExceptionStackTrace, + this.stackTraceInterface = new StackTraceInterface( + throwable.getStackTrace(), + childExceptionStackTrace, FrameCache.get(throwable)); } diff --git a/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java b/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java index 4c20ff0ade0..34dfde1a084 100644 --- a/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java @@ -115,10 +115,46 @@ public static SentryStackTraceElement[] fromStackTraceElements(StackTraceElement */ public static SentryStackTraceElement[] fromStackTraceElements(StackTraceElement[] stackTraceElements, Frame[] cachedFrames) { + + /* + This loop is a bit hairy because it has to deal with cached frames (from FrameCache, set by + the agent if it is in use). + + - If cachedFrames is null (most commonly: because the agent isn't being used) nothing fancy + needs to happen. + - If cachedFrames is not null we need to pair frames from stackTraceElements (the exception + being captured) to frames found in the FrameCache. + + The issue is that some frameworks/libraries seem to trim stacktraces in some cases, and + so the array in the FrameCache (set when the original exception was *thrown*) may slightly + differ (be larger) than the actual exception that is being captured. For this reason we + need to iterate cachedFrames separately, sometimes advancing 'further' than the equivalent + index in stackTraceElements (thus skipping elements). In addition, the only information + we have to "match" frames with is the method name, which is checked for equality between + the two arrays at each step. + + In the worst case, if something is mangled or weird, we just iterate through the cachedFrames + immediately (not finding a match) and locals are not set/sent with the event. + */ SentryStackTraceElement[] sentryStackTraceElements = new SentryStackTraceElement[stackTraceElements.length]; - for (int i = 0; i < stackTraceElements.length; i++) { - sentryStackTraceElements[i] = fromStackTraceElement(stackTraceElements[i], - cachedFrames != null ? cachedFrames[i].getLocals() : null); + for (int i = 0, j = 0; i < stackTraceElements.length; i++, j++) { + StackTraceElement stackTraceElement = stackTraceElements[i]; + + Map locals = null; + if (cachedFrames != null) { + // step through cachedFrames until we hit a match on the method in the stackTraceElement + while (j < cachedFrames.length + && !cachedFrames[j].getMethod().getName().equals(stackTraceElement.getMethodName())) { + j++; + } + + // only use cachedFrame locals if we haven't exhausted the array + if (j < cachedFrames.length) { + locals = cachedFrames[j].getLocals(); + } + } + + sentryStackTraceElements[i] = fromStackTraceElement(stackTraceElement, locals); } return sentryStackTraceElements; diff --git a/sentry/src/main/java/io/sentry/jvmti/Frame.java b/sentry/src/main/java/io/sentry/jvmti/Frame.java index cbaf426cad2..2ebf44f8a2c 100644 --- a/sentry/src/main/java/io/sentry/jvmti/Frame.java +++ b/sentry/src/main/java/io/sentry/jvmti/Frame.java @@ -1,5 +1,6 @@ package io.sentry.jvmti; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -9,6 +10,10 @@ * Class representing a single call frame. */ public final class Frame { + /** + * Method that this frame originated in. + */ + private Method method; /** * Local variable information for this frame. */ @@ -17,12 +22,18 @@ public final class Frame { /** * Construct a {@link Frame}. * + * @param method Method that this frame originated in. * @param locals Local variable information for this frame. */ - public Frame(LocalVariable[] locals) { + public Frame(Method method, LocalVariable[] locals) { + this.method = method; this.locals = locals; } + public Method getMethod() { + return method; + } + /** * Converts the locals array to a Map of variable-name -> variable-value. * @@ -69,7 +80,7 @@ public static final class LocalVariable { * @param name Variable name. * @param value Variable value. */ - private LocalVariable(String name, Object value) { + public LocalVariable(String name, Object value) { this.name = name; this.value = value; } diff --git a/sentry/src/main/java/io/sentry/jvmti/FrameCache.java b/sentry/src/main/java/io/sentry/jvmti/FrameCache.java index e416fd60a2f..b1b6c31987d 100644 --- a/sentry/src/main/java/io/sentry/jvmti/FrameCache.java +++ b/sentry/src/main/java/io/sentry/jvmti/FrameCache.java @@ -1,14 +1,15 @@ package io.sentry.jvmti; -import java.util.Map; -import java.util.WeakHashMap; +import java.util.*; /** * Utility class used by the Sentry Java Agent to store per-frame local variable * information for the last thrown exception. */ public final class FrameCache { - private static ThreadLocal> result = + private static Set appPackages = new HashSet<>(); + + private static ThreadLocal> cache = new ThreadLocal>() { @Override protected WeakHashMap initialValue() { @@ -30,7 +31,7 @@ private FrameCache() { * @param frames Array of {@link Frame}s to store */ public static void add(Throwable throwable, Frame[] frames) { - Map weakMap = result.get(); + Map weakMap = cache.get(); weakMap.put(throwable, frames); } @@ -41,7 +42,59 @@ public static void add(Throwable throwable, Frame[] frames) { * @return Array of {@link Frame}s */ public static Frame[] get(Throwable throwable) { - Map weakMap = result.get(); + Map weakMap = cache.get(); return weakMap.get(throwable); } + + /** + * Check whether the provided {@link Throwable} should be cached or not. Called by + * the native agent code so that the Java side (this code) can check the existing + * cache and user configuration, such as which packages are "in app". + * + * @param throwable Throwable to be checked + * @param numFrames Number of frames in the Throwable's stacktrace + * @return true if the Throwable should be processed and cached + */ + public static boolean shouldCacheThrowable(Throwable throwable, int numFrames) { + // only cache frames when 'in app' packages are provided + if (appPackages.isEmpty()) { + return false; + } + + // many libraries/frameworks seem to rethrow the same object with trimmed + // stacktraces, which means later ("smaller") throws would overwrite the existing + // object in cache. for this reason we prefer the throw with the greatest stack + // length... + Map weakMap = cache.get(); + Frame[] existing = weakMap.get(throwable); + if (existing != null && numFrames <= existing.length) { + return false; + } + + // check each frame against all "in app" package prefixes + for (StackTraceElement stackTraceElement : throwable.getStackTrace()) { + for (String appFrame : appPackages) { + if (stackTraceElement.getClassName().startsWith(appFrame)) { + return true; + } + } + } + + return false; + } + + /** + * Add an "in app" package prefix to the set of packages for which exception + * local variables will be cached. + * + * When an exception is thrown it must contain at least one frame that originates + * from a package in this set, otherwise local variable information will not be + * cached. See {@link FrameCache#shouldCacheThrowable(Throwable, int)}. + * + * @param newAppPackage package prefix to add to the set + */ + public static void addAppPackage(String newAppPackage) { + appPackages.add(newAppPackage); + } + } diff --git a/sentry/src/test/java/io/sentry/event/interfaces/SentryStackTraceElementTest.java b/sentry/src/test/java/io/sentry/event/interfaces/SentryStackTraceElementTest.java new file mode 100644 index 00000000000..c89cbaa5244 --- /dev/null +++ b/sentry/src/test/java/io/sentry/event/interfaces/SentryStackTraceElementTest.java @@ -0,0 +1,183 @@ +package io.sentry.event.interfaces; + +import io.sentry.BaseTest; +import io.sentry.jvmti.Frame; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.lang.reflect.Method; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; + +public class SentryStackTraceElementTest extends BaseTest { + private Method method1; + private Method method2; + private Method method3; + + @BeforeMethod + public void setup() { + // setup valid Method instances (since they can't be easily constructed) + method1(); + method2(); + method3(); + } + + @Test + public void testMatchingCachedFrames() throws Exception { + // 2 exception frames, method1, method2 + StackTraceElement[] stackTraceElements = new StackTraceElement[2]; + stackTraceElements[0] = new StackTraceElement("class1", method1.getName(), "file1", 1); + stackTraceElements[1] = new StackTraceElement("class2", method2.getName(), "file2", 2); + + // 2 cached frames, method1, method2 + Frame[] cachedFrames = new Frame[2]; + Frame.LocalVariable[] locals1 = new Frame.LocalVariable[1]; + locals1[0] = new Frame.LocalVariable("var1", "val1"); + cachedFrames[0] = new Frame(method1, locals1); + + Frame.LocalVariable[] locals2 = new Frame.LocalVariable[1]; + locals2[0] = new Frame.LocalVariable("var2", "val2"); + cachedFrames[1] = new Frame(method2, locals2); + + // convert + SentryStackTraceElement[] sentryStackTraceElements = SentryStackTraceElement.fromStackTraceElements( + stackTraceElements, cachedFrames); + + // assert + assertThat(sentryStackTraceElements.length, is(2)); + assertThat((String) sentryStackTraceElements[0].getLocals().get("var1"), is("val1")); + assertThat((String) sentryStackTraceElements[1].getLocals().get("var2"), is("val2")); + } + + @Test + public void testCachedFramesTooLong() throws Exception { + // 2 exception frames, method1, method2 + StackTraceElement[] stackTraceElements = new StackTraceElement[2]; + stackTraceElements[0] = new StackTraceElement("class1", method1.getName(), "file1", 1); + stackTraceElements[1] = new StackTraceElement("class2", method2.getName(), "file2", 2); + + // 3 cached frames, method1, method2, method3 + Frame[] cachedFrames = new Frame[3]; + Frame.LocalVariable[] locals1 = new Frame.LocalVariable[1]; + locals1[0] = new Frame.LocalVariable("var1", "val1"); + cachedFrames[0] = new Frame(method1, locals1); + + Frame.LocalVariable[] locals2 = new Frame.LocalVariable[1]; + locals2[0] = new Frame.LocalVariable("var2", "val2"); + cachedFrames[1] = new Frame(method2, locals2); + + Frame.LocalVariable[] locals3 = new Frame.LocalVariable[1]; + locals3[0] = new Frame.LocalVariable("var3", "val3"); + cachedFrames[2] = new Frame(method3, locals3); + + // convert + SentryStackTraceElement[] sentryStackTraceElements = SentryStackTraceElement.fromStackTraceElements( + stackTraceElements, cachedFrames); + + // assert + assertThat(sentryStackTraceElements.length, is(2)); + assertThat((String) sentryStackTraceElements[0].getLocals().get("var1"), is("val1")); + assertThat((String) sentryStackTraceElements[1].getLocals().get("var2"), is("val2")); + } + + @Test + public void testCachedFramesPartialMissing() throws Exception { + // 3 exception frames, method1, method2, method + StackTraceElement[] stackTraceElements = new StackTraceElement[3]; + stackTraceElements[0] = new StackTraceElement("class1", method1.getName(), "file1", 1); + stackTraceElements[1] = new StackTraceElement("class2", method2.getName(), "file2", 2); + stackTraceElements[2] = new StackTraceElement("class3", method3.getName(), "file3", 3); + + // 2 cached frames, method1, method3 + Frame[] cachedFrames = new Frame[2]; + Frame.LocalVariable[] locals1 = new Frame.LocalVariable[1]; + locals1[0] = new Frame.LocalVariable("var1", "val1"); + cachedFrames[0] = new Frame(method1, locals1); + + Frame.LocalVariable[] locals3 = new Frame.LocalVariable[1]; + locals3[0] = new Frame.LocalVariable("var3", "val3"); + cachedFrames[1] = new Frame(method3, locals3); + + // convert + SentryStackTraceElement[] sentryStackTraceElements = SentryStackTraceElement.fromStackTraceElements( + stackTraceElements, cachedFrames); + + // assert + assertThat(sentryStackTraceElements.length, is(3)); + assertThat((String) sentryStackTraceElements[0].getLocals().get("var1"), is("val1")); + assertThat(sentryStackTraceElements[1].getLocals(), is(nullValue())); + assertThat(sentryStackTraceElements[2].getLocals(), is(nullValue())); + } + + @Test + public void testCachedFramesTooShort() throws Exception { + // 3 exception frames, method1, method2, method + StackTraceElement[] stackTraceElements = new StackTraceElement[3]; + stackTraceElements[0] = new StackTraceElement("class1", method1.getName(), "file1", 1); + stackTraceElements[1] = new StackTraceElement("class2", method2.getName(), "file2", 2); + stackTraceElements[2] = new StackTraceElement("class3", method3.getName(), "file3", 3); + + // 2 cached frames, method1, method3 + Frame[] cachedFrames = new Frame[2]; + Frame.LocalVariable[] locals1 = new Frame.LocalVariable[1]; + locals1[0] = new Frame.LocalVariable("var1", "val1"); + cachedFrames[0] = new Frame(method1, locals1); + + Frame.LocalVariable[] locals2 = new Frame.LocalVariable[1]; + locals2[0] = new Frame.LocalVariable("var2", "val2"); + cachedFrames[1] = new Frame(method2, locals2); + + // convert + SentryStackTraceElement[] sentryStackTraceElements = SentryStackTraceElement.fromStackTraceElements( + stackTraceElements, cachedFrames); + + // assert + assertThat(sentryStackTraceElements.length, is(3)); + assertThat((String) sentryStackTraceElements[0].getLocals().get("var1"), is("val1")); + assertThat((String) sentryStackTraceElements[1].getLocals().get("var2"), is("val2")); + assertThat(sentryStackTraceElements[2].getLocals(), is(nullValue())); + } + + @Test + public void testNullCachedFrames() throws Exception { + StackTraceElement[] stackTraceElements = new StackTraceElement[1]; + stackTraceElements[0] = new StackTraceElement("class", "method", "file", 1); + + + SentryStackTraceElement[] sentryStackTraceElements = SentryStackTraceElement.fromStackTraceElements( + stackTraceElements, null); + + assertThat(sentryStackTraceElements.length, is(1)); + assertThat(sentryStackTraceElements[0].getLocals(), is(nullValue())); + } + + + @Test + public void testEmptyCachedFrames() throws Exception { + StackTraceElement[] stackTraceElements = new StackTraceElement[1]; + stackTraceElements[0] = new StackTraceElement("class", "method", "file", 1); + + Frame[] cachedFrames = new Frame[0]; + + SentryStackTraceElement[] sentryStackTraceElements = SentryStackTraceElement.fromStackTraceElements( + stackTraceElements, cachedFrames); + + assertThat(sentryStackTraceElements.length, is(1)); + assertThat(sentryStackTraceElements[0].getLocals(), is(nullValue())); + } + + public void method1() { + method1 = new Object() {}.getClass().getEnclosingMethod(); + } + + public void method2() { + method2 = new Object() {}.getClass().getEnclosingMethod(); + } + + public void method3() { + method3 = new Object() {}.getClass().getEnclosingMethod(); + } + +} diff --git a/sentry/src/test/java/io/sentry/jvmti/FrameCacheTest.java b/sentry/src/test/java/io/sentry/jvmti/FrameCacheTest.java new file mode 100644 index 00000000000..fcaa66d464b --- /dev/null +++ b/sentry/src/test/java/io/sentry/jvmti/FrameCacheTest.java @@ -0,0 +1,65 @@ +package io.sentry.jvmti; + +import io.sentry.BaseTest; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.Set; +import java.util.WeakHashMap; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static mockit.Deencapsulation.getField; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; + +public class FrameCacheTest extends BaseTest { + ThreadLocal> cache; + + @BeforeMethod + @AfterMethod + public void setup() { + cache = getField(FrameCache.class, "cache"); + cache.get().clear(); + + Set appPackages = getField(FrameCache.class, "appPackages"); + appPackages.clear(); + } + + + @Test + public void test() throws Exception { + FrameCache.addAppPackage("io.sentry.jvmti"); + + Throwable t; + + try { + throw new RuntimeException("foo"); + } catch (Exception e) { + t = e; + + // throwable shouldn't exist in the cache at all, so we expect true + assertThat(FrameCache.shouldCacheThrowable(t, t.getStackTrace().length), is(true)); + + FrameCache.add(t, new Frame[t.getStackTrace().length]); + + // throwable exists in the cache (with the same length), so we expect false + assertThat(FrameCache.shouldCacheThrowable(t, t.getStackTrace().length), is(false)); + } + + assertThat(t, is(notNullValue())); + + // trim the stacktrace + StackTraceElement[] originalStackTrace = t.getStackTrace(); + StackTraceElement[] newStackTrace = new StackTraceElement[originalStackTrace.length - 1]; + for (int i = 0; i < newStackTrace.length; i++) { + newStackTrace[i] = originalStackTrace[i]; + } + t.setStackTrace(newStackTrace); + + // stacktrace is smaller than existing, so we expect false + assertThat(FrameCache.shouldCacheThrowable(t, t.getStackTrace().length), is(false)); + } + +} From 55203592df7e84d44141c384ba66130014030cb4 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 18 Aug 2017 10:52:59 -0500 Subject: [PATCH 1740/2152] Change version to 1.5.0. --- pom.xml | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index e7f0efa4991..5eba96888a5 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.4.1-SNAPSHOT + 1.5.0-SNAPSHOT pom Sentry-Java diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index db06f2808af..79ab1691cc3 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.4.1-SNAPSHOT +version = 1.5.0-SNAPSHOT diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 42f8aa24741..282101f6145 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.4.1-SNAPSHOT + 1.5.0-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 8e66add3ae4..77515ff592d 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.4.1-SNAPSHOT + 1.5.0-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index f2bc75c38fa..73e2b232399 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.4.1-SNAPSHOT + 1.5.0-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index e28a4e85759..4c98d7727d7 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.4.1-SNAPSHOT + 1.5.0-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 5bf29266db8..c014d3922e7 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.4.1-SNAPSHOT + 1.5.0-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index a30f77a7542..6b47e4e9dd3 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.4.1-SNAPSHOT + 1.5.0-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 848db9926f6..35245c5d10c 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.4.1-SNAPSHOT + 1.5.0-SNAPSHOT sentry From 56d0775a9c2b6fea0d12ca672f9b94769c6d07c6 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 18 Aug 2017 10:53:47 -0500 Subject: [PATCH 1741/2152] Update sentry-cli for Gradle plugin. --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 75238475a6c..31238c59ec7 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.18.0 +VERSION=1.19.1 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* From 3ac512eff1897f933b9120931f1060b5b51beaef Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 18 Aug 2017 10:54:20 -0500 Subject: [PATCH 1742/2152] Update CHANGES to 1.5.0, add Agent. --- CHANGES | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 25f825a1b1c..37839b46f0c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,8 +1,9 @@ -Version 1.4.1 +Version 1.5.0 ------------- - Reduce logging noise if JNDI is missing. - Add OSGi support. (thanks markwoon) +- Local variable agent beta. Version 1.4.0 ------------- From 2372957ad1c7c1709d8af04f4962045676904480 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 18 Aug 2017 10:55:12 -0500 Subject: [PATCH 1743/2152] Bump docs to 1.5.0 --- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index a79a08d8653..76dd821d6f0 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.4.0`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.5.0`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.4.0' + compile 'io.sentry:sentry-android:1.5.0' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.4.0' + classpath 'io.sentry:sentry-android-gradle-plugin:1.5.0' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index cdb023cc633..bc9121fde4e 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.4.0 + 1.5.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.4.0' + compile 'io.sentry:sentry-appengine:1.5.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.4.0" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.5.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 300aacd92ed..25f445bb2fe 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.4.0 + 1.5.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.4.0' + compile 'io.sentry:sentry:1.5.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.4.0" + libraryDependencies += "io.sentry" % "sentry" % "1.5.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index d8e61d627e3..51fdaed1faf 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.4.0 + 1.5.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.4.0' + compile 'io.sentry:sentry-log4j:1.5.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.4.0" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.5.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 29083de8627..67d6c1cb119 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.4.0 + 1.5.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.4.0' + compile 'io.sentry:sentry-log4j2:1.5.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.4.0" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.5.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index cc7af9045a1..aff060a6d53 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.4.0 + 1.5.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.4.0' + compile 'io.sentry:sentry-logback:1.5.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.4.0" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.5.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index b322fb7870c..3a25194205b 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.4.0 + 1.5.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.4.0' + compile 'io.sentry:sentry-spring:1.5.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.4.0" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.5.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index 9d87dc65fe7..2c701f3726e 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.4.0 + 1.5.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.4.0' + compile 'io.sentry:sentry:1.5.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.4.0" + libraryDependencies += "io.sentry" % "sentry" % "1.5.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From da82200d7045eeb9eaaef2ea0a6fafb9068f1935 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 18 Aug 2017 10:56:31 -0500 Subject: [PATCH 1744/2152] [maven-release-plugin] prepare release v1.5.0 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 5eba96888a5..e2ba2b1ea17 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.5.0-SNAPSHOT + 1.5.0 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.5.0 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 282101f6145..0e810316fa9 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.0-SNAPSHOT + 1.5.0 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 77515ff592d..4ac137ec97b 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.5.0-SNAPSHOT + 1.5.0 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 73e2b232399..fb8cccdbdda 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.0-SNAPSHOT + 1.5.0 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 4c98d7727d7..c3bac47c67b 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.0-SNAPSHOT + 1.5.0 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index c014d3922e7..9cc6d02e30c 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.0-SNAPSHOT + 1.5.0 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 6b47e4e9dd3..ca02f0abc50 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.0-SNAPSHOT + 1.5.0 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 35245c5d10c..ee425837e6a 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.0-SNAPSHOT + 1.5.0 sentry From 2dd0b50d3eeb31eb5a13909d8a8a4471fd58fb9a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 18 Aug 2017 10:56:31 -0500 Subject: [PATCH 1745/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index e2ba2b1ea17..1cad2fc5872 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.5.0 + 1.5.1-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.5.0 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 0e810316fa9..7d4f70f4812 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.0 + 1.5.1-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 4ac137ec97b..52b4fa12210 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.5.0 + 1.5.1-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index fb8cccdbdda..166c35e8936 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.0 + 1.5.1-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index c3bac47c67b..a1f06609795 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.0 + 1.5.1-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 9cc6d02e30c..8ada24ce4d8 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.0 + 1.5.1-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index ca02f0abc50..7b61cb430e9 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.0 + 1.5.1-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index ee425837e6a..d3e74cc4307 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.0 + 1.5.1-SNAPSHOT sentry From cf3329c5e1e6ec5b3b2a950d62bf544a1c3c622b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 18 Aug 2017 10:56:34 -0500 Subject: [PATCH 1746/2152] Bump CHANGES to 1.5.1 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 37839b46f0c..7dacf386a67 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.5.1 +------------- + +- + Version 1.5.0 ------------- From 69c201abff3bbb7b44e64f875c1cee330c069dbb Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 18 Aug 2017 11:08:19 -0500 Subject: [PATCH 1747/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 79ab1691cc3..4e9625b7a4a 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.5.0-SNAPSHOT +version = 1.5.1-SNAPSHOT From 2d4743f6020dfcf772baec7c9d62c6b9e5cb564b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 18 Aug 2017 14:40:39 -0500 Subject: [PATCH 1748/2152] Add Agent documentation. --- docs/agent.rst | 25 +++++++++++++++++++++++++ docs/index.rst | 3 ++- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 docs/agent.rst diff --git a/docs/agent.rst b/docs/agent.rst new file mode 100644 index 00000000000..b65b3ed389b --- /dev/null +++ b/docs/agent.rst @@ -0,0 +1,25 @@ +Agent (Beta) +============ + +As of version 1.5.0 there is a new **experimental (beta)** Java Agent available that +enhances the existing Sentry Java SDK. The agent will enhance your application stacktraces +on Sentry by adding the names and values of local variables to each frame. + +Usage +----- + +The latest agent can be `downloaded from Github `_. + +Once you have downloaded the correct agent, you need to run your Java application with +the ``-agentpath`` argument. For example: + +.. sourcecode:: shell + + java -agentpath:/path/to/libsentry_agent_linux-x86_64.so -jar app.jar + +You will still need to install and configure the `Sentry Java SDK `_. +In addition, **you must set the** ``stacktrace.app.packages`` option. Only exceptions that contain at +least one frame from your application will be processed by the agent. You can find details about this option +`on the configuration page `_. + +With the SDK configured the agent should automatically enhance your events where applicable. diff --git a/docs/index.rst b/docs/index.rst index c2e25e5ef87..b6f0a320594 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -27,8 +27,9 @@ for more information. If you are still using ``raven`` you can config context usage - modules/index + agent migration + modules/index Resources: From f9f04c29b2f8267183ed621b7474a0fd76da67c2 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 22 Aug 2017 11:58:54 -0700 Subject: [PATCH 1749/2152] Don't attempt to serialize Iterables and don't special case serialization for Path objects, fixes broken Android release. (#495) --- CHANGES | 2 +- .../marshaller/json/SentryJsonGenerator.java | 34 ++++++++----------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/CHANGES b/CHANGES index 7dacf386a67..83a0d6be272 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.5.1 ------------- -- +- Don't attempt to serialize Iterables and don't special case serialization for Path objects, fixes broken Android release. Version 1.5.0 ------------- diff --git a/sentry/src/main/java/io/sentry/marshaller/json/SentryJsonGenerator.java b/sentry/src/main/java/io/sentry/marshaller/json/SentryJsonGenerator.java index 569682af771..0b409b54c68 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/SentryJsonGenerator.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/SentryJsonGenerator.java @@ -9,7 +9,7 @@ import java.io.InputStream; import java.math.BigDecimal; import java.math.BigInteger; -import java.nio.file.Path; +import java.util.Collection; import java.util.Map; /** @@ -72,24 +72,6 @@ private void writeObject(Object value, int recursionLevel) throws IOException { generator.writeStartArray(); writeArray(value, recursionLevel); generator.writeEndArray(); - } else if (value instanceof Path) { - // Path is weird because it implements Iterable, and then the iterator returns - // more Paths, which are iterable... which would cause a stack overflow below. - generator.writeString(Util.trimString(value.toString(), maxLengthString)); - } else if (value instanceof Iterable) { - generator.writeStartArray(); - int i = 0; - for (Object subValue : (Iterable) value) { - if (i >= maxLengthList) { - writeElided(); - break; - } - - writeObject(subValue, recursionLevel + 1); - - i++; - } - generator.writeEndArray(); } else if (value instanceof Map) { generator.writeStartObject(); int i = 0; @@ -108,6 +90,20 @@ private void writeObject(Object value, int recursionLevel) throws IOException { i++; } generator.writeEndObject(); + } else if (value instanceof Collection) { + generator.writeStartArray(); + int i = 0; + for (Object subValue : (Collection) value) { + if (i >= maxLengthList) { + writeElided(); + break; + } + + writeObject(subValue, recursionLevel + 1); + + i++; + } + generator.writeEndArray(); } else if (value instanceof String) { generator.writeString(Util.trimString((String) value, maxLengthString)); } else { From 45be489864d2f2ca1bb9894708c3166b0e3f4ba8 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 22 Aug 2017 11:59:11 -0700 Subject: [PATCH 1750/2152] Bump docs to 1.5.1 --- docs/agent.rst | 2 +- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/agent.rst b/docs/agent.rst index b65b3ed389b..3edf7698f1f 100644 --- a/docs/agent.rst +++ b/docs/agent.rst @@ -1,7 +1,7 @@ Agent (Beta) ============ -As of version 1.5.0 there is a new **experimental (beta)** Java Agent available that +As of version 1.5.1 there is a new **experimental (beta)** Java Agent available that enhances the existing Sentry Java SDK. The agent will enhance your application stacktraces on Sentry by adding the names and values of local variables to each frame. diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 76dd821d6f0..ec278a1d0bb 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.5.0`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.5.1`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.5.0' + compile 'io.sentry:sentry-android:1.5.1' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.5.0' + classpath 'io.sentry:sentry-android-gradle-plugin:1.5.1' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index bc9121fde4e..f960975db0d 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.5.0 + 1.5.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.5.0' + compile 'io.sentry:sentry-appengine:1.5.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.5.0" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.5.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 25f445bb2fe..1adafd487b7 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.5.0 + 1.5.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.5.0' + compile 'io.sentry:sentry:1.5.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.5.0" + libraryDependencies += "io.sentry" % "sentry" % "1.5.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 51fdaed1faf..990f0811b1d 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.5.0 + 1.5.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.5.0' + compile 'io.sentry:sentry-log4j:1.5.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.5.0" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.5.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 67d6c1cb119..2910d8d1277 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.5.0 + 1.5.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.5.0' + compile 'io.sentry:sentry-log4j2:1.5.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.5.0" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.5.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index aff060a6d53..0c7e84ed799 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.5.0 + 1.5.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.5.0' + compile 'io.sentry:sentry-logback:1.5.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.5.0" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.5.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index 3a25194205b..9ade3613976 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.5.0 + 1.5.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.5.0' + compile 'io.sentry:sentry-spring:1.5.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.5.0" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.5.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index 2c701f3726e..f1f2cfe9a6e 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.5.0 + 1.5.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.5.0' + compile 'io.sentry:sentry:1.5.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.5.0" + libraryDependencies += "io.sentry" % "sentry" % "1.5.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 57a5a5b3a4bfdfe1af25ad5137a43ae3ec99a4a1 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 22 Aug 2017 12:00:41 -0700 Subject: [PATCH 1751/2152] [maven-release-plugin] prepare release v1.5.1 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 1cad2fc5872..4f3cffc3cd9 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.5.1-SNAPSHOT + 1.5.1 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.5.1 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 7d4f70f4812..17a4659b440 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.1-SNAPSHOT + 1.5.1 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 52b4fa12210..e7c1343ec62 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.5.1-SNAPSHOT + 1.5.1 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 166c35e8936..361a1481295 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.1-SNAPSHOT + 1.5.1 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index a1f06609795..03f8e02569b 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.1-SNAPSHOT + 1.5.1 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 8ada24ce4d8..09e0fc64753 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.1-SNAPSHOT + 1.5.1 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 7b61cb430e9..83c682341b6 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.1-SNAPSHOT + 1.5.1 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index d3e74cc4307..0a42b7a0d2f 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.1-SNAPSHOT + 1.5.1 sentry From bd8d8f3a396fe374de4416088c695f2a16c0659d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 22 Aug 2017 12:00:42 -0700 Subject: [PATCH 1752/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 4f3cffc3cd9..4ad432c2bc6 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.5.1 + 1.5.2-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.5.1 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 17a4659b440..e9ccbc5bef7 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.1 + 1.5.2-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index e7c1343ec62..608009299b0 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.5.1 + 1.5.2-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 361a1481295..ff8912d556f 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.1 + 1.5.2-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 03f8e02569b..8b00d6a3755 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.1 + 1.5.2-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 09e0fc64753..4a5d479e901 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.1 + 1.5.2-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 83c682341b6..36bd01de253 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.1 + 1.5.2-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 0a42b7a0d2f..a35e41f7b45 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.1 + 1.5.2-SNAPSHOT sentry From 0a8a9e4f83c25ec282fb21ec19002ea62d30dbf8 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 22 Aug 2017 12:00:45 -0700 Subject: [PATCH 1753/2152] Bump CHANGES to 1.5.2 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 83a0d6be272..57a8733b415 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.5.2 +------------- + +- + Version 1.5.1 ------------- From 9489a3a890155100daad96134f77ee85f195bb1f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 22 Aug 2017 12:51:13 -0700 Subject: [PATCH 1754/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 4e9625b7a4a..b54d2d7605d 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.5.1-SNAPSHOT +version = 1.5.2-SNAPSHOT From 77e9e3d694e41e874b5259ec6e21bec0f36c2fcd Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 25 Aug 2017 11:38:50 -0700 Subject: [PATCH 1755/2152] Add ability to remove individual tags or extra data from the context, or clear them entirely. (#497) --- CHANGES | 2 +- .../main/java/io/sentry/context/Context.java | 71 +++++++++++++++---- .../src/test/java/io/sentry/ContextTest.java | 46 ++++++++++-- 3 files changed, 102 insertions(+), 17 deletions(-) diff --git a/CHANGES b/CHANGES index 57a8733b415..80d7b498afe 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.5.2 ------------- -- +- Add ability to remove individual tags or extra data from the context, or clear them entirely. Version 1.5.1 ------------- diff --git a/sentry/src/main/java/io/sentry/context/Context.java b/sentry/src/main/java/io/sentry/context/Context.java index babd0a26cde..3acdd9cd266 100644 --- a/sentry/src/main/java/io/sentry/context/Context.java +++ b/sentry/src/main/java/io/sentry/context/Context.java @@ -62,15 +62,15 @@ public Context(int breadcrumbLimit) { * Clear state from this context. */ public synchronized void clear() { - breadcrumbs = null; - lastEventId = null; - user = null; - tags = null; - extra = null; + setLastEventId(null); + clearBreadcrumbs(); + clearUser(); + clearTags(); + clearExtra(); } /** - * Return {@link Breadcrumb}s attached to this Context. + * Return {@link Breadcrumb}s attached to this context. * * @return List of {@link Breadcrumb}s. */ @@ -85,9 +85,9 @@ public synchronized List getBreadcrumbs() { } /** - * Return tags attached to this Context. + * Return tags attached to this context. * - * @return tags attached to this Context. + * @return tags attached to this context. */ public synchronized Map getTags() { if (tags == null || tags.isEmpty()) { @@ -98,9 +98,9 @@ public synchronized Map getTags() { } /** - * Return extra data attached to this Context. + * Return extra data attached to this context. * - * @return extra data attached to this Context. + * @return extra data attached to this context. */ public synchronized Map getExtra() { if (extra == null || extra.isEmpty()) { @@ -111,7 +111,7 @@ public synchronized Map getExtra() { } /** - * Add tag to the current Context. + * Add tag to the current context. * * @param name tag name * @param value tag value @@ -125,7 +125,27 @@ public synchronized void addTag(String name, String value) { } /** - * Add extra data to the current Context. + * Remove a tag name and value from the current context. + * + * @param name tag name to remove + */ + public synchronized void removeTag(String name) { + if (tags == null) { + return; + } + + tags.remove(name); + } + + /** + * Clear all tags from the current context. + */ + public synchronized void clearTags() { + tags = null; + } + + /** + * Add extra data to the current context. * * @param name extra name * @param value extra value @@ -138,6 +158,26 @@ public synchronized void addExtra(String name, Object value) { extra.put(name, value); } + /** + * Remove an extra data name and value from the current context. + * + * @param name extra name to remove + */ + public synchronized void removeExtra(String name) { + if (extra == null) { + return; + } + + extra.remove(name); + } + + /** + * Clear all extra data from this context. + */ + public synchronized void clearExtra() { + extra = null; + } + /** * Record a single {@link Breadcrumb} into this context. * @@ -151,6 +191,13 @@ public synchronized void recordBreadcrumb(Breadcrumb breadcrumb) { breadcrumbs.add(breadcrumb); } + /** + * Clear all breadcrumbs from this context. + */ + public synchronized void clearBreadcrumbs() { + breadcrumbs = null; + } + /** * Store the UUID of the last sent event by this thread, useful for handling user feedback. * diff --git a/sentry/src/test/java/io/sentry/ContextTest.java b/sentry/src/test/java/io/sentry/ContextTest.java index fac28c3dbfd..76a17618c2f 100644 --- a/sentry/src/test/java/io/sentry/ContextTest.java +++ b/sentry/src/test/java/io/sentry/ContextTest.java @@ -7,9 +7,7 @@ import io.sentry.event.UserBuilder; import org.testng.annotations.Test; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; +import java.util.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -19,6 +17,7 @@ public class ContextTest extends BaseTest { @Test public void testBreadcrumbs() { Context context = new Context(); + assertThat(context.getBreadcrumbs(), equalTo(Collections.emptyList())); Breadcrumb breadcrumb = new BreadcrumbBuilder() .setLevel(Breadcrumb.Level.INFO) @@ -31,6 +30,9 @@ public void testBreadcrumbs() { breadcrumbMatch.add(breadcrumb); assertThat(context.getBreadcrumbs(), equalTo(breadcrumbMatch)); + + context.clearBreadcrumbs(); + assertThat(context.getBreadcrumbs(), equalTo(Collections.emptyList())); } @Test @@ -55,7 +57,6 @@ public void breadcrumbLimit() { breadcrumbMatch.add(breadcrumb2); assertThat(context.getBreadcrumbs(), equalTo(breadcrumbMatch)); - } @Test @@ -74,4 +75,41 @@ public void testUser() { context.clearUser(); assertThat(context.getUser(), equalTo(null)); } + + @Test + public void testTags() { + Context context = new Context(); + + assertThat(context.getTags(), equalTo(Collections.emptyMap())); + + context.removeTag("nonExistant"); + assertThat(context.getTags(), equalTo(Collections.emptyMap())); + + context.addTag("foo", "bar"); + Map matchingTags = new HashMap<>(); + matchingTags.put("foo", "bar"); + assertThat(context.getTags(), equalTo(matchingTags)); + + context.removeTag("foo"); + assertThat(context.getTags(), equalTo(Collections.emptyMap())); + } + + @Test + public void testExtras() { + Context context = new Context(); + + assertThat(context.getExtra(), equalTo(Collections.emptyMap())); + + context.removeExtra("nonExistant"); + assertThat(context.getExtra(), equalTo(Collections.emptyMap())); + + context.addExtra("foo", "bar"); + Map matchingExtras = new HashMap<>(); + matchingExtras.put("foo", "bar"); + assertThat(context.getExtra(), equalTo(matchingExtras)); + + context.removeExtra("foo"); + assertThat(context.getExtra(), equalTo(Collections.emptyMap())); + } + } From 8b2763998117e9eb21bfc7c60b7bdf9b067ea796 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 25 Aug 2017 13:27:15 -0700 Subject: [PATCH 1756/2152] Add to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7ce9ca80fed..cb7092da29a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ target/ docs/_build infer-out/ +agent/build/ From f2a1451637a1da02c8b606364f52b89ffb9177c6 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 25 Aug 2017 13:27:22 -0700 Subject: [PATCH 1757/2152] Bump docs to 1.5.2 --- docs/agent.rst | 2 +- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/agent.rst b/docs/agent.rst index 3edf7698f1f..6d7c943245d 100644 --- a/docs/agent.rst +++ b/docs/agent.rst @@ -1,7 +1,7 @@ Agent (Beta) ============ -As of version 1.5.1 there is a new **experimental (beta)** Java Agent available that +As of version 1.5.2 there is a new **experimental (beta)** Java Agent available that enhances the existing Sentry Java SDK. The agent will enhance your application stacktraces on Sentry by adding the names and values of local variables to each frame. diff --git a/docs/modules/android.rst b/docs/modules/android.rst index ec278a1d0bb..a92b5532f8d 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.5.1`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.5.2`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.5.1' + compile 'io.sentry:sentry-android:1.5.2' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.5.1' + classpath 'io.sentry:sentry-android-gradle-plugin:1.5.2' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index f960975db0d..7e69ebf5c16 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.5.1 + 1.5.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.5.1' + compile 'io.sentry:sentry-appengine:1.5.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.5.1" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.5.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 1adafd487b7..428a2b579e0 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.5.1 + 1.5.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.5.1' + compile 'io.sentry:sentry:1.5.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.5.1" + libraryDependencies += "io.sentry" % "sentry" % "1.5.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 990f0811b1d..b1e5a350b68 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.5.1 + 1.5.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.5.1' + compile 'io.sentry:sentry-log4j:1.5.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.5.1" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.5.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 2910d8d1277..5c25784a501 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.5.1 + 1.5.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.5.1' + compile 'io.sentry:sentry-log4j2:1.5.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.5.1" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.5.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 0c7e84ed799..1eef5cb07ac 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.5.1 + 1.5.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.5.1' + compile 'io.sentry:sentry-logback:1.5.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.5.1" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.5.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index 9ade3613976..c8f41b1bf7f 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.5.1 + 1.5.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.5.1' + compile 'io.sentry:sentry-spring:1.5.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.5.1" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.5.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index f1f2cfe9a6e..2c996db5d39 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.5.1 + 1.5.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.5.1' + compile 'io.sentry:sentry:1.5.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.5.1" + libraryDependencies += "io.sentry" % "sentry" % "1.5.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From c5770404ba4878de6930e9f2624aa12f8bbccfea Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 25 Aug 2017 13:28:41 -0700 Subject: [PATCH 1758/2152] [maven-release-plugin] prepare release v1.5.2 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 4ad432c2bc6..dcd83086dac 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.5.2-SNAPSHOT + 1.5.2 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.5.2 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index e9ccbc5bef7..2edd981dabc 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.2-SNAPSHOT + 1.5.2 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 608009299b0..6847b7b0955 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.5.2-SNAPSHOT + 1.5.2 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index ff8912d556f..b6dc8a25dac 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.2-SNAPSHOT + 1.5.2 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 8b00d6a3755..326b869cc39 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.2-SNAPSHOT + 1.5.2 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 4a5d479e901..51b6b7692cc 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.2-SNAPSHOT + 1.5.2 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 36bd01de253..7c6d88bc99e 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.2-SNAPSHOT + 1.5.2 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index a35e41f7b45..6e06b4519dd 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.2-SNAPSHOT + 1.5.2 sentry From 0c020635b14ae25648b1482ce4f14734fbf9d73f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 25 Aug 2017 13:28:41 -0700 Subject: [PATCH 1759/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index dcd83086dac..4755bcbc523 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.5.2 + 1.5.3-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.5.2 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 2edd981dabc..4dfd03970a7 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.2 + 1.5.3-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 6847b7b0955..b2472ed3819 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.5.2 + 1.5.3-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index b6dc8a25dac..9bc33b670f4 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.2 + 1.5.3-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 326b869cc39..72084bb2829 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.2 + 1.5.3-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 51b6b7692cc..995e26b6c7d 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.2 + 1.5.3-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 7c6d88bc99e..3fba97090eb 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.2 + 1.5.3-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 6e06b4519dd..4fb18295180 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.2 + 1.5.3-SNAPSHOT sentry From babb143af48d6c3617407ecaf6247cb1a459cc96 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 25 Aug 2017 13:28:44 -0700 Subject: [PATCH 1760/2152] Bump CHANGES to 1.5.3 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 80d7b498afe..61136e8960a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.5.3 +------------- + +- + Version 1.5.2 ------------- From b3b7aaeab3e16c6a770602ffc5b37e13c0f1183f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 25 Aug 2017 13:33:24 -0700 Subject: [PATCH 1761/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index b54d2d7605d..5b0c9a05ea8 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.5.2-SNAPSHOT +version = 1.5.3-SNAPSHOT From c53c38d14a0a0b04bee59901a98d916c0575e3c5 Mon Sep 17 00:00:00 2001 From: Frieder Bluemle Date: Sat, 26 Aug 2017 15:11:11 -0700 Subject: [PATCH 1762/2152] Update Gradle wrapper to 4.1 --- .../gradle/wrapper/gradle-wrapper.jar | Bin 54712 -> 54712 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.jar index 48e7610e2b59fbe5b4430887b710a26d50b5d98a..2aad956cf8c61442315ba247692d5a5e4b19add8 100644 GIT binary patch delta 76 zcmdn7nt8`+<_YF39P!#l6Ro4!J{El}Vq};gwQ-xxVV0CiDeuYO4qJ0EF$8$Cb5u`= ZJTck&h(B2I=Ds7h1UNt{z`_MrJpe^+9X$X5 delta 76 zcmdn7nt8`+<_YF3H&nYzCt63deJc7`#K Date: Tue, 29 Aug 2017 17:08:08 +0200 Subject: [PATCH 1763/2152] Typo in docs --- docs/config.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index f9197758fc0..7d6a2e3f471 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -234,7 +234,7 @@ is visible in the Sentry web interface where only the "in application" frames ar displayed by default. You can configure which package prefixes your application uses with the -stacktrace.app.packages`` option, which takes a comma separated list. +``stacktrace.app.packages`` option, which takes a comma separated list. :: From ebea973815759256d4ed2c6dde113977867eaade Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 30 Aug 2017 09:01:11 -0500 Subject: [PATCH 1764/2152] Add logo to README. --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f8485f2180f..ce21c3e9e5e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ -# sentry-java +

    + + + +
    +

    sentry-java - Sentry SDK for Java

    +

    This is the Java SDK for [Sentry](https://sentry.io/). It provides out-of-the-box support for many popular JVM based frameworks and libraries, including Android, Log4j, Logback, and more. From d1257513bf67ec188d94140396e207b724196ea5 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 5 Sep 2017 12:01:11 -0500 Subject: [PATCH 1765/2152] Disable stacktrace.app.packages warning when set to empty string --- docs/config.rst | 7 +++++++ .../java/io/sentry/DefaultSentryClientFactory.java | 10 ++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 7d6a2e3f471..48cd7fd9f81 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -240,6 +240,13 @@ You can configure which package prefixes your application uses with the stacktrace.app.packages=com.mycompany,com.other.name +If you don't want to use this feature but want to disable the warning, simply +set it to an empty string: + +:: + + stacktrace.app.packages= + Same Frame as Enclosing Exception ````````````````````````````````` diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index 9a14a90b8f5..24d704bd009 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -503,10 +503,12 @@ protected ContextManager getContextManager(Dsn dsn) { protected Collection getInAppFrames(Dsn dsn) { String inAppFramesOption = Lookup.lookup(IN_APP_FRAMES_OPTION, dsn); if (Util.isNullOrEmpty(inAppFramesOption)) { - - logger.warn("No '" + IN_APP_FRAMES_OPTION + "' was configured, this option is highly recommended " - + "as it affects stacktrace grouping and display on Sentry. See documentation: " - + "https://docs.sentry.io/clients/java/config/#in-application-stack-frames"); + // Only warn if the user didn't set it at all + if (inAppFramesOption == null) { + logger.warn("No '" + IN_APP_FRAMES_OPTION + "' was configured, this option is highly recommended " + + "as it affects stacktrace grouping and display on Sentry. See documentation: " + + "https://docs.sentry.io/clients/java/config/#in-application-stack-frames"); + } return Collections.emptyList(); } From 71b72e4e026a2400443e178a69bb08eecd32072b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 5 Sep 2017 12:29:12 -0500 Subject: [PATCH 1766/2152] Fix for serializing null values in Event extras map. (#502) Fixes GH-500 --- CHANGES | 2 +- sentry/src/main/java/io/sentry/event/Event.java | 4 +++- sentry/src/test/java/io/sentry/event/EventTest.java | 3 +++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 61136e8960a..b9b82c3c409 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.5.3 ------------- -- +- Fix for buffering Events to disk that have null values in their extras map. Version 1.5.2 ------------- diff --git a/sentry/src/main/java/io/sentry/event/Event.java b/sentry/src/main/java/io/sentry/event/Event.java index 16430759ce3..545398b4c5c 100644 --- a/sentry/src/main/java/io/sentry/event/Event.java +++ b/sentry/src/main/java/io/sentry/event/Event.java @@ -303,7 +303,9 @@ private void writeObject(ObjectOutputStream stream) private static HashMap convertToSerializable(Map objectMap) { HashMap serializableMap = new HashMap<>(objectMap.size()); for (Map.Entry objectEntry : objectMap.entrySet()) { - if (objectEntry.getValue() instanceof Serializable) { + if (objectEntry.getValue() == null) { + serializableMap.put(objectEntry.getKey(), (String) null); + } else if (objectEntry.getValue() instanceof Serializable) { serializableMap.put(objectEntry.getKey(), (Serializable) objectEntry.getValue()); } else { serializableMap.put(objectEntry.getKey(), objectEntry.getValue().toString()); diff --git a/sentry/src/test/java/io/sentry/event/EventTest.java b/sentry/src/test/java/io/sentry/event/EventTest.java index b6f245f22ea..fb83f9de3c4 100644 --- a/sentry/src/test/java/io/sentry/event/EventTest.java +++ b/sentry/src/test/java/io/sentry/event/EventTest.java @@ -47,6 +47,7 @@ public void serializedEventContainsSerializableExtras(@Injectable final Object n }}; event.getExtra().put("SerializableEntry", 38295L); event.getExtra().put("NonSerializableEntry", nonSerializableObject); + event.getExtra().put("NullEntry", null); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); @@ -58,5 +59,7 @@ public void serializedEventContainsSerializableExtras(@Injectable final Object n assertThat(receivedEvent.getExtra().get("SerializableEntry"), Matchers.equalTo(38295L)); assertThat(receivedEvent.getExtra().get("NonSerializableEntry"), Matchers.equalTo("3c644639-9721-4e32-8cc8-a2b5b77f4424")); + assertThat(receivedEvent.getExtra().get("NullEntry"), + Matchers.equalTo(null)); } } From a98250602218e3ff4004b0f233695f7e2e2307d1 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 5 Sep 2017 12:48:58 -0500 Subject: [PATCH 1767/2152] Cleanup SentryEnvironment state to avoid memory leak warnings on Tomcat. (#501) Fixes GH-487 --- CHANGES | 1 + .../main/java/io/sentry/environment/SentryEnvironment.java | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index b9b82c3c409..57cfb88451f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ Version 1.5.3 ------------- +- Cleanup SentryEnvironment state to avoid memory leak warnings on Tomcat. - Fix for buffering Events to disk that have null values in their extras map. Version 1.5.2 diff --git a/sentry/src/main/java/io/sentry/environment/SentryEnvironment.java b/sentry/src/main/java/io/sentry/environment/SentryEnvironment.java index dbe7d615508..b406d8fc8fb 100644 --- a/sentry/src/main/java/io/sentry/environment/SentryEnvironment.java +++ b/sentry/src/main/java/io/sentry/environment/SentryEnvironment.java @@ -73,7 +73,11 @@ public static void stopManagingThread() { logger.warn("Thread not yet managed by Sentry"); } } finally { - SENTRY_THREAD.get().decrementAndGet(); + if (SENTRY_THREAD.get().decrementAndGet() == 0) { + // Remove the ThreadLocal so we don't log leak warnings on Tomcat. + // The next get/incr (if any) will re-initialize it to 0. + SENTRY_THREAD.remove(); + } } } From 669853e3fe3358a9c5368d8d22b54c9badf360dc Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 6 Sep 2017 09:17:58 -0500 Subject: [PATCH 1768/2152] Make config via DSN/code more clear in docs. --- docs/config.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index 48cd7fd9f81..6adb99dd1c5 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -108,7 +108,7 @@ The DSN itself can also be configured directly in code: import io.sentry.Sentry; - Sentry.init("https://public:private@host:port/1"); + Sentry.init("https://public:private@host:port/1?option=value&other.option=othervalue"); Note that Sentry will not be able to do anything with events until this line is run, so this method of configuration is not recommended if you might have errors occur during startup. From 61695af615adb1a7ac0711b09763fc817ecdf801 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 6 Sep 2017 09:30:06 -0500 Subject: [PATCH 1769/2152] Fix MDC references in Log4j-2.x docs. --- docs/modules/log4j2.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 5c25784a501..383b7ca744b 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -98,9 +98,9 @@ filters within the Sentry UI. .. sourcecode:: java void logWithExtras() { - // MDC extras - MDC.put("Environment", "Development"); - MDC.put("OS", "Linux"); + // ThreadContext ("MDC") extras + ThreadContext.put("Environment", "Development"); + ThreadContext.put("OS", "Linux"); // This sends an event where the Environment and OS MDC values are set as additional data logger.error("This is a test"); From aefde8daf0abbb5c0bd98dccb93db37e35938ec4 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 7 Sep 2017 09:08:30 -0500 Subject: [PATCH 1770/2152] Bump docs to 1.5.3 --- docs/agent.rst | 2 +- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/agent.rst b/docs/agent.rst index 6d7c943245d..87ba155cace 100644 --- a/docs/agent.rst +++ b/docs/agent.rst @@ -1,7 +1,7 @@ Agent (Beta) ============ -As of version 1.5.2 there is a new **experimental (beta)** Java Agent available that +As of version 1.5.3 there is a new **experimental (beta)** Java Agent available that enhances the existing Sentry Java SDK. The agent will enhance your application stacktraces on Sentry by adding the names and values of local variables to each frame. diff --git a/docs/modules/android.rst b/docs/modules/android.rst index a92b5532f8d..85883bb8bdf 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.5.2`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.5.3`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.5.2' + compile 'io.sentry:sentry-android:1.5.3' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.5.2' + classpath 'io.sentry:sentry-android-gradle-plugin:1.5.3' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 7e69ebf5c16..a94767d143b 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.5.2 + 1.5.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.5.2' + compile 'io.sentry:sentry-appengine:1.5.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.5.2" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.5.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 428a2b579e0..96c53909cf5 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.5.2 + 1.5.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.5.2' + compile 'io.sentry:sentry:1.5.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.5.2" + libraryDependencies += "io.sentry" % "sentry" % "1.5.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index b1e5a350b68..7a4d2ea8d36 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.5.2 + 1.5.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.5.2' + compile 'io.sentry:sentry-log4j:1.5.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.5.2" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.5.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 383b7ca744b..f4ab7d768f7 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.5.2 + 1.5.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.5.2' + compile 'io.sentry:sentry-log4j2:1.5.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.5.2" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.5.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 1eef5cb07ac..601e4891b79 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.5.2 + 1.5.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.5.2' + compile 'io.sentry:sentry-logback:1.5.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.5.2" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.5.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index c8f41b1bf7f..c78b2ea1e0f 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.5.2 + 1.5.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.5.2' + compile 'io.sentry:sentry-spring:1.5.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.5.2" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.5.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index 2c996db5d39..eb084be6ce4 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.5.2 + 1.5.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.5.2' + compile 'io.sentry:sentry:1.5.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.5.2" + libraryDependencies += "io.sentry" % "sentry" % "1.5.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 78802c19d582e1f771e029ca52455100becd1e56 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 7 Sep 2017 09:09:49 -0500 Subject: [PATCH 1771/2152] [maven-release-plugin] prepare release v1.5.3 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 4755bcbc523..882e0b3d8a8 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.5.3-SNAPSHOT + 1.5.3 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.5.3 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 4dfd03970a7..fac839ac8d0 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.3-SNAPSHOT + 1.5.3 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index b2472ed3819..9c8e502855d 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.5.3-SNAPSHOT + 1.5.3 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 9bc33b670f4..1d8e3e0513b 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.3-SNAPSHOT + 1.5.3 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 72084bb2829..ec71b7459cf 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.3-SNAPSHOT + 1.5.3 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 995e26b6c7d..1d8494e4489 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.3-SNAPSHOT + 1.5.3 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 3fba97090eb..a61a349bb97 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.3-SNAPSHOT + 1.5.3 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 4fb18295180..fc588b2b152 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.3-SNAPSHOT + 1.5.3 sentry From a1e67cbd04a45801d79acb44c0736e93e8a73d16 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 7 Sep 2017 09:09:49 -0500 Subject: [PATCH 1772/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 882e0b3d8a8..0b7d7b9968f 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.5.3 + 1.5.4-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.5.3 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index fac839ac8d0..81c88cfe481 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.3 + 1.5.4-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 9c8e502855d..f731c8b8d93 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.5.3 + 1.5.4-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 1d8e3e0513b..5dff0611281 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.3 + 1.5.4-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index ec71b7459cf..996852fdcc3 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.3 + 1.5.4-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 1d8494e4489..97fa4739843 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.3 + 1.5.4-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index a61a349bb97..3054149b762 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.3 + 1.5.4-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index fc588b2b152..c8afff2b935 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.3 + 1.5.4-SNAPSHOT sentry From 21301ecab2a53c5def886626b6ba91c4063621b0 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 7 Sep 2017 09:09:51 -0500 Subject: [PATCH 1773/2152] Bump CHANGES to 1.5.4 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 57cfb88451f..b4e423ebbe0 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.5.4 +------------- + +- + Version 1.5.3 ------------- From b6211674e5c9f62566d6263cf099974db75baf7f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 7 Sep 2017 09:32:52 -0500 Subject: [PATCH 1774/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 5b0c9a05ea8..51659d24be1 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.5.3-SNAPSHOT +version = 1.5.4-SNAPSHOT From 017d4aa54888bc5cbb5b65a7e1d0d2504ca66756 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 2 Oct 2017 12:32:27 -0500 Subject: [PATCH 1775/2152] Change ``EventBuilder$HostnameCache`` timeout to only log at debug level. --- CHANGES | 2 +- sentry/src/main/java/io/sentry/event/EventBuilder.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index b4e423ebbe0..e04ad98e696 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.5.4 ------------- -- +- Change ``EventBuilder$HostnameCache`` timeout to only log at debug level. Version 1.5.3 ------------- diff --git a/sentry/src/main/java/io/sentry/event/EventBuilder.java b/sentry/src/main/java/io/sentry/event/EventBuilder.java index 3e259ab3bf1..c2163991434 100644 --- a/sentry/src/main/java/io/sentry/event/EventBuilder.java +++ b/sentry/src/main/java/io/sentry/event/EventBuilder.java @@ -524,7 +524,7 @@ public Void call() throws Exception { futureTask.get(GET_HOSTNAME_TIMEOUT, TimeUnit.MILLISECONDS); } catch (Exception e) { expirationTimestamp = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(1); - logger.warn("Localhost hostname lookup failed, keeping the value '{}'." + logger.debug("Localhost hostname lookup failed, keeping the value '{}'." + " If this persists it may mean your DNS is incorrectly configured and" + " you may want to hardcode your server name: https://docs.sentry.io/clients/java/config/", hostname, e); From 98c63c84fd8ce8706b34c5b6bd2f1265497ddd2f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 2 Oct 2017 12:36:09 -0500 Subject: [PATCH 1776/2152] Bump docs to 1.5.4 --- docs/agent.rst | 2 +- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/agent.rst b/docs/agent.rst index 87ba155cace..7ec083be037 100644 --- a/docs/agent.rst +++ b/docs/agent.rst @@ -1,7 +1,7 @@ Agent (Beta) ============ -As of version 1.5.3 there is a new **experimental (beta)** Java Agent available that +As of version 1.5.4 there is a new **experimental (beta)** Java Agent available that enhances the existing Sentry Java SDK. The agent will enhance your application stacktraces on Sentry by adding the names and values of local variables to each frame. diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 85883bb8bdf..752464da262 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.5.3`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.5.4`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.5.3' + compile 'io.sentry:sentry-android:1.5.4' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.5.3' + classpath 'io.sentry:sentry-android-gradle-plugin:1.5.4' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index a94767d143b..fcb3eb9a188 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.5.3 + 1.5.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.5.3' + compile 'io.sentry:sentry-appengine:1.5.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.5.3" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.5.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 96c53909cf5..459a9913657 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.5.3 + 1.5.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.5.3' + compile 'io.sentry:sentry:1.5.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.5.3" + libraryDependencies += "io.sentry" % "sentry" % "1.5.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 7a4d2ea8d36..ad214da8840 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.5.3 + 1.5.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.5.3' + compile 'io.sentry:sentry-log4j:1.5.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.5.3" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.5.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index f4ab7d768f7..6847ece79bb 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.5.3 + 1.5.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.5.3' + compile 'io.sentry:sentry-log4j2:1.5.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.5.3" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.5.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 601e4891b79..8451eea24f7 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.5.3 + 1.5.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.5.3' + compile 'io.sentry:sentry-logback:1.5.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.5.3" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.5.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index c78b2ea1e0f..d28a728acb4 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.5.3 + 1.5.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.5.3' + compile 'io.sentry:sentry-spring:1.5.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.5.3" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.5.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index eb084be6ce4..9c26ab13bf0 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.5.3 + 1.5.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.5.3' + compile 'io.sentry:sentry:1.5.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.5.3" + libraryDependencies += "io.sentry" % "sentry" % "1.5.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 29969f4196adf98ec4505d75702eb675e2710ae0 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 2 Oct 2017 12:37:43 -0500 Subject: [PATCH 1777/2152] [maven-release-plugin] prepare release v1.5.4 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 0b7d7b9968f..7b400d3544a 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.5.4-SNAPSHOT + 1.5.4 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.5.4 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 81c88cfe481..a91562b86ba 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.4-SNAPSHOT + 1.5.4 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index f731c8b8d93..e3481b2b452 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.5.4-SNAPSHOT + 1.5.4 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 5dff0611281..0f1f75f4cc3 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.4-SNAPSHOT + 1.5.4 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 996852fdcc3..f2266408b1c 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.4-SNAPSHOT + 1.5.4 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 97fa4739843..83890facf6d 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.4-SNAPSHOT + 1.5.4 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 3054149b762..12eab0bf618 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.4-SNAPSHOT + 1.5.4 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index c8afff2b935..8a98bf77ff1 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.4-SNAPSHOT + 1.5.4 sentry From 0b77f2f8a3461be9d24d1b7a807a7e039d2c800c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 2 Oct 2017 12:37:43 -0500 Subject: [PATCH 1778/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 7b400d3544a..f7c693706b7 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.5.4 + 1.5.5-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.5.4 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index a91562b86ba..08f704a9e3a 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.4 + 1.5.5-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index e3481b2b452..e4a0b8b1db0 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.5.4 + 1.5.5-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 0f1f75f4cc3..b93e65ad8ff 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.4 + 1.5.5-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index f2266408b1c..881d00634e2 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.4 + 1.5.5-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 83890facf6d..809b316a172 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.4 + 1.5.5-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 12eab0bf618..f1cb4f4ae0a 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.4 + 1.5.5-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 8a98bf77ff1..68f2fdeeb71 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.4 + 1.5.5-SNAPSHOT sentry From a91e8926fc4df5c43b3fb06248787036823b918b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 2 Oct 2017 12:37:47 -0500 Subject: [PATCH 1779/2152] Bump CHANGES to 1.5.5 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index e04ad98e696..11bec147aa6 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.5.5 +------------- + +- + Version 1.5.4 ------------- From 9c156c667ee73d040122bef0b20cbf12571aca7a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 2 Oct 2017 12:56:28 -0500 Subject: [PATCH 1780/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 31238c59ec7..52ce2f8658e 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.19.1 +VERSION=1.20.0 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 51659d24be1..58831fec940 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.5.4-SNAPSHOT +version = 1.5.5-SNAPSHOT From fbd8b5d6cf99f5fb6787500190f5dca0d3295207 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 5 Oct 2017 19:10:11 -0500 Subject: [PATCH 1781/2152] Add example links. --- README.md | 1 + examples/README.md | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 examples/README.md diff --git a/README.md b/README.md index ce21c3e9e5e..15a9246060b 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ based application. ## Resources * [Documentation](https://docs.sentry.io/clients/java/) +* [Examples](https://github.com/getsentry/examples) * [Bug Tracker](http://github.com/getsentry/sentry-java/issues) * [Code](http://github.com/getsentry/sentry-java) * [Mailing List](https://groups.google.com/group/getsentry) diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000000..eb904faf2c3 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,4 @@ +# Examples + +Please see [the Sentry examples repo](https://github.com/getsentry/examples) for Java and Android example projects. + From dfc86e0a8d78ec29954e4249f2ca1ac1b93afc33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Jim=C3=A9nez=20Torres?= Date: Mon, 9 Oct 2017 22:49:55 +0200 Subject: [PATCH 1782/2152] Fixes crash on Android API < 16 (#511) (thanks Syhids) --- .../android/event/helper/AndroidEventBuilderHelper.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java index b66e123724b..c14f5363fa5 100644 --- a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java +++ b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java @@ -121,7 +121,9 @@ private Map> getContexts() { ActivityManager.MemoryInfo memInfo = getMemInfo(ctx); if (memInfo != null) { deviceMap.put("free_memory", memInfo.availMem); - deviceMap.put("memory_size", memInfo.totalMem); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + deviceMap.put("memory_size", memInfo.totalMem); + } deviceMap.put("low_memory", memInfo.lowMemory); } From 6c5a7ef76437f8093e431806e0b13ddedc06eee5 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 9 Oct 2017 15:51:02 -0500 Subject: [PATCH 1783/2152] Add to CHANGES. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 11bec147aa6..872afa9fb02 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.5.5 ------------- -- +- Fix crash on Android API < 16. (thanks Syhids) Version 1.5.4 ------------- From 97dce47deef7abb2b6614a70fa97f4a7bdd5f303 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 10 Oct 2017 09:04:00 -0500 Subject: [PATCH 1784/2152] Pin Android ``app_start_time`` locale to English. (#515) Fixes GH-514 --- .../android/event/helper/AndroidEventBuilderHelper.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java index c14f5363fa5..a6cca54723f 100644 --- a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java +++ b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java @@ -22,10 +22,7 @@ import java.io.*; import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; +import java.util.*; import static android.content.Context.ACTIVITY_SERVICE; @@ -509,7 +506,7 @@ private static DisplayMetrics getDisplayMetrics(Context ctx) { * @return String representing the provided Date in ISO8601 format */ private static String stringifyDate(Date date) { - return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(date); + return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH).format(date); } /** From 00cfd45b19f6bfe2a8de83c6eb10545f860d1e71 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 10 Oct 2017 09:05:12 -0500 Subject: [PATCH 1785/2152] Bump docs to 1.5.5 --- docs/agent.rst | 2 +- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/agent.rst b/docs/agent.rst index 7ec083be037..f7da7aeae3b 100644 --- a/docs/agent.rst +++ b/docs/agent.rst @@ -1,7 +1,7 @@ Agent (Beta) ============ -As of version 1.5.4 there is a new **experimental (beta)** Java Agent available that +As of version 1.5.5 there is a new **experimental (beta)** Java Agent available that enhances the existing Sentry Java SDK. The agent will enhance your application stacktraces on Sentry by adding the names and values of local variables to each frame. diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 752464da262..3fc34bcfcb2 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.5.4`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.5.5`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.5.4' + compile 'io.sentry:sentry-android:1.5.5' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.5.4' + classpath 'io.sentry:sentry-android-gradle-plugin:1.5.5' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index fcb3eb9a188..3ae5fdb9678 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.5.4 + 1.5.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.5.4' + compile 'io.sentry:sentry-appengine:1.5.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.5.4" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.5.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 459a9913657..6447658a292 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.5.4 + 1.5.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.5.4' + compile 'io.sentry:sentry:1.5.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.5.4" + libraryDependencies += "io.sentry" % "sentry" % "1.5.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index ad214da8840..a6bde4fc31c 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.5.4 + 1.5.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.5.4' + compile 'io.sentry:sentry-log4j:1.5.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.5.4" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.5.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 6847ece79bb..c0eb3405539 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.5.4 + 1.5.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.5.4' + compile 'io.sentry:sentry-log4j2:1.5.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.5.4" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.5.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 8451eea24f7..4493267a49f 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.5.4 + 1.5.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.5.4' + compile 'io.sentry:sentry-logback:1.5.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.5.4" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.5.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index d28a728acb4..59db287804d 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.5.4 + 1.5.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.5.4' + compile 'io.sentry:sentry-spring:1.5.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.5.4" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.5.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index 9c26ab13bf0..e75933c385d 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.5.4 + 1.5.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.5.4' + compile 'io.sentry:sentry:1.5.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.5.4" + libraryDependencies += "io.sentry" % "sentry" % "1.5.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 4e03fd862b3960979db8a33bd9a32e19e7797944 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 10 Oct 2017 09:07:40 -0500 Subject: [PATCH 1786/2152] [maven-release-plugin] prepare release v1.5.5 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index f7c693706b7..cb728687424 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.5.5-SNAPSHOT + 1.5.5 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.5.5 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 08f704a9e3a..d72df44907a 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.5-SNAPSHOT + 1.5.5 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index e4a0b8b1db0..710c23811b5 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.5.5-SNAPSHOT + 1.5.5 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index b93e65ad8ff..aa12b08ca44 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.5-SNAPSHOT + 1.5.5 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 881d00634e2..411f0673567 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.5-SNAPSHOT + 1.5.5 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 809b316a172..cababce1f3c 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.5-SNAPSHOT + 1.5.5 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index f1cb4f4ae0a..c4c1f2bac59 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.5-SNAPSHOT + 1.5.5 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 68f2fdeeb71..ccfbc3d33cd 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.5-SNAPSHOT + 1.5.5 sentry From 0b0a5871ff23d8d93a0dcada66efb6fe3f990d38 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 10 Oct 2017 09:07:40 -0500 Subject: [PATCH 1787/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index cb728687424..6802b9e1f99 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.5.5 + 1.5.6-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.5.5 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index d72df44907a..d274bd1d4b4 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.5 + 1.5.6-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 710c23811b5..cd965ca69b1 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.5.5 + 1.5.6-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index aa12b08ca44..517f23eb12f 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.5 + 1.5.6-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 411f0673567..c7952c992ce 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.5 + 1.5.6-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index cababce1f3c..050e99460b5 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.5 + 1.5.6-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index c4c1f2bac59..7f1169daa9e 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.5 + 1.5.6-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index ccfbc3d33cd..5184583ebfd 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.5 + 1.5.6-SNAPSHOT sentry From f357780934b9c7cb41e3e91642513ac2858255c7 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 10 Oct 2017 09:07:43 -0500 Subject: [PATCH 1788/2152] Bump CHANGES to 1.5.6 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 872afa9fb02..a781ab389fe 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.5.6 +------------- + +- + Version 1.5.5 ------------- From 5920e856bb5916563418ad3c9ae4c0198c39f9dc Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 10 Oct 2017 09:28:08 -0500 Subject: [PATCH 1789/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 58831fec940..df7942c4216 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.5.5-SNAPSHOT +version = 1.5.6-SNAPSHOT From 6cbbd36a5f37ee86ea21c2fd309913b63ccb4ccd Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 12 Oct 2017 10:14:11 -0500 Subject: [PATCH 1790/2152] =?UTF-8?q?Add=20``in=5Fapp``=20frame=20blacklis?= =?UTF-8?q?t=20regex=20list=20to=20skip=20CGLIB=20generated=20cla=E2=80=A6?= =?UTF-8?q?=20(#517)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES | 2 +- .../json/StackTraceInterfaceBinding.java | 25 +++++++++++--- .../json/StackTraceInterfaceBindingTest.java | 33 +++++++++++++++++++ .../marshaller/json/StackTraceBlacklist.json | 25 ++++++++++++++ 4 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 sentry/src/test/resources/io/sentry/marshaller/json/StackTraceBlacklist.json diff --git a/CHANGES b/CHANGES index a781ab389fe..52f1ba95938 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.5.6 ------------- -- +- Add ``in_app`` frame blacklist regex list to skip CGLIB generated classes in Spring. Version 1.5.5 ------------- diff --git a/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java index e90fa7597d9..378dba7edf5 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java @@ -5,9 +5,8 @@ import io.sentry.event.interfaces.StackTraceInterface; import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; +import java.util.*; +import java.util.regex.Pattern; /** * Binding allowing to convert a {@link StackTraceInterface} into a JSON stream. @@ -26,9 +25,16 @@ public class StackTraceInterfaceBinding implements InterfaceBinding inAppBlacklistRegexps = new ArrayList<>(); private Collection inAppFrames = Collections.emptyList(); private boolean removeCommonFramesWithEnclosing = true; + static { + // skip CGLIB generated classes like Foo$$FastClassBySpringCGLIB$$4ed8b6b + inAppBlacklistRegexps.add(Pattern.compile("\\$\\$FastClass[a-zA-Z]*CGLIB\\$\\$")); + inAppBlacklistRegexps.add(Pattern.compile("\\$\\$Enhancer[a-zA-Z]*CGLIB\\$\\$")); + } + /** * Writes a single frame based on a {@code StackTraceElement}. * @@ -73,13 +79,24 @@ private boolean isFrameInApp(SentryStackTraceElement stackTraceElement) { // TODO: A linear search is not efficient here, a Trie could be a better solution. for (String inAppFrame : inAppFrames) { String className = stackTraceElement.getModule(); - if (className.startsWith(inAppFrame)) { + if (className.startsWith(inAppFrame) && !isBlacklistedFromInApp(className)) { return true; } } return false; } + private boolean isBlacklistedFromInApp(String className) { + for (Pattern inAppBlacklistRegexp : inAppBlacklistRegexps) { + boolean found = inAppBlacklistRegexp.matcher(className).find(); + if (found) { + return true; + } + } + + return false; + } + @Override public void writeInterface(JsonGenerator generator, StackTraceInterface stackTraceInterface) throws IOException { generator.writeStartObject(); diff --git a/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java index ea466960401..02abb0bcbd7 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java @@ -8,6 +8,9 @@ import io.sentry.event.interfaces.StackTraceInterface; import org.testng.annotations.Test; +import java.util.ArrayList; +import java.util.List; + import static io.sentry.marshaller.json.JsonComparisonUtil.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -86,4 +89,34 @@ public void testFramesCommonWithEnclosingDisabled() throws Exception { assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/StackTrace3.json"))); } + + @Test + public void testInAppFrames() throws Exception { + final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); + + final SentryStackTraceElement stackTraceElement1 = new SentryStackTraceElement( + "inAppModule.foo", "inAppMethod", + "File.java", 1, null, null, null, null); + + final SentryStackTraceElement stackTraceElement2 = new SentryStackTraceElement( + "notInAppModule.bar", "notInAppMethod", + "File.java", 2, null, null, null, null); + + final SentryStackTraceElement stackTraceElement3 = new SentryStackTraceElement( + "inAppModule.Blacklisted$$FastClassBySpringCGLIB$$", "blacklisted", + "File.java", 3, null, null, null, null); + + new NonStrictExpectations() {{ + mockStackTraceInterface.getStackTrace(); + result = new SentryStackTraceElement[]{stackTraceElement1, stackTraceElement2, stackTraceElement3}; + }}; + + List inAppModules = new ArrayList<>(); + inAppModules.add("inAppModule"); + interfaceBinding.setInAppFrames(inAppModules); + interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockStackTraceInterface); + + assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/StackTraceBlacklist.json"))); + } + } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/StackTraceBlacklist.json b/sentry/src/test/resources/io/sentry/marshaller/json/StackTraceBlacklist.json new file mode 100644 index 00000000000..436870fb2ed --- /dev/null +++ b/sentry/src/test/resources/io/sentry/marshaller/json/StackTraceBlacklist.json @@ -0,0 +1,25 @@ +{ + "frames":[ + { + "filename":"File.java", + "module":"inAppModule.Blacklisted$$FastClassBySpringCGLIB$$", + "in_app":false, + "function":"blacklisted", + "lineno":3 + }, + { + "filename":"File.java", + "module":"notInAppModule.bar", + "in_app":false, + "function":"notInAppMethod", + "lineno":2 + }, + { + "filename":"File.java", + "module":"inAppModule.foo", + "in_app":true, + "function":"inAppMethod", + "lineno":1 + } + ] +} \ No newline at end of file From 38006e14ea43764d762e8bae231eb764cac8af9d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 12 Oct 2017 10:19:33 -0500 Subject: [PATCH 1791/2152] Add warnings whe DiskBuffer deletes fail. --- .../src/main/java/io/sentry/buffer/DiskBuffer.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java b/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java index d36f1628f12..47aea24a31d 100644 --- a/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java +++ b/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java @@ -100,7 +100,9 @@ public void discard(Event event) { File eventFile = new File(bufferDir, event.getId().toString() + FILE_SUFFIX); if (eventFile.exists()) { logger.debug("Discarding Event from offline storage: " + eventFile.getAbsolutePath()); - eventFile.delete(); + if (!eventFile.delete()) { + logger.warn("Failed to delete Event: " + eventFile.getAbsolutePath()); + } } } @@ -118,7 +120,9 @@ private Event fileToEvent(File eventFile) { eventObj = ois.readObject(); } catch (Exception e) { logger.error("Error reading Event file: " + eventFile.getAbsolutePath(), e); - eventFile.delete(); + if (!eventFile.delete()) { + logger.warn("Failed to delete Event: " + eventFile.getAbsolutePath()); + } return null; } @@ -126,7 +130,9 @@ private Event fileToEvent(File eventFile) { return (Event) eventObj; } catch (Exception e) { logger.error("Error casting Object to Event: " + eventFile.getAbsolutePath(), e); - eventFile.delete(); + if (!eventFile.delete()) { + logger.warn("Failed to delete Event: " + eventFile.getAbsolutePath()); + } return null; } } From 308768128a6045d3cd67b397e71a3d8495093723 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 12 Oct 2017 10:19:48 -0500 Subject: [PATCH 1792/2152] Bump docs to 1.5.6 --- docs/agent.rst | 2 +- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/agent.rst b/docs/agent.rst index f7da7aeae3b..77cf316db0e 100644 --- a/docs/agent.rst +++ b/docs/agent.rst @@ -1,7 +1,7 @@ Agent (Beta) ============ -As of version 1.5.5 there is a new **experimental (beta)** Java Agent available that +As of version 1.5.6 there is a new **experimental (beta)** Java Agent available that enhances the existing Sentry Java SDK. The agent will enhance your application stacktraces on Sentry by adding the names and values of local variables to each frame. diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 3fc34bcfcb2..827ba00c642 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.5.5`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.5.6`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.5.5' + compile 'io.sentry:sentry-android:1.5.6' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.5.5' + classpath 'io.sentry:sentry-android-gradle-plugin:1.5.6' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 3ae5fdb9678..e2388fca0c0 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.5.5 + 1.5.6 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.5.5' + compile 'io.sentry:sentry-appengine:1.5.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.5.5" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.5.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 6447658a292..f6e607be8a1 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.5.5 + 1.5.6 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.5.5' + compile 'io.sentry:sentry:1.5.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.5.5" + libraryDependencies += "io.sentry" % "sentry" % "1.5.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index a6bde4fc31c..4b5df48e055 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.5.5 + 1.5.6 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.5.5' + compile 'io.sentry:sentry-log4j:1.5.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.5.5" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.5.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index c0eb3405539..645f800deec 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.5.5 + 1.5.6 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.5.5' + compile 'io.sentry:sentry-log4j2:1.5.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.5.5" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.5.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 4493267a49f..c3bd735747f 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.5.5 + 1.5.6 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.5.5' + compile 'io.sentry:sentry-logback:1.5.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.5.5" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.5.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index 59db287804d..e3862dea07d 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.5.5 + 1.5.6 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.5.5' + compile 'io.sentry:sentry-spring:1.5.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.5.5" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.5.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index e75933c385d..d515e1e9c3e 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.5.5 + 1.5.6 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.5.5' + compile 'io.sentry:sentry:1.5.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.5.5" + libraryDependencies += "io.sentry" % "sentry" % "1.5.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From c24effe5dca81259a9f5177fa31cd0c20f534427 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 12 Oct 2017 10:22:16 -0500 Subject: [PATCH 1793/2152] [maven-release-plugin] prepare release v1.5.6 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 6802b9e1f99..f604d63e6a6 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.5.6-SNAPSHOT + 1.5.6 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.5.6 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index d274bd1d4b4..dd6b487e83d 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.6-SNAPSHOT + 1.5.6 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index cd965ca69b1..8a07d51bcca 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.5.6-SNAPSHOT + 1.5.6 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 517f23eb12f..b8a0a5e1636 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.6-SNAPSHOT + 1.5.6 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index c7952c992ce..ff9028ec30c 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.6-SNAPSHOT + 1.5.6 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 050e99460b5..b9c3953f0f2 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.6-SNAPSHOT + 1.5.6 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 7f1169daa9e..56eab2058cc 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.6-SNAPSHOT + 1.5.6 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 5184583ebfd..ea8eff74609 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.6-SNAPSHOT + 1.5.6 sentry From 4823033d62738802aeabc8bf8a21ae05a3269131 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 12 Oct 2017 10:22:17 -0500 Subject: [PATCH 1794/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index f604d63e6a6..7c50418d339 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.5.6 + 1.5.7-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.5.6 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index dd6b487e83d..7dec61ccf3f 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.6 + 1.5.7-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 8a07d51bcca..9d0eea87309 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.5.6 + 1.5.7-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index b8a0a5e1636..89caa42ca44 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.6 + 1.5.7-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index ff9028ec30c..fa9a64b35bb 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.6 + 1.5.7-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index b9c3953f0f2..24c3833b56e 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.6 + 1.5.7-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 56eab2058cc..72135b6c66d 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.6 + 1.5.7-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index ea8eff74609..361bb026a0b 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.6 + 1.5.7-SNAPSHOT sentry From 1c9e1e44c88bca8b776baaf32de2498d8d9aacd4 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 12 Oct 2017 10:22:19 -0500 Subject: [PATCH 1795/2152] Bump CHANGES to 1.5.7 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 52f1ba95938..5d970f3e173 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.5.7 +------------- + +- + Version 1.5.6 ------------- From d899d19ab1200c48728d7e2bcec041654a7a16d9 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 12 Oct 2017 10:31:43 -0500 Subject: [PATCH 1796/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 52ce2f8658e..75107854c5b 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.20.0 +VERSION=1.21.0 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index df7942c4216..949c3e3da09 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.5.6-SNAPSHOT +version = 1.5.7-SNAPSHOT From f05c7e32feb08bee46b994f40aae75ac55b5602b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 18 Oct 2017 08:42:52 -0500 Subject: [PATCH 1797/2152] =?UTF-8?q?Add=20transaction=20field=20to=20Even?= =?UTF-8?q?t,=20deprecate=20culprits=20and=20no=20longer=20set=20=E2=80=A6?= =?UTF-8?q?=20(#520)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add transaction field to Event, deprecate culprits and no longer set them automatically in logging integrations. --- CHANGES | 4 +- .../java/io/sentry/log4j/SentryAppender.java | 7 --- .../SentryAppenderEventBuildingTest.java | 49 ------------------- .../java/io/sentry/log4j2/SentryAppender.java | 6 --- .../SentryAppenderEventBuildingTest.java | 31 ------------ .../io/sentry/logback/SentryAppender.java | 6 --- .../SentryAppenderEventBuildingTest.java | 32 ------------ .../src/main/java/io/sentry/event/Event.java | 19 +++++++ .../java/io/sentry/event/EventBuilder.java | 19 +++++++ .../java/io/sentry/jul/SentryHandler.java | 8 --- .../marshaller/json/JsonMarshaller.java | 5 ++ .../jul/SentryHandlerEventBuildingTest.java | 33 ------------- .../marshaller/json/JsonMarshallerTest.java | 13 +++++ .../unmarshaller/event/UnmarshalledEvent.java | 2 + .../jsonmarshallertest/testBreadcrumbs.json | 1 + .../json/jsonmarshallertest/testChecksum.json | 1 + .../jsonmarshallertest/testCompression.json | 1 + .../json/jsonmarshallertest/testContexts.json | 1 + .../json/jsonmarshallertest/testCulprit.json | 1 + .../json/jsonmarshallertest/testDist.json | 1 + .../jsonmarshallertest/testEnvironment.json | 1 + .../json/jsonmarshallertest/testEventId.json | 1 + .../jsonmarshallertest/testExtraArray.json | 1 + .../jsonmarshallertest/testExtraBoolean.json | 1 + .../testExtraCustomValue.json | 1 + .../jsonmarshallertest/testExtraIterable.json | 1 + .../json/jsonmarshallertest/testExtraMap.json | 1 + .../jsonmarshallertest/testExtraNull.json | 1 + .../testExtraNullKeyMap.json | 1 + .../jsonmarshallertest/testExtraNumber.json | 1 + .../testExtraObjectKeyMap.json | 1 + .../testExtraRecursiveArray.json | 1 + .../testExtraRecursiveMap.json | 1 + .../jsonmarshallertest/testExtraString.json | 1 + .../jsonmarshallertest/testFingerprint.json | 1 + .../testInterfaceBinding.json | 1 + .../jsonmarshallertest/testLevelDebug.json | 1 + .../jsonmarshallertest/testLevelError.json | 1 + .../jsonmarshallertest/testLevelFatal.json | 1 + .../jsonmarshallertest/testLevelInfo.json | 1 + .../jsonmarshallertest/testLevelWarning.json | 1 + .../json/jsonmarshallertest/testLogger.json | 1 + .../json/jsonmarshallertest/testMessage.json | 1 + .../json/jsonmarshallertest/testPlatform.json | 1 + .../json/jsonmarshallertest/testRelease.json | 1 + .../json/jsonmarshallertest/testSdk.json | 1 + .../jsonmarshallertest/testServerName.json | 1 + .../json/jsonmarshallertest/testTags.json | 1 + .../jsonmarshallertest/testTimestamp.json | 1 + .../jsonmarshallertest/testTransaction.json | 21 ++++++++ 50 files changed, 117 insertions(+), 173 deletions(-) create mode 100644 sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTransaction.json diff --git a/CHANGES b/CHANGES index 5d970f3e173..7d44b5cc43b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,9 @@ Version 1.5.7 ------------- -- +- Add transaction field to Event and EventBuilder. +- Deprecate culprits in favor of transactions. +- Culprit is no longer set automatically in logging integrations. Version 1.5.6 ------------- diff --git a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java index 6e79e3ffb61..088e3ef0f67 100644 --- a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java +++ b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java @@ -124,13 +124,6 @@ protected EventBuilder createEventBuilder(LoggingEvent loggingEvent) { } } - // Set culprit - if (loggingEvent.getLocationInformation().fullInfo != null) { - eventBuilder.withCulprit(asStackTraceElement(loggingEvent.getLocationInformation())); - } else { - eventBuilder.withCulprit(loggingEvent.getLoggerName()); - } - if (loggingEvent.getNDC() != null) { eventBuilder.withExtra(LOG4J_NDC, loggingEvent.getNDC()); } diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java index e2c1d3b2d56..03c46cce583 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java @@ -189,55 +189,6 @@ public void testSourceUsedAsStacktrace(@Injectable final LocationInfo locationIn assertNoErrorsInErrorHandler(); } - @Test - public void testCulpritWithSource(@Injectable final LocationInfo locationInfo) throws Exception { - final String className = "a"; - final String methodName = "b"; - final String fileName = "c"; - final int line = 42; - new NonStrictExpectations() {{ - locationInfo.getClassName(); - result = className; - locationInfo.getMethodName(); - result = methodName; - locationInfo.getFileName(); - result = fileName; - locationInfo.getLineNumber(); - result = Integer.toString(line); - setField(locationInfo, "fullInfo", ""); - }}; - - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, - null, null, locationInfo, null)); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getCulprit(), is("a.b(c:42)")); - }}; - assertNoErrorsInErrorHandler(); - } - - @Test - public void testCulpritWithoutSource() throws Exception { - final String loggerName = "2b27da1d-e03a-4292-9a81-78be5491a7e1"; - new Expectations() {{ - mockLogger.getName(); - result = loggerName; - }}; - - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null)); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getCulprit(), is(loggerName)); - }}; - assertNoErrorsInErrorHandler(); - } - @Test public void testExtraTagObtainedFromMdc() throws Exception { Map properties = new HashMap<>(); diff --git a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java index eb5b47a2b5a..f6d2893c576 100644 --- a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -171,12 +171,6 @@ protected EventBuilder createEventBuilder(LogEvent event) { eventBuilder.withSentryInterface(new StackTraceInterface(stackTrace)); } - if (event.getSource() != null) { - eventBuilder.withCulprit(event.getSource()); - } else { - eventBuilder.withCulprit(event.getLoggerName()); - } - if (event.getContextStack() != null) { eventBuilder.withExtra(LOG4J_NDC, event.getContextStack().asList()); } diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java index d1a10de27ae..02ac9d39892 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java @@ -212,37 +212,6 @@ public void testSourceUsedAsStacktrace() throws Exception { assertNoErrorsInErrorHandler(); } - @Test - public void testCulpritWithSource() throws Exception { - final StackTraceElement location = new StackTraceElement("a", "b", "c", 42); - - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, null, - null, location, 0)); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getCulprit(), is("a.b(c:42)")); - }}; - assertNoErrorsInErrorHandler(); - } - - @Test - public void testCulpritWithoutSource() throws Exception { - final String loggerName = "150bbbfa-f729-460e-921b-a0fe1f7ab392 "; - - sentryAppender.append(new Log4jLogEvent(loggerName, null, null, Level.INFO, new SimpleMessage(""), null)); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getCulprit(), is(loggerName)); - }}; - assertNoErrorsInErrorHandler(); - } - @Test public void testExtraTagObtainedFromMdc() throws Exception { Map mdc = new HashMap<>(); diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index cd447a741cf..f2328654a95 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -143,12 +143,6 @@ protected EventBuilder createEventBuilder(ILoggingEvent iLoggingEvent) { eventBuilder.withSentryInterface(new StackTraceInterface(iLoggingEvent.getCallerData())); } - if (iLoggingEvent.getCallerData().length > 0) { - eventBuilder.withCulprit(iLoggingEvent.getCallerData()[0]); - } else { - eventBuilder.withCulprit(iLoggingEvent.getLoggerName()); - } - for (Map.Entry contextEntry : iLoggingEvent.getLoggerContextVO().getPropertyMap().entrySet()) { eventBuilder.withExtra(contextEntry.getKey(), contextEntry.getValue()); } diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java index 7e5e3bf6711..5c72d68ad8c 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java @@ -243,38 +243,6 @@ public void testSourceUsedAsStacktrace() throws Exception { assertNoErrorsInStatusManager(); } - @Test - public void testCulpritWithSource() throws Exception { - final StackTraceElement[] location = {new StackTraceElement("a", "b", "c", 42), - new StackTraceElement("d", "e", "f", 69)}; - - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null, null, null, location, 0) - .getMockInstance()); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getCulprit(), is("a.b(c:42)")); - }}; - assertNoErrorsInStatusManager(); - } - - @Test - public void testCulpritWithoutSource() throws Exception { - final String loggerName = "ee1c33e8-2f2e-4613-9c2c-2564624a9d4f"; - - sentryAppender.append(new MockUpLoggingEvent(loggerName, null, Level.INFO, null, null, null).getMockInstance()); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getCulprit(), is(loggerName)); - }}; - assertNoErrorsInStatusManager(); - } - @Test public void testExtraTagObtainedFromMdc() throws Exception { Map mdcPropertyMap = new HashMap<>(); diff --git a/sentry/src/main/java/io/sentry/event/Event.java b/sentry/src/main/java/io/sentry/event/Event.java index 545398b4c5c..ba29b175085 100644 --- a/sentry/src/main/java/io/sentry/event/Event.java +++ b/sentry/src/main/java/io/sentry/event/Event.java @@ -63,6 +63,10 @@ public class Event implements Serializable { * Function call which was the primary perpetrator of this event. */ private String culprit; + /** + * Name of the transaction that this event occurred inside of. + */ + private String transaction; /** * A map or list of tags for this event. *

    @@ -185,10 +189,25 @@ public String getCulprit() { return culprit; } + /** + * Sets the culprit. + * + * @param culprit Function call which was the primary perpetrator of this event. + * @deprecated Culprit has been removed in favor of Transaction. + */ + @Deprecated void setCulprit(String culprit) { this.culprit = culprit; } + public String getTransaction() { + return transaction; + } + + void setTransaction(String transaction) { + this.transaction = transaction; + } + public List getBreadcrumbs() { return breadcrumbs; } diff --git a/sentry/src/main/java/io/sentry/event/EventBuilder.java b/sentry/src/main/java/io/sentry/event/EventBuilder.java index c2163991434..d6e01bbec00 100644 --- a/sentry/src/main/java/io/sentry/event/EventBuilder.java +++ b/sentry/src/main/java/io/sentry/event/EventBuilder.java @@ -220,12 +220,16 @@ public EventBuilder withSdkIntegration(String integration) { return this; } + + /** * Sets the culprit in the event based on a {@link SentryStackTraceElement}. * * @param frame stack frame during which the event was captured. * @return the current {@code EventBuilder} for chained calls. + * @deprecated Culprit has been removed in favor of Transaction. */ + @Deprecated public EventBuilder withCulprit(SentryStackTraceElement frame) { return withCulprit(buildCulpritString(frame.getModule(), frame.getFunction(), frame.getFileName(), frame.getLineno())); @@ -237,7 +241,9 @@ public EventBuilder withCulprit(SentryStackTraceElement frame) { * * @param frame stack frame during which the event was captured. * @return the current {@code EventBuilder} for chained calls. + * @deprecated Culprit has been removed in favor of Transaction. */ + @Deprecated public EventBuilder withCulprit(StackTraceElement frame) { return withCulprit(buildCulpritString(frame.getClassName(), frame.getMethodName(), frame.getFileName(), frame.getLineNumber())); @@ -266,12 +272,25 @@ private String buildCulpritString(String className, String methodName, String fi * * @param culprit culprit. * @return the current {@code EventBuilder} for chained calls. + * @deprecated Culprit has been removed in favor of Transaction. */ + @Deprecated public EventBuilder withCulprit(String culprit) { event.setCulprit(culprit); return this; } + /** + * Sets the transaction in the event. + * + * @param transaction transaction. + * @return the current {@code EventBuilder} for chained calls. + */ + public EventBuilder withTransaction(String transaction) { + event.setTransaction(transaction); + return this; + } + /** * Adds a tag to an event. *

    diff --git a/sentry/src/main/java/io/sentry/jul/SentryHandler.java b/sentry/src/main/java/io/sentry/jul/SentryHandler.java index f6f922211bd..0cc4d89948b 100644 --- a/sentry/src/main/java/io/sentry/jul/SentryHandler.java +++ b/sentry/src/main/java/io/sentry/jul/SentryHandler.java @@ -141,14 +141,6 @@ protected EventBuilder createEventBuilder(LogRecord record) { eventBuilder.withSentryInterface(new ExceptionInterface(throwable)); } - if (record.getSourceClassName() != null && record.getSourceMethodName() != null) { - StackTraceElement fakeFrame = new StackTraceElement(record.getSourceClassName(), - record.getSourceMethodName(), null, -1); - eventBuilder.withCulprit(fakeFrame); - } else { - eventBuilder.withCulprit(record.getLoggerName()); - } - Map mdc = MDC.getMDCAdapter().getCopyOfContextMap(); if (mdc != null) { for (Map.Entry mdcEntry : mdc.entrySet()) { diff --git a/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java b/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java index 3b959f3dab8..bf197c531c5 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java @@ -52,6 +52,10 @@ public class JsonMarshaller implements Marshaller { * Function call which was the primary perpetrator of this event. */ public static final String CULPRIT = "culprit"; + /** + * Name of the transaction that this event occurred inside of. + */ + public static final String TRANSACTION = "transaction"; /** * An object representing the SDK name and version. */ @@ -189,6 +193,7 @@ private void writeContent(JsonGenerator generator, Event event) throws IOExcepti generator.writeStringField(LOGGER, event.getLogger()); generator.writeStringField(PLATFORM, event.getPlatform()); generator.writeStringField(CULPRIT, event.getCulprit()); + generator.writeStringField(TRANSACTION, event.getTransaction()); writeSdk(generator, event.getSdk()); writeTags(generator, event.getTags()); writeBreadcumbs(generator, event.getBreadcrumbs()); diff --git a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java index 359fc6806c3..fac2e38ee8c 100644 --- a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java +++ b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java @@ -120,39 +120,6 @@ public void testExceptionLogging() throws Exception { assertNoErrorsInErrorManager(); } - @Test - public void testCulpritWithSource() throws Exception { - final String className = "a"; - final String methodName = "b"; - final StackTraceElement stackTraceElement = new StackTraceElement(className, methodName, null, 0); - - sentryHandler.publish(newLogRecord(null, Level.SEVERE, null, null, null, - new StackTraceElement[]{stackTraceElement}, 0, 0)); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getCulprit(), is("a.b")); - }}; - assertNoErrorsInErrorManager(); - } - - @Test - public void testCulpritWithoutSource() throws Exception { - final String loggerName = "0c929a2e-f2bc-4ebb-ad41-a29fb1591ffe"; - - sentryHandler.publish(newLogRecord(loggerName, Level.SEVERE, null, null, null)); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getCulprit(), is(loggerName)); - }}; - assertNoErrorsInErrorManager(); - } - private LogRecord newLogRecord(String loggerName, Level level, String message, Object[] argumentArray, Throwable t) { return newLogRecord(loggerName, level, message, argumentArray, t, null, diff --git a/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java b/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java index 47a88654092..fd9689834fb 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java @@ -173,6 +173,19 @@ public void testEventCulpritWrittenProperly(@Injectable("culprit") final String assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testCulprit.json"))); } + @Test + public void testEventTransactionWrittenProperly(@Injectable("transaction") final String mockTransaction) throws Exception { + final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); + new NonStrictExpectations() {{ + mockEvent.getTransaction(); + result = mockTransaction; + }}; + + jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); + + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testTransaction.json"))); + } + @Test public void testEventTagsWrittenProperly(@Injectable("tagName") final String mockTagName, @Injectable("tagValue") final String mockTagValue) throws Exception { diff --git a/sentry/src/test/java/io/sentry/unmarshaller/event/UnmarshalledEvent.java b/sentry/src/test/java/io/sentry/unmarshaller/event/UnmarshalledEvent.java index 5e937a62309..ddd7025328e 100644 --- a/sentry/src/test/java/io/sentry/unmarshaller/event/UnmarshalledEvent.java +++ b/sentry/src/test/java/io/sentry/unmarshaller/event/UnmarshalledEvent.java @@ -29,6 +29,8 @@ public class UnmarshalledEvent { private Map sdk; @JsonProperty(value = "culprit") private String culprit; + @JsonProperty(value = "transaction") + private String transaction; @JsonProperty(value = "tags") private Map tags; @JsonProperty(value = "server_name") diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json index b2be143dd43..629db9efa97 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testChecksum.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testChecksum.json index 81cf201a0ac..73de4060db1 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testChecksum.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testChecksum.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCompression.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCompression.json index ec55c184ad6..1dece95be48 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCompression.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCompression.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testContexts.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testContexts.json index 1b39db03086..55933e2bad4 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testContexts.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testContexts.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCulprit.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCulprit.json index 2c91e2bfeb9..35074544405 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCulprit.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCulprit.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": "culprit", + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testDist.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testDist.json index 1bff5bd5974..c5ce2b35b04 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testDist.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testDist.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": "release", diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEnvironment.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEnvironment.json index f8551e45e2d..221e9d01b78 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEnvironment.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEnvironment.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEventId.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEventId.json index fb2257cb609..48f8e51ed66 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEventId.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEventId.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraArray.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraArray.json index d63ce5b4305..2b96ab2965c 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraArray.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraArray.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraBoolean.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraBoolean.json index 77b30ac06f3..808243eb0c3 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraBoolean.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraBoolean.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraCustomValue.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraCustomValue.json index 18dce9ca406..c2b117509b6 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraCustomValue.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraCustomValue.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraIterable.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraIterable.json index ae8f4cf3892..7a70a6fbd14 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraIterable.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraIterable.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraMap.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraMap.json index 64938136d07..5eb00cc9492 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraMap.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraMap.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNull.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNull.json index f1f89f41e70..35232470250 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNull.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNull.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json index 83e1ade91a5..04bff4d8d8f 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNumber.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNumber.json index 805ffd0f76a..02b89e27388 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNumber.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNumber.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json index 3690d2af0ff..150eb30e9ee 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json index 8198a588750..310b646031e 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json index aead22802d9..d6a1695c63a 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraString.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraString.json index aaac5137a9b..66f1fce9af0 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraString.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraString.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testFingerprint.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testFingerprint.json index d61bfed0479..f1b48e0e7ab 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testFingerprint.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testFingerprint.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testInterfaceBinding.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testInterfaceBinding.json index 2ec7285b472..5ee24105c09 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testInterfaceBinding.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testInterfaceBinding.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelDebug.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelDebug.json index 218e5eea8b3..c25e44359af 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelDebug.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelDebug.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelError.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelError.json index db444c29792..cae444d8bdf 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelError.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelError.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelFatal.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelFatal.json index 6e4bda95bab..1e4f902c51a 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelFatal.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelFatal.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelInfo.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelInfo.json index 9969e36f342..4290f0b6375 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelInfo.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelInfo.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelWarning.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelWarning.json index 7a29641c8d6..e29c0fd897b 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelWarning.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelWarning.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLogger.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLogger.json index ee2d8fb2273..0e33d1f80af 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLogger.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLogger.json @@ -6,6 +6,7 @@ "logger": "logger", "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testMessage.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testMessage.json index 72e57587d3f..bd39eff3655 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testMessage.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testMessage.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testPlatform.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testPlatform.json index 7a84d9cc2f4..8f728df9dc7 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testPlatform.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testPlatform.json @@ -6,6 +6,7 @@ "logger": null, "platform": "platform", "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testRelease.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testRelease.json index e21a6f10da9..52b2bf77956 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testRelease.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testRelease.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": "release", diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json index 55ea6a5344c..6641a444064 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testServerName.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testServerName.json index 1dbf076ddd5..19d34f501a5 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testServerName.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testServerName.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": "serverName", "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTags.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTags.json index 28d6eeb7f5c..dd6f244819c 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTags.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTags.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": { "tagName": "tagValue" }, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTimestamp.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTimestamp.json index 49533e30bd8..08ad26aca15 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTimestamp.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTimestamp.json @@ -6,6 +6,7 @@ "logger": null, "platform": null, "culprit": null, + "transaction": null, "tags": {}, "server_name": null, "release": null, diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTransaction.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTransaction.json new file mode 100644 index 00000000000..6bdd1a9cbd2 --- /dev/null +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTransaction.json @@ -0,0 +1,21 @@ +{ + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": "transaction", + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": {}, + "checksum": null, + "sdk": { + "name": null, + "version": null + } +} From 9f99ab1654e3e7ff657d896dcf5189602dadb018 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 18 Oct 2017 09:35:14 -0500 Subject: [PATCH 1798/2152] Catch two Buffer related exceptions. --- sentry/src/main/java/io/sentry/buffer/DiskBuffer.java | 9 ++++----- .../java/io/sentry/connection/BufferedConnection.java | 2 ++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java b/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java index 47aea24a31d..33a1c9c8902 100644 --- a/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java +++ b/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java @@ -4,11 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; +import java.io.*; import java.util.Arrays; import java.util.Iterator; @@ -118,6 +114,9 @@ private Event fileToEvent(File eventFile) { try (FileInputStream fileInputStream = new FileInputStream(new File(eventFile.getAbsolutePath())); ObjectInputStream ois = new ObjectInputStream(fileInputStream)) { eventObj = ois.readObject(); + } catch (FileNotFoundException e) { + // event was deleted while we were iterating the array of files + return null; } catch (Exception e) { logger.error("Error reading Event file: " + eventFile.getAbsolutePath(), e); if (!eventFile.delete()) { diff --git a/sentry/src/main/java/io/sentry/connection/BufferedConnection.java b/sentry/src/main/java/io/sentry/connection/BufferedConnection.java index 918e04f21ac..3a0a8be2069 100644 --- a/sentry/src/main/java/io/sentry/connection/BufferedConnection.java +++ b/sentry/src/main/java/io/sentry/connection/BufferedConnection.java @@ -239,6 +239,8 @@ public void run() { } } logger.trace("Flusher run exiting, no more events to send."); + } catch (Exception e) { + logger.error("Error running Flusher: ", e); } finally { SentryEnvironment.stopManagingThread(); } From 6112e629233d9d039e6e6cfbda592329082d7557 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 18 Oct 2017 10:28:47 -0500 Subject: [PATCH 1799/2152] Drop apt-get update because #basho --- .ci/agent-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/agent-install.sh b/.ci/agent-install.sh index f00fd4a9363..fd3c64ce222 100755 --- a/.ci/agent-install.sh +++ b/.ci/agent-install.sh @@ -2,5 +2,5 @@ set -ex -sudo apt-get -qq update +# sudo apt-get -qq update sudo apt-get install -y g++-multilib From edb5de6d5c97ec786245b6cf81ac9d6ae30cef2f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 18 Oct 2017 10:50:32 -0500 Subject: [PATCH 1800/2152] Change development version to 1.6.0-SNAPSHOT --- CHANGES | 2 +- pom.xml | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index 7d44b5cc43b..76c43348dce 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,4 @@ -Version 1.5.7 +Version 1.6.0 ------------- - Add transaction field to Event and EventBuilder. diff --git a/pom.xml b/pom.xml index 7c50418d339..f78088aeb23 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.5.7-SNAPSHOT + 1.6.0-SNAPSHOT pom Sentry-Java diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 949c3e3da09..b741553303c 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.5.7-SNAPSHOT +version = 1.6.0-SNAPSHOT diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 7dec61ccf3f..0855e7ef2d2 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.7-SNAPSHOT + 1.6.0-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 9d0eea87309..a69773ac03f 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.5.7-SNAPSHOT + 1.6.0-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 89caa42ca44..1f453190526 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.7-SNAPSHOT + 1.6.0-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index fa9a64b35bb..836b3d6fb64 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.7-SNAPSHOT + 1.6.0-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 24c3833b56e..b0ae1e65902 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.7-SNAPSHOT + 1.6.0-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 72135b6c66d..4baa0d363a5 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.7-SNAPSHOT + 1.6.0-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 361bb026a0b..9a8692a95b7 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.5.7-SNAPSHOT + 1.6.0-SNAPSHOT sentry From 45cc6b432f39b3ac345acebd2b7820c65c64b4bd Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 18 Oct 2017 10:50:38 -0500 Subject: [PATCH 1801/2152] Bump docs to 1.6.0 --- docs/agent.rst | 2 +- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/agent.rst b/docs/agent.rst index 77cf316db0e..56ec45d7140 100644 --- a/docs/agent.rst +++ b/docs/agent.rst @@ -1,7 +1,7 @@ Agent (Beta) ============ -As of version 1.5.6 there is a new **experimental (beta)** Java Agent available that +As of version 1.6.0 there is a new **experimental (beta)** Java Agent available that enhances the existing Sentry Java SDK. The agent will enhance your application stacktraces on Sentry by adding the names and values of local variables to each frame. diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 827ba00c642..5e4f0ee70f1 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.5.6`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.6.0`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.5.6' + compile 'io.sentry:sentry-android:1.6.0' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.5.6' + classpath 'io.sentry:sentry-android-gradle-plugin:1.6.0' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index e2388fca0c0..02752beb557 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.5.6 + 1.6.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.5.6' + compile 'io.sentry:sentry-appengine:1.6.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.5.6" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index f6e607be8a1..2247a92c451 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.5.6 + 1.6.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.5.6' + compile 'io.sentry:sentry:1.6.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.5.6" + libraryDependencies += "io.sentry" % "sentry" % "1.6.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 4b5df48e055..592667347c8 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.5.6 + 1.6.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.5.6' + compile 'io.sentry:sentry-log4j:1.6.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.5.6" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 645f800deec..f598aca17fb 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.5.6 + 1.6.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.5.6' + compile 'io.sentry:sentry-log4j2:1.6.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.5.6" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index c3bd735747f..2bf66831607 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.5.6 + 1.6.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.5.6' + compile 'io.sentry:sentry-logback:1.6.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.5.6" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index e3862dea07d..e93c6724bf3 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.5.6 + 1.6.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.5.6' + compile 'io.sentry:sentry-spring:1.6.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.5.6" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index d515e1e9c3e..6b4e79ebdff 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.5.6 + 1.6.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.5.6' + compile 'io.sentry:sentry:1.6.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.5.6" + libraryDependencies += "io.sentry" % "sentry" % "1.6.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 8e966fdd7873dfa5d9a81c0728686b53d67fb82d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 18 Oct 2017 10:52:35 -0500 Subject: [PATCH 1802/2152] [maven-release-plugin] prepare release v1.6.0 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index f78088aeb23..e4a0ca14ce1 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.0-SNAPSHOT + 1.6.0 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.6.0 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 0855e7ef2d2..1556d306116 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.0-SNAPSHOT + 1.6.0 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index a69773ac03f..02804a4562d 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.0-SNAPSHOT + 1.6.0 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 1f453190526..51493ef47c1 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.0-SNAPSHOT + 1.6.0 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 836b3d6fb64..ff8965a40d5 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.0-SNAPSHOT + 1.6.0 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index b0ae1e65902..4c439b41612 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.0-SNAPSHOT + 1.6.0 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 4baa0d363a5..8b3a289c15f 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.0-SNAPSHOT + 1.6.0 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 9a8692a95b7..a6f4eb6a90d 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.0-SNAPSHOT + 1.6.0 sentry From 3e051e48c50bb9490eca44017bae9cc236c658f4 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 18 Oct 2017 10:52:35 -0500 Subject: [PATCH 1803/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index e4a0ca14ce1..3816d7c46fe 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.0 + 1.6.1-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.6.0 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 1556d306116..dbf14305896 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.0 + 1.6.1-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 02804a4562d..c620406b496 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.0 + 1.6.1-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 51493ef47c1..edcd2248088 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.0 + 1.6.1-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index ff8965a40d5..60a7eec1bf1 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.0 + 1.6.1-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 4c439b41612..4ac7cae218c 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.0 + 1.6.1-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 8b3a289c15f..5b38a8b87a1 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.0 + 1.6.1-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index a6f4eb6a90d..b16c52704c7 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.0 + 1.6.1-SNAPSHOT sentry From 4385a193090f984a1c2e5605876783005dd3c092 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 18 Oct 2017 10:52:38 -0500 Subject: [PATCH 1804/2152] Bump CHANGES to 1.6.1 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 76c43348dce..fd217e84171 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.6.1 +------------- + +- + Version 1.6.0 ------------- From 32d094bf29f1186a462eb01aa2de6e6ae360cdb8 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 18 Oct 2017 10:56:11 -0500 Subject: [PATCH 1805/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index b741553303c..a5eab5c3d42 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.6.0-SNAPSHOT +version = 1.6.1-SNAPSHOT From 776043dfba3be74420db1efc0457eb12ee1f53fc Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 19 Oct 2017 07:42:11 -0500 Subject: [PATCH 1806/2152] Fix first version of the Agent. --- docs/agent.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/agent.rst b/docs/agent.rst index 56ec45d7140..b65b3ed389b 100644 --- a/docs/agent.rst +++ b/docs/agent.rst @@ -1,7 +1,7 @@ Agent (Beta) ============ -As of version 1.6.0 there is a new **experimental (beta)** Java Agent available that +As of version 1.5.0 there is a new **experimental (beta)** Java Agent available that enhances the existing Sentry Java SDK. The agent will enhance your application stacktraces on Sentry by adding the names and values of local variables to each frame. From 5db7ba718d2ad2ad8e8d734076a44813e8483b36 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 20 Oct 2017 09:01:37 -0500 Subject: [PATCH 1807/2152] =?UTF-8?q?Don't=20throw=20a=20ConnectionExcepti?= =?UTF-8?q?on=20if=20an=20event=20is=20filtered=20by=20the=20Sent=E2=80=A6?= =?UTF-8?q?=20(#521)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Don't throw a ConnectionException if an event is filtered by the Sentry server (HTTP 403). Fixes GH-519 --- CHANGES | 2 +- .../io/sentry/connection/HttpConnection.java | 10 +++ sentry/src/test/java/io/sentry/BaseIT.java | 8 ++- sentry/src/test/java/io/sentry/SentryIT.java | 67 +++++++++++++++++++ .../sentry/connection/HttpConnectionTest.java | 12 ++++ 5 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 sentry/src/test/java/io/sentry/SentryIT.java diff --git a/CHANGES b/CHANGES index fd217e84171..1fcbba37831 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.6.1 ------------- -- +- Don't throw a ConnectionException if an event is filtered by the Sentry server (HTTP 403). Version 1.6.0 ------------- diff --git a/sentry/src/main/java/io/sentry/connection/HttpConnection.java b/sentry/src/main/java/io/sentry/connection/HttpConnection.java index 83540b855de..efe2d521415 100644 --- a/sentry/src/main/java/io/sentry/connection/HttpConnection.java +++ b/sentry/src/main/java/io/sentry/connection/HttpConnection.java @@ -156,6 +156,16 @@ protected void doSend(Event event) throws ConnectionException { outputStream.close(); connection.getInputStream().close(); } catch (IOException e) { + try { + int responseCode = connection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_FORBIDDEN) { + logger.debug("Event '" + event.getId() + "' was rejected by the Sentry server due to a filter."); + return; + } + } catch (IOException responseCodeException) { + // pass + } + String errorMessage = null; final InputStream errorStream = connection.getErrorStream(); if (errorStream != null) { diff --git a/sentry/src/test/java/io/sentry/BaseIT.java b/sentry/src/test/java/io/sentry/BaseIT.java index e25dd0b4e0f..d4b3dfda784 100644 --- a/sentry/src/test/java/io/sentry/BaseIT.java +++ b/sentry/src/test/java/io/sentry/BaseIT.java @@ -32,13 +32,17 @@ public class BaseIT extends BaseTest { @Rule public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().port(8080)); - public void stub200ForProject1Store() { + public void stubForProject1Store(int responseCode) { wireMockRule.stubFor( post(urlEqualTo(PROJECT1_STORE_URL)) .withHeader(AUTH_HEADER, AUTH_HEADER_PATTERN) .withHeader("Content-Type", new EqualToPattern("application/json")) .withHeader("Content-Encoding", new EqualToPattern("gzip")) - .willReturn(aResponse().withStatus(200))); + .willReturn(aResponse().withStatus(responseCode))); + } + + public void stub200ForProject1Store() { + stubForProject1Store(200); } public String getDsn(String projectId) { diff --git a/sentry/src/test/java/io/sentry/SentryIT.java b/sentry/src/test/java/io/sentry/SentryIT.java new file mode 100644 index 00000000000..6b6a521990f --- /dev/null +++ b/sentry/src/test/java/io/sentry/SentryIT.java @@ -0,0 +1,67 @@ +package io.sentry; + +import io.sentry.connection.AbstractConnection; +import io.sentry.connection.LockdownManager; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static mockit.Deencapsulation.getField; + +public class SentryIT extends BaseIT { + + @Test + public void test500DoesLockdown() throws Exception { + stubForProject1Store(500); + + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); + + SentryClient client = SentryClientFactory.sentryClient(); + client.sendMessage("Test"); + + verifyProject1PostRequestCount(1); + verifyStoredEventCount(0); + + assertTrue(isLockedDown(client)); + } + + @Test + public void test403FilteredDoesNotLockdown() throws Exception { + stubForProject1Store(403); + + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); + + SentryClient client = SentryClientFactory.sentryClient(); + client.sendMessage("Test"); + + verifyProject1PostRequestCount(1); + verifyStoredEventCount(0); + + assertFalse(isLockedDown(client)); + } + + @Test + public void testSuccess() throws Exception { + stub200ForProject1Store(); + + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); + + SentryClient client = SentryClientFactory.sentryClient(); + client.sendMessage("Test"); + + verifyProject1PostRequestCount(1); + verifyStoredEventCount(1); + + assertFalse(isLockedDown(client)); + } + + private boolean isLockedDown(SentryClient client) { + AbstractConnection connection = getField(client, "connection"); + LockdownManager lockdownManager = getField(connection, "lockdownManager"); + return lockdownManager.isLockedDown(); + } + +} diff --git a/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java b/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java index fd7ffeba784..0a107e235e4 100644 --- a/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java @@ -142,6 +142,18 @@ public void testHttpErrorThrowsAnException(@Injectable final Event mockEvent) th httpConnection.doSend(mockEvent); } + @Test + public void testHttp403DoesntThrow(@Injectable final Event mockEvent) throws Exception { + new NonStrictExpectations() {{ + mockUrlConnection.getOutputStream(); + result = new IOException(); + mockUrlConnection.getResponseCode(); + result = 403; + }}; + + httpConnection.doSend(mockEvent); + } + @Test public void testRetryAfterHeader(@Injectable final Event mockEvent) throws Exception { final String httpErrorMessage = "93e3ddb1-c4f3-46c3-9900-529de83678b7"; From 6dd96db120537faccb5d5c57ab999f2678fdcef6 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 20 Oct 2017 09:02:17 -0500 Subject: [PATCH 1808/2152] Bump docs to 1.6.1 --- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 5e4f0ee70f1..8ddc56595ff 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.6.0`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.6.1`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.6.0' + compile 'io.sentry:sentry-android:1.6.1' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.6.0' + classpath 'io.sentry:sentry-android-gradle-plugin:1.6.1' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 02752beb557..7f773000611 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.6.0 + 1.6.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.6.0' + compile 'io.sentry:sentry-appengine:1.6.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.0" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 2247a92c451..8bc93a9d6d6 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.6.0 + 1.6.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.0' + compile 'io.sentry:sentry:1.6.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.0" + libraryDependencies += "io.sentry" % "sentry" % "1.6.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 592667347c8..50cf689963c 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.6.0 + 1.6.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.6.0' + compile 'io.sentry:sentry-log4j:1.6.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.0" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index f598aca17fb..715dea3ab54 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.6.0 + 1.6.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.6.0' + compile 'io.sentry:sentry-log4j2:1.6.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.0" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 2bf66831607..dac30cfd442 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.6.0 + 1.6.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.6.0' + compile 'io.sentry:sentry-logback:1.6.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.0" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index e93c6724bf3..f64848a9b5d 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.6.0 + 1.6.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.6.0' + compile 'io.sentry:sentry-spring:1.6.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.0" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index 6b4e79ebdff..81c8715d57e 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.6.0 + 1.6.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.0' + compile 'io.sentry:sentry:1.6.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.0" + libraryDependencies += "io.sentry" % "sentry" % "1.6.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From d1e3a6097779324a1aa051b06b3240f61adf258f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 20 Oct 2017 09:03:44 -0500 Subject: [PATCH 1809/2152] [maven-release-plugin] prepare release v1.6.1 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 3816d7c46fe..4947e9552d2 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.1-SNAPSHOT + 1.6.1 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.6.1 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index dbf14305896..73944633ac5 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.1-SNAPSHOT + 1.6.1 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index c620406b496..47710d12f9d 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.1-SNAPSHOT + 1.6.1 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index edcd2248088..32f8517a242 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.1-SNAPSHOT + 1.6.1 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 60a7eec1bf1..a31910f6332 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.1-SNAPSHOT + 1.6.1 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 4ac7cae218c..bd97b11553c 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.1-SNAPSHOT + 1.6.1 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 5b38a8b87a1..83b8be8a9c6 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.1-SNAPSHOT + 1.6.1 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index b16c52704c7..a1661c97efd 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.1-SNAPSHOT + 1.6.1 sentry From 94e12bdb83702bc3072b08bb222ef8bf266b6bae Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 20 Oct 2017 09:03:45 -0500 Subject: [PATCH 1810/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 4947e9552d2..ac5a639dfd7 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.1 + 1.6.2-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.6.1 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 73944633ac5..1dc9fb67c79 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.1 + 1.6.2-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 47710d12f9d..3eba98538be 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.1 + 1.6.2-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 32f8517a242..82fa265fecd 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.1 + 1.6.2-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index a31910f6332..37bd6c644ae 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.1 + 1.6.2-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index bd97b11553c..c3bb921d1eb 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.1 + 1.6.2-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 83b8be8a9c6..268ea1094fa 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.1 + 1.6.2-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index a1661c97efd..d5694cc89a6 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.1 + 1.6.2-SNAPSHOT sentry From ebcadec5f1ee351fcfd313ef02702d9870f36500 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 20 Oct 2017 09:03:47 -0500 Subject: [PATCH 1811/2152] Bump CHANGES to 1.6.2 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 1fcbba37831..5e47c4892e7 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.6.2 +------------- + +- + Version 1.6.1 ------------- From 0370a9324ff6ba6dc30e1676e069f6e615fd754a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 20 Oct 2017 09:25:48 -0500 Subject: [PATCH 1812/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index a5eab5c3d42..a40c87f576b 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.6.1-SNAPSHOT +version = 1.6.2-SNAPSHOT From 092589e0d391bbcd6b5f81a3739d2413bf74fec3 Mon Sep 17 00:00:00 2001 From: Yash Thakkar Date: Sun, 22 Oct 2017 18:50:34 +0530 Subject: [PATCH 1813/2152] Fix missing quote in migration docs. (#522) --- docs/migration.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/migration.rst b/docs/migration.rst index 46956301944..56fc1c98d47 100644 --- a/docs/migration.rst +++ b/docs/migration.rst @@ -113,7 +113,7 @@ Raven Class Changes // The static SentryClient can be lazily initialized from anywhere in your application. // Your DSN needs to be provided somehow, check the configuration documentation! - Sentry.capture("Hello, world!) + Sentry.capture("Hello, world!") Configuration via DSN --------------------- @@ -194,4 +194,4 @@ Android Android users now use the same ``Sentry`` and ``SentryClient`` classes as everyone, they just need to initialize it with their application context and the ``AndroidSentryClientFactory``. For an example, `see the Android documentation - `_. \ No newline at end of file + `_. From d1f6501c50472ff6a1b96b285c5c1c7be012e31f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 9 Nov 2017 09:09:33 -0800 Subject: [PATCH 1814/2152] Handle multi-APK projects with different version codes. (#530) This changes the Gradle plugin from only checking the first variant with `variant.outputs.first()` to iterating over all variants. It now uploads/links multiple version code releases to the same DSym files: https://developer.android.com/studio/build/configure-apk-splits.html#configure-APK-versions In addition, we now check multiple well known places for the AndroidManifest.xml file, and print an error if none is found. This change also fixed the plugin for my example project. --- .../sentry/android/gradle/SentryPlugin.groovy | 193 ++++++++++-------- 1 file changed, 105 insertions(+), 88 deletions(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index e1c1e2def08..9d9d4fa54a6 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -94,8 +94,8 @@ class SentryPlugin implements Plugin { * @return */ static Task getProguardTask(Project project, ApplicationVariant variant) { - def name = variant.name.capitalize() - def rv = project.tasks.findByName("transformClassesAndResourcesWithProguardFor${name}") + def name = "transformClassesAndResourcesWithProguardFor${variant.name.capitalize()}" + def rv = project.tasks.findByName(name) if (rv != null) { return rv } @@ -110,8 +110,8 @@ class SentryPlugin implements Plugin { * @return */ static Task getDexTask(Project project, ApplicationVariant variant) { - def name = variant.name.capitalize() - def rv = project.tasks.findByName("transformClassesWithDexFor${name}") + def name = "transformClassesWithDexFor${variant.name.capitalize()}" + def rv = project.tasks.findByName(name) if (rv != null) { return rv } @@ -138,102 +138,119 @@ class SentryPlugin implements Plugin { } project.android.applicationVariants.all { ApplicationVariant variant -> - def variantOutput = variant.outputs.first() - - def manifestPath - try { - // Android Gradle Plugin < 3.0.0 - manifestPath = variantOutput.processManifest.manifestOutputFile - } catch (Exception ignored) { - // Android Gradle Plugin >= 3.0.0 - manifestPath = new File(variantOutput.processManifest.manifestOutputDirectory, "AndroidManifest.xml") - } - - def mappingFile = variant.getMappingFile() - def proguardTask = getProguardTask(project, variant) - def dexTask = getDexTask(project, variant) + variant.outputs.each { variantOutput -> + def manifestPath + try { + // Android Gradle Plugin < 3.0.0 + manifestPath = variantOutput.processManifest.manifestOutputFile + } catch (Exception ignored) { + // Android Gradle Plugin >= 3.0.0 + manifestPath = new File( + variantOutput.processManifest.manifestOutputDirectory, + "AndroidManifest.xml") + if (!manifestPath.isFile()) { + manifestPath = new File( + new File( + variantOutput.processManifest.manifestOutputDirectory, + variantOutput.dirName), + "AndroidManifest.xml") + if (!manifestPath.isFile()) { + System.err.println("sentry-plugin: Cannot find AndroidManifst.xml") + return + } + } + } - if (proguardTask == null) { - return - } + def mappingFile = variant.getMappingFile() + def proguardTask = getProguardTask(project, variant) + def dexTask = getDexTask(project, variant) - // create a task to configure proguard automatically unless the user disabled it. - if (project.sentry.autoProguardConfig) { - SentryProguardConfigTask proguardConfigTask = project.tasks.create( - "addSentryProguardSettingsFor${variant.name.capitalize()}", - SentryProguardConfigTask) - proguardConfigTask.group = GROUP_NAME - proguardConfigTask.applicationVariant = variant - proguardTask.dependsOn proguardConfigTask - } + if (proguardTask == null) { + return + } - def cli = getSentryCli(project) - - // create a task that persists our proguard uuid as android asset - def persistIdsTask = project.tasks.create( - name: "persistSentryProguardUuidsFor${variant.name.capitalize()}", - type: Exec) { - description "Write references to proguard UUIDs to the android assets." - workingDir project.rootDir - - def variantName = variant.buildType.name - def flavorName = variant.flavorName - def propName = "sentry.properties" - def possibleProps = [ - "${project.rootDir.toPath()}/app/src/${variantName}/${propName}", - "${project.rootDir.toPath()}/app/src/${variantName}/${flavorName}/${propName}", - "${project.rootDir.toPath()}/app/src/${flavorName}/${variantName}/${propName}", - "${project.rootDir.toPath()}/src/${variantName}/${propName}", - "${project.rootDir.toPath()}/src/${variantName}/${flavorName}/${propName}", - "${project.rootDir.toPath()}/src/${flavorName}/${variantName}/${propName}", - "${project.rootDir.toPath()}/${propName}" - ] - - def propsFile = null - possibleProps.each { - if (propsFile == null && new File(it).isFile()) { - propsFile = it + // create a task to configure proguard automatically unless the user disabled it. + if (project.sentry.autoProguardConfig) { + def addProguardSettingsTaskName = "addSentryProguardSettingsFor${variant.name.capitalize()}" + if (!project.tasks.findByName(addProguardSettingsTaskName)) { + SentryProguardConfigTask proguardConfigTask = project.tasks.create( + addProguardSettingsTaskName, + SentryProguardConfigTask) + proguardConfigTask.group = GROUP_NAME + proguardConfigTask.applicationVariant = variant + proguardTask.dependsOn proguardConfigTask } } - if (propsFile != null) { - environment("SENTRY_PROPERTIES", propsFile) - } + def cli = getSentryCli(project) - def args = [ - cli, - "upload-proguard", - "--android-manifest", - manifestPath, - "--write-properties", - getDebugMetaPropPath(project, variant), - mappingFile - ] - - if (!project.sentry.autoUpload) { - args.push("--no-upload") - } + def persistIdsTaskName = "persistSentryProguardUuidsFor${variant.name.capitalize()}${variantOutput.name.capitalize()}" + // create a task that persists our proguard uuid as android asset + def persistIdsTask = project.tasks.create( + name: persistIdsTaskName, + type: Exec) { + description "Write references to proguard UUIDs to the android assets." + workingDir project.rootDir - if (Os.isFamily(Os.FAMILY_WINDOWS)) { - commandLine("cmd", "/c", *args) - } else { - commandLine(*args) - } + def variantName = variant.buildType.name + def flavorName = variant.flavorName + def propName = "sentry.properties" + def possibleProps = [ + "${project.rootDir.toPath()}/app/src/${variantName}/${propName}", + "${project.rootDir.toPath()}/app/src/${variantName}/${flavorName}/${propName}", + "${project.rootDir.toPath()}/app/src/${flavorName}/${variantName}/${propName}", + "${project.rootDir.toPath()}/src/${variantName}/${propName}", + "${project.rootDir.toPath()}/src/${variantName}/${flavorName}/${propName}", + "${project.rootDir.toPath()}/src/${flavorName}/${variantName}/${propName}", + "${project.rootDir.toPath()}/${propName}" + ] - enabled true - } + def propsFile = null + possibleProps.each { + if (propsFile == null && new File(it).isFile()) { + propsFile = it + } + } + + if (propsFile != null) { + environment("SENTRY_PROPERTIES", propsFile) + } + + def args = [ + cli, + "upload-proguard", + "--android-manifest", + manifestPath, + "--write-properties", + getDebugMetaPropPath(project, variant), + mappingFile + ] + + if (!project.sentry.autoUpload) { + args.push("--no-upload") + } - // and run before dex transformation. If we managed to find the dex task - // we set ourselves as dependency, otherwise we just hack outselves into - // the proguard task's doLast. - if (dexTask != null) { - dexTask.dependsOn persistIdsTask - } else { - proguardTask.doLast { - persistIdsTask.execute() + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + commandLine("cmd", "/c", *args) + } else { + commandLine(*args) + } + + enabled true + } + + // and run before dex transformation. If we managed to find the dex task + // we set ourselves as dependency, otherwise we just hack outselves into + // the proguard task's doLast. + if (dexTask != null) { + dexTask.dependsOn persistIdsTask + } else { + proguardTask.doLast { + persistIdsTask.execute() + } } + persistIdsTask.dependsOn proguardTask } - persistIdsTask.dependsOn proguardTask } } } From 032dd82d156857aed44ff648ae660f938aece888 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 9 Nov 2017 09:11:11 -0800 Subject: [PATCH 1815/2152] Ignore local.properties --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index cb7092da29a..98e5c1b41ed 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ target/ docs/_build infer-out/ agent/build/ +local.properties From 2287d5592542d7134135f91ef328021f813fcac5 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 9 Nov 2017 09:12:09 -0800 Subject: [PATCH 1816/2152] Bump sentry-cli to 1.22.0 --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 75107854c5b..4ced8caeb9f 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.21.0 +VERSION=1.22.0 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* From a51f0080e9fed050b03f384bfdf5eb4c542fd581 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 9 Nov 2017 09:27:31 -0800 Subject: [PATCH 1817/2152] Bump docs to 1.6.2 --- Makefile | 6 ++---- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 9 files changed, 34 insertions(+), 36 deletions(-) diff --git a/Makefile b/Makefile index 97ac72b72b8..4644a26b6ef 100644 --- a/Makefile +++ b/Makefile @@ -4,15 +4,13 @@ # the test suite currently only works with 1.7.0_80 # https://github.com/getsentry/sentry-java/issues/478 -export JAVA_HOME := $(shell /usr/libexec/java_home -v 1.7.0_80) -export PATH := $(JAVA_HOME)/bin:$(PATH) .PHONY: checkstyle compile test install clean prepare prepareDocs prepareMvn prepareChanges perform verify # TODO: Fix to work between macOS and Linux MVN=mvn -e -ECHO=gecho -SED=gsed +ECHO=echo +SED=sed all: checkstyle test install diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 8ddc56595ff..ddf2e4e30df 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.6.1`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.6.2`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.6.1' + compile 'io.sentry:sentry-android:1.6.2' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.6.1' + classpath 'io.sentry:sentry-android-gradle-plugin:1.6.2' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 7f773000611..91ab77f0b03 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.6.1 + 1.6.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.6.1' + compile 'io.sentry:sentry-appengine:1.6.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.1" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 8bc93a9d6d6..cdee4736594 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.6.1 + 1.6.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.1' + compile 'io.sentry:sentry:1.6.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.1" + libraryDependencies += "io.sentry" % "sentry" % "1.6.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 50cf689963c..90ad49ddeb1 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.6.1 + 1.6.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.6.1' + compile 'io.sentry:sentry-log4j:1.6.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.1" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 715dea3ab54..143b9958ada 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.6.1 + 1.6.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.6.1' + compile 'io.sentry:sentry-log4j2:1.6.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.1" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index dac30cfd442..f8f80cd4ca8 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.6.1 + 1.6.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.6.1' + compile 'io.sentry:sentry-logback:1.6.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.1" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index f64848a9b5d..52ded3dc4d0 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.6.1 + 1.6.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.6.1' + compile 'io.sentry:sentry-spring:1.6.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.1" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index 81c8715d57e..2d2e8f80b2b 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.6.1 + 1.6.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.1' + compile 'io.sentry:sentry:1.6.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.1" + libraryDependencies += "io.sentry" % "sentry" % "1.6.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From cec3cc41b205983ec7c34ad23fd97c986fe3fe0d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 9 Nov 2017 09:28:52 -0800 Subject: [PATCH 1818/2152] [maven-release-plugin] prepare release v1.6.2 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index ac5a639dfd7..52c5af2e20a 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.2-SNAPSHOT + 1.6.2 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.6.2 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 1dc9fb67c79..8e9a9e3001b 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.2-SNAPSHOT + 1.6.2 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 3eba98538be..d903229ffb8 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.2-SNAPSHOT + 1.6.2 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 82fa265fecd..2d7e68532f1 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.2-SNAPSHOT + 1.6.2 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 37bd6c644ae..78c2e8c0b90 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.2-SNAPSHOT + 1.6.2 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index c3bb921d1eb..865de76c818 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.2-SNAPSHOT + 1.6.2 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 268ea1094fa..7f603bdaefa 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.2-SNAPSHOT + 1.6.2 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index d5694cc89a6..8077040f16f 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.2-SNAPSHOT + 1.6.2 sentry From 1c61b2ea63e222002cabc62c9cce97e00b6310c7 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 9 Nov 2017 09:28:52 -0800 Subject: [PATCH 1819/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 52c5af2e20a..7ccc2266d28 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.2 + 1.6.3-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.6.2 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 8e9a9e3001b..28a4f9362f3 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.2 + 1.6.3-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index d903229ffb8..da417777624 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.2 + 1.6.3-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 2d7e68532f1..c5bac606ef9 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.2 + 1.6.3-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 78c2e8c0b90..20881192336 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.2 + 1.6.3-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 865de76c818..36f759a4ccf 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.2 + 1.6.3-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 7f603bdaefa..88642ef1e8b 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.2 + 1.6.3-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 8077040f16f..819bdf1faf5 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.2 + 1.6.3-SNAPSHOT sentry From 66acb054363d5961905d01fa00404def8371cc32 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 9 Nov 2017 09:28:54 -0800 Subject: [PATCH 1820/2152] Bump CHANGES to 1.6.3 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 5e47c4892e7..7461b6c80f3 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.6.3 +------------- + +- + Version 1.6.2 ------------- From a8382b5741035930c809be33c44cfcefe60fb459 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 9 Nov 2017 09:37:41 -0800 Subject: [PATCH 1821/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index a40c87f576b..139b0cdbcd4 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.6.2-SNAPSHOT +version = 1.6.3-SNAPSHOT From 65b060984e9890ac71b31fb87d229820bfac7018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Szyma=C5=84ski?= Date: Fri, 10 Nov 2017 23:40:24 +0100 Subject: [PATCH 1822/2152] Fix issue where Gradle tasks were not created (#533) --- .../main/groovy/io/sentry/android/gradle/SentryPlugin.groovy | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 9d9d4fa54a6..0fd36b06cd9 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -154,10 +154,6 @@ class SentryPlugin implements Plugin { variantOutput.processManifest.manifestOutputDirectory, variantOutput.dirName), "AndroidManifest.xml") - if (!manifestPath.isFile()) { - System.err.println("sentry-plugin: Cannot find AndroidManifst.xml") - return - } } } From 7a5fc3e587028b22a9f74de4ad38013b6c62557f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Nov 2017 16:40:58 -0600 Subject: [PATCH 1823/2152] Bump docs to 1.6.3 --- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index ddf2e4e30df..bfd68ed3702 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.6.2`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.6.3`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.6.2' + compile 'io.sentry:sentry-android:1.6.3' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.6.2' + classpath 'io.sentry:sentry-android-gradle-plugin:1.6.3' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 91ab77f0b03..3b4209635dd 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.6.2 + 1.6.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.6.2' + compile 'io.sentry:sentry-appengine:1.6.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.2" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index cdee4736594..5f6f2bc7ef5 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.6.2 + 1.6.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.2' + compile 'io.sentry:sentry:1.6.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.2" + libraryDependencies += "io.sentry" % "sentry" % "1.6.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 90ad49ddeb1..9021546f7fd 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.6.2 + 1.6.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.6.2' + compile 'io.sentry:sentry-log4j:1.6.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.2" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 143b9958ada..029651b8e76 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.6.2 + 1.6.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.6.2' + compile 'io.sentry:sentry-log4j2:1.6.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.2" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index f8f80cd4ca8..ea8fdb649bd 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.6.2 + 1.6.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.6.2' + compile 'io.sentry:sentry-logback:1.6.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.2" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index 52ded3dc4d0..a280a016d83 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.6.2 + 1.6.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.6.2' + compile 'io.sentry:sentry-spring:1.6.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.2" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index 2d2e8f80b2b..83112f279f9 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.6.2 + 1.6.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.2' + compile 'io.sentry:sentry:1.6.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.2" + libraryDependencies += "io.sentry" % "sentry" % "1.6.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 14cd2e149a82d877e564e18c9011fa241212477e Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Nov 2017 16:42:21 -0600 Subject: [PATCH 1824/2152] [maven-release-plugin] prepare release v1.6.3 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 7ccc2266d28..84851f35a1b 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.3-SNAPSHOT + 1.6.3 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.6.3 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 28a4f9362f3..4f0acc9c24b 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.3-SNAPSHOT + 1.6.3 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index da417777624..0583da93a9d 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.3-SNAPSHOT + 1.6.3 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index c5bac606ef9..c09a4e6c978 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.3-SNAPSHOT + 1.6.3 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 20881192336..41abb35dfba 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.3-SNAPSHOT + 1.6.3 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 36f759a4ccf..6976f658f82 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.3-SNAPSHOT + 1.6.3 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 88642ef1e8b..1d696dbc2af 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.3-SNAPSHOT + 1.6.3 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 819bdf1faf5..c5cce566073 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.3-SNAPSHOT + 1.6.3 sentry From bfa67886ae86e287d2bd998fef9741eaf803a30a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Nov 2017 16:42:21 -0600 Subject: [PATCH 1825/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 84851f35a1b..45d3821f0e6 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.3 + 1.6.4-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.6.3 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 4f0acc9c24b..4045790ff51 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.3 + 1.6.4-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 0583da93a9d..b3c702fc5ce 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.3 + 1.6.4-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index c09a4e6c978..c66c0a74581 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.3 + 1.6.4-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 41abb35dfba..dee5f4fa710 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.3 + 1.6.4-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 6976f658f82..98ee991e3b1 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.3 + 1.6.4-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 1d696dbc2af..da31edac740 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.3 + 1.6.4-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index c5cce566073..5e90c6de3ad 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.3 + 1.6.4-SNAPSHOT sentry From 0f7346f8e9b4ce54e3d5dfde7193459884f8de87 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Nov 2017 16:42:24 -0600 Subject: [PATCH 1826/2152] Bump CHANGES to 1.6.4 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 7461b6c80f3..14424a61094 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.6.4 +------------- + +- + Version 1.6.3 ------------- From 5de62dd72c4dbc85fc1d03abc35ddee99a13fb92 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Nov 2017 16:48:07 -0600 Subject: [PATCH 1827/2152] Fix CHANGES. --- CHANGES | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 14424a61094..5ff741700ce 100644 --- a/CHANGES +++ b/CHANGES @@ -6,12 +6,12 @@ Version 1.6.4 Version 1.6.3 ------------- -- +- Fix issue where Gradle tasks were not created. (thanks szymanskip) Version 1.6.2 ------------- -- +- Handle multi-APK projects with different version codes. Version 1.6.1 ------------- From 4c04233353b9537a235ec1883280dd233f86556a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 10 Nov 2017 16:48:26 -0600 Subject: [PATCH 1828/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 139b0cdbcd4..90c151b776f 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.6.3-SNAPSHOT +version = 1.6.4-SNAPSHOT From b548f1849dc297a45efe4c5777b515cf01356488 Mon Sep 17 00:00:00 2001 From: Patrick Favre-Bulle Date: Tue, 14 Nov 2017 22:45:15 +0100 Subject: [PATCH 1829/2152] Refactor creating Json Marshaller to be more friendly to extensions (#536) --- .../io/sentry/DefaultSentryClientFactory.java | 13 ++++++++++++- .../io/sentry/marshaller/json/JsonMarshaller.java | 15 ++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index 24d704bd009..399f5a48407 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -454,7 +454,7 @@ protected Connection createStdOutConnection(Dsn dsn) { */ protected Marshaller createMarshaller(Dsn dsn) { int maxMessageLength = getMaxMessageLength(dsn); - JsonMarshaller marshaller = new JsonMarshaller(maxMessageLength); + JsonMarshaller marshaller = createJsonMarshaller(maxMessageLength); // Set JSON marshaller bindings StackTraceInterfaceBinding stackTraceBinding = new StackTraceInterfaceBinding(); @@ -478,6 +478,17 @@ protected Marshaller createMarshaller(Dsn dsn) { return marshaller; } + /** + * Create a {@link JsonMarshaller}. This method makes it easier to provide a custom implementation. + * + * @param maxMessageLength of the whole json output + * @return new marshaller + */ + @SuppressWarnings("WeakerAccess") + protected JsonMarshaller createJsonMarshaller(int maxMessageLength) { + return new JsonMarshaller(maxMessageLength); + } + /** * Returns the {@link ContextManager} to use for locating and storing data that is context specific, * such as {@link io.sentry.event.Breadcrumb}s. diff --git a/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java b/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java index bf197c531c5..a39d27ee0b3 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java @@ -157,7 +157,7 @@ public void marshall(Event event, OutputStream destination) throws IOException { destination = new GZIPOutputStream(destination); } - try (SentryJsonGenerator generator = new SentryJsonGenerator(jsonFactory.createGenerator(destination))) { + try (JsonGenerator generator = createJsonGenerator(destination)) { writeContent(generator, event); } catch (IOException e) { logger.error("An exception occurred while serialising the event.", e); @@ -170,6 +170,19 @@ public void marshall(Event event, OutputStream destination) throws IOException { } } + /** + * Creates the {@link JsonGenerator} used to marshall to json. + * This method makes it easier to provide a custom implementation. + * + * @param destination used to read the content + * @return a new instance + * @throws IOException on error reading the stream + */ + @SuppressWarnings("WeakerAccess") + protected JsonGenerator createJsonGenerator(OutputStream destination) throws IOException { + return new SentryJsonGenerator(jsonFactory.createGenerator(destination)); + } + @Override public String getContentType() { return "application/json"; From f907cd1d42b8822686d5131f3504c44cafa6640a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 22 Nov 2017 12:00:53 -0600 Subject: [PATCH 1830/2152] Add web-fragment.xml to prevent classpath scanning. (#539) * Add web-fragment.xml to prevent classpath scanning. Fixes GH-538 * add distributable --- CHANGES | 2 +- sentry/src/main/resources/META-INF/web-fragment.xml | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 sentry/src/main/resources/META-INF/web-fragment.xml diff --git a/CHANGES b/CHANGES index 5ff741700ce..f5676133715 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.6.4 ------------- -- +- Add web-fragment.xml to prevent classpath scanning. Version 1.6.3 ------------- diff --git a/sentry/src/main/resources/META-INF/web-fragment.xml b/sentry/src/main/resources/META-INF/web-fragment.xml new file mode 100644 index 00000000000..39ce7c4c67f --- /dev/null +++ b/sentry/src/main/resources/META-INF/web-fragment.xml @@ -0,0 +1,10 @@ + + + sentry + + From 027544f8a4063740294bde23073ea409d38ed5e1 Mon Sep 17 00:00:00 2001 From: Jon Wu Date: Wed, 13 Dec 2017 06:19:26 -0800 Subject: [PATCH 1831/2152] Fix class for sentry-java logback.xml example (#540) The class for the `logback.xml` config wasn't updated for the Now (sentry-java) example --- docs/migration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migration.rst b/docs/migration.rst index 56fc1c98d47..afa987a5115 100644 --- a/docs/migration.rst +++ b/docs/migration.rst @@ -73,7 +73,7 @@ Logging Integration Configuration .. sourcecode:: xml - + WARN From 168ba1a67a2ad897af1c918f274d041b4d138dbe Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Wed, 27 Dec 2017 20:09:53 +0100 Subject: [PATCH 1832/2152] feat: Update proguard plugin to use new npm package (#541) --- .../sentry/android/gradle/SentryPlugin.groovy | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 0fd36b06cd9..8de3a400bcc 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -37,12 +37,18 @@ class SentryPlugin implements Plugin { // in case there is a version from npm right around the corner use that one. This // is the case for react-native-sentry for instance - def exePath = "${project.rootDir.toPath()}/../node_modules/sentry-cli-binary/bin/sentry-cli" - if ((new File(exePath)).exists()) { - return exePath - } - if ((new File(exePath + ".exe")).exists()) { - return exePath + ".exe" + def possibleExePaths = [ + "${project.rootDir.toPath()}/../node_modules/@sentry/cli/bin/sentry-cli", + "${project.rootDir.toPath()}/../node_modules/sentry-cli-binary/bin/sentry-cli" + ] + + possibleExePaths.each { + if ((new File(it)).exists()) { + return it + } + if ((new File(it + ".exe")).exists()) { + return it + ".exe" + } } // next up try a packaged version of sentry-cli From 61b90d64436c17b701581c71352121aac3f82fa1 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 28 Dec 2017 12:42:08 -0600 Subject: [PATCH 1833/2152] Update CHANGES. --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index f5676133715..5ede533ca1c 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 1.6.4 ------------- - Add web-fragment.xml to prevent classpath scanning. +- Update Android Gradle plugin to use new NPM package if available. Version 1.6.3 ------------- From bb058dd473e5c61a5cbea04ed4864e4ee73afa9f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 28 Dec 2017 12:45:13 -0600 Subject: [PATCH 1834/2152] Bump docs to 1.6.4 --- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index bfd68ed3702..d2a2187d509 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.6.3`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.6.4`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.6.3' + compile 'io.sentry:sentry-android:1.6.4' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.6.3' + classpath 'io.sentry:sentry-android-gradle-plugin:1.6.4' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 3b4209635dd..6ab93bf591b 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.6.3 + 1.6.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.6.3' + compile 'io.sentry:sentry-appengine:1.6.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.3" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 5f6f2bc7ef5..4aab900211c 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.6.3 + 1.6.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.3' + compile 'io.sentry:sentry:1.6.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.3" + libraryDependencies += "io.sentry" % "sentry" % "1.6.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 9021546f7fd..1e376bb6bfc 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.6.3 + 1.6.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.6.3' + compile 'io.sentry:sentry-log4j:1.6.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.3" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 029651b8e76..98bb8eefc7d 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.6.3 + 1.6.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.6.3' + compile 'io.sentry:sentry-log4j2:1.6.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.3" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index ea8fdb649bd..4bd9199349e 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.6.3 + 1.6.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.6.3' + compile 'io.sentry:sentry-logback:1.6.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.3" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index a280a016d83..d01baa0ea27 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.6.3 + 1.6.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.6.3' + compile 'io.sentry:sentry-spring:1.6.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.3" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index 83112f279f9..5ffe97c7ff6 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.6.3 + 1.6.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.3' + compile 'io.sentry:sentry:1.6.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.3" + libraryDependencies += "io.sentry" % "sentry" % "1.6.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From c9c74724018b417ca6aae514164350074ab759b0 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 28 Dec 2017 12:46:18 -0600 Subject: [PATCH 1835/2152] [maven-release-plugin] prepare release v1.6.4 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 45d3821f0e6..851d0e56bd5 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.4-SNAPSHOT + 1.6.4 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.6.4 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 4045790ff51..51b470981a4 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.4-SNAPSHOT + 1.6.4 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index b3c702fc5ce..6cbc92bcd9e 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.4-SNAPSHOT + 1.6.4 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index c66c0a74581..59c9723ee2a 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.4-SNAPSHOT + 1.6.4 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index dee5f4fa710..7c268fc5eea 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.4-SNAPSHOT + 1.6.4 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 98ee991e3b1..4b47666a0f8 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.4-SNAPSHOT + 1.6.4 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index da31edac740..1bd4564e9b9 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.4-SNAPSHOT + 1.6.4 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 5e90c6de3ad..1633b14cb35 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.4-SNAPSHOT + 1.6.4 sentry From bc4d4d95da415ed8c8fe26c22c08f57f8f8daa11 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 28 Dec 2017 12:46:18 -0600 Subject: [PATCH 1836/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 851d0e56bd5..f3cbbebbc1e 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.4 + 1.6.5-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.6.4 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 51b470981a4..0668fe6cfbe 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.4 + 1.6.5-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 6cbc92bcd9e..ba362b78fac 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.4 + 1.6.5-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 59c9723ee2a..44a8996913c 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.4 + 1.6.5-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 7c268fc5eea..b9d0bc4d83c 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.4 + 1.6.5-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 4b47666a0f8..d873fbfbcbf 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.4 + 1.6.5-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 1bd4564e9b9..c415dbeb4a3 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.4 + 1.6.5-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 1633b14cb35..703776297c5 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.4 + 1.6.5-SNAPSHOT sentry From 61ff62a295d8aefe4e940b11c10d5db98bde6fff Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 28 Dec 2017 12:46:20 -0600 Subject: [PATCH 1837/2152] Bump CHANGES to 1.6.5 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 5ede533ca1c..5e2583eb84a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.6.5 +------------- + +- + Version 1.6.4 ------------- From fc4cb2de8c53a3d24189f8c34fdc82574ce81626 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 28 Dec 2017 12:51:47 -0600 Subject: [PATCH 1838/2152] Bump sentry-cli. --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 4ced8caeb9f..1d00e10915b 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.22.0 +VERSION=1.26.1 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* From e74aff411319c962e0ff63ace0f201982f5aab16 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 28 Dec 2017 12:54:34 -0600 Subject: [PATCH 1839/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 90c151b776f..a1722e3f5e0 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.6.4-SNAPSHOT +version = 1.6.5-SNAPSHOT From c856b762047fe00115000a8d76b027e5157be950 Mon Sep 17 00:00:00 2001 From: Edward McFarlane Date: Mon, 15 Jan 2018 16:54:46 +0000 Subject: [PATCH 1840/2152] Set default dsn when null or empty. (#546) --- sentry/src/main/java/io/sentry/dsn/Dsn.java | 5 +++-- sentry/src/test/java/io/sentry/dsn/DsnTest.java | 11 +++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/sentry/src/main/java/io/sentry/dsn/Dsn.java b/sentry/src/main/java/io/sentry/dsn/Dsn.java index ac5dbdbc82b..9664cebb97c 100644 --- a/sentry/src/main/java/io/sentry/dsn/Dsn.java +++ b/sentry/src/main/java/io/sentry/dsn/Dsn.java @@ -1,6 +1,7 @@ package io.sentry.dsn; import io.sentry.config.Lookup; +import io.sentry.util.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -79,12 +80,12 @@ public Dsn(URI dsn) throws InvalidDsnException { public static String dsnLookup() { String dsn = Lookup.lookup("dsn"); - if (dsn == null) { + if (Util.isNullOrEmpty(dsn)) { // check if the user accidentally set "dns" instead of "dsn" dsn = Lookup.lookup("dns"); } - if (dsn == null) { + if (Util.isNullOrEmpty(dsn)) { logger.warn("*** Couldn't find a suitable DSN, Sentry operations will do nothing!" + " See documentation: https://docs.sentry.io/clients/java/ ***"); dsn = DEFAULT_DSN; diff --git a/sentry/src/test/java/io/sentry/dsn/DsnTest.java b/sentry/src/test/java/io/sentry/dsn/DsnTest.java index 292a7f791b3..ee9415bb8f6 100644 --- a/sentry/src/test/java/io/sentry/dsn/DsnTest.java +++ b/sentry/src/test/java/io/sentry/dsn/DsnTest.java @@ -116,6 +116,17 @@ public void testDsnLookupWithEnvironmentVariable(@Mocked("getenv") final System assertThat(Dsn.dsnLookup(), is(dsn)); } + @Test + public void testDsnLookupWithEmptyEnvironmentVariable(@Mocked("getenv") final System system) throws Exception { + final String dsn = ""; + new NonStrictExpectations() {{ + System.getenv("SENTRY_DSN"); + result = dsn; + }}; + + assertThat(Dsn.dsnLookup(), is(Dsn.DEFAULT_DSN)); + } + @Test(expectedExceptions = InvalidDsnException.class) public void testMissingSecretKeyInvalid() throws Exception { new Dsn("http://publicKey:@host/9"); From 194fd2e2378a39ce2e7f5162ec69c0f19966e347 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 24 Jan 2018 15:20:03 -0600 Subject: [PATCH 1841/2152] Implement Serializable for DebugImage. --- .../java/io/sentry/event/interfaces/DebugMetaInterface.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java b/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java index c5ebffda907..2d17f7eb185 100644 --- a/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/DebugMetaInterface.java @@ -1,5 +1,6 @@ package io.sentry.event.interfaces; +import java.io.Serializable; import java.util.ArrayList; /** @@ -45,7 +46,7 @@ public String toString() { /** * Object that represents a single debug image. */ - public static class DebugImage { + public static class DebugImage implements Serializable { private static final String DEFAULT_TYPE = "proguard"; private final String uuid; private final String type; From 5aa95e75f3c4c260c37a517fbc66a3d4939f8a53 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 24 Jan 2018 15:21:37 -0600 Subject: [PATCH 1842/2152] Update CHANGES. --- CHANGES | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 5e2583eb84a..17a08a52a87 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,8 @@ Version 1.6.5 ------------- -- +- Implemented Serializable for DebugImage, fixes buffering with Proguard symbols. +- Use default DSN when empty string is provided. (thanks afking) Version 1.6.4 ------------- From 1bad2b76818cdb3c6a9eebb1631ac808f4d6681f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 24 Jan 2018 15:26:31 -0600 Subject: [PATCH 1843/2152] Bump docs to 1.6.5 --- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index d2a2187d509..a9a71b2254a 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.6.4`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.6.5`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.6.4' + compile 'io.sentry:sentry-android:1.6.5' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.6.4' + classpath 'io.sentry:sentry-android-gradle-plugin:1.6.5' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 6ab93bf591b..1376d4ae3bf 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.6.4 + 1.6.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.6.4' + compile 'io.sentry:sentry-appengine:1.6.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.4" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 4aab900211c..3924363c57b 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.6.4 + 1.6.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.4' + compile 'io.sentry:sentry:1.6.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.4" + libraryDependencies += "io.sentry" % "sentry" % "1.6.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 1e376bb6bfc..3e3546b2483 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.6.4 + 1.6.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.6.4' + compile 'io.sentry:sentry-log4j:1.6.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.4" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 98bb8eefc7d..8ffb95b639d 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.6.4 + 1.6.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.6.4' + compile 'io.sentry:sentry-log4j2:1.6.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.4" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 4bd9199349e..2ce04f5b09d 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.6.4 + 1.6.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.6.4' + compile 'io.sentry:sentry-logback:1.6.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.4" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index d01baa0ea27..1c114ce7c78 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.6.4 + 1.6.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.6.4' + compile 'io.sentry:sentry-spring:1.6.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.4" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index 5ffe97c7ff6..ace58d4adbe 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.6.4 + 1.6.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.4' + compile 'io.sentry:sentry:1.6.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.4" + libraryDependencies += "io.sentry" % "sentry" % "1.6.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 8f222a758a7e3314a5f52a722c455abced60a7de Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 24 Jan 2018 15:28:31 -0600 Subject: [PATCH 1844/2152] [maven-release-plugin] prepare release v1.6.5 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index f3cbbebbc1e..37ea73aa0c9 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.5-SNAPSHOT + 1.6.5 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.6.5 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 0668fe6cfbe..de282813cfb 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.5-SNAPSHOT + 1.6.5 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index ba362b78fac..40f5fad0f1d 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.5-SNAPSHOT + 1.6.5 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 44a8996913c..0f351ed15d0 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.5-SNAPSHOT + 1.6.5 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index b9d0bc4d83c..ac7999a84dc 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.5-SNAPSHOT + 1.6.5 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index d873fbfbcbf..91f76ee9d28 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.5-SNAPSHOT + 1.6.5 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index c415dbeb4a3..b5656e06b37 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.5-SNAPSHOT + 1.6.5 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 703776297c5..e26bc225f1b 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.5-SNAPSHOT + 1.6.5 sentry From b8b58ed8b0a38359cc21bea1243c1f7e835e4c13 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 24 Jan 2018 15:28:31 -0600 Subject: [PATCH 1845/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 37ea73aa0c9..dad5aa99477 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.5 + 1.6.6-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.6.5 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index de282813cfb..541c68ff4c7 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.5 + 1.6.6-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 40f5fad0f1d..06b8d893d54 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.5 + 1.6.6-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 0f351ed15d0..60583d110d5 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.5 + 1.6.6-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index ac7999a84dc..31a6adcf3dc 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.5 + 1.6.6-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 91f76ee9d28..d0928ee685b 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.5 + 1.6.6-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index b5656e06b37..0b5cbf90387 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.5 + 1.6.6-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index e26bc225f1b..26421993d46 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.5 + 1.6.6-SNAPSHOT sentry From 34928dcdbacf686163b6314950070f18bfd4ec33 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 24 Jan 2018 15:28:34 -0600 Subject: [PATCH 1846/2152] Bump CHANGES to 1.6.6 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 17a08a52a87..68ebf994ddd 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.6.6 +------------- + +- + Version 1.6.5 ------------- From ba4016e8b2118213c898e3d15b5f9eaf4d0aee10 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 24 Jan 2018 15:33:25 -0600 Subject: [PATCH 1847/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 1d00e10915b..2cbb79b95b0 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.26.1 +VERSION=1.28.2 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index a1722e3f5e0..a53588ebdab 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.6.5-SNAPSHOT +version = 1.6.6-SNAPSHOT From 212488e98efe132b792913659be23b34582f9f07 Mon Sep 17 00:00:00 2001 From: Olivier Bourgain Date: Thu, 25 Jan 2018 18:58:57 +0100 Subject: [PATCH 1848/2152] Unregister shutdown hooks on close. (#550) --- .../io/sentry/connection/AsyncConnection.java | 2 ++ .../sentry/connection/BufferedConnection.java | 2 ++ sentry/src/main/java/io/sentry/util/Util.java | 19 +++++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/sentry/src/main/java/io/sentry/connection/AsyncConnection.java b/sentry/src/main/java/io/sentry/connection/AsyncConnection.java index 2f01b6ff53b..76f8f3c5503 100644 --- a/sentry/src/main/java/io/sentry/connection/AsyncConnection.java +++ b/sentry/src/main/java/io/sentry/connection/AsyncConnection.java @@ -3,6 +3,7 @@ import io.sentry.SentryClient; import io.sentry.environment.SentryEnvironment; import io.sentry.event.Event; +import io.sentry.util.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -110,6 +111,7 @@ public void addEventSendCallback(EventSendCallback eventSendCallback) { @Override public void close() throws IOException { if (gracefulShutdown) { + Util.safelyRemoveShutdownHook(shutDownHook); shutDownHook.enabled = false; } diff --git a/sentry/src/main/java/io/sentry/connection/BufferedConnection.java b/sentry/src/main/java/io/sentry/connection/BufferedConnection.java index 3a0a8be2069..2102bb05f63 100644 --- a/sentry/src/main/java/io/sentry/connection/BufferedConnection.java +++ b/sentry/src/main/java/io/sentry/connection/BufferedConnection.java @@ -3,6 +3,7 @@ import io.sentry.buffer.Buffer; import io.sentry.environment.SentryEnvironment; import io.sentry.event.Event; +import io.sentry.util.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -114,6 +115,7 @@ public void addEventSendCallback(EventSendCallback eventSendCallback) { @SuppressWarnings("checkstyle:magicnumber") public void close() throws IOException { if (gracefulShutdown) { + Util.safelyRemoveShutdownHook(shutDownHook); shutDownHook.enabled = false; } diff --git a/sentry/src/main/java/io/sentry/util/Util.java b/sentry/src/main/java/io/sentry/util/Util.java index 3487693baaa..750cb70362f 100644 --- a/sentry/src/main/java/io/sentry/util/Util.java +++ b/sentry/src/main/java/io/sentry/util/Util.java @@ -154,4 +154,23 @@ public static String trimString(String string, int maxMessageLength) { } } + /** + * Try to remove the shutDownHook, handling the case where the VM is in shutdown process. + * @param shutDownHook the shutDownHook to remove + * @return true if the hook was removed, false otherwise + */ + public static boolean safelyRemoveShutdownHook(Thread shutDownHook) { + try { + return Runtime.getRuntime().removeShutdownHook(shutDownHook); + } catch (IllegalStateException e) { + // CHECKSTYLE.OFF: EmptyBlock + if (e.getMessage().equals("Shutdown in progress")) { + // ignore + } else { + throw e; + } + // CHECKSTYLE.ON: EmptyBlock + } + return false; + } } From d7a2b55f0026159ed7fdc3328ab5d89cc2a9e90a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 25 Jan 2018 12:03:03 -0600 Subject: [PATCH 1849/2152] Update CHANGES. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 68ebf994ddd..ee951fddc5c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.6.6 ------------- -- +- Attempt to unregister async/buffer shutdown hooks when connection is closed. (thanks obourgain) Version 1.6.5 ------------- From 6d8e3c44cf908bb28e9ffea52849418f347642ce Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 29 Jan 2018 08:58:33 -0600 Subject: [PATCH 1850/2152] Bump docs to 1.6.6 --- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index a9a71b2254a..cebe323f219 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.6.5`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.6.6`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.6.5' + compile 'io.sentry:sentry-android:1.6.6' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.6.5' + classpath 'io.sentry:sentry-android-gradle-plugin:1.6.6' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 1376d4ae3bf..1c6fe94d48e 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.6.5 + 1.6.6 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.6.5' + compile 'io.sentry:sentry-appengine:1.6.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.5" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 3924363c57b..20275ae952a 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.6.5 + 1.6.6 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.5' + compile 'io.sentry:sentry:1.6.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.5" + libraryDependencies += "io.sentry" % "sentry" % "1.6.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 3e3546b2483..19f5f9e7f03 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.6.5 + 1.6.6 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.6.5' + compile 'io.sentry:sentry-log4j:1.6.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.5" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 8ffb95b639d..45b0ddba0d1 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.6.5 + 1.6.6 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.6.5' + compile 'io.sentry:sentry-log4j2:1.6.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.5" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 2ce04f5b09d..c63658cbce5 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.6.5 + 1.6.6 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.6.5' + compile 'io.sentry:sentry-logback:1.6.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.5" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index 1c114ce7c78..334f447eb52 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.6.5 + 1.6.6 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.6.5' + compile 'io.sentry:sentry-spring:1.6.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.5" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index ace58d4adbe..e5eaeacfc5a 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.6.5 + 1.6.6 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.5' + compile 'io.sentry:sentry:1.6.6' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.5" + libraryDependencies += "io.sentry" % "sentry" % "1.6.6" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 6382ed77bcfaedd6afcfc00c5006fad33ce55c19 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 29 Jan 2018 08:59:35 -0600 Subject: [PATCH 1851/2152] [maven-release-plugin] prepare release v1.6.6 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index dad5aa99477..cbf69af13af 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.6-SNAPSHOT + 1.6.6 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.6.6 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 541c68ff4c7..23bbf0157af 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.6-SNAPSHOT + 1.6.6 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 06b8d893d54..e41cf9af708 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.6-SNAPSHOT + 1.6.6 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 60583d110d5..56ad3b25afc 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.6-SNAPSHOT + 1.6.6 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 31a6adcf3dc..2eaaa6c3c9f 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.6-SNAPSHOT + 1.6.6 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index d0928ee685b..376e770ad7b 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.6-SNAPSHOT + 1.6.6 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 0b5cbf90387..c2ad6ba7679 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.6-SNAPSHOT + 1.6.6 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 26421993d46..ec9638a173f 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.6-SNAPSHOT + 1.6.6 sentry From 677e0450d2ae2e231bf077c74da5cf45e53d968b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 29 Jan 2018 08:59:36 -0600 Subject: [PATCH 1852/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index cbf69af13af..529dbcecd09 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.6 + 1.6.7-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.6.6 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 23bbf0157af..7625b8580ae 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.6 + 1.6.7-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index e41cf9af708..fbc146c92ee 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.6 + 1.6.7-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 56ad3b25afc..fce516abc9c 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.6 + 1.6.7-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 2eaaa6c3c9f..24edd4b8c8e 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.6 + 1.6.7-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 376e770ad7b..93174cced08 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.6 + 1.6.7-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index c2ad6ba7679..33d704b9454 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.6 + 1.6.7-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index ec9638a173f..a2e5c1d7484 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.6 + 1.6.7-SNAPSHOT sentry From 64809865d6b6b99e6581bde326e249d633ea7f31 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 29 Jan 2018 08:59:38 -0600 Subject: [PATCH 1853/2152] Bump CHANGES to 1.6.7 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index ee951fddc5c..2a464a57de5 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.6.7 +------------- + +- + Version 1.6.6 ------------- From edd51e525258047501431c69c18e8cdb22e55f50 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 29 Jan 2018 09:04:21 -0600 Subject: [PATCH 1854/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 2cbb79b95b0..f84988317d0 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.28.2 +VERSION=1.28.4 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index a53588ebdab..20d382fc91c 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.6.6-SNAPSHOT +version = 1.6.7 From ca68cb9b117c1bcbc4dd6a13f6afb1581628180a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 1 Feb 2018 08:55:35 -0600 Subject: [PATCH 1855/2152] Add Automatic-Module-Name to manifest so we have basic Java 9 support. (#551) --- CHANGES | 2 +- sentry-android/pom.xml | 3 +++ sentry-log4j/pom.xml | 3 +++ sentry-log4j2/pom.xml | 3 +++ sentry-logback/pom.xml | 3 +++ sentry-spring/pom.xml | 3 +++ sentry/pom.xml | 3 +++ 7 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 2a464a57de5..42e8bfbc240 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.6.7 ------------- -- +- Add Automatic-Module-Name to manifest so we have basic Java 9 support. Version 1.6.6 ------------- diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 7625b8580ae..a5085439803 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -108,6 +108,9 @@ ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + io.sentry.android + diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index fce516abc9c..605a555d351 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -112,6 +112,9 @@ ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + io.sentry.log4j + diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 24edd4b8c8e..6f2ffa49a59 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -117,6 +117,9 @@ ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + io.sentry.log4j2 + diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 93174cced08..35a8d25f542 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -110,6 +110,9 @@ ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + io.sentry.logback + diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 33d704b9454..f2ce1bf70cb 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -115,6 +115,9 @@ ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + io.sentry.spring + diff --git a/sentry/pom.xml b/sentry/pom.xml index a2e5c1d7484..39b6a560d25 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -138,6 +138,9 @@ ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + io.sentry + From 11240d8cfd53f129b85a4a5466c83ef42f2502bb Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 1 Feb 2018 09:24:47 -0600 Subject: [PATCH 1856/2152] Print unhandled exceptions to stderr by default (like ThreadGroup does). --- CHANGES | 1 + .../main/java/io/sentry/SentryUncaughtExceptionHandler.java | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGES b/CHANGES index 42e8bfbc240..699242c90c5 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 1.6.7 ------------- - Add Automatic-Module-Name to manifest so we have basic Java 9 support. +- Print unhandled exceptions to stderr by default (like ThreadGroup does). Version 1.6.6 ------------- diff --git a/sentry/src/main/java/io/sentry/SentryUncaughtExceptionHandler.java b/sentry/src/main/java/io/sentry/SentryUncaughtExceptionHandler.java index 298d1352a79..868f5446e4e 100644 --- a/sentry/src/main/java/io/sentry/SentryUncaughtExceptionHandler.java +++ b/sentry/src/main/java/io/sentry/SentryUncaughtExceptionHandler.java @@ -58,9 +58,15 @@ public void uncaughtException(Thread thread, Throwable thrown) { } } + // taken from ThreadGroup#uncaughtException if (defaultExceptionHandler != null) { // call the original handler defaultExceptionHandler.uncaughtException(thread, thrown); + } else if (!(thrown instanceof ThreadDeath)) { + // CHECKSTYLE.OFF: RegexpSinglelineJava + System.err.print("Exception in thread \"" + thread.getName() + "\" "); + thrown.printStackTrace(System.err); + // CHECKSTYLE.ON: RegexpSinglelineJava } } From 2351089d5420d0ff30cc0f831eeadd77ec7e3f74 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 1 Feb 2018 09:32:27 -0600 Subject: [PATCH 1857/2152] Bump docs to 1.6.7 --- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index cebe323f219..abdfbf204ee 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.6.6`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.6.7`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.6.6' + compile 'io.sentry:sentry-android:1.6.7' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.6.6' + classpath 'io.sentry:sentry-android-gradle-plugin:1.6.7' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 1c6fe94d48e..3711b048c89 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.6.6 + 1.6.7 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.6.6' + compile 'io.sentry:sentry-appengine:1.6.7' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.6" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.7" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 20275ae952a..70bc0fb6a3e 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.6.6 + 1.6.7 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.6' + compile 'io.sentry:sentry:1.6.7' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.6" + libraryDependencies += "io.sentry" % "sentry" % "1.6.7" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 19f5f9e7f03..bd8e16080c2 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.6.6 + 1.6.7 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.6.6' + compile 'io.sentry:sentry-log4j:1.6.7' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.6" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.7" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 45b0ddba0d1..adbee8edbc8 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.6.6 + 1.6.7 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.6.6' + compile 'io.sentry:sentry-log4j2:1.6.7' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.6" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.7" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index c63658cbce5..6badb2b5a21 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.6.6 + 1.6.7 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.6.6' + compile 'io.sentry:sentry-logback:1.6.7' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.6" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.7" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index 334f447eb52..8715f9138cb 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.6.6 + 1.6.7 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.6.6' + compile 'io.sentry:sentry-spring:1.6.7' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.6" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.7" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index e5eaeacfc5a..5f3f656d289 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.6.6 + 1.6.7 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.6' + compile 'io.sentry:sentry:1.6.7' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.6" + libraryDependencies += "io.sentry" % "sentry" % "1.6.7" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From a5ab053fe84dbeeb072474ef9f727a9dc1cde81f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 1 Feb 2018 09:33:27 -0600 Subject: [PATCH 1858/2152] [maven-release-plugin] prepare release v1.6.7 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 529dbcecd09..c472621f5e2 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.7-SNAPSHOT + 1.6.7 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.6.7 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index a5085439803..40588e474e1 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.7-SNAPSHOT + 1.6.7 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index fbc146c92ee..949122ac6c7 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.7-SNAPSHOT + 1.6.7 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 605a555d351..fa1adbfc356 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.7-SNAPSHOT + 1.6.7 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 6f2ffa49a59..4becc2fc2bc 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.7-SNAPSHOT + 1.6.7 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 35a8d25f542..82608941e5b 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.7-SNAPSHOT + 1.6.7 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index f2ce1bf70cb..518a4cbc1ff 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.7-SNAPSHOT + 1.6.7 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 39b6a560d25..fc2f0bf072c 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.7-SNAPSHOT + 1.6.7 sentry From e1dc85ae0143fff8cb444c1665f8eed5a8944c7f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 1 Feb 2018 09:33:28 -0600 Subject: [PATCH 1859/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index c472621f5e2..0a44e91d519 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.7 + 1.6.8-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.6.7 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 40588e474e1..3e07c9c2e2c 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.7 + 1.6.8-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 949122ac6c7..2b753fa7a6e 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.7 + 1.6.8-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index fa1adbfc356..b701871f7e3 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.7 + 1.6.8-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 4becc2fc2bc..62957cdab91 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.7 + 1.6.8-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 82608941e5b..0525bb0130f 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.7 + 1.6.8-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 518a4cbc1ff..a560f3d26eb 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.7 + 1.6.8-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index fc2f0bf072c..ca2007f1ff4 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.7 + 1.6.8-SNAPSHOT sentry From 2129d50dca19eaba0b2f7f4c3a85d0406fcc24b8 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 1 Feb 2018 09:33:30 -0600 Subject: [PATCH 1860/2152] Bump CHANGES to 1.6.8 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 699242c90c5..1c2689176c8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.6.8 +------------- + +- + Version 1.6.7 ------------- From 97fc170d0eec16906e56262cfedd075f0153fa72 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 1 Feb 2018 09:44:09 -0600 Subject: [PATCH 1861/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 20d382fc91c..6e45de6abc7 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.6.7 +version = 1.6.8-SNAPSHOT From d5569c73f3b6d6165c6cea5f76661cb26e9b3365 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 9 Feb 2018 08:44:08 -0600 Subject: [PATCH 1862/2152] Use project.name instead of hardcoding 'app' in Android Gradle Plugin. (#555) --- .../groovy/io/sentry/android/gradle/SentryPlugin.groovy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 8de3a400bcc..45d7fc76039 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -132,7 +132,7 @@ class SentryPlugin implements Plugin { * @return */ static String getDebugMetaPropPath(Project project, ApplicationVariant variant) { - return "${project.rootDir.toPath()}/app/build/intermediates/assets/${variant.dirName}/sentry-debug-meta.properties" + return "${project.rootDir.toPath()}/${project.name}/build/intermediates/assets/${variant.dirName}/sentry-debug-meta.properties" } void apply(Project project) { @@ -198,9 +198,9 @@ class SentryPlugin implements Plugin { def flavorName = variant.flavorName def propName = "sentry.properties" def possibleProps = [ - "${project.rootDir.toPath()}/app/src/${variantName}/${propName}", - "${project.rootDir.toPath()}/app/src/${variantName}/${flavorName}/${propName}", - "${project.rootDir.toPath()}/app/src/${flavorName}/${variantName}/${propName}", + "${project.rootDir.toPath()}/${project.name}/src/${variantName}/${propName}", + "${project.rootDir.toPath()}/${project.name}/src/${variantName}/${flavorName}/${propName}", + "${project.rootDir.toPath()}/${project.name}/src/${flavorName}/${variantName}/${propName}", "${project.rootDir.toPath()}/src/${variantName}/${propName}", "${project.rootDir.toPath()}/src/${variantName}/${flavorName}/${propName}", "${project.rootDir.toPath()}/src/${flavorName}/${variantName}/${propName}", From b6bb08103d80d0afd294b9ec2d91aaa2121e584b Mon Sep 17 00:00:00 2001 From: Dave McLain Date: Fri, 9 Feb 2018 14:35:00 -0600 Subject: [PATCH 1863/2152] Create a noop sentry client when the user provides bad configuration (#556) --- .../io/sentry/DefaultSentryClientFactory.java | 27 +++++++++++-------- .../DefaultSentryClientFactoryTest.java | 14 ++++++++-- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index 399f5a48407..e38ec68bf55 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -232,19 +232,24 @@ public class DefaultSentryClientFactory extends SentryClientFactory { @Override public SentryClient createSentryClient(Dsn dsn) { - SentryClient sentryClient = new SentryClient(createConnection(dsn), getContextManager(dsn)); try { - // `ServletRequestListener` was added in the Servlet 2.4 API, and - // is used as part of the `HttpEventBuilderHelper`, see: - // https://tomcat.apache.org/tomcat-5.5-doc/servletapi/ - Class.forName("javax.servlet.ServletRequestListener", false, this.getClass().getClassLoader()); - sentryClient.addBuilderHelper(new HttpEventBuilderHelper()); - } catch (ClassNotFoundException e) { - logger.debug("The current environment doesn't provide access to servlets," - + " or provides an unsupported version."); + SentryClient sentryClient = new SentryClient(createConnection(dsn), getContextManager(dsn)); + try { + // `ServletRequestListener` was added in the Servlet 2.4 API, and + // is used as part of the `HttpEventBuilderHelper`, see: + // https://tomcat.apache.org/tomcat-5.5-doc/servletapi/ + Class.forName("javax.servlet.ServletRequestListener", false, this.getClass().getClassLoader()); + sentryClient.addBuilderHelper(new HttpEventBuilderHelper()); + } catch (ClassNotFoundException e) { + logger.debug("The current environment doesn't provide access to servlets," + + " or provides an unsupported version."); + } + sentryClient.addBuilderHelper(new ContextBuilderHelper(sentryClient)); + return configureSentryClient(sentryClient, dsn); + } catch (Exception e) { + logger.error("Failed to initialize sentry, falling back to no-op client", e); + return new SentryClient(new NoopConnection(), new ThreadLocalContextManager()); } - sentryClient.addBuilderHelper(new ContextBuilderHelper(sentryClient)); - return configureSentryClient(sentryClient, dsn); } /** diff --git a/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java b/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java index f6817a7ee6b..36914baec67 100644 --- a/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java +++ b/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java @@ -5,8 +5,9 @@ import java.util.*; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; + +import io.sentry.connection.NoopConnection; public class DefaultSentryClientFactoryTest extends BaseTest { @Test @@ -43,7 +44,16 @@ public void testFieldsFromDsn() throws Exception { extrasMap.put("red", "blue"); extrasMap.put("green", "yellow"); assertThat(sentryClient.getExtra(), is(extrasMap)); + } + @Test + public void testBadDataInitializesWithoutException() { + String badTags = "foo:"; + + String dsn = String.format("https://user:pass@example.com/1?tags=%s", badTags); + SentryClient sentryClient = DefaultSentryClientFactory.sentryClient(dsn); + assertThat(sentryClient, isA(SentryClient.class)); + assertThat(sentryClient.getContext(), notNullValue()); } } From c6c3e22898150e0508b676f26baaeccae1ff9b70 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 12 Feb 2018 10:13:35 -0600 Subject: [PATCH 1864/2152] Update CHANGES. --- CHANGES | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 1c2689176c8..9e68f4e4ccd 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,8 @@ Version 1.6.8 ------------- -- +- Create a noop sentry client when the user provides bad configuration. (thanks dmclain) +- Use project.name instead of hardcoding 'app' in Android Gradle Plugin. Version 1.6.7 ------------- From 2152883dc3257de543e1193356781f9a58d23e3b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 12 Feb 2018 10:14:28 -0600 Subject: [PATCH 1865/2152] Bump docs to 1.6.8 --- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index abdfbf204ee..050ec56229c 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.6.7`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.6.8`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.6.7' + compile 'io.sentry:sentry-android:1.6.8' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.6.7' + classpath 'io.sentry:sentry-android-gradle-plugin:1.6.8' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 3711b048c89..04fcef5f22a 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.6.7 + 1.6.8 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.6.7' + compile 'io.sentry:sentry-appengine:1.6.8' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.7" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.8" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 70bc0fb6a3e..8811ad30053 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.6.7 + 1.6.8 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.7' + compile 'io.sentry:sentry:1.6.8' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.7" + libraryDependencies += "io.sentry" % "sentry" % "1.6.8" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index bd8e16080c2..20e52b45ad7 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.6.7 + 1.6.8 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.6.7' + compile 'io.sentry:sentry-log4j:1.6.8' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.7" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.8" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index adbee8edbc8..b17c7967913 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.6.7 + 1.6.8 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.6.7' + compile 'io.sentry:sentry-log4j2:1.6.8' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.7" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.8" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 6badb2b5a21..efa8523901b 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.6.7 + 1.6.8 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.6.7' + compile 'io.sentry:sentry-logback:1.6.8' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.7" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.8" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index 8715f9138cb..0b3a235a809 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.6.7 + 1.6.8 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.6.7' + compile 'io.sentry:sentry-spring:1.6.8' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.7" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.8" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index 5f3f656d289..f71749b335c 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.6.7 + 1.6.8 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.7' + compile 'io.sentry:sentry:1.6.8' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.7" + libraryDependencies += "io.sentry" % "sentry" % "1.6.8" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 1c0bbe181f8c5f4253184f601e5c83430d455d66 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 12 Feb 2018 10:15:27 -0600 Subject: [PATCH 1866/2152] [maven-release-plugin] prepare release v1.6.8 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 0a44e91d519..f91566770b0 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.8-SNAPSHOT + 1.6.8 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.6.8 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 3e07c9c2e2c..3984f6592ed 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.8-SNAPSHOT + 1.6.8 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 2b753fa7a6e..995185c2afc 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.8-SNAPSHOT + 1.6.8 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index b701871f7e3..3774e63b110 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.8-SNAPSHOT + 1.6.8 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 62957cdab91..b7a3fee819f 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.8-SNAPSHOT + 1.6.8 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 0525bb0130f..abba9916a4b 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.8-SNAPSHOT + 1.6.8 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index a560f3d26eb..8e6ef8a6478 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.8-SNAPSHOT + 1.6.8 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index ca2007f1ff4..5fbbd90f4df 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.8-SNAPSHOT + 1.6.8 sentry From 71ec1c3b7246a18793c399f8a1f5074e310ea9ff Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 12 Feb 2018 10:15:27 -0600 Subject: [PATCH 1867/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index f91566770b0..b584e182b7b 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.8 + 1.6.9-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.6.8 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 3984f6592ed..ae8eacae6c6 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.8 + 1.6.9-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 995185c2afc..02f261485a3 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.8 + 1.6.9-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 3774e63b110..e241a0301eb 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.8 + 1.6.9-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index b7a3fee819f..e67d31cef8c 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.8 + 1.6.9-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index abba9916a4b..70017a66581 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.8 + 1.6.9-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 8e6ef8a6478..2260b20a972 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.8 + 1.6.9-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 5fbbd90f4df..8efec6f1242 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.8 + 1.6.9-SNAPSHOT sentry From 6d6c5183cb056bd841a7bfe7ae296be16fdf4eb5 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 12 Feb 2018 10:15:29 -0600 Subject: [PATCH 1868/2152] Bump CHANGES to 1.6.9 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 9e68f4e4ccd..9e33f88f997 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.6.9 +------------- + +- + Version 1.6.8 ------------- From f57a8e7020527ff077cff4400faabae631560289 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 12 Feb 2018 10:25:25 -0600 Subject: [PATCH 1869/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 6e45de6abc7..4d13bb9bdec 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.6.8-SNAPSHOT +version = 1.6.9-SNAPSHOT From 5291aefe2ce0fda5c88fa4a11a502a00ddb187a8 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 23 Feb 2018 10:51:48 -0600 Subject: [PATCH 1870/2152] =?UTF-8?q?Log=20backoff=20responses=20(HTTP=204?= =?UTF-8?q?29=20Too=20Many=20Requests)=20as=20a=20warning=20to=20th?= =?UTF-8?q?=E2=80=A6=20(#560)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Log backoff responses (HTTP 429 Too Many Requests) as a warning to the `lockdown` logger instead of an error. * Add TooManyRequestsException so we can special case the logging of throttling events. * Renamed some LockdownManager methods for clarity. * Return a boolean from LockdownManager.lockdown so the caller knows if they initiated the lockdown and should log (once). * Fix parsing of Retry-After header which apparently changed to a floating point number. Fixes GH-559 * Fix CHANGES. * Clarify comment. --- CHANGES | 5 ++- .../src/main/java/io/sentry/SentryClient.java | 5 ++- .../sentry/connection/AbstractConnection.java | 17 +++++--- .../io/sentry/connection/AsyncConnection.java | 4 +- .../io/sentry/connection/HttpConnection.java | 39 ++++++++++++------- .../io/sentry/connection/LockdownManager.java | 9 +++-- .../connection/LockedDownException.java | 9 ----- .../connection/TooManyRequestsException.java | 22 +++++++++++ .../sentry/connection/HttpConnectionTest.java | 4 +- 9 files changed, 76 insertions(+), 38 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/connection/TooManyRequestsException.java diff --git a/CHANGES b/CHANGES index 9e33f88f997..e884c39be9c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,10 @@ Version 1.6.9 ------------- -- +- Warn (once) when a client lockdown is first initiated and why. +- Log events skipped by a lockdown at the DEBUG level. +- Don't log a full stacktrace when an HTTP 429 (Too Many Requests) is returned. +- Fix parsing of Retry-After header which apparently changed to a floating point number. Version 1.6.8 ------------- diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 6eabe40cdea..b9d510af93d 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -3,6 +3,7 @@ import io.sentry.connection.Connection; import io.sentry.connection.EventSendCallback; import io.sentry.connection.LockedDownException; +import io.sentry.connection.TooManyRequestsException; import io.sentry.context.Context; import io.sentry.context.ContextManager; import io.sentry.event.Event; @@ -132,8 +133,8 @@ public void sendEvent(Event event) { try { connection.send(event); - } catch (LockedDownException e) { - lockdownLogger.warn("The connection to Sentry is currently locked down.", e); + } catch (LockedDownException | TooManyRequestsException e) { + logger.debug("Dropping an Event due to lockdown: " + event); } catch (Exception e) { logger.error("An exception occurred while sending the event to Sentry.", e); } finally { diff --git a/sentry/src/main/java/io/sentry/connection/AbstractConnection.java b/sentry/src/main/java/io/sentry/connection/AbstractConnection.java index dfdd82499a1..353d9e3c45d 100644 --- a/sentry/src/main/java/io/sentry/connection/AbstractConnection.java +++ b/sentry/src/main/java/io/sentry/connection/AbstractConnection.java @@ -21,6 +21,10 @@ public abstract class AbstractConnection implements Connection { */ public static final String SENTRY_PROTOCOL_VERSION = "6"; private static final Logger logger = LoggerFactory.getLogger(AbstractConnection.class); + // CHECKSTYLE.OFF: ConstantName + private static final Logger lockdownLogger = + LoggerFactory.getLogger(AbstractConnection.class.getName() + ".lockdown"); + // CHECKSTYLE.ON: ConstantName /** * Value of the X-Sentry-Auth header. */ @@ -65,11 +69,14 @@ public final void send(Event event) throws ConnectionException { important in, for example, a BufferedConnection where the Event would be deleted from the Buffer if an exception isn't raised in the call to send. */ - throw new LockedDownException("Dropping an Event due to lockdown: " + event); + throw new LockedDownException(); } doSend(event); + // success! re-open the floodgates + lockdownManager.unlock(); + for (EventSendCallback eventSendCallback : eventSendCallbacks) { try { eventSendCallback.onSuccess(event); @@ -78,8 +85,6 @@ public final void send(Event event) throws ConnectionException { + eventSendCallback.getClass().getName(), exc); } } - - lockdownManager.resetState(); } catch (ConnectionException e) { for (EventSendCallback eventSendCallback : eventSendCallbacks) { try { @@ -90,8 +95,10 @@ public final void send(Event event) throws ConnectionException { } } - logger.warn("An exception due to the connection occurred, a lockdown will be initiated.", e); - lockdownManager.setState(e); + boolean lockedDown = lockdownManager.lockdown(e); + if (lockedDown) { + lockdownLogger.warn("Initiated a temporary lockdown because of exception: " + e.getMessage()); + } throw e; } diff --git a/sentry/src/main/java/io/sentry/connection/AsyncConnection.java b/sentry/src/main/java/io/sentry/connection/AsyncConnection.java index 76f8f3c5503..a109409794b 100644 --- a/sentry/src/main/java/io/sentry/connection/AsyncConnection.java +++ b/sentry/src/main/java/io/sentry/connection/AsyncConnection.java @@ -171,8 +171,8 @@ public void run() { try { // The current thread is managed by sentry actualConnection.send(event); - } catch (LockedDownException e) { - lockdownLogger.warn("The connection to Sentry is currently locked down.", e); + } catch (LockedDownException | TooManyRequestsException e) { + logger.debug("Dropping an Event due to lockdown: " + event); } catch (Exception e) { logger.error("An exception occurred while sending the event to Sentry.", e); } finally { diff --git a/sentry/src/main/java/io/sentry/connection/HttpConnection.java b/sentry/src/main/java/io/sentry/connection/HttpConnection.java index efe2d521415..baacffceca0 100644 --- a/sentry/src/main/java/io/sentry/connection/HttpConnection.java +++ b/sentry/src/main/java/io/sentry/connection/HttpConnection.java @@ -34,6 +34,10 @@ public class HttpConnection extends AbstractConnection { * HTTP Header for the authentication to Sentry. */ private static final String SENTRY_AUTH = "X-Sentry-Auth"; + /** + * HTTP code `429 Too Many Requests`, which is not included in HttpURLConnection. + */ + private static final int HTTP_TOO_MANY_REQUESTS = 429; /** * Default timeout of an HTTP connection to Sentry. */ @@ -156,11 +160,32 @@ protected void doSend(Event event) throws ConnectionException { outputStream.close(); connection.getInputStream().close(); } catch (IOException e) { + Long retryAfterMs = null; + String retryAfterHeader = connection.getHeaderField("Retry-After"); + if (retryAfterHeader != null) { + // CHECKSTYLE.OFF: EmptyCatchBlock + try { + // CHECKSTYLE.OFF: MagicNumber + retryAfterMs = (long) (Double.parseDouble(retryAfterHeader) * 1000L); // seconds -> milliseconds + // CHECKSTYLE.ON: MagicNumber + } catch (NumberFormatException nfe) { + // noop, use default retry + } + // CHECKSTYLE.ON: EmptyCatchBlock + } + try { int responseCode = connection.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_FORBIDDEN) { logger.debug("Event '" + event.getId() + "' was rejected by the Sentry server due to a filter."); return; + } else if (responseCode == HTTP_TOO_MANY_REQUESTS) { + /* + If the response is a 429 we rethrow as a TooManyRequestsException so that we can + avoid logging this is an error. + */ + throw new TooManyRequestsException( + "Too many requests to Sentry: https://docs.sentry.io/learn/quotas/", e, retryAfterMs); } } catch (IOException responseCodeException) { // pass @@ -175,20 +200,6 @@ protected void doSend(Event event) throws ConnectionException { errorMessage = "An exception occurred while submitting the event to the Sentry server."; } - Long retryAfterMs = null; - String retryAfterHeader = connection.getHeaderField("Retry-After"); - if (retryAfterHeader != null) { - // CHECKSTYLE.OFF: EmptyCatchBlock - try { - // CHECKSTYLE.OFF: MagicNumber - retryAfterMs = Long.parseLong(retryAfterHeader) * 1000L; // seconds -> milliseconds - // CHECKSTYLE.ON: MagicNumber - } catch (NumberFormatException nfe) { - // noop, use default retry - } - // CHECKSTYLE.ON: EmptyCatchBlock - } - throw new ConnectionException(errorMessage, e, retryAfterMs); } finally { connection.disconnect(); diff --git a/sentry/src/main/java/io/sentry/connection/LockdownManager.java b/sentry/src/main/java/io/sentry/connection/LockdownManager.java index 2f414b582b6..fa4d68ec795 100644 --- a/sentry/src/main/java/io/sentry/connection/LockdownManager.java +++ b/sentry/src/main/java/io/sentry/connection/LockdownManager.java @@ -70,7 +70,7 @@ public synchronized boolean isLockedDown() { /** * Reset the lockdown state, disabling lockdown and setting the backoff time to zero. */ - public synchronized void resetState() { + public synchronized void unlock() { lockdownTime = 0; lockdownStartTime = null; } @@ -81,11 +81,12 @@ public synchronized void resetState() { * * @param connectionException ConnectionException to check for a recommended * lockdown time, may be null + * @return whether or not this call actually locked the system down */ - public synchronized void setState(ConnectionException connectionException) { + public synchronized boolean lockdown(ConnectionException connectionException) { // If we are already in a lockdown state, don't change anything if (isLockedDown()) { - return; + return false; } if (connectionException != null && connectionException.getRecommendedLockdownTime() != null) { @@ -98,6 +99,8 @@ public synchronized void setState(ConnectionException connectionException) { lockdownTime = Math.min(maxLockdownTime, lockdownTime); lockdownStartTime = clock.date(); + + return true; } public synchronized void setBaseLockdownTime(long baseLockdownTime) { diff --git a/sentry/src/main/java/io/sentry/connection/LockedDownException.java b/sentry/src/main/java/io/sentry/connection/LockedDownException.java index 41f661db578..39d9bc5eb1e 100644 --- a/sentry/src/main/java/io/sentry/connection/LockedDownException.java +++ b/sentry/src/main/java/io/sentry/connection/LockedDownException.java @@ -5,13 +5,4 @@ */ public class LockedDownException extends RuntimeException { - /** - * Construct a LockedDownException with a message. - * - * @param message Exception message. - */ - public LockedDownException(String message) { - super(message); - } - } diff --git a/sentry/src/main/java/io/sentry/connection/TooManyRequestsException.java b/sentry/src/main/java/io/sentry/connection/TooManyRequestsException.java new file mode 100644 index 00000000000..562341a1cf6 --- /dev/null +++ b/sentry/src/main/java/io/sentry/connection/TooManyRequestsException.java @@ -0,0 +1,22 @@ +package io.sentry.connection; + +/** + * Exception thrown when attempting to send Events while in a lockdown. + */ +public class TooManyRequestsException extends ConnectionException { + + //CHECKSTYLE.OFF: JavadocMethod + public TooManyRequestsException(String message) { + super(message); + } + + public TooManyRequestsException(String message, Throwable cause) { + super(message, cause); + } + + public TooManyRequestsException(String message, Throwable cause, Long recommendedLockdownTime) { + super(message, cause, recommendedLockdownTime); + } + //CHECKSTYLE.ON: JavadocMethod + +} diff --git a/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java b/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java index 0a107e235e4..ba3bcb57926 100644 --- a/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java @@ -163,14 +163,14 @@ public void testRetryAfterHeader(@Injectable final Event mockEvent) throws Excep mockUrlConnection.getErrorStream(); result = new ByteArrayInputStream(httpErrorMessage.getBytes()); mockUrlConnection.getHeaderField("Retry-After"); - result = "12345"; + result = "12345.25"; }}; try { httpConnection.doSend(mockEvent); fail(); } catch (ConnectionException e) { - assertThat(e.getRecommendedLockdownTime(), is(12345L * 1000L)); + assertThat(e.getRecommendedLockdownTime(), is(12345250L)); } } From 563904d2f0f7a5f48c03210d0e1c447e9320c178 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 23 Feb 2018 10:52:28 -0600 Subject: [PATCH 1871/2152] Pass MDC context down to AsyncConnection worker threads. (#558) Fixes GH-557 --- CHANGES | 1 + .../io/sentry/connection/AsyncConnection.java | 22 +++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index e884c39be9c..71f3b2f2d90 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,7 @@ Version 1.6.9 - Log events skipped by a lockdown at the DEBUG level. - Don't log a full stacktrace when an HTTP 429 (Too Many Requests) is returned. - Fix parsing of Retry-After header which apparently changed to a floating point number. +- Pass MDC context down to AsyncConnection worker threads. Version 1.6.8 ------------- diff --git a/sentry/src/main/java/io/sentry/connection/AsyncConnection.java b/sentry/src/main/java/io/sentry/connection/AsyncConnection.java index a109409794b..8e40b4b6268 100644 --- a/sentry/src/main/java/io/sentry/connection/AsyncConnection.java +++ b/sentry/src/main/java/io/sentry/connection/AsyncConnection.java @@ -6,9 +6,11 @@ import io.sentry.util.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.MDC; import java.io.IOException; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -91,7 +93,7 @@ private void addShutdownHook() { @Override public void send(Event event) { if (!closed) { - executorService.execute(new EventSubmitter(event)); + executorService.execute(new EventSubmitter(event, MDC.getCopyOfContextMap())); } } @@ -160,14 +162,24 @@ private void doClose() throws IOException { */ private final class EventSubmitter implements Runnable { private final Event event; + private Map mdcContext; - private EventSubmitter(Event event) { + private EventSubmitter(Event event, Map mdcContext) { this.event = event; + this.mdcContext = mdcContext; } @Override public void run() { SentryEnvironment.startManagingThread(); + + Map previous = MDC.getCopyOfContextMap(); + if (mdcContext == null) { + MDC.clear(); + } else { + MDC.setContextMap(mdcContext); + } + try { // The current thread is managed by sentry actualConnection.send(event); @@ -176,6 +188,12 @@ public void run() { } catch (Exception e) { logger.error("An exception occurred while sending the event to Sentry.", e); } finally { + if (previous == null) { + MDC.clear(); + } else { + MDC.setContextMap(previous); + } + SentryEnvironment.stopManagingThread(); } } From 21cd593fc90d256921297fd8bdccd20aa891c30b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 23 Feb 2018 13:43:28 -0600 Subject: [PATCH 1872/2152] Changed buffer code to only retry in the case of HTTP 429 or (#562) connectivity issues. --- CHANGES | 3 + docs/config.rst | 2 +- .../io/sentry/DefaultSentryClientFactory.java | 4 +- .../sentry/connection/BufferedConnection.java | 19 ++++- .../connection/ConnectionException.java | 20 +++--- .../io/sentry/connection/HttpConnection.java | 16 +++-- .../connection/TooManyRequestsException.java | 16 ++--- .../connection/AbstractConnectionTest.java | 2 +- .../connection/BufferedConnectionTest.java | 72 +++++++++++++++---- 9 files changed, 111 insertions(+), 43 deletions(-) diff --git a/CHANGES b/CHANGES index 71f3b2f2d90..6915374b3be 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,9 @@ Version 1.6.9 - Don't log a full stacktrace when an HTTP 429 (Too Many Requests) is returned. - Fix parsing of Retry-After header which apparently changed to a floating point number. - Pass MDC context down to AsyncConnection worker threads. +- Changed `buffer.size` default from 50 to 10. +- When buffering is enabled, only retry sending events when the network is down or the project + is being throttled (HTTP 429). Version 1.6.8 ------------- diff --git a/docs/config.rst b/docs/config.rst index 6adb99dd1c5..b2f8d71418e 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -315,7 +315,7 @@ Sentry always requires write permission on the buffer directory itself. buffer.dir=sentry-events -The maximum number of events that will be stored on disk defaults to 50, +The maximum number of events that will be stored on disk defaults to 10, but can also be configured with the option buffer.size``: :: diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index e38ec68bf55..f5a13834b70 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -71,9 +71,9 @@ public class DefaultSentryClientFactory extends SentryClientFactory { */ public static final String BUFFER_SIZE_OPTION = "buffer.size"; /** - * Default number of events to cache offline when network is down. + * Default number of events to cache offline when network is down or project is throttled. */ - public static final int BUFFER_SIZE_DEFAULT = 50; + public static final int BUFFER_SIZE_DEFAULT = 10; /** * Option for how long to wait between attempts to flush the disk buffer, in milliseconds. */ diff --git a/sentry/src/main/java/io/sentry/connection/BufferedConnection.java b/sentry/src/main/java/io/sentry/connection/BufferedConnection.java index 2102bb05f63..d904c36b0fe 100644 --- a/sentry/src/main/java/io/sentry/connection/BufferedConnection.java +++ b/sentry/src/main/java/io/sentry/connection/BufferedConnection.java @@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory; import java.io.IOException; +import java.io.NotSerializableException; import java.util.Iterator; import java.util.List; import java.util.concurrent.Executors; @@ -100,7 +101,23 @@ public BufferedConnection(Connection actualConnection, Buffer buffer, long flush @Override public void send(Event event) { - actualConnection.send(event); + try { + actualConnection.send(event); + } catch (ConnectionException e) { + boolean notSerializable = e.getCause() instanceof NotSerializableException; + + Integer responseCode = e.getResponseCode(); + if (notSerializable || (responseCode != null && responseCode != HttpConnection.HTTP_TOO_MANY_REQUESTS)) { + // don't retry events (discard from the buffer) if: + // 1. they aren't serializable + // 2. the connection is up (valid response code was returned) and it's not an HTTP 429 + buffer.discard(event); + } + + // throw regardless + throw e; + } + // success, remove the event from the buffer buffer.discard(event); diff --git a/sentry/src/main/java/io/sentry/connection/ConnectionException.java b/sentry/src/main/java/io/sentry/connection/ConnectionException.java index d56fd865ce4..4191c4c2b91 100644 --- a/sentry/src/main/java/io/sentry/connection/ConnectionException.java +++ b/sentry/src/main/java/io/sentry/connection/ConnectionException.java @@ -11,30 +11,32 @@ public class ConnectionException extends RuntimeException { */ private Long recommendedLockdownTime = null; + /** + * HTTP response status code, if available. + */ + private Integer responseCode = null; + //CHECKSTYLE.OFF: JavadocMethod public ConnectionException() { } - public ConnectionException(String message) { - super(message); - } - public ConnectionException(String message, Throwable cause) { super(message, cause); } - public ConnectionException(String message, Throwable cause, Long recommendedLockdownTime) { + public ConnectionException(String message, Throwable cause, Long recommendedLockdownTime, Integer responseCode) { super(message, cause); this.recommendedLockdownTime = recommendedLockdownTime; - } - - public ConnectionException(Throwable cause) { - super(cause); + this.responseCode = responseCode; } public Long getRecommendedLockdownTime() { return recommendedLockdownTime; } + + public Integer getResponseCode() { + return responseCode; + } //CHECKSTYLE.ON: JavadocMethod } diff --git a/sentry/src/main/java/io/sentry/connection/HttpConnection.java b/sentry/src/main/java/io/sentry/connection/HttpConnection.java index baacffceca0..4aaccfaf1d8 100644 --- a/sentry/src/main/java/io/sentry/connection/HttpConnection.java +++ b/sentry/src/main/java/io/sentry/connection/HttpConnection.java @@ -24,6 +24,10 @@ * It is possible to enable the "naive mode" to allow a connection over SSL using a certificate with a wildcard. */ public class HttpConnection extends AbstractConnection { + /** + * HTTP code `429 Too Many Requests`, which is not included in HttpURLConnection. + */ + public static final int HTTP_TOO_MANY_REQUESTS = 429; private static final Charset UTF_8 = Charset.forName("UTF-8"); private static final Logger logger = LoggerFactory.getLogger(HttpConnection.class); /** @@ -34,10 +38,6 @@ public class HttpConnection extends AbstractConnection { * HTTP Header for the authentication to Sentry. */ private static final String SENTRY_AUTH = "X-Sentry-Auth"; - /** - * HTTP code `429 Too Many Requests`, which is not included in HttpURLConnection. - */ - private static final int HTTP_TOO_MANY_REQUESTS = 429; /** * Default timeout of an HTTP connection to Sentry. */ @@ -174,8 +174,9 @@ protected void doSend(Event event) throws ConnectionException { // CHECKSTYLE.ON: EmptyCatchBlock } + Integer responseCode = null; try { - int responseCode = connection.getResponseCode(); + responseCode = connection.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_FORBIDDEN) { logger.debug("Event '" + event.getId() + "' was rejected by the Sentry server due to a filter."); return; @@ -185,7 +186,8 @@ protected void doSend(Event event) throws ConnectionException { avoid logging this is an error. */ throw new TooManyRequestsException( - "Too many requests to Sentry: https://docs.sentry.io/learn/quotas/", e, retryAfterMs); + "Too many requests to Sentry: https://docs.sentry.io/learn/quotas/", + e, retryAfterMs, responseCode); } } catch (IOException responseCodeException) { // pass @@ -200,7 +202,7 @@ protected void doSend(Event event) throws ConnectionException { errorMessage = "An exception occurred while submitting the event to the Sentry server."; } - throw new ConnectionException(errorMessage, e, retryAfterMs); + throw new ConnectionException(errorMessage, e, retryAfterMs, responseCode); } finally { connection.disconnect(); } diff --git a/sentry/src/main/java/io/sentry/connection/TooManyRequestsException.java b/sentry/src/main/java/io/sentry/connection/TooManyRequestsException.java index 562341a1cf6..27b0759d871 100644 --- a/sentry/src/main/java/io/sentry/connection/TooManyRequestsException.java +++ b/sentry/src/main/java/io/sentry/connection/TooManyRequestsException.java @@ -6,16 +6,12 @@ public class TooManyRequestsException extends ConnectionException { //CHECKSTYLE.OFF: JavadocMethod - public TooManyRequestsException(String message) { - super(message); - } - - public TooManyRequestsException(String message, Throwable cause) { - super(message, cause); - } - - public TooManyRequestsException(String message, Throwable cause, Long recommendedLockdownTime) { - super(message, cause, recommendedLockdownTime); + public TooManyRequestsException( + String message, + Throwable cause, + Long recommendedLockdownTime, + Integer responseCode) { + super(message, cause, recommendedLockdownTime, responseCode); } //CHECKSTYLE.ON: JavadocMethod diff --git a/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java b/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java index 9387c267957..1d5ac9b8983 100644 --- a/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java @@ -202,7 +202,7 @@ public void testRecommendedLockdownRespected(@Injectable final Event mockEvent) final long recommendedLockdownWaitTime = 12345L; new NonStrictExpectations() {{ abstractConnection.doSend((Event) any); - result = new ConnectionException("Message", null, recommendedLockdownWaitTime); + result = new ConnectionException("Message", null, recommendedLockdownWaitTime, HttpConnection.HTTP_TOO_MANY_REQUESTS); }}; try { diff --git a/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java b/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java index bc404501eca..46d8a6fa08f 100644 --- a/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java @@ -12,10 +12,9 @@ import org.testng.collections.Sets; import java.io.IOException; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Set; +import java.io.NotSerializableException; +import java.net.HttpURLConnection; +import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; @@ -34,22 +33,22 @@ public class BufferedConnectionTest extends BaseTest { private Buffer mockBuffer; private Connection mockConnection; private Connection bufferedConnection; - private volatile boolean connectionUp; + private ConnectionException connectionException; @BeforeMethod public void setup() { bufferedEvents = Sets.newHashSet(); sentEvents = Lists.newArrayList(); - connectionUp = true; + connectionException = null; mockConnection = new AbstractConnection("public", "private") { @Override protected void doSend(Event event) throws ConnectionException { - if (connectionUp) { - sentEvents.add(event); - } else { - throw new ConnectionException("Connection is down."); + if (connectionException != null) { + throw connectionException; } + + sentEvents.add(event); } @Override @@ -99,7 +98,7 @@ public void test() throws Exception { setField(mockConnection, "lockdownManager", lockdownManager); Event event = new EventBuilder().build(); - connectionUp = false; + connectionException = new ConnectionException(); try { bufferedConnection.send(event); } catch (Exception e) { @@ -120,7 +119,7 @@ public void test() throws Exception { // End the lockdown fixedClock.tick(LockdownManager.DEFAULT_MAX_LOCKDOWN_TIME, TimeUnit.MILLISECONDS); - connectionUp = true; + connectionException = null; waitUntilTrue(1000, new Callable() { @Override public Boolean call() throws Exception { @@ -131,4 +130,53 @@ public Boolean call() throws Exception { assertThat(sentEvents.contains(event), is(true)); assertThat(sentEvents.contains(event2), is(true)); } + + @Test + public void testNotSerializableNotBuffered() throws Exception { + Event event = new EventBuilder().build(); + connectionException = new ConnectionException("NonSerializable", new NotSerializableException()); + try { + bufferedConnection.send(event); + } catch (Exception e) { + + } + assertThat(bufferedEvents.size(), equalTo(0)); + } + + @Test + public void test500NotBuffered() throws Exception { + Event event = new EventBuilder().build(); + connectionException = new ConnectionException("500", new IOException(), null, HttpURLConnection.HTTP_INTERNAL_ERROR); + try { + bufferedConnection.send(event); + } catch (Exception e) { + + } + assertThat(bufferedEvents.size(), equalTo(0)); + } + + @Test + public void test429IsBuffered() throws Exception { + Event event = new EventBuilder().build(); + connectionException = new ConnectionException("429", new IOException(), null, HttpConnection.HTTP_TOO_MANY_REQUESTS); + try { + bufferedConnection.send(event); + } catch (Exception e) { + + } + assertThat(bufferedEvents.size(), equalTo(1)); + } + + @Test + public void testNoResponseCodeIsBuffered() throws Exception { + Event event = new EventBuilder().build(); + connectionException = new ConnectionException("NoResponseCode", new IOException(), null, null); + try { + bufferedConnection.send(event); + } catch (Exception e) { + + } + assertThat(bufferedEvents.size(), equalTo(1)); + } + } From e439975a2392120ee311ef56e1f4b630866e95fc Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 23 Feb 2018 14:59:08 -0600 Subject: [PATCH 1873/2152] Update version in CHANGES. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 6915374b3be..df97b192574 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,4 @@ -Version 1.6.9 +Version 1.7.0 ------------- - Warn (once) when a client lockdown is first initiated and why. From 42b0850163dd647506008c24e88af71d9a2c9e9c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 23 Feb 2018 14:59:13 -0600 Subject: [PATCH 1874/2152] Bump docs to 1.6.9 --- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 050ec56229c..b23c883fabe 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.6.8`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.6.9`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.6.8' + compile 'io.sentry:sentry-android:1.6.9' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.6.8' + classpath 'io.sentry:sentry-android-gradle-plugin:1.6.9' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 04fcef5f22a..f5ad3672688 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.6.8 + 1.6.9 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.6.8' + compile 'io.sentry:sentry-appengine:1.6.9' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.8" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.9" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 8811ad30053..1ae3739f76e 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.6.8 + 1.6.9 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.8' + compile 'io.sentry:sentry:1.6.9' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.8" + libraryDependencies += "io.sentry" % "sentry" % "1.6.9" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 20e52b45ad7..76bf5cd2156 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.6.8 + 1.6.9 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.6.8' + compile 'io.sentry:sentry-log4j:1.6.9' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.8" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.9" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index b17c7967913..b066c8ca9a8 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.6.8 + 1.6.9 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.6.8' + compile 'io.sentry:sentry-log4j2:1.6.9' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.8" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.9" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index efa8523901b..65b06b8dd75 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.6.8 + 1.6.9 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.6.8' + compile 'io.sentry:sentry-logback:1.6.9' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.8" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.9" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index 0b3a235a809..eca6a0c326c 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.6.8 + 1.6.9 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.6.8' + compile 'io.sentry:sentry-spring:1.6.9' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.8" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.9" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index f71749b335c..3ccde2896c7 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.6.8 + 1.6.9 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.8' + compile 'io.sentry:sentry:1.6.9' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.8" + libraryDependencies += "io.sentry" % "sentry" % "1.6.9" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 36011401cef9045c123f9ba906da3285e34e2e75 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 23 Feb 2018 15:00:23 -0600 Subject: [PATCH 1875/2152] [maven-release-plugin] prepare release v1.7.0 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index b584e182b7b..00ab4a6f022 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.6.9-SNAPSHOT + 1.7.0 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.0 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index ae8eacae6c6..b35ba16bba5 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.9-SNAPSHOT + 1.7.0 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 02f261485a3..106eee64504 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.6.9-SNAPSHOT + 1.7.0 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index e241a0301eb..bf7ba8f9f33 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.9-SNAPSHOT + 1.7.0 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index e67d31cef8c..99698deef04 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.9-SNAPSHOT + 1.7.0 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 70017a66581..35b875806b0 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.9-SNAPSHOT + 1.7.0 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 2260b20a972..4f8abc12be7 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.9-SNAPSHOT + 1.7.0 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 8efec6f1242..341ffc4a941 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.6.9-SNAPSHOT + 1.7.0 sentry From 9b7b6d4281855332a3665dbb84d6a830440bdfe7 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 23 Feb 2018 15:00:23 -0600 Subject: [PATCH 1876/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 00ab4a6f022..be6ac5a3d11 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.0 + 1.7.1-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.0 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index b35ba16bba5..8a55f86f114 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.0 + 1.7.1-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 106eee64504..9441c6d28c8 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.0 + 1.7.1-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index bf7ba8f9f33..05be8a979e6 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.0 + 1.7.1-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 99698deef04..fe25fe2cf9a 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.0 + 1.7.1-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 35b875806b0..e02fc89b9e2 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.0 + 1.7.1-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 4f8abc12be7..a5ccde5a0ed 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.0 + 1.7.1-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 341ffc4a941..6833fedd8d6 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.0 + 1.7.1-SNAPSHOT sentry From 6df8a2005d869c6f3df096f3822ca071f5001102 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 23 Feb 2018 15:00:25 -0600 Subject: [PATCH 1877/2152] Bump CHANGES to 1.7.1 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index df97b192574..3d70be2b528 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.1 +------------- + +- + Version 1.7.0 ------------- From e260297cc5ea13920b0f60f1377bc4e862fb61e4 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 23 Feb 2018 15:05:32 -0600 Subject: [PATCH 1878/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 4d13bb9bdec..533f8226578 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.6.9-SNAPSHOT +version = 1.7.1-SNAPSHOT From f0c30c9bd841e615dd490eff0aba7c5fc1859cd9 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 26 Feb 2018 11:38:52 -0600 Subject: [PATCH 1879/2152] Fix version in docs. --- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/modules/android.rst b/docs/modules/android.rst index b23c883fabe..8dce25d0aa7 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.6.9`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.7.0`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.6.9' + compile 'io.sentry:sentry-android:1.7.0' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.6.9' + classpath 'io.sentry:sentry-android-gradle-plugin:1.7.0' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index f5ad3672688..ab06fe04def 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.6.9 + 1.7.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.6.9' + compile 'io.sentry:sentry-appengine:1.7.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.6.9" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.7.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 1ae3739f76e..60e0bf0f508 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.6.9 + 1.7.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.9' + compile 'io.sentry:sentry:1.7.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.9" + libraryDependencies += "io.sentry" % "sentry" % "1.7.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 76bf5cd2156..0f082b433cf 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.6.9 + 1.7.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.6.9' + compile 'io.sentry:sentry-log4j:1.7.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.6.9" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.7.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index b066c8ca9a8..f3bf0610fe3 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.6.9 + 1.7.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.6.9' + compile 'io.sentry:sentry-log4j2:1.7.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.6.9" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.7.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 65b06b8dd75..ce71fd58522 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.6.9 + 1.7.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.6.9' + compile 'io.sentry:sentry-logback:1.7.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.6.9" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.7.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index eca6a0c326c..15df91c1f05 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -22,22 +22,22 @@ Using Maven: io.sentry sentry-spring - 1.6.9 + 1.7.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.6.9' + compile 'io.sentry:sentry-spring:1.7.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.6.9" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.7.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index 3ccde2896c7..a591d6286a0 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.6.9 + 1.7.0 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.6.9' + compile 'io.sentry:sentry:1.7.0' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.6.9" + libraryDependencies += "io.sentry" % "sentry" % "1.7.0" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 5219772b9951af7a3ea8431e2e6403fc221bf2af Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 28 Feb 2018 15:37:54 -0800 Subject: [PATCH 1880/2152] Documentation updates. --- docs/config.rst | 26 +++++++++++++++----------- docs/modules/spring.rst | 19 ++++++++++++------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index b2f8d71418e..a7acb35f25d 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -279,16 +279,6 @@ Similar behaviour is enabled by default in Sentry. To disable it, use the stacktrace.hidecommon=false -Uncaught Exception Handler -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -By default, an ``UncaughtExceptionHandler`` is configured that will send exceptions -to Sentry. To disable it, use the ``uncaught.handler.enabled`` option. - -:: - - uncaught.handler.enabled=false - Event Sampling ~~~~~~~~~~~~~~ @@ -302,6 +292,19 @@ This option takes a number from 0.0 to 1.0, representing the percent of events to allow through to server (from 0% to 100%). By default all events will be sent to the Sentry server. +Uncaught Exception Handler +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, an ``UncaughtExceptionHandler`` is configured that will attempt to +send exceptions to Sentry. To disable it, use the ``uncaught.handler.enabled`` +option. Note that exceptions are sent asynchronously by default, and there is +no guarantee they will be sent before the JVM exits. This option is best used +in conjunction with the disk buffering system described below. + +:: + + uncaught.handler.enabled=false + Buffering Events to Disk ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -309,7 +312,8 @@ Sentry can be configured to write events to a specified directory on disk anytime communication with the Sentry server fails with the buffer.dir`` option. If the directory doesn't exist, Sentry will attempt to create it on startup and may therefore need write permission on the parent directory. -Sentry always requires write permission on the buffer directory itself. +Sentry always requires write permission on the buffer directory itself. This +is enabled by default if the ``AndroidSentryClientFactory`` is used. :: diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index 15df91c1f05..cf73c02aad8 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -12,6 +12,18 @@ events. The source can be found `on Github `_. +Important Note About Logging Integrations +----------------------------------------- + +**Note** that you should **not** configure the ``sentry-spring`` +alongside one of the Sentry logging integrations (such as ``sentry-logback``), +or you will most likely double-report exceptions. + +A Sentry logging integration is more general and will capture errors (and +possibly warnings, depending on your configuration) that occur inside *or outside* +of a Spring controller. In most scenarios, using one of the logging integrations +instead of ``sentry-spring`` is preferred. + Installation ------------ @@ -53,13 +65,6 @@ In order to record all exceptions thrown by your controllers, you can register registered, all exceptions will be sent to Sentry and then passed on to the default exception handlers. -**Note** that you should **not** configure the ``SentryExceptionResolver`` -alongside a logging integration (such as ``sentry-logback``), or you will most -likely double-report exceptions. You should use one or the other depending on -your needs. A logging integration is more general and will capture errors (and -possibly warnings, depending on your configuration) that occur inside *or outside* -of a Spring controller. - Configuration via ``web.xml``: .. sourcecode:: xml From f5198d0f74d100305e94ced0c116468716afa429 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 28 Feb 2018 16:10:18 -0800 Subject: [PATCH 1881/2152] Grammar. --- docs/modules/spring.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index cf73c02aad8..7b500d8b1a9 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -15,8 +15,8 @@ The source can be found `on Github Important Note About Logging Integrations ----------------------------------------- -**Note** that you should **not** configure the ``sentry-spring`` -alongside one of the Sentry logging integrations (such as ``sentry-logback``), +**Note** that you should **not** configure ``sentry-spring`` +alongside a Sentry logging integration (such as ``sentry-logback``), or you will most likely double-report exceptions. A Sentry logging integration is more general and will capture errors (and @@ -107,4 +107,4 @@ Or via a configuration class: return new io.sentry.spring.SentryServletContextInitializer(); } -After that, your Sentry events should contain information such as HTTP request headers. \ No newline at end of file +After that, your Sentry events should contain information such as HTTP request headers. From 23e8a24f53d421e5c17594ae73b46aeca16594e4 Mon Sep 17 00:00:00 2001 From: ruZZ Date: Thu, 8 Mar 2018 18:37:49 +0200 Subject: [PATCH 1882/2152] sentry-android-gradle-plugin: improve various directory structure support (#563) --- .../groovy/io/sentry/android/gradle/SentryPlugin.groovy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 45d7fc76039..a77080eecdc 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -132,7 +132,7 @@ class SentryPlugin implements Plugin { * @return */ static String getDebugMetaPropPath(Project project, ApplicationVariant variant) { - return "${project.rootDir.toPath()}/${project.name}/build/intermediates/assets/${variant.dirName}/sentry-debug-meta.properties" + return "${project.buildDir}/intermediates/assets/${variant.dirName}/sentry-debug-meta.properties" } void apply(Project project) { @@ -198,9 +198,9 @@ class SentryPlugin implements Plugin { def flavorName = variant.flavorName def propName = "sentry.properties" def possibleProps = [ - "${project.rootDir.toPath()}/${project.name}/src/${variantName}/${propName}", - "${project.rootDir.toPath()}/${project.name}/src/${variantName}/${flavorName}/${propName}", - "${project.rootDir.toPath()}/${project.name}/src/${flavorName}/${variantName}/${propName}", + "${project.projectDir}/src/${variantName}/${propName}", + "${project.projectDir}/src/${variantName}/${flavorName}/${propName}", + "${project.projectDir}/src/${flavorName}/${variantName}/${propName}", "${project.rootDir.toPath()}/src/${variantName}/${propName}", "${project.rootDir.toPath()}/src/${variantName}/${flavorName}/${propName}", "${project.rootDir.toPath()}/src/${flavorName}/${variantName}/${propName}", From d9d0555815e84c1ac205dd9adaf50bd9e5d48e36 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 8 Mar 2018 10:38:53 -0600 Subject: [PATCH 1883/2152] Update CHANGES. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 3d70be2b528..7cb8cd460f2 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.7.1 ------------- -- +- Android Gradle Plugin: Improve various directory structure support. (thanks ruZZil) Version 1.7.0 ------------- From 249b2ccdd983a197f4e4bb44abb0ebea706c8c6d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 8 Mar 2018 11:03:29 -0600 Subject: [PATCH 1884/2152] Bump docs to 1.7.1 --- CONTRIBUTING.md | 2 +- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 013abbd1533..74b5d739508 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ We love pull requests from everyone. -The test suite currently requires you run JDK version `1.7.0`. +The test suite currently requires you run JDK version `1.7.1`. See [#487](https://github.com/getsentry/sentry-java/issues/478) for more information. diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 8dce25d0aa7..8225e90d973 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.7.0`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.7.1`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.7.0' + compile 'io.sentry:sentry-android:1.7.1' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.7.0' + classpath 'io.sentry:sentry-android-gradle-plugin:1.7.1' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index ab06fe04def..05e8e2a475e 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.7.0 + 1.7.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.7.0' + compile 'io.sentry:sentry-appengine:1.7.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.7.0" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.7.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 60e0bf0f508..d7e1b34df46 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.7.0 + 1.7.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.7.0' + compile 'io.sentry:sentry:1.7.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.7.0" + libraryDependencies += "io.sentry" % "sentry" % "1.7.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 0f082b433cf..9470e55b8e8 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.7.0 + 1.7.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.7.0' + compile 'io.sentry:sentry-log4j:1.7.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.7.0" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.7.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index f3bf0610fe3..53c38f0ee40 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.7.0 + 1.7.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.7.0' + compile 'io.sentry:sentry-log4j2:1.7.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.7.0" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.7.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index ce71fd58522..7d85a71913c 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.7.0 + 1.7.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.7.0' + compile 'io.sentry:sentry-logback:1.7.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.7.0" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.7.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index 7b500d8b1a9..410c0ab3f5a 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -34,22 +34,22 @@ Using Maven: io.sentry sentry-spring - 1.7.0 + 1.7.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.7.0' + compile 'io.sentry:sentry-spring:1.7.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.7.0" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.7.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index a591d6286a0..1a5ef109ad5 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.7.0 + 1.7.1 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.7.0' + compile 'io.sentry:sentry:1.7.1' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.7.0" + libraryDependencies += "io.sentry" % "sentry" % "1.7.1" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From be59bfbf76dbb9ae0a2add663709d6e2fd65b79a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 8 Mar 2018 11:04:42 -0600 Subject: [PATCH 1885/2152] [maven-release-plugin] prepare release v1.7.1 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index be6ac5a3d11..1f4d25c15a5 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.1-SNAPSHOT + 1.7.1 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.1 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 8a55f86f114..885f22bad9b 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.1-SNAPSHOT + 1.7.1 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 9441c6d28c8..c25f131842c 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.1-SNAPSHOT + 1.7.1 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 05be8a979e6..4920dbef03c 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.1-SNAPSHOT + 1.7.1 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index fe25fe2cf9a..da79c053bbd 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.1-SNAPSHOT + 1.7.1 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index e02fc89b9e2..1d981cff1d9 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.1-SNAPSHOT + 1.7.1 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index a5ccde5a0ed..b8742c523d0 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.1-SNAPSHOT + 1.7.1 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 6833fedd8d6..01706d99500 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.1-SNAPSHOT + 1.7.1 sentry From faf8a72a2af3bf55eaf8c7406198bde8e4f00a0e Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 8 Mar 2018 11:04:42 -0600 Subject: [PATCH 1886/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 1f4d25c15a5..e80d19b993b 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.1 + 1.7.2-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.1 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 885f22bad9b..a855e864742 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.1 + 1.7.2-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index c25f131842c..8bb697a1055 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.1 + 1.7.2-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 4920dbef03c..40d92d355c0 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.1 + 1.7.2-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index da79c053bbd..ea7ecb89251 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.1 + 1.7.2-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 1d981cff1d9..0f84e4719ad 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.1 + 1.7.2-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index b8742c523d0..5326762e94e 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.1 + 1.7.2-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 01706d99500..e929750c831 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.1 + 1.7.2-SNAPSHOT sentry From 4575323f51368745cf88583d7bd21390da4c104b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 8 Mar 2018 11:04:44 -0600 Subject: [PATCH 1887/2152] Bump CHANGES to 1.7.2 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 7cb8cd460f2..6a60262a3b9 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.2 +------------- + +- + Version 1.7.1 ------------- From 3ac681b92e95bd27142ec6462f961d30e7cf8ad3 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 8 Mar 2018 11:09:43 -0600 Subject: [PATCH 1888/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 533f8226578..a0768db6edc 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.1-SNAPSHOT +version = 1.7.2-SNAPSHOT From 5421f49272c8074fa54c5e501cbed9aed76b684c Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 14 Mar 2018 19:41:31 +0100 Subject: [PATCH 1889/2152] Actively set connection timeout and read timeout for sentry connection. (#567) --- .../io/sentry/DefaultSentryClientFactory.java | 2 +- .../io/sentry/connection/HttpConnection.java | 51 ++++++++++++++++--- .../sentry/connection/HttpConnectionTest.java | 17 ++++++- 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index f5a13834b70..56bd1daaaef 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -428,7 +428,7 @@ protected Connection createHttpConnection(Dsn dsn) { httpConnection.setMarshaller(marshaller); int timeout = getTimeout(dsn); - httpConnection.setTimeout(timeout); + httpConnection.setConnectionTimeout(timeout); boolean bypassSecurityEnabled = getBypassSecurityEnabled(dsn); httpConnection.setBypassSecurity(bypassSecurityEnabled); diff --git a/sentry/src/main/java/io/sentry/connection/HttpConnection.java b/sentry/src/main/java/io/sentry/connection/HttpConnection.java index 4aaccfaf1d8..276cab7ef26 100644 --- a/sentry/src/main/java/io/sentry/connection/HttpConnection.java +++ b/sentry/src/main/java/io/sentry/connection/HttpConnection.java @@ -39,9 +39,14 @@ public class HttpConnection extends AbstractConnection { */ private static final String SENTRY_AUTH = "X-Sentry-Auth"; /** - * Default timeout of an HTTP connection to Sentry. + * Default connection timeout of an HTTP connection to Sentry. */ - private static final int DEFAULT_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(1); + private static final int DEFAULT_CONNECTION_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(1); + /** + * Default read timeout of an HTTP connection to Sentry. + */ + private static final int DEFAULT_READ_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(5); + /** * HostnameVerifier allowing wildcard certificates to work without adding them to the truststore. */ @@ -68,9 +73,14 @@ public boolean verify(String hostname, SSLSession sslSession) { */ private Marshaller marshaller; /** - * Timeout of an HTTP connection to Sentry. + * Timeout to connect of an HTTP connection to Sentry. + */ + private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT; + /** + * Read timeout of an HTTP connection to Sentry. */ - private int timeout = DEFAULT_TIMEOUT; + private int readTimeout = DEFAULT_READ_TIMEOUT; + /** * Setting allowing to bypass the security system which requires wildcard certificates * to be added to the truststore. @@ -128,7 +138,8 @@ protected HttpURLConnection getConnection() { } connection.setRequestMethod("POST"); connection.setDoOutput(true); - connection.setConnectTimeout(timeout); + connection.setConnectTimeout(connectionTimeout); + connection.setReadTimeout(readTimeout); connection.setRequestProperty(USER_AGENT, SentryEnvironment.getSentryName()); connection.setRequestProperty(SENTRY_AUTH, getAuthHeader()); @@ -228,8 +239,36 @@ private String getErrorMessageFromStream(InputStream errorStream) { return sb.toString(); } + /** + * This will set the timeout that is used in establishing a connection to the url. + * By default this is set to 1 second. + * + * @deprecated Use setConnectionTimeout instead. + * @param timeout New timeout to set. If 0 is used (java default) wait forever. + */ + @Deprecated public void setTimeout(int timeout) { - this.timeout = timeout; + this.connectionTimeout = timeout; + } + + /** + * This will set the timeout that is used in establishing a connection to the url. + * By default this is set to 5 second. + * + * @param timeout New timeout to set. If 0 is used (java default) wait forever. + */ + public void setConnectionTimeout(int timeout) { + this.connectionTimeout = timeout; + } + + /** + * This will set the timeout that is used in reading data on an already established connection. + * By default this is set to 1 seconds. + * + * @param timeout New timeout to set. If 0 is used (java default) wait forever. + */ + public void setReadTimeout(int timeout) { + this.readTimeout = timeout; } public void setMarshaller(Marshaller marshaller) { diff --git a/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java b/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java index ba3bcb57926..8aa4eda7771 100644 --- a/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java @@ -62,9 +62,9 @@ public void setUp() throws Exception { } @Test - public void testTimeout(@Injectable final Event mockEvent) throws Exception { + public void testConnectionTimeout(@Injectable final Event mockEvent) throws Exception { final int timeout = 12; - httpConnection.setTimeout(timeout); + httpConnection.setConnectionTimeout(timeout); httpConnection.send(mockEvent); @@ -73,6 +73,19 @@ public void testTimeout(@Injectable final Event mockEvent) throws Exception { }}; } + @Test + public void testReadTimeout(@Injectable final Event mockEvent) throws Exception { + final int timeout = 42; + httpConnection.setReadTimeout(timeout); + + httpConnection.send(mockEvent); + + new Verifications() {{ + mockUrlConnection.setReadTimeout(timeout); + }}; + } + + @Test public void testByPassSecurityDefaultsToFalse(@Injectable final Event mockEvent) throws Exception { httpConnection.send(mockEvent); From 1fc4fc5ab4cd672708c63072a8429952ad8effe8 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 14 Mar 2018 13:50:24 -0500 Subject: [PATCH 1890/2152] Update CHANGES. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 6a60262a3b9..b92d88c6f62 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.7.2 ------------- -- +- Set HTTPConnection read timeout to 5 seconds. (thanks kalaspuffar) Version 1.7.1 ------------- From 041cb955858f5949ae8da4ff0e730156998b38ac Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 15 Mar 2018 11:34:53 -0500 Subject: [PATCH 1891/2152] Bump docs to 1.7.2 --- CONTRIBUTING.md | 2 +- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 74b5d739508..706c3594faa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ We love pull requests from everyone. -The test suite currently requires you run JDK version `1.7.1`. +The test suite currently requires you run JDK version `1.7.2`. See [#487](https://github.com/getsentry/sentry-java/issues/478) for more information. diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 8225e90d973..4369a9f52f3 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.7.1`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.7.2`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.7.1' + compile 'io.sentry:sentry-android:1.7.2' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.7.1' + classpath 'io.sentry:sentry-android-gradle-plugin:1.7.2' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 05e8e2a475e..99769953d23 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.7.1 + 1.7.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.7.1' + compile 'io.sentry:sentry-appengine:1.7.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.7.1" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.7.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index d7e1b34df46..eb22c52159f 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.7.1 + 1.7.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.7.1' + compile 'io.sentry:sentry:1.7.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.7.1" + libraryDependencies += "io.sentry" % "sentry" % "1.7.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 9470e55b8e8..e1eb263a836 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.7.1 + 1.7.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.7.1' + compile 'io.sentry:sentry-log4j:1.7.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.7.1" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.7.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 53c38f0ee40..7b122ce365a 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.7.1 + 1.7.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.7.1' + compile 'io.sentry:sentry-log4j2:1.7.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.7.1" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.7.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 7d85a71913c..87e21b7e919 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.7.1 + 1.7.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.7.1' + compile 'io.sentry:sentry-logback:1.7.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.7.1" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.7.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index 410c0ab3f5a..4382e9f236f 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -34,22 +34,22 @@ Using Maven: io.sentry sentry-spring - 1.7.1 + 1.7.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.7.1' + compile 'io.sentry:sentry-spring:1.7.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.7.1" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.7.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index 1a5ef109ad5..4ac0014e564 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.7.1 + 1.7.2 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.7.1' + compile 'io.sentry:sentry:1.7.2' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.7.1" + libraryDependencies += "io.sentry" % "sentry" % "1.7.2" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 02be989968a65d93d92b867e44b02fecbace737e Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 15 Mar 2018 11:36:26 -0500 Subject: [PATCH 1892/2152] [maven-release-plugin] prepare release v1.7.2 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index e80d19b993b..debd09c3863 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.2-SNAPSHOT + 1.7.2 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.2 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index a855e864742..af7e1df7f78 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.2-SNAPSHOT + 1.7.2 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 8bb697a1055..9417fd79959 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.2-SNAPSHOT + 1.7.2 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 40d92d355c0..872785ce88c 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.2-SNAPSHOT + 1.7.2 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index ea7ecb89251..3ce7135bc75 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.2-SNAPSHOT + 1.7.2 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 0f84e4719ad..27c2ddf446c 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.2-SNAPSHOT + 1.7.2 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 5326762e94e..e771fab2033 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.2-SNAPSHOT + 1.7.2 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index e929750c831..2f07149810f 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.2-SNAPSHOT + 1.7.2 sentry From 25375da4663ed26b0bd0ccb452b2735ddae3db36 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 15 Mar 2018 11:36:26 -0500 Subject: [PATCH 1893/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index debd09c3863..fb3323ddcc8 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.2 + 1.7.3-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.2 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index af7e1df7f78..2b29e4c73a8 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.2 + 1.7.3-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 9417fd79959..ca7f623f5da 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.2 + 1.7.3-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 872785ce88c..8003fdb459f 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.2 + 1.7.3-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 3ce7135bc75..fccd2395460 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.2 + 1.7.3-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 27c2ddf446c..fd1479de461 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.2 + 1.7.3-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index e771fab2033..3d6f9ac644e 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.2 + 1.7.3-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 2f07149810f..1f2bf22a5f5 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.2 + 1.7.3-SNAPSHOT sentry From 25052fe4c6e252461ec9adaa3be3d1104a9783dc Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 15 Mar 2018 11:36:29 -0500 Subject: [PATCH 1894/2152] Bump CHANGES to 1.7.3 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index b92d88c6f62..9cfcc377c53 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.3 +------------- + +- + Version 1.7.2 ------------- From 38aaffa2d863fdc858cc5f9b24ff2e87d14b5a7a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 15 Mar 2018 11:46:04 -0500 Subject: [PATCH 1895/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index a0768db6edc..a814d19f4dc 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.2-SNAPSHOT +version = 1.7.3-SNAPSHOT From 2342be63f58a29a2e0f1339cf10ed43e3f7a33a3 Mon Sep 17 00:00:00 2001 From: Sitbon Date: Mon, 9 Apr 2018 17:46:52 +0200 Subject: [PATCH 1896/2152] Fix support for JUL handler level. (#576) --- sentry/src/main/java/io/sentry/jul/SentryHandler.java | 9 +++++++++ .../io/sentry/jul/SentryHandlerEventBuildingTest.java | 2 ++ 2 files changed, 11 insertions(+) diff --git a/sentry/src/main/java/io/sentry/jul/SentryHandler.java b/sentry/src/main/java/io/sentry/jul/SentryHandler.java index 0cc4d89948b..805695d274e 100644 --- a/sentry/src/main/java/io/sentry/jul/SentryHandler.java +++ b/sentry/src/main/java/io/sentry/jul/SentryHandler.java @@ -81,6 +81,15 @@ protected void retrieveProperties() { LogManager manager = LogManager.getLogManager(); String className = SentryHandler.class.getName(); setPrintfStyle(Boolean.valueOf(manager.getProperty(className + ".printfStyle"))); + setLevel(parseLevelOrDefault(manager.getProperty(className + ".level"))); + } + + private Level parseLevelOrDefault(String levelName) { + try { + return Level.parse(levelName.trim()); + } catch (Exception e) { + return Level.WARNING; + } } @Override diff --git a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java index fac2e38ee8c..15983e42630 100644 --- a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java +++ b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java @@ -52,6 +52,7 @@ public void testSimpleMessageLogging() throws Exception { final Date date = new Date(1373883196416L); final long threadId = 12; + sentryHandler.setLevel(Level.INFO); sentryHandler.publish(newLogRecord(loggerName, Level.INFO, message, arguments, null, null, threadId, date.getTime())); @@ -89,6 +90,7 @@ private Object[][] levelConversions() { @Test(dataProvider = "levels") public void testLevelConversion(final Event.Level expectedLevel, Level level) throws Exception { + sentryHandler.setLevel(Level.ALL); sentryHandler.publish(newLogRecord(null, level, null, null, null)); new Verifications() {{ From 7c2fb3572dd7b0e400cd873d743a490fcccbf743 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 9 Apr 2018 10:47:50 -0500 Subject: [PATCH 1897/2152] Update CHANGES. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 9cfcc377c53..7c31003c3d1 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.7.3 ------------- -- +- Fix support for JUL handler level. (thanks nsitbon) Version 1.7.2 ------------- From 60f24f38fd3d16eb5ffb96e60896decc818412cb Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 9 Apr 2018 11:12:36 -0500 Subject: [PATCH 1898/2152] =?UTF-8?q?Handle=20Event=20`extra`=20deserializ?= =?UTF-8?q?ation=20failures=20when=20using=20Proguard.=20(t=E2=80=A6=20(#5?= =?UTF-8?q?73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES | 1 + docs/modules/android.rst | 1 + .../gradle/SentryProguardConfigTask.groovy | 3 ++- sentry/src/main/java/io/sentry/event/Event.java | 15 +++++++++++++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 7c31003c3d1..bc7e2909288 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ Version 1.7.3 ------------- +- Handle Event `extra` deserialization failures when using Proguard. (thanks mcomella) - Fix support for JUL handler level. (thanks nsitbon) Version 1.7.2 diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 4369a9f52f3..ea96e4e39ce 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -196,6 +196,7 @@ First, you need to add the following to your ProGuard rules file:: -keepattributes LineNumberTable,SourceFile -dontwarn org.slf4j.** -dontwarn javax.** + -keep class io.sentry.event.Event { *; } ProGuard UUIDs `````````````` diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy index 3beca229c10..0cd7a358df0 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy @@ -22,7 +22,8 @@ class SentryProguardConfigTask extends DefaultTask { f.write("-keepattributes LineNumberTable,SourceFile\n" + "-dontwarn com.facebook.fbui.**\n" + "-dontwarn org.slf4j.**\n" + - "-dontwarn javax.**\n") + "-dontwarn javax.**\n" + + "-keep class io.sentry.event.Event { *; }\n") f.close() applicationVariant.getBuildType().buildType.proguardFiles(file) } diff --git a/sentry/src/main/java/io/sentry/event/Event.java b/sentry/src/main/java/io/sentry/event/Event.java index ba29b175085..48796f82dd6 100644 --- a/sentry/src/main/java/io/sentry/event/Event.java +++ b/sentry/src/main/java/io/sentry/event/Event.java @@ -1,6 +1,8 @@ package io.sentry.event; import io.sentry.event.interfaces.SentryInterface; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.ObjectInputStream; @@ -30,6 +32,7 @@ * */ public class Event implements Serializable { + private static final Logger _logger = LoggerFactory.getLogger(Event.class); /** * Unique identifier of the event. */ @@ -264,9 +267,21 @@ void setEnvironment(String environment) { this.environment = environment; } + //CHECKSTYLE.OFF: JavadocMethod public Map getExtra() { + if (extra == null) { + // `extra` *should* never be null, but there are scenarios such as + // when an application is run through ProGuard which may cause deserialization + // code to be removed (and thus silently not run). In this case, our overridden + // `readObject` may never be called and `extra` will remain null. :( + extra = new HashMap<>(); + _logger.warn("`extra` field was null, deserialization must not have been called," + + " please check your ProGuard (or other obfuscation) configuration."); + } + return extra; } + //CHECKSTYLE.ON: JavadocMethod void setExtra(Map extra) { this.extra = extra; From 3f5c5eeb2981be0acd055a4383402eb3647b54f1 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 9 Apr 2018 16:16:05 -0500 Subject: [PATCH 1899/2152] Bump docs to 1.7.3 --- CONTRIBUTING.md | 2 +- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 706c3594faa..6b3527c2f75 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ We love pull requests from everyone. -The test suite currently requires you run JDK version `1.7.2`. +The test suite currently requires you run JDK version `1.7.3`. See [#487](https://github.com/getsentry/sentry-java/issues/478) for more information. diff --git a/docs/modules/android.rst b/docs/modules/android.rst index ea96e4e39ce..9a9bb63007d 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.7.2`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.7.3`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.7.2' + compile 'io.sentry:sentry-android:1.7.3' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.7.2' + classpath 'io.sentry:sentry-android-gradle-plugin:1.7.3' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 99769953d23..4bdd0c3d4de 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.7.2 + 1.7.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.7.2' + compile 'io.sentry:sentry-appengine:1.7.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.7.2" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.7.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index eb22c52159f..199e11bd512 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.7.2 + 1.7.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.7.2' + compile 'io.sentry:sentry:1.7.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.7.2" + libraryDependencies += "io.sentry" % "sentry" % "1.7.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index e1eb263a836..664f2c1d755 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.7.2 + 1.7.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.7.2' + compile 'io.sentry:sentry-log4j:1.7.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.7.2" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.7.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 7b122ce365a..372604f83f2 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.7.2 + 1.7.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.7.2' + compile 'io.sentry:sentry-log4j2:1.7.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.7.2" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.7.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 87e21b7e919..c0d3e9ba589 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.7.2 + 1.7.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.7.2' + compile 'io.sentry:sentry-logback:1.7.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.7.2" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.7.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index 4382e9f236f..73c3858f8fb 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -34,22 +34,22 @@ Using Maven: io.sentry sentry-spring - 1.7.2 + 1.7.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.7.2' + compile 'io.sentry:sentry-spring:1.7.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.7.2" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.7.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index 4ac0014e564..da9fa5d768c 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.7.2 + 1.7.3 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.7.2' + compile 'io.sentry:sentry:1.7.3' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.7.2" + libraryDependencies += "io.sentry" % "sentry" % "1.7.3" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From c2920a1579d433dfd11b588e83f11c006bc820ea Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 9 Apr 2018 16:17:27 -0500 Subject: [PATCH 1900/2152] [maven-release-plugin] prepare release v1.7.3 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index fb3323ddcc8..35b7c645786 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.3-SNAPSHOT + 1.7.3 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.3 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 2b29e4c73a8..e2cc5468224 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.3-SNAPSHOT + 1.7.3 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index ca7f623f5da..75f23049365 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.3-SNAPSHOT + 1.7.3 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 8003fdb459f..ba99289aa2c 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.3-SNAPSHOT + 1.7.3 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index fccd2395460..5e47ecc568e 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.3-SNAPSHOT + 1.7.3 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index fd1479de461..0da12f957be 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.3-SNAPSHOT + 1.7.3 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 3d6f9ac644e..92dc00d3777 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.3-SNAPSHOT + 1.7.3 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 1f2bf22a5f5..49a478be608 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.3-SNAPSHOT + 1.7.3 sentry From a588ed194843e355053e31354cbe8108762c8d97 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 9 Apr 2018 16:17:27 -0500 Subject: [PATCH 1901/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 35b7c645786..ebeb625f2bd 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.3 + 1.7.4-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.3 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index e2cc5468224..977418190d3 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.3 + 1.7.4-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 75f23049365..e3f858755e4 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.3 + 1.7.4-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index ba99289aa2c..edefde131f1 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.3 + 1.7.4-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 5e47ecc568e..f5e5f766dec 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.3 + 1.7.4-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 0da12f957be..5e0713ef004 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.3 + 1.7.4-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 92dc00d3777..9acb33bca66 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.3 + 1.7.4-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 49a478be608..69c3c85eba5 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.3 + 1.7.4-SNAPSHOT sentry From 4eba3b3f3b012685c0520751064d6e1f7bc4dc9f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 9 Apr 2018 16:17:30 -0500 Subject: [PATCH 1902/2152] Bump CHANGES to 1.7.4 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index bc7e2909288..5c496aad779 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.4 +------------- + +- + Version 1.7.3 ------------- From a39c7efd03a5c265a269065e43a01bc72182064f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 9 Apr 2018 16:23:01 -0500 Subject: [PATCH 1903/2152] Bump sentry-cli. --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index f84988317d0..8ec71a2cab0 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.28.4 +VERSION=1.30.3 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* From f3d66780871d95aa54b6d3c0bd1f992e9f25f144 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 9 Apr 2018 16:28:00 -0500 Subject: [PATCH 1904/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index a814d19f4dc..052d87684cc 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.3-SNAPSHOT +version = 1.7.4-SNAPSHOT From 941ac2f4cfe34eed880918efdebb302e074ba847 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 1 May 2018 15:12:04 -0500 Subject: [PATCH 1905/2152] Allow new-style DSNs without the secret key. --- CHANGES | 2 +- .../sentry/connection/AbstractConnection.java | 5 +++-- sentry/src/main/java/io/sentry/dsn/Dsn.java | 5 +---- sentry/src/test/java/io/sentry/dsn/DsnTest.java | 17 ++++++++++++----- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/CHANGES b/CHANGES index 5c496aad779..13a9d9bc5bd 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.7.4 ------------- -- +- Allow new-style DSNs without the secret key. Version 1.7.3 ------------- diff --git a/sentry/src/main/java/io/sentry/connection/AbstractConnection.java b/sentry/src/main/java/io/sentry/connection/AbstractConnection.java index 353d9e3c45d..c7c091afa59 100644 --- a/sentry/src/main/java/io/sentry/connection/AbstractConnection.java +++ b/sentry/src/main/java/io/sentry/connection/AbstractConnection.java @@ -2,6 +2,7 @@ import io.sentry.environment.SentryEnvironment; import io.sentry.event.Event; +import io.sentry.util.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,8 +48,8 @@ protected AbstractConnection(String publicKey, String secretKey) { this.eventSendCallbacks = new HashSet<>(); this.authHeader = "Sentry sentry_version=" + SENTRY_PROTOCOL_VERSION + "," + "sentry_client=" + SentryEnvironment.getSentryName() + "," - + "sentry_key=" + publicKey + "," - + "sentry_secret=" + secretKey; + + "sentry_key=" + publicKey + + (!Util.isNullOrEmpty(secretKey) ? (",sentry_secret=" + secretKey) : ""); } /** diff --git a/sentry/src/main/java/io/sentry/dsn/Dsn.java b/sentry/src/main/java/io/sentry/dsn/Dsn.java index 9664cebb97c..71801cbd88b 100644 --- a/sentry/src/main/java/io/sentry/dsn/Dsn.java +++ b/sentry/src/main/java/io/sentry/dsn/Dsn.java @@ -185,7 +185,7 @@ private void makeOptionsImmutable() { /** * Validates internally the DSN, and check for mandatory elements. *

    - * Mandatory elements are the {@link #host}, {@link #publicKey}, {@link #secretKey} and {@link #projectId}. + * Mandatory elements are the {@link #host}, {@link #publicKey} and {@link #projectId}. */ private void validate() { List missingElements = new LinkedList<>(); @@ -200,9 +200,6 @@ private void validate() { if (publicKey == null) { missingElements.add("public key"); } - if (secretKey == null) { - missingElements.add("secret key"); - } if (projectId == null || projectId.isEmpty()) { missingElements.add("project ID"); } diff --git a/sentry/src/test/java/io/sentry/dsn/DsnTest.java b/sentry/src/test/java/io/sentry/dsn/DsnTest.java index ee9415bb8f6..6bdc8201822 100644 --- a/sentry/src/test/java/io/sentry/dsn/DsnTest.java +++ b/sentry/src/test/java/io/sentry/dsn/DsnTest.java @@ -57,6 +57,18 @@ public void testSimpleDsnFromValidURI() throws Exception { assertThat(dsn.getProjectId(), is("9")); } + @Test + public void testSimpleDsnNoSecretValid() throws Exception { + Dsn dsn = new Dsn("http://publicKey@host/9"); + + assertThat(dsn.getProtocol(), is("http")); + assertThat(dsn.getPublicKey(), is("publicKey")); + assertThat(dsn.getSecretKey(), isEmptyOrNullString()); + assertThat(dsn.getHost(), is("host")); + assertThat(dsn.getPath(), is("/")); + assertThat(dsn.getProjectId(), is("9")); + } + @Test public void testDsnLookupWithNothingSet() throws Exception { assertThat(Dsn.dsnLookup(), is(Dsn.DEFAULT_DSN)); @@ -127,11 +139,6 @@ public void testDsnLookupWithEmptyEnvironmentVariable(@Mocked("getenv") final Sy assertThat(Dsn.dsnLookup(), is(Dsn.DEFAULT_DSN)); } - @Test(expectedExceptions = InvalidDsnException.class) - public void testMissingSecretKeyInvalid() throws Exception { - new Dsn("http://publicKey:@host/9"); - } - @Test(expectedExceptions = InvalidDsnException.class) public void testMissingHostInvalid() throws Exception { new Dsn("http://publicKey:secretKey@/9"); From a4b7375a95150e48286834b5de6b37d2cbd4401f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 1 May 2018 15:14:15 -0500 Subject: [PATCH 1906/2152] Bump docs to 1.7.4 --- CONTRIBUTING.md | 2 +- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b3527c2f75..e695518f974 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ We love pull requests from everyone. -The test suite currently requires you run JDK version `1.7.3`. +The test suite currently requires you run JDK version `1.7.4`. See [#487](https://github.com/getsentry/sentry-java/issues/478) for more information. diff --git a/docs/modules/android.rst b/docs/modules/android.rst index 9a9bb63007d..dea8e442881 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.7.3`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.7.4`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.7.3' + compile 'io.sentry:sentry-android:1.7.4' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.7.3' + classpath 'io.sentry:sentry-android-gradle-plugin:1.7.4' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 4bdd0c3d4de..623b3bad2b7 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.7.3 + 1.7.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.7.3' + compile 'io.sentry:sentry-appengine:1.7.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.7.3" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.7.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index 199e11bd512..bef18f4c163 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.7.3 + 1.7.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.7.3' + compile 'io.sentry:sentry:1.7.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.7.3" + libraryDependencies += "io.sentry" % "sentry" % "1.7.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 664f2c1d755..2226eeeb68b 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.7.3 + 1.7.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.7.3' + compile 'io.sentry:sentry-log4j:1.7.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.7.3" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.7.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 372604f83f2..75aa092bb80 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.7.3 + 1.7.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.7.3' + compile 'io.sentry:sentry-log4j2:1.7.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.7.3" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.7.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index c0d3e9ba589..3b5d391eeeb 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.7.3 + 1.7.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.7.3' + compile 'io.sentry:sentry-logback:1.7.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.7.3" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.7.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index 73c3858f8fb..32269c6b9c4 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -34,22 +34,22 @@ Using Maven: io.sentry sentry-spring - 1.7.3 + 1.7.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.7.3' + compile 'io.sentry:sentry-spring:1.7.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.7.3" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.7.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index da9fa5d768c..a564d6e8ae8 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.7.3 + 1.7.4 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.7.3' + compile 'io.sentry:sentry:1.7.4' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.7.3" + libraryDependencies += "io.sentry" % "sentry" % "1.7.4" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From 94da4afbf692ceb56f95b77dcdd200bd75177aa2 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 1 May 2018 15:15:29 -0500 Subject: [PATCH 1907/2152] [maven-release-plugin] prepare release v1.7.4 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index ebeb625f2bd..15bc49e72fd 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.4-SNAPSHOT + 1.7.4 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.4 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 977418190d3..a2e542bfef9 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.4-SNAPSHOT + 1.7.4 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index e3f858755e4..6b18b75c2cc 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.4-SNAPSHOT + 1.7.4 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index edefde131f1..4ed242a90da 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.4-SNAPSHOT + 1.7.4 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index f5e5f766dec..479e6bd63fc 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.4-SNAPSHOT + 1.7.4 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 5e0713ef004..38cd8a10a8e 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.4-SNAPSHOT + 1.7.4 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 9acb33bca66..d940ce592f6 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.4-SNAPSHOT + 1.7.4 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 69c3c85eba5..66abd0dac2a 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.4-SNAPSHOT + 1.7.4 sentry From fad563dbdf252b1144932ed6fb365e95609be605 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 1 May 2018 15:15:29 -0500 Subject: [PATCH 1908/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 15bc49e72fd..33df02b5f76 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.4 + 1.7.5-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.4 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index a2e542bfef9..f0ecb38c44c 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.4 + 1.7.5-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 6b18b75c2cc..480626a091a 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.4 + 1.7.5-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 4ed242a90da..0f4f0510b53 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.4 + 1.7.5-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 479e6bd63fc..ebc05add160 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.4 + 1.7.5-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 38cd8a10a8e..ed4b22dc87a 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.4 + 1.7.5-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index d940ce592f6..db8fa04ad7f 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.4 + 1.7.5-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 66abd0dac2a..34b44c692c9 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.4 + 1.7.5-SNAPSHOT sentry From bdea20858800290f595fec2f2b5c3eadd54341e5 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 1 May 2018 15:15:31 -0500 Subject: [PATCH 1909/2152] Bump CHANGES to 1.7.5 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 13a9d9bc5bd..b1587874b46 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.5 +------------- + +- + Version 1.7.4 ------------- From 9e11a3a3c5d099acc7a9ee28304c8f89257f6cdf Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 1 May 2018 15:25:40 -0500 Subject: [PATCH 1910/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 8ec71a2cab0..997a07c598d 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.30.3 +VERSION=1.30.5 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 052d87684cc..1785959018f 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.4-SNAPSHOT +version = 1.7.5-SNAPSHOT From ac6d49abf5eb4d2525be2b3956db1fda215d7985 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 21 May 2018 09:17:20 -0500 Subject: [PATCH 1911/2152] Run apt-get update on Travis. (#585) --- .ci/agent-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/agent-install.sh b/.ci/agent-install.sh index fd3c64ce222..f00fd4a9363 100755 --- a/.ci/agent-install.sh +++ b/.ci/agent-install.sh @@ -2,5 +2,5 @@ set -ex -# sudo apt-get -qq update +sudo apt-get -qq update sudo apt-get install -y g++-multilib From 119f888fe50fdeabe8f56ac2db8c54f54c337b28 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 21 May 2018 09:26:14 -0500 Subject: [PATCH 1912/2152] Bump animal-sniffer-maven-plugin to 1.16 to avoid IllegalArgumentException. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 33df02b5f76..cf7ce989977 100644 --- a/pom.xml +++ b/pom.xml @@ -242,7 +242,7 @@ org.codehaus.mojo animal-sniffer-maven-plugin - 1.15 + 1.16 org.codehaus.mojo.signature From 86978f7503db3c98ca979048d9d299970c2e826c Mon Sep 17 00:00:00 2001 From: MK Date: Wed, 23 May 2018 12:23:43 -0400 Subject: [PATCH 1913/2152] Add `BreadcrumbBuilder.withData` like `UserBuilder` (#586) --- .../java/io/sentry/event/BreadcrumbBuilder.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/sentry/src/main/java/io/sentry/event/BreadcrumbBuilder.java b/sentry/src/main/java/io/sentry/event/BreadcrumbBuilder.java index 6091ad7615e..8d3421dd82b 100644 --- a/sentry/src/main/java/io/sentry/event/BreadcrumbBuilder.java +++ b/sentry/src/main/java/io/sentry/event/BreadcrumbBuilder.java @@ -1,6 +1,7 @@ package io.sentry.event; import java.util.Date; +import java.util.HashMap; import java.util.Map; /** @@ -83,6 +84,22 @@ public BreadcrumbBuilder setData(Map newData) { return this; } + /** + * Adds to the related data for the {@link breadcrumb}. + * + * @param name Name of the data + * @param value Value of the data + * @return current BreadcrumbBuilder + */ + public BreadcrumbBuilder withData(String name, String value) { + if (this.data == null) { + this.data = new HashMap<>(); + } + + this.data.put(name, value); + return this; + } + /** * Build and return the {@link Breadcrumb} object. * From 67564c1850b2fbd5b63bca29f8d9ed43e5877efe Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 23 May 2018 11:24:23 -0500 Subject: [PATCH 1914/2152] Update CHANGES. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index b1587874b46..908ab3dc2b3 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.7.5 ------------- -- +- Add `BreadcrumbBuilder.withData` (thanks kohenkatz) Version 1.7.4 ------------- From bb5a31f9d9d4d21f40e4bd2002f0b5d4fa45b392 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 24 May 2018 16:15:56 -0500 Subject: [PATCH 1915/2152] Fix configuration README. --- docs/config.rst | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index a7acb35f25d..eeed337e95e 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -25,13 +25,13 @@ In a properties file on your filesystem or classpath (defaults to ``sentry.prope dsn=https://public:private@host:port/1 -Via the Java System Properties: +Via the Java System Properties *(not available on Android)*: .. sourcecode:: shell java -Dsentry.dsn=https://public:private@host:port/1 -jar app.jar -Via a System Environment Variable: +Via a System Environment Variable *(not available on Android)*: .. sourcecode:: shell @@ -80,7 +80,9 @@ Configuration via the runtime environment This is the most flexible method for configuring the Sentry client because it can be easily changed based on the environment you run your -application in. +application in. *(Note that neither Java System Properties or System Environment +Variables are available for Android applications. Please configure Sentry for +Android via code or the properties file.)* Two methods are available for runtime configuration, checked in this order: Java System Properties and System Environment Variables. @@ -282,7 +284,7 @@ Similar behaviour is enabled by default in Sentry. To disable it, use the Event Sampling ~~~~~~~~~~~~~~ -Sentry can be configured to sample events with the sample.rate`` option: +Sentry can be configured to sample events with the ``sample.rate`` option: :: @@ -309,7 +311,7 @@ Buffering Events to Disk ~~~~~~~~~~~~~~~~~~~~~~~~ Sentry can be configured to write events to a specified directory on disk -anytime communication with the Sentry server fails with the buffer.dir`` +anytime communication with the Sentry server fails with the ``buffer.dir`` option. If the directory doesn't exist, Sentry will attempt to create it on startup and may therefore need write permission on the parent directory. Sentry always requires write permission on the buffer directory itself. This @@ -320,7 +322,7 @@ is enabled by default if the ``AndroidSentryClientFactory`` is used. buffer.dir=sentry-events The maximum number of events that will be stored on disk defaults to 10, -but can also be configured with the option buffer.size``: +but can also be configured with the option ``buffer.size``: :: @@ -329,7 +331,7 @@ but can also be configured with the option buffer.size``: If a buffer directory is provided, a background thread will periodically attempt to re-send the events that are found on disk. By default it will attempt to send events every 60 seconds. You can change this with the -buffer.flushtime`` option (in milliseconds): +``buffer.flushtime`` option (in milliseconds): :: @@ -341,7 +343,7 @@ Graceful Shutdown of Buffering (Advanced) In order to shutdown the buffer flushing thread gracefully, a ``ShutdownHook`` is created. By default, the buffer flushing thread is given 1 second to shutdown gracefully, but this can be adjusted via -buffer.shutdowntimeout`` (represented in milliseconds): +``buffer.shutdowntimeout`` (represented in milliseconds): :: @@ -357,7 +359,7 @@ An example would be in a JEE environment where the application using Sentry could be deployed and undeployed regularly. To avoid this behaviour, it is possible to disable the graceful shutdown -by setting the buffer.gracefulshutdown`` option: +by setting the ``buffer.gracefulshutdown`` option: :: @@ -370,7 +372,7 @@ In order to avoid performance issues due to a large amount of logs being generated or a slow connection to the Sentry server, an asynchronous connection is set up, using a low priority thread pool to submit events to Sentry. -To disable the async mode, add async=false`` to your options: +To disable the async mode, add ``async=false`` to your options: :: @@ -382,7 +384,7 @@ Graceful Shutdown of Async (Advanced) In order to shutdown the asynchronous connection gracefully, a ``ShutdownHook`` is created. By default, the asynchronous connection is given 1 second to shutdown gracefully, but this can be adjusted via -async.shutdowntimeout`` (represented in milliseconds): +``async.shutdowntimeout`` (represented in milliseconds): :: @@ -401,7 +403,7 @@ To avoid this behaviour, it is possible to disable the graceful shutdown. This might lead to some log entries being lost if the log application doesn't shut down the ``SentryClient`` instance nicely. -The option to do so is async.gracefulshutdown``: +The option to do so is ``async.gracefulshutdown``: :: @@ -416,7 +418,7 @@ never sent to the Sentry server. Depending on the environment (if the memory is sparse) it is important to be able to control the size of that queue to avoid memory issues. -It is possible to set a maximum with the option async.queuesize``: +It is possible to set a maximum with the option ``async.queuesize``: :: @@ -436,7 +438,7 @@ By default the thread pool used by the async connection contains one thread per processor available to the JVM. It's possible to manually set the number of threads (for example if you want -only one thread) with the option async.threads``: +only one thread) with the option ``async.threads``: :: @@ -450,7 +452,7 @@ running smoothly, so the threads have a `minimal priority `_. It is possible to customise this value to increase the priority of those threads -with the option async.priority``: +with the option ``async.priority``: :: @@ -468,7 +470,7 @@ limited connection, Sentry hosted on an external network), it can be useful to compress the data beforehand or not. It's possible to manually enable/disable the compression with the option -compression`` +``compression`` :: @@ -478,7 +480,7 @@ Max Message Size ~~~~~~~~~~~~~~~~ By default only the first 1000 characters of a message will be sent to -the server. This can be changed with the maxmessagelength`` option. +the server. This can be changed with the ``maxmessagelength`` option. :: @@ -490,7 +492,7 @@ Timeout (Advanced) A timeout is set to avoid blocking Sentry threads because establishing a connection is taking too long. -It's possible to manually set the timeout length with timeout`` +It's possible to manually set the timeout length with ``timeout`` (in milliseconds): :: From 7974d98979f2e7c162281c1099dfd46d41dd9a6e Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 25 May 2018 09:27:58 -0500 Subject: [PATCH 1916/2152] Bump sentry-cli. --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 997a07c598d..7dfc121b2e0 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.30.5 +VERSION=1.32.0 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* From 3013bc0021162a243098d137b83e841303410e80 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 25 May 2018 09:28:03 -0500 Subject: [PATCH 1917/2152] Bump docs to 1.7.5 --- CONTRIBUTING.md | 2 +- docs/modules/android.rst | 8 ++++---- docs/modules/appengine.rst | 8 ++++---- docs/modules/jul.rst | 8 ++++---- docs/modules/log4j.rst | 8 ++++---- docs/modules/log4j2.rst | 8 ++++---- docs/modules/logback.rst | 8 ++++---- docs/modules/spring.rst | 8 ++++---- docs/usage.rst | 8 ++++---- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e695518f974..d1195971e79 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ We love pull requests from everyone. -The test suite currently requires you run JDK version `1.7.4`. +The test suite currently requires you run JDK version `1.7.5`. See [#487](https://github.com/getsentry/sentry-java/issues/478) for more information. diff --git a/docs/modules/android.rst b/docs/modules/android.rst index dea8e442881..7d516fbdcab 100644 --- a/docs/modules/android.rst +++ b/docs/modules/android.rst @@ -6,7 +6,7 @@ Features The Sentry Android SDK is built on top of the main Java SDK and supports all of the same features, `configuration options `_, and more. -Adding version ``1.7.4`` of the Android SDK to a sample application that doesn't even use +Adding version ``1.7.5`` of the Android SDK to a sample application that doesn't even use Proguard only increased the release ``.apk`` size by approximately 200KB. Events will be `buffered to disk `_ @@ -29,9 +29,9 @@ Using Gradle (Android Studio) in your ``app/build.gradle`` add: .. sourcecode:: groovy - compile 'io.sentry:sentry-android:1.7.4' + compile 'io.sentry:sentry-android:1.7.5' -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Initialization -------------- @@ -146,7 +146,7 @@ And declare a dependency in your toplevel ``build.gradle``: buildscript { dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.7.4' + classpath 'io.sentry:sentry-android-gradle-plugin:1.7.5' } } diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst index 623b3bad2b7..0a25ff5818a 100644 --- a/docs/modules/appengine.rst +++ b/docs/modules/appengine.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry-appengine - 1.7.4 + 1.7.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-appengine:1.7.4' + compile 'io.sentry:sentry-appengine:1.7.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.7.4" + libraryDependencies += "io.sentry" % "sentry-appengine" % "1.7.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst index bef18f4c163..537cb3f15a1 100644 --- a/docs/modules/jul.rst +++ b/docs/modules/jul.rst @@ -27,22 +27,22 @@ Using Maven: io.sentry sentry - 1.7.4 + 1.7.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.7.4' + compile 'io.sentry:sentry:1.7.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.7.4" + libraryDependencies += "io.sentry" % "sentry" % "1.7.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst index 2226eeeb68b..f07b68ff8c2 100644 --- a/docs/modules/log4j.rst +++ b/docs/modules/log4j.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j - 1.7.4 + 1.7.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j:1.7.4' + compile 'io.sentry:sentry-log4j:1.7.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.7.4" + libraryDependencies += "io.sentry" % "sentry-log4j" % "1.7.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst index 75aa092bb80..78466f282d9 100644 --- a/docs/modules/log4j2.rst +++ b/docs/modules/log4j2.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-log4j2 - 1.7.4 + 1.7.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-log4j2:1.7.4' + compile 'io.sentry:sentry-log4j2:1.7.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.7.4" + libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.7.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst index 3b5d391eeeb..d904fa5b840 100644 --- a/docs/modules/logback.rst +++ b/docs/modules/logback.rst @@ -28,22 +28,22 @@ Using Maven: io.sentry sentry-logback - 1.7.4 + 1.7.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-logback:1.7.4' + compile 'io.sentry:sentry-logback:1.7.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-logback" % "1.7.4" + libraryDependencies += "io.sentry" % "sentry-logback" % "1.7.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst index 32269c6b9c4..faed5a2eafd 100644 --- a/docs/modules/spring.rst +++ b/docs/modules/spring.rst @@ -34,22 +34,22 @@ Using Maven: io.sentry sentry-spring - 1.7.4 + 1.7.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry-spring:1.7.4' + compile 'io.sentry:sentry-spring:1.7.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry-spring" % "1.7.4" + libraryDependencies += "io.sentry" % "sentry-spring" % "1.7.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Usage ----- diff --git a/docs/usage.rst b/docs/usage.rst index a564d6e8ae8..4747e44509c 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,22 +18,22 @@ Using Maven: io.sentry sentry - 1.7.4 + 1.7.5 Using Gradle: .. sourcecode:: groovy - compile 'io.sentry:sentry:1.7.4' + compile 'io.sentry:sentry:1.7.5' Using SBT: .. sourcecode:: scala - libraryDependencies += "io.sentry" % "sentry" % "1.7.4" + libraryDependencies += "io.sentry" % "sentry" % "1.7.5" -For other dependency managers see the `central Maven repository `_. +For other dependency managers see the `central Maven repository `_. Capture an Error ---------------- From dccb1ef6c4fe2d8a7a5c7eb7b0bcc04b4ad5cb2b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 25 May 2018 09:32:18 -0500 Subject: [PATCH 1918/2152] [maven-release-plugin] prepare release v1.7.5 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index cf7ce989977..61aac0b9e76 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.5-SNAPSHOT + 1.7.5 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.5 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index f0ecb38c44c..5323ef8359a 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.5-SNAPSHOT + 1.7.5 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 480626a091a..eb512fb2c52 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.5-SNAPSHOT + 1.7.5 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 0f4f0510b53..45801c8d5c7 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.5-SNAPSHOT + 1.7.5 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index ebc05add160..9dcbe083081 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.5-SNAPSHOT + 1.7.5 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index ed4b22dc87a..c1ee848aef3 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.5-SNAPSHOT + 1.7.5 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index db8fa04ad7f..5dd6327f292 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.5-SNAPSHOT + 1.7.5 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 34b44c692c9..fa2ba609bea 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.5-SNAPSHOT + 1.7.5 sentry From c772dd8f4f66e5dad102a389998b9e2ade36a427 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 25 May 2018 09:32:18 -0500 Subject: [PATCH 1919/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 61aac0b9e76..7cec153a3cd 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.5 + 1.7.6-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.5 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 5323ef8359a..84c8fc3f11c 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.5 + 1.7.6-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index eb512fb2c52..cd35e316cbe 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.5 + 1.7.6-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 45801c8d5c7..5c0aa2b80f1 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.5 + 1.7.6-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 9dcbe083081..a8a34f9818d 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.5 + 1.7.6-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index c1ee848aef3..97171bc9a70 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.5 + 1.7.6-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 5dd6327f292..d4a2130b42f 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.5 + 1.7.6-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index fa2ba609bea..9658e4d1b82 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.5 + 1.7.6-SNAPSHOT sentry From c33681f549acd532239d03d82109ba4ac2fed2f8 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 25 May 2018 09:32:21 -0500 Subject: [PATCH 1920/2152] Bump CHANGES to 1.7.6 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 908ab3dc2b3..fd57684afb0 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.6 +------------- + +- + Version 1.7.5 ------------- From 7b4a77d3ad9a58f02a847203b5640dd4c10a155a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 25 May 2018 09:43:03 -0500 Subject: [PATCH 1921/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 1785959018f..090212926f3 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.5-SNAPSHOT +version = 1.7.6-SNAPSHOT From 4e5742d3eb9c34de6e5139420e3a6a27d8e84181 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 13 Jun 2018 09:18:39 -0500 Subject: [PATCH 1922/2152] Note where to add sentry.properties to Classpath (by default). --- docs/config.rst | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index eeed337e95e..f714afff476 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -57,11 +57,16 @@ differ between them. Configuration via properties file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The Java SDK can be configured via a `.properties file `_ -that is located on the filesystem or on your application's classpath. By default the SDK will look for -a ``sentry.properties`` file in the current directory or in the root of your classpath. You can override -the location of the properties file by using either the ``sentry.properties.file`` Java System Property -or the ``SENTRY_PROPERTIES_FILE`` System Environment Variable. +The Java SDK can be configured via a `.properties file +`_ that is located on the filesystem +or in your application's classpath. By default the SDK will look for a +``sentry.properties`` file in the application's current working directory or in +the root of your classpath. In most server side applications the default +directory to add resources to your classpath is ``src/main/resources/``, and on +Android the default is ``app/src/main/resources/``. You can override the +location of the properties file by using either the ``sentry.properties.file`` +Java System Property or the ``SENTRY_PROPERTIES_FILE`` System Environment +Variable. Because this file is often bundled with your application, the values cannot be changed easily once your application has been packaged. For this reason, the properties file is useful for setting defaults or options From 4b810150969108429bbb36d28227cb128b986aa0 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Mon, 18 Jun 2018 13:30:59 +0100 Subject: [PATCH 1923/2152] fix: sentry-log4j2/pom.xml to reduce vulnerabilities (#591) The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JAVA-ORGAPACHELOGGINGLOG4J-31409 --- sentry-log4j2/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index a8a34f9818d..fdc3ad73c51 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -15,7 +15,7 @@ log4j2 appender allowing to send logs to the Sentry-Java client. - 2.8.1 + 2.8.2 From 8f74205a027ac6a038eaf2b0fb097e9a3237c399 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 27 Aug 2018 23:35:32 +0200 Subject: [PATCH 1924/2152] ref: Removed in-repo docs --- .gitignore | 1 - .gitmodules | 3 - Makefile | 19 +- docs/Makefile | 153 ---------- docs/_sentryext | 1 - docs/agent.rst | 25 -- docs/conf.py | 249 --------------- docs/config.rst | 583 ------------------------------------ docs/context.rst | 118 -------- docs/index.rst | 42 --- docs/make.bat | 190 ------------ docs/migration.rst | 197 ------------ docs/modules/android.rst | 270 ----------------- docs/modules/appengine.rst | 66 ---- docs/modules/index.rst | 18 -- docs/modules/jul.rst | 117 -------- docs/modules/log4j.rst | 193 ------------ docs/modules/log4j2.rst | 165 ---------- docs/modules/logback.rst | 166 ---------- docs/modules/spring.rst | 110 ------- docs/sentry-doc-config.json | 68 ----- docs/usage.rst | 237 --------------- 22 files changed, 2 insertions(+), 2989 deletions(-) delete mode 100644 docs/Makefile delete mode 160000 docs/_sentryext delete mode 100644 docs/agent.rst delete mode 100644 docs/conf.py delete mode 100644 docs/config.rst delete mode 100644 docs/context.rst delete mode 100644 docs/index.rst delete mode 100644 docs/make.bat delete mode 100644 docs/migration.rst delete mode 100644 docs/modules/android.rst delete mode 100644 docs/modules/appengine.rst delete mode 100644 docs/modules/index.rst delete mode 100644 docs/modules/jul.rst delete mode 100644 docs/modules/log4j.rst delete mode 100644 docs/modules/log4j2.rst delete mode 100644 docs/modules/logback.rst delete mode 100644 docs/modules/spring.rst delete mode 100644 docs/sentry-doc-config.json delete mode 100644 docs/usage.rst diff --git a/.gitignore b/.gitignore index 98e5c1b41ed..bfd95355746 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ target/ -docs/_build infer-out/ agent/build/ local.properties diff --git a/.gitmodules b/.gitmodules index 1e6464ab94b..e69de29bb2d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "docs/_sentryext"] - path = docs/_sentryext - url = https://github.com/getsentry/sentry-doc-support diff --git a/Makefile b/Makefile index 4644a26b6ef..4b1ce2d1b33 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ # the test suite currently only works with 1.7.0_80 # https://github.com/getsentry/sentry-java/issues/478 -.PHONY: checkstyle compile test install clean prepare prepareDocs prepareMvn prepareChanges perform verify +.PHONY: checkstyle compile test install clean prepare prepareMvn prepareChanges perform verify # TODO: Fix to work between macOS and Linux MVN=mvn -e @@ -31,21 +31,6 @@ install: clean: $(MVN) clean -prepareDocs: -# Store previously released version - $(eval PREVIOUS_RELEASE=$(shell grep -E "^Version" CHANGES | head -2 | tail -1 | $(SED) -e 's/Version //')) - @echo Previous release: $(PREVIOUS_RELEASE) -# Store release project version - $(eval RELEASE_VERSION=$(shell mvn help:evaluate -Dexpression=project.version | grep -Ev '^\[' | $(SED) -e 's/-SNAPSHOT//')) - @echo This release: $(RELEASE_VERSION) -# Fix released version in documentation - @echo Fixing documentation versions - $(eval PREVIOUS_ESCAPED=$(shell echo $(PREVIOUS_RELEASE) | $(SED) -e 's/\./\\\./g')) - find . \( -name '*.md' -or -name '*.rst' \) -exec $(SED) -i -e 's/$(PREVIOUS_ESCAPED)/$(RELEASE_VERSION)/g' {} \; -# Commit documentation changes - @echo Committing documentation version changes - git commit -a -m 'Bump docs to $(RELEASE_VERSION)' - prepareMvn: # Prepare release (interactive) $(MVN) release:prepare @@ -66,7 +51,7 @@ change-version: $(MVN) release:update-versions # Prepare is broken into stages because otherwise `make` will run things out of order -prepare: prepareDocs prepareMvn prepareChanges +prepare: prepareMvn prepareChanges perform: $(MVN) release:perform diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 60f8b84390a..00000000000 --- a/docs/Makefile +++ /dev/null @@ -1,153 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/raven-js.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/raven-js.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/raven-js" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/raven-js" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/_sentryext b/docs/_sentryext deleted file mode 160000 index 7a2aab71ee1..00000000000 --- a/docs/_sentryext +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7a2aab71ee148ac47df530c84375b7d86f2dfdb8 diff --git a/docs/agent.rst b/docs/agent.rst deleted file mode 100644 index b65b3ed389b..00000000000 --- a/docs/agent.rst +++ /dev/null @@ -1,25 +0,0 @@ -Agent (Beta) -============ - -As of version 1.5.0 there is a new **experimental (beta)** Java Agent available that -enhances the existing Sentry Java SDK. The agent will enhance your application stacktraces -on Sentry by adding the names and values of local variables to each frame. - -Usage ------ - -The latest agent can be `downloaded from Github `_. - -Once you have downloaded the correct agent, you need to run your Java application with -the ``-agentpath`` argument. For example: - -.. sourcecode:: shell - - java -agentpath:/path/to/libsentry_agent_linux-x86_64.so -jar app.jar - -You will still need to install and configure the `Sentry Java SDK `_. -In addition, **you must set the** ``stacktrace.app.packages`` option. Only exceptions that contain at -least one frame from your application will be processed by the agent. You can find details about this option -`on the configuration page `_. - -With the SDK configured the agent should automatically enhance your events where applicable. diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 3cce7d56815..00000000000 --- a/docs/conf.py +++ /dev/null @@ -1,249 +0,0 @@ -# -*- coding: utf-8 -*- -# -# sentry-java documentation build configuration file, created by -# sphinx-quickstart on Mon Jan 21 21:04:27 2013. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os, datetime, re - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'sentry-java' -copyright = u'%s, Functional Software Inc.' % datetime.date.today().year - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# - -# The full version, including alpha/beta/rc tags. -with open('../pom.xml') as f: - release = re.search(r'(.*?)', f.read()).group(1) -# The short X.Y version. -version = release.rsplit('.', 1)[0] - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'sentry-javadoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'sentry-java.tex', u'sentry-java Documentation', - u'Functional Software Inc.', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'sentry-java', u'sentry-java Documentation', - [u'Functional Software Inc.'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------------ - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'sentry-java', u'sentry-java Documentation', - u'Functional Software Inc.', 'sentry-java', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -if os.environ.get('SENTRY_FEDERATED_DOCS') != '1': - sys.path.insert(0, os.path.abspath('_sentryext')) - import sentryext - sentryext.activate() diff --git a/docs/config.rst b/docs/config.rst deleted file mode 100644 index f714afff476..00000000000 --- a/docs/config.rst +++ /dev/null @@ -1,583 +0,0 @@ -.. _configuration: - -Configuration -============= - -**Note:** Sentry's library and framework integration documentation explains how to to do -the initial Sentry configuration for each of the supported integrations. The configuration -below can be used in combination with any of the integrations *once you set Sentry up with -the integration*. Please check :ref:`the integration documentation ` before -you attempt to do any advanced configuration. - -.. _setting_the_dsn: - -Setting the DSN (Data Source Name) ----------------------------------- - -The DSN is the first and most important thing to configure because it tells the SDK where -to send events. You can find your project's DSN in the "Client Keys" section of your "Project Settings" -in Sentry. It can be configured in multiple ways. Explanations of the :ref:`configuration methods are -detailed below `. - -In a properties file on your filesystem or classpath (defaults to ``sentry.properties``): - -.. sourcecode:: properties - - dsn=https://public:private@host:port/1 - -Via the Java System Properties *(not available on Android)*: - -.. sourcecode:: shell - - java -Dsentry.dsn=https://public:private@host:port/1 -jar app.jar - -Via a System Environment Variable *(not available on Android)*: - -.. sourcecode:: shell - - SENTRY_DSN=https://public:private@host:port/1 java -jar app.jar - -In code: - -.. sourcecode:: java - - import io.sentry.Sentry; - - Sentry.init("https://public:private@host:port/1"); - -.. _configuration_methods: - -Configuration methods ---------------------- - -There are multiple ways to configure the Java SDK, but all of them take the same options. -See below for how to use each configuration method and how the option names might -differ between them. - -Configuration via properties file -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The Java SDK can be configured via a `.properties file -`_ that is located on the filesystem -or in your application's classpath. By default the SDK will look for a -``sentry.properties`` file in the application's current working directory or in -the root of your classpath. In most server side applications the default -directory to add resources to your classpath is ``src/main/resources/``, and on -Android the default is ``app/src/main/resources/``. You can override the -location of the properties file by using either the ``sentry.properties.file`` -Java System Property or the ``SENTRY_PROPERTIES_FILE`` System Environment -Variable. - -Because this file is often bundled with your application, the values cannot be changed easily once your -application has been packaged. For this reason, the properties file is useful for setting defaults or options -that you don't expect to change often. The properties file is the last place checked for -each option value, so runtime configuration (described below) will override it if available. - -Option names in the property file exactly match the examples given below. For example, to enable -sampling, in your properties file: - -.. sourcecode:: properties - - sample.rate=0.75 - -Configuration via the runtime environment -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This is the most flexible method for configuring the Sentry client -because it can be easily changed based on the environment you run your -application in. *(Note that neither Java System Properties or System Environment -Variables are available for Android applications. Please configure Sentry for -Android via code or the properties file.)* - -Two methods are available for runtime configuration, checked in this order: Java System Properties -and System Environment Variables. - -Java System Property option names are exactly like the examples given below except that they are -prefixed with ``sentry.``. For example, to enable sampling: - -.. sourcecode:: shell - - java -Dsentry.sample.rate=0.75 -jar app.jar - -System Environment Variable option names require that you replace the ``.`` with ``_``, capitalize -them, and add a ``SENTRY_`` prefix. For example, to enable sampling: - -.. sourcecode:: shell - - SENTRY_SAMPLE_RATE=0.75 java -jar app.jar - -Configuration via code -~~~~~~~~~~~~~~~~~~~~~~ - -The DSN itself can also be configured directly in code: - -.. sourcecode:: java - - import io.sentry.Sentry; - - Sentry.init("https://public:private@host:port/1?option=value&other.option=othervalue"); - -Note that Sentry will not be able to do anything with events until this line is run, so this -method of configuration is not recommended if you might have errors occur during startup. -In addition, by passing a hardcoded DSN you are no longer able to override the DSN at runtime -via Java System Properties or System Environment Variables. - -Configuration via the DSN -~~~~~~~~~~~~~~~~~~~~~~~~~ - -The SDK can also be configured by setting querystring parameters on the DSN itself. This is a bit -recursive because your DSN itself is an option that you must set somewhere (and not in the DSN!). - -Option names in the DSN exactly match the examples given below. For example, to enable sampling -if you are setting your DSN via the environment: - -.. sourcecode:: shell - - SENTRY_DSN=https://public:private@host:port/1?sample.rate=0.75 java -jar app.jar - -You can, of course, pass this DSN in using the other methods described above. - -Options -------- - -The following options can all be configured as described above: via a ``sentry.properties`` file, via -Java System Properties, via System Environment variables, or via the DSN. - -Release -~~~~~~~ - -To set the application version that will be sent with each event, use the -``release`` option: - -:: - - release=1.0.0 - -Distribution -```````````` - -To set the application distribution that will be sent with each event, use the -``dist`` option: - -:: - - release=1.0.0 - dist=x86 - -Note that the distribution is only useful (and used) if the ``release`` is also -set. - -Environment -~~~~~~~~~~~ - -To set the application environment that will be sent with each event, use the -``environment`` option: - -:: - - environment=staging - -Server Name -~~~~~~~~~~~ - -To set the server name that will be sent with each event, use the -``servername`` option: - -:: - - servername=host1 - -Tags -~~~~ - -To set tags that will be sent with each event, use the ``tags`` option with -comma separated pairs of keys and values that are joined by a colon: - -:: - - tags=tag1:value1,tag2:value2 - -MDC Tags -~~~~~~~~ - -To set tag names that are extracted from the SLF4J MDC system, use the -``mdctags`` option with comma separated key names. Note that this option -is only useful when are you using one of the logging integrations. - -:: - - mdctags=foo,bar - -.. sourcecode:: java - - import org.slf4j.MDC; - - MDC.put("foo", "value1"); - MDC.put("bar", "value2"); - - // This sends an event where the 'foo' and 'bar' MDC values are set as additional tags - logger.error("This is a test"); - - -Extra Data -~~~~~~~~~~ - -To set extra data that will be sent with each event (but not as tags), use the -``extra`` option with comma separated pairs of keys and values that are joined -by a colon: - -:: - - extra=key1:value1,key2:value2 - - -"In Application" Stack Frames -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Sentry differentiates stack frames that are directly related to your application -("in application") from stack frames that come from other packages such as the -standard library, frameworks, or other dependencies. The difference -is visible in the Sentry web interface where only the "in application" frames are -displayed by default. - -You can configure which package prefixes your application uses with the -``stacktrace.app.packages`` option, which takes a comma separated list. - -:: - - stacktrace.app.packages=com.mycompany,com.other.name - -If you don't want to use this feature but want to disable the warning, simply -set it to an empty string: - -:: - - stacktrace.app.packages= - -Same Frame as Enclosing Exception -````````````````````````````````` - -Sentry can use the "in application" system to hide frames in chained exceptions. Usually when a -StackTrace is printed, the result looks like this: - -:: - - HighLevelException: MidLevelException: LowLevelException - at Main.a(Main.java:13) - at Main.main(Main.java:4) - Caused by: MidLevelException: LowLevelException - at Main.c(Main.java:23) - at Main.b(Main.java:17) - at Main.a(Main.java:11) - ... 1 more - Caused by: LowLevelException - at Main.e(Main.java:30) - at Main.d(Main.java:27) - at Main.c(Main.java:21) - ... 3 more - -Some frames are replaced by the ``... N more`` line as they are the same frames -as in the enclosing exception. - -Similar behaviour is enabled by default in Sentry. To disable it, use the -``stacktrace.hidecommon`` option. - -:: - - stacktrace.hidecommon=false - -Event Sampling -~~~~~~~~~~~~~~ - -Sentry can be configured to sample events with the ``sample.rate`` option: - -:: - - sample.rate=0.75 - -This option takes a number from 0.0 to 1.0, representing the percent of -events to allow through to server (from 0% to 100%). By default all -events will be sent to the Sentry server. - -Uncaught Exception Handler -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -By default, an ``UncaughtExceptionHandler`` is configured that will attempt to -send exceptions to Sentry. To disable it, use the ``uncaught.handler.enabled`` -option. Note that exceptions are sent asynchronously by default, and there is -no guarantee they will be sent before the JVM exits. This option is best used -in conjunction with the disk buffering system described below. - -:: - - uncaught.handler.enabled=false - -Buffering Events to Disk -~~~~~~~~~~~~~~~~~~~~~~~~ - -Sentry can be configured to write events to a specified directory on disk -anytime communication with the Sentry server fails with the ``buffer.dir`` -option. If the directory doesn't exist, Sentry will attempt to create it -on startup and may therefore need write permission on the parent directory. -Sentry always requires write permission on the buffer directory itself. This -is enabled by default if the ``AndroidSentryClientFactory`` is used. - -:: - - buffer.dir=sentry-events - -The maximum number of events that will be stored on disk defaults to 10, -but can also be configured with the option ``buffer.size``: - -:: - - buffer.size=100 - -If a buffer directory is provided, a background thread will periodically -attempt to re-send the events that are found on disk. By default it will -attempt to send events every 60 seconds. You can change this with the -``buffer.flushtime`` option (in milliseconds): - -:: - - buffer.flushtime=10000 - -Graceful Shutdown of Buffering (Advanced) -````````````````````````````````````````` - -In order to shutdown the buffer flushing thread gracefully, a ``ShutdownHook`` -is created. By default, the buffer flushing thread is given 1 second -to shutdown gracefully, but this can be adjusted via -``buffer.shutdowntimeout`` (represented in milliseconds): - -:: - - buffer.shutdowntimeout=5000 - -The special value ``-1`` can be used to disable the timeout and wait -indefinitely for the executor to terminate. - -The ``ShutdownHook`` could lead to memory leaks in an environment where -the life cycle of Sentry doesn't match the life cycle of the JVM. - -An example would be in a JEE environment where the application using Sentry -could be deployed and undeployed regularly. - -To avoid this behaviour, it is possible to disable the graceful shutdown -by setting the ``buffer.gracefulshutdown`` option: - -:: - - buffer.gracefulshutdown=false - -Async Connection -~~~~~~~~~~~~~~~~ - -In order to avoid performance issues due to a large amount of logs being -generated or a slow connection to the Sentry server, an asynchronous connection -is set up, using a low priority thread pool to submit events to Sentry. - -To disable the async mode, add ``async=false`` to your options: - -:: - - async=false - -Graceful Shutdown of Async (Advanced) -````````````````````````````````````` - -In order to shutdown the asynchronous connection gracefully, a ``ShutdownHook`` -is created. By default, the asynchronous connection is given 1 second -to shutdown gracefully, but this can be adjusted via -``async.shutdowntimeout`` (represented in milliseconds): - -:: - - async.shutdowntimeout=5000 - -The special value ``-1`` can be used to disable the timeout and wait -indefinitely for the executor to terminate. - -The ``ShutdownHook`` could lead to memory leaks in an environment where -the life cycle of Sentry doesn't match the life cycle of the JVM. - -An example would be in a JEE environment where the application using Sentry -could be deployed and undeployed regularly. - -To avoid this behaviour, it is possible to disable the graceful shutdown. -This might lead to some log entries being lost if the log application -doesn't shut down the ``SentryClient`` instance nicely. - -The option to do so is ``async.gracefulshutdown``: - -:: - - async.gracefulshutdown=false - -Async Queue Size (Advanced) -``````````````````````````` - -The default queue used to store unprocessed events is limited to 50 -items. Additional items added once the queue is full are dropped and -never sent to the Sentry server. -Depending on the environment (if the memory is sparse) it is important to be -able to control the size of that queue to avoid memory issues. - -It is possible to set a maximum with the option ``async.queuesize``: - -:: - - async.queuesize=100 - -This means that if the connection to the Sentry server is down, only the 100 -most recent events will be stored and processed as soon as the server is back up. - -The special value ``-1`` can be used to enable an unlimited queue. Beware -that network connectivity or Sentry server issues could mean your process -will run out of memory. - -Async Threads Count (Advanced) -`````````````````````````````` - -By default the thread pool used by the async connection contains one thread per -processor available to the JVM. - -It's possible to manually set the number of threads (for example if you want -only one thread) with the option ``async.threads``: - -:: - - async.threads=1 - -Async Threads Priority (Advanced) -````````````````````````````````` - -In most cases sending logs to Sentry isn't as important as an application -running smoothly, so the threads have a -`minimal priority `_. - -It is possible to customise this value to increase the priority of those threads -with the option ``async.priority``: - -:: - - async.priority=10 - -Compression -~~~~~~~~~~~ - -By default the content sent to Sentry is compressed before being sent. -However, compressing and encoding the data adds a small CPU and memory hit which -might not be useful if the connection to Sentry is fast and reliable. - -Depending on the limitations of the project (e.g. a mobile application with a -limited connection, Sentry hosted on an external network), it can be useful -to compress the data beforehand or not. - -It's possible to manually enable/disable the compression with the option -``compression`` - -:: - - compression=false - -Max Message Size -~~~~~~~~~~~~~~~~ - -By default only the first 1000 characters of a message will be sent to -the server. This can be changed with the ``maxmessagelength`` option. - -:: - - maxmessagelength=1500 - -Timeout (Advanced) -~~~~~~~~~~~~~~~~~~ - -A timeout is set to avoid blocking Sentry threads because establishing a -connection is taking too long. - -It's possible to manually set the timeout length with ``timeout`` -(in milliseconds): - -:: - - timeout=10000 - -Using a Proxy -~~~~~~~~~~~~~ - -If your application needs to send outbound requests through an HTTP proxy, -you can configure the proxy information via JVM networking properties or -as a Sentry option. - -For example, using JVM networking properties (affects the entire JVM process), - -:: - - java \ - # if you are using the HTTP protocol \ - -Dhttp.proxyHost=proxy.example.com \ - -Dhttp.proxyPort=8080 \ - \ - # if you are using the HTTPS protocol \ - -Dhttps.proxyHost=proxy.example.com \ - -Dhttps.proxyPort=8080 \ - \ - # relevant to both HTTP and HTTPS - -Dhttp.nonProxyHosts=”localhost|host.example.com” \ - \ - MyApp - -See `Java Networking and -Proxies `_ -for more information about the proxy properties. - -Alternatively, using Sentry options (only affects the Sentry HTTP client, -useful inside shared application containers), - -:: - - http.proxy.host=proxy.example.com - http.proxy.port=8080 - -Custom functionality --------------------- - -At times, you may require custom functionality that is not included in the Java SDK -already. The most common way to do this is to create your own ``SentryClientFactory`` instance -as seen in the example below. - -Implementation -~~~~~~~~~~~~~~ - -.. sourcecode:: java - - public class MySentryClientFactory extends DefaultSentryClientFactory { - @Override - public SentryClient createSentryClient(Dsn dsn) { - SentryClient sentryClient = new SentryClient(createConnection(dsn), getContextManager(dsn)); - - /* - Create and use the ForwardedAddressResolver, which will use the - X-FORWARDED-FOR header for the remote address if it exists. - */ - ForwardedAddressResolver forwardedAddressResolver = new ForwardedAddressResolver(); - sentryClient.addBuilderHelper(new HttpEventBuilderHelper(forwardedAddressResolver)); - - sentryClient.addBuilderHelper(new ContextBuilderHelper(sentryClient)); - return configureSentryClient(sentryClient, dsn); - } - } - -Usage -~~~~~ - -To use your custom ``SentryClientFactory`` implementation, use the ``factory`` option: - -:: - - factory=my.company.SentryClientFactory - -Your factory class will need to be available on your classpath with a zero argument constructor -or an error will be thrown. diff --git a/docs/context.rst b/docs/context.rst deleted file mode 100644 index b1b936ea7cb..00000000000 --- a/docs/context.rst +++ /dev/null @@ -1,118 +0,0 @@ -Context & Breadcrumbs -===================== - -The Java SDK implements the idea of a "context" to support attaching additional -information to events, such as breadcrumbs. A context may refer to a single -request to a web framework, to the entire lifetime of an Android application, -or something else that better suits your application's needs. - -There is no single definition of context that applies to every application, -for this reason a specific implementation must be chosen depending on what your -application does and how it is structured. By default Sentry uses a -``ThreadLocalContextManager`` that maintains a single ``Context`` instance per thread. -This is useful for frameworks that use one thread per user request such as those based -on synchronous servlet APIs. Sentry also installs a ``ServletRequestListener`` that will -clear the thread's context after each servlet request finishes. - -Sentry defaults to the ``SingletonContextManager`` on Android, which maintains a single -context instance for all threads for the lifetime of the application. - -To override the ``ContextManager`` you will need to override the ``getContextManager`` -method in the ``DefaultSentryClientFactory``. A simpler API will likely be provided in -the future. - -Usage ------ - -Breadcrumbs can be used to describe actions that occurred in your application leading -up to an event being sent. For example, whether external API requests were made, -or whether a user clicked on something in an Android application. By default the last -100 breadcrumbs per context will be stored and sent with future events. - -The user can be set per context so that you know who was affected by each event. - -Once a ``SentryClient`` instance has been initialized you can begin setting state in -the current context. - -.. sourcecode:: java - - import io.sentry.Sentry; - import io.sentry.context.Context; - import io.sentry.event.BreadcrumbBuilder; - import io.sentry.event.UserBuilder; - - public class MyClass { - - /** - * Examples using the (recommended) static API. - */ - public void staticAPIExample() { - // Manually initialize the static client, you may also pass in a DSN and/or - // SentryClientFactory to use. Note that the client will attempt to automatically - // initialize on the first use of the static API, so this isn't strictly necessary. - Sentry.init(); - - // Note that all fields set on the context are optional. Context data is copied onto - // all future events in the current context (until the context is cleared). - - // Set the current user in the context. - Sentry.getContext().setUser( - new UserBuilder().setUsername("user1").build() - ); - - // Record a breadcrumb in the context. - Sentry.getContext().recordBreadcrumb( - new BreadcrumbBuilder().setMessage("User did something specific again!").build() - ); - - // Add extra data to future events in this context. - Sentry.getContext().addExtra("extra", "thing"); - - // Add an additional tag to future events in this context. - Sentry.getContext().addTag("tagName", "tagValue"); - - // Send an event with the context data attached. - Sentry.capture("New event message"); - - // Clear the context, useful if you need to add hooks in a framework - // to empty context between requests. - Sentry.clearContext(); - } - - /** - * Examples that use the SentryClient instance directly. - */ - public void instanceAPIExample() { - // Create a SentryClient instance that you manage manually. - SentryClient sentryClient = SentryClientFactory.sentryClient(); - - // Get the current context instance. - Context context = sentryClient.getContext(); - - // Note that all fields set on the context are optional. Context data is copied onto - // all future events in the current context (until the context is cleared). - - // Set the current user in the context. - context.setUser( - new UserBuilder().setUsername("user1").build() - ); - - // Record a breadcrumb in the context. - context.recordBreadcrumb( - new BreadcrumbBuilder().setMessage("User did something specific!").build() - ); - - // Add extra data to future events in this context. - context.addExtra("extra", "thing"); - - // Add an additional tag to future events in this context. - context.addTag("tagName", "tagValue"); - - // Send an event with the context data attached. - sentryClient.sendMessage("New event message"); - - // Clear the context, useful if you need to add hooks in a framework - // to empty context between requests. - context.clear(); - } - } diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index b6f0a320594..00000000000 --- a/docs/index.rst +++ /dev/null @@ -1,42 +0,0 @@ -.. sentry:edition:: self - - Sentry Java - =========== - -.. sentry:edition:: on-premise, hosted - - .. class:: platform-java - - Java - ==== - -Sentry for Java is the official Java SDK for Sentry. At its core it provides -a raw client for sending events to Sentry, but it is highly recommended that you -use one of the included library or framework integrations listed below if at all possible. - -**Note:** The old ``raven`` library is no longer maintained. It is highly recommended that -you `migrate `_ to ``sentry`` (which this -documentation covers). `Check out the migration guide `_ -for more information. If you are still using ``raven`` you can -`find the old documentation here `_. - -.. toctree:: - :maxdepth: 2 - :titlesonly: - - config - context - usage - agent - migration - modules/index - -Resources: - -* `Documentation `_ -* `Examples `_ -* `Bug Tracker `_ -* `Code `_ -* `Mailing List `_ -* `IRC `_ (irc.freenode.net, #sentry) -* `Travis CI `_ diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 13e2848a47d..00000000000 --- a/docs/make.bat +++ /dev/null @@ -1,190 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\raven-js.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\raven-js.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -:end diff --git a/docs/migration.rst b/docs/migration.rst deleted file mode 100644 index afa987a5115..00000000000 --- a/docs/migration.rst +++ /dev/null @@ -1,197 +0,0 @@ -Migration from Raven Java -========================= - -The old ``raven-java`` library has been overhauled and renamed to ``sentry-java``. The focus -of the new release was to improve APIs, make the underlying client completely independent -of logging integrations, and to rename (from ``raven-*``) for clarity. - -What follows is a small guide explaining the major changes. - -New Artifacts -------------- - -.. describe:: Before (raven-java) - - Artifact named ``raven`` (and others: ``raven-*``) under the ``com.getsentry.raven`` group. - Final minor release was version ``8.0.x``. - -.. describe:: Now (sentry-java) - - Artifact named ``sentry`` (and others: ``sentry-*``) under the ``io.sentry`` group. - Started over with version ``1.0.0`` (but please use the latest version!). - -New Packages ------------- - -.. describe:: Before (raven-java) - - Package root was ``com.getsentry.raven``. - - For example, the ``logback`` appender used to be referenced in configuration by using - ``com.getsentry.raven.logback.SentryAppender``. - -.. describe:: Now (sentry-java) - - Package root is ``io.sentry``. - - For example, the ``logback`` appender is now referenced in configuration by using - ``io.sentry.logback.SentryAppender``. - -Logging Integration Configuration ---------------------------------- - -.. describe:: Before (raven-java) - - Most (or all) configuration would be done inside of the logging appender itself. For example: - - .. sourcecode:: xml - - - - WARN - - https://host:port/1?options - 1.0.0 - - -.. describe:: Now (sentry-java) - - While setting up the ``SentryAppender`` itself is still required for logging integrations, - **configuration** of Sentry is no longer done in the same place. - - This is because appenders are initialized only when the first message (at or above the threshold) - is sent to them, which means Sentry has no idea how to initialize and configure itself until - the first event is sent. This may seem OK, but it prevented users from being able to do - things before an error was sent, such as: record breadcrumbs, set the current user, and more. - - For this reason, all configuration is now done "outside" of the logging integration itself. - You may configure Sentry using a properties file (default: ``sentry.properties``) if you - preferred the old style, :ref:`more information can be found on the configuration page `. - - For example: - - .. sourcecode:: xml - - - - - WARN - - - - .. sourcecode:: properties - - # sentry.properties - dsn=https://host:port/1?options - release=1.0.0 - - .. sourcecode:: java - - // you can now record breadcrumbs *before* the first event is even sent - Sentry.getContext().recordBreadcrumb( - new BreadcrumbBuilder().setMessage("Made a call to the database.").build() - ); - -Raven Class Changes -------------------- - -.. describe:: Before (raven-java) - - The ``Raven`` class was both the core client class and also had the ability to - statically store a client and send events without keeping track of the instance - yourself. - -.. describe:: Now (sentry-java) - - The core client class is now called ``SentryClient`` and there is now separate - ``Sentry`` class that you may use to handle initializing Sentry statically and - sending events. - - For example: - - .. sourcecode:: java - - // The static SentryClient can be lazily initialized from anywhere in your application. - // Your DSN needs to be provided somehow, check the configuration documentation! - Sentry.capture("Hello, world!") - -Configuration via DSN ---------------------- - -.. describe:: Before (raven-java) - - Options were prefixed with ``raven.``, for example: ``raven.async``. - -.. describe:: Now (sentry-java) - - Options are no longer prefixed, for example: ``async``. - -Configuration via Java System Properties ----------------------------------------- - -.. describe:: Before (raven-java) - - Only certain options could be set, and only in the logging integrations. For example: - ``sentry.release`` was allowed but ``sentry.async`` did nothing. - -.. describe:: Now (sentry-java) - - All options can be configured via Java System Properties, for example: ``sentry.async=false`` - is respected. - -Configuration via Environment Variables ---------------------------------------- - -.. describe:: Before (raven-java) - - Only certain options could be set, and only in the logging integrations. For example: - ``SENTRY_RELEASE`` was allowed but ``SENTRY_ASYNC`` did nothing. - -.. describe:: Now (sentry-java) - - All options can be configured via Environment Variables, for example: ``SENTRY_ASYNC=false`` - is respected. - -Classes Renamed ---------------- - -.. describe:: Before (raven-java) - - Many classes contained the word ``Raven``. For example ``RavenServletRequestListener``. - -.. describe:: Now (sentry-java) - - All instances of ``Raven`` have been renamed ``Sentry``. For example ``SentryServletRequestListener``. - - In addition, as noted above, the underlying client class ``Raven`` became ``SentryClient``, and - so ``RavenFactory`` also became ``SentryClientFactory`` and ``DefaultRavenFactory`` became - ``DefaultSentryClientFactory``. - -Custom Factories ----------------- - -.. describe:: Before (raven-java) - - To do customization users would typically create a ``DefaultRavenFactory`` subclass - and register it in one of multiple (painful) ways. - -.. describe:: Now (sentry-java) - - To do customization users subclass ``DefaultSentryClientFactory`` and then call out - that class with the ``factory`` option, like ``factory=my.company.MySentryClientFactory``. - :ref:`See the configuration page ` for more information. - -Android -------- - -.. describe:: Before (raven-java) - - There used to be a ``Raven`` wrapper called ``com.getsentry.raven.android.Raven`` that - was a second class interface for interacting with Sentry on Android. - -.. describe:: Now (sentry-java) - - Android users now use the same ``Sentry`` and ``SentryClient`` classes as everyone, - they just need to initialize it with their application context and the - ``AndroidSentryClientFactory``. For an example, `see the Android documentation - `_. diff --git a/docs/modules/android.rst b/docs/modules/android.rst deleted file mode 100644 index 7d516fbdcab..00000000000 --- a/docs/modules/android.rst +++ /dev/null @@ -1,270 +0,0 @@ -Android -======= - -Features --------- - -The Sentry Android SDK is built on top of the main Java SDK and supports all of the same -features, `configuration options `_, and more. -Adding version ``1.7.5`` of the Android SDK to a sample application that doesn't even use -Proguard only increased the release ``.apk`` size by approximately 200KB. - -Events will be `buffered to disk `_ -(in the application's cache directory) by default. This allows events to be sent at a -later time if the device does not have connectivity when an event is created. This can -be disabled by :ref:`setting the option ` ``buffer.enabled`` to ``false``. - -An ``UncaughtExceptionHandler`` is configured so that crash events will be -stored to disk and sent the next time the application is run. - -The ``AndroidEventBuilderHelper`` is enabled by default, which will automatically -enrich events with data about the current state of the device, such as memory usage, -storage usage, display resolution, connectivity, battery level, model, Android version, -whether the device is rooted or not, etc. - -Installation ------------- - -Using Gradle (Android Studio) in your ``app/build.gradle`` add: - -.. sourcecode:: groovy - - compile 'io.sentry:sentry-android:1.7.5' - -For other dependency managers see the `central Maven repository `_. - -Initialization --------------- - -Your application must have permission to access the internet in order to send -events to the Sentry server. In your ``AndroidManifest.xml``: - -.. sourcecode:: xml - - - - -Then initialize the Sentry client in your application's main ``onCreate`` method: - -.. sourcecode:: java - - import io.sentry.Sentry; - import io.sentry.android.AndroidSentryClientFactory; - - public class MainActivity extends Activity { - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - Context ctx = this.getApplicationContext(); - - // Use the Sentry DSN (client key) from the Project Settings page on Sentry - String sentryDsn = "https://publicKey:secretKey@host:port/1?options"; - Sentry.init(sentryDsn, new AndroidSentryClientFactory(ctx)); - - // Alternatively, if you configured your DSN in a `sentry.properties` - // file (see the configuration documentation). - Sentry.init(new AndroidSentryClientFactory(ctx)); - } - } - -You can optionally configure other values such as ``environment`` and ``release``. -:ref:`See the configuration page ` for ways you can do this. - -Usage ------ - -Now you can use ``Sentry`` to capture events anywhere in your application: - -.. sourcecode:: java - - import io.sentry.context.Context; - import io.sentry.event.BreadcrumbBuilder; - import io.sentry.event.UserBuilder; - - public class MyClass { - /** - * An example method that throws an exception. - */ - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call this!"); - } - - /** - * Note that the ``Sentry.init`` method must be called before the static API - * is used, otherwise a ``NullPointerException`` will be thrown. - */ - void logWithStaticAPI() { - /* - Record a breadcrumb in the current context which will be sent - with the next event(s). By default the last 100 breadcrumbs are kept. - */ - Sentry.getContext().recordBreadcrumb( - new BreadcrumbBuilder().setMessage("User made an action").build() - ); - - // Set the user in the current context. - Sentry.getContext().setUser( - new UserBuilder().setEmail("hello@sentry.io").build() - ); - - /* - This sends a simple event to Sentry using the statically stored instance - that was created in the ``main`` method. - */ - Sentry.capture("This is a test"); - - try { - unsafeMethod(); - } catch (Exception e) { - // This sends an exception event to Sentry using the statically stored instance - // that was created in the ``main`` method. - Sentry.capture(e); - } - } - } - -ProGuard --------- - -In order to use ProGuard with Sentry you will need to upload -the proguard mapping files to Sentry by using our Gradle integration -(recommended) or manually by using :doc:`sentry-cli ` - -Gradle Integration -~~~~~~~~~~~~~~~~~~ - -Using Gradle (Android Studio) in your ``app/build.gradle`` add: - -.. sourcecode:: groovy - - apply plugin: 'io.sentry.android.gradle' - -And declare a dependency in your toplevel ``build.gradle``: - -.. sourcecode:: groovy - - buildscript { - dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:1.7.5' - } - } - -The plugin will then automatically generate appropriate ProGuard mapping files -and upload them when you run ``gradle assembleRelease``. The credentials -for the upload step are loaded from a ``sentry.properties`` file in -your project root *or* via environment variables, for more information -`see the sentry-cli documentation `_. -At the very minimum you will need something like this:: - - defaults.project=___PROJECT_NAME___ - defaults.org=___ORG_NAME___ - auth.token=YOUR_AUTH_TOKEN - -You can find your authentication token `on the Sentry API page `_. -For more information about the available configuration options see -`/learn/cli/configuration`. - -Gradle Configuration -```````````````````` - -Additionally we expose a few configuration values directly in Gradle: - -.. sourcecode:: groovy - - sentry { - // Disables or enables the automatic configuration of proguard - // for Sentry. This injects a default config for proguard so - // you don't need to do it manually. - autoProguardConfig true - - // Enables or disables the automatic upload of mapping files - // during a build. If you disable this you'll need to manually - // upload the mapping files with sentry-cli when you do a release. - autoUpload true - } - -Manual Integration -~~~~~~~~~~~~~~~~~~ - -If you choose not to use the Gradle integration, you may handle the processing -and upload steps manually. However, it is highly recommended that you use the -Gradle integration if at all possible. - -First, you need to add the following to your ProGuard rules file:: - - -keepattributes LineNumberTable,SourceFile - -dontwarn org.slf4j.** - -dontwarn javax.** - -keep class io.sentry.event.Event { *; } - -ProGuard UUIDs -`````````````` - -After ProGuard files are generated you will need to embed the UUIDs of the -ProGuard mapping files in a properties file named ``sentry-debug-meta.properties`` in -the assets folder. The Java SDK will look for the UUIDs there to link events to -the correct mapping files on the server side. - -.. admonition:: Note - - Sentry calculates UUIDs for proguard files. For more information - about how this works see :ref:`proguard-uuids`. - -``sentry-cli`` can write the ``sentry-debug-meta.properties`` file for you:: - - sentry-cli upload-proguard \ - --android-manifest app/build/intermediates/manifests/full/release/AndroidManifest.xml \ - --write-properties app/build/intermediates/assets/release/sentry-debug-meta.properties \ - --no-upload \ - app/build/outputs/mapping/release/mapping.txt - -Note that this file needs to be in your APK, so this needs to be run before -the APK is packaged. You can do that by creating a gradle task that runs -before the dex packaging. - -You can for example add a gradle task after the proguard step and before -the dex one which executes ``sentry-cli`` to validate and process -the mapping files and to write the UUIDs into the properties file: - -.. sourcecode:: groovy - - gradle.projectsEvaluated { - android.applicationVariants.each { variant -> - def variantName = variant.name.capitalize(); - def proguardTask = project.tasks.findByName( - "transformClassesAndResourcesWithProguardFor${variantName}") - def dexTask = project.tasks.findByName( - "transformClassesWithDexFor${variantName}") - def task = project.tasks.create( - name: "processSentryProguardFor${variantName}", - type: Exec) { - workingDir project.rootDir - commandLine *[ - "sentry-cli", - "upload-proguard", - "--write-properties", - "${project.rootDir.toPath()}/app/build/intermediates/assets" + - "/${variant.dirName}/sentry-debug-meta.properties", - variant.getMappingFile(), - "--no-upload" - ] - } - dexTask.dependsOn task - task.dependsOn proguardTask - } - } - -Alternatively you can generate a UUID upfront yourself and then force -Sentry to honor that UUID after upload. However this is strongly -discouraged! - -Uploading ProGuard Files -```````````````````````` - -Finally, you need manually upload ProGuard files with ``sentry-cli`` as -follows:: - - sentry-cli upload-proguard \ - --android-manifest app/build/intermediates/manifests/full/release/AndroidManifest.xml \ - app/build/outputs/mapping/release/mapping.txt diff --git a/docs/modules/appengine.rst b/docs/modules/appengine.rst deleted file mode 100644 index 0a25ff5818a..00000000000 --- a/docs/modules/appengine.rst +++ /dev/null @@ -1,66 +0,0 @@ -Google App Engine -================= - -The ``sentry-appengine`` library provides `Google App Engine `_ -support for Sentry via the `Task Queue API -`_. - -The source can be found `on Github -`_. - -Installation ------------- - -Using Maven: - -.. sourcecode:: xml - - - io.sentry - sentry-appengine - 1.7.5 - - -Using Gradle: - -.. sourcecode:: groovy - - compile 'io.sentry:sentry-appengine:1.7.5' - -Using SBT: - -.. sourcecode:: scala - - libraryDependencies += "io.sentry" % "sentry-appengine" % "1.7.5" - -For other dependency managers see the `central Maven repository `_. - -Usage ------ - -This module provides a new ``SentryClientFactory`` implementation which replaces the default async -system with a Google App Engine compatible one. You'll need to configure Sentry to use the -``io.sentry.appengine.AppEngineSentryClientFactory`` as its factory. - -The queue size and thread options will not be used as they are specific to -the default Java threading system. - -Queue Name ----------- - -By default, the default task queue will be used, but it's possible to -specify which one will be used with the ``sentry.async.gae.queuename`` option:: - - http://public:private@host:port/1?async.gae.queuename=MyQueueName - -Connection Name ---------------- - -As the queued tasks are sent across different instances of the -application, it's important to be able to identify which connection should -be used when processing the event. To do so, the GAE module will identify -each connection based on an identifier either automatically generated or -user defined. To manually set the connection identifier (only used -internally) use the option ``sentry.async.gae.connectionid``:: - - http://public:private@host:port/1?async.gae.connectionid=MyConnection diff --git a/docs/modules/index.rst b/docs/modules/index.rst deleted file mode 100644 index f2332aeab06..00000000000 --- a/docs/modules/index.rst +++ /dev/null @@ -1,18 +0,0 @@ -.. _integrations: - -Integrations -============ - -The Sentry Java SDK comes with support for some frameworks and libraries so that -you don't have to capture and send errors manually. - -.. toctree:: - :maxdepth: 1 - - android - appengine - jul - log4j - log4j2 - logback - spring diff --git a/docs/modules/jul.rst b/docs/modules/jul.rst deleted file mode 100644 index 537cb3f15a1..00000000000 --- a/docs/modules/jul.rst +++ /dev/null @@ -1,117 +0,0 @@ -java.util.logging -================= - -The ``sentry`` library provides a `java.util.logging Handler -`_ -that sends logged exceptions to Sentry. Once this integration is configured -you can *also* use Sentry's static API, :ref:`as shown on the usage page `, -in order to do things like record breadcrumbs, set the current user, or manually send -events. - -The source for ``sentry`` can be found `on Github -`_. - -**Note:** The old ``raven`` library is no longer maintained. It is highly recommended that -you `migrate `_ to ``sentry`` (which this -documentation covers). `Check out the migration guide `_ -for more information. If you are still using ``raven`` you can -`find the old documentation here `_. - -Installation ------------- - -Using Maven: - -.. sourcecode:: xml - - - io.sentry - sentry - 1.7.5 - - -Using Gradle: - -.. sourcecode:: groovy - - compile 'io.sentry:sentry:1.7.5' - -Using SBT: - -.. sourcecode:: scala - - libraryDependencies += "io.sentry" % "sentry" % "1.7.5" - -For other dependency managers see the `central Maven repository `_. - -Usage ------ - -The following example configures a ``ConsoleHandler`` that logs to standard out -at the ``INFO`` level and a ``SentryHandler`` that logs to the Sentry server at -the ``WARN`` level. The ``ConsoleHandler`` is only provided as an example of -a non-Sentry appender that is set to a different logging threshold, like one you -may already have in your project. - -Example configuration using the ``logging.properties`` format: - -.. sourcecode:: ini - - # Enable the Console and Sentry handlers - handlers=java.util.logging.ConsoleHandler,io.sentry.jul.SentryHandler - - # Set the default log level to INFO - .level=INFO - - # Override the Sentry handler log level to WARNING - io.sentry.jul.SentryHandler.level=WARNING - -When starting your application, add the ``java.util.logging.config.file`` to -the system properties, with the full path to the ``logging.properties`` as -its value:: - - $ java -Djava.util.logging.config.file=/path/to/app.properties MyClass - -Next, **you'll need to configure your DSN** (client key) and optionally other values such as -``environment`` and ``release``. :ref:`See the configuration page ` for ways you can do this. - -In Practice ------------ - -.. sourcecode:: java - - import java.util.logging.Level; - import java.util.logging.Logger; - - public class MyClass { - private static final Logger logger = Logger.getLogger(MyClass.class.getName()); - - void logSimpleMessage() { - // This sends a simple event to Sentry - logger.error(Level.INFO, "This is a test"); - } - - void logWithBreadcrumbs() { - // Record a breadcrumb that will be sent with the next event(s), - // by default the last 100 breadcrumbs are kept. - Sentry.record( - new BreadcrumbBuilder().setMessage("User made an action").build() - ); - - // This sends a simple event to Sentry - logger.error("This is a test"); - } - - void logException() { - try { - unsafeMethod(); - } catch (Exception e) { - // This sends an exception event to Sentry - logger.error(Level.SEVERE, "Exception caught", e); - } - } - - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call this!"); - } - } diff --git a/docs/modules/log4j.rst b/docs/modules/log4j.rst deleted file mode 100644 index f07b68ff8c2..00000000000 --- a/docs/modules/log4j.rst +++ /dev/null @@ -1,193 +0,0 @@ -Log4j 1.x -========= - -The ``sentry-log4j`` library provides `Log4j 1.x `_ -support for Sentry via an `Appender -`_ -that sends logged exceptions to Sentry. Once this integration is configured -you can *also* use Sentry's static API, :ref:`as shown on the usage page `, -in order to do things like record breadcrumbs, set the current user, or manually send -events. - -The source can be found `on Github -`_. - -**Note:** The old ``raven-log4j`` library is no longer maintained. It is highly recommended that -you `migrate `_ to ``sentry-log4j`` (which this -documentation covers). `Check out the migration guide `_ -for more information. If you are still using ``raven-log4j`` you can -`find the old documentation here `_. - -Installation ------------- - -Using Maven: - -.. sourcecode:: xml - - - io.sentry - sentry-log4j - 1.7.5 - - -Using Gradle: - -.. sourcecode:: groovy - - compile 'io.sentry:sentry-log4j:1.7.5' - -Using SBT: - -.. sourcecode:: scala - - libraryDependencies += "io.sentry" % "sentry-log4j" % "1.7.5" - -For other dependency managers see the `central Maven repository `_. - -Usage ------ - -The following examples configure a ``ConsoleAppender`` that logs to standard out -at the ``INFO`` level and a ``SentryAppender`` that logs to the Sentry server at -the ``WARN`` level. The ``ConsoleAppender`` is only provided as an example of -a non-Sentry appender that is set to a different logging threshold, like one you -may already have in your project. - -Example configuration using the ``log4j.properties`` format: - -.. sourcecode:: ini - - # Enable the Console and Sentry appenders - log4j.rootLogger=INFO, Console, Sentry - - # Configure the Console appender - log4j.appender.Console=org.apache.log4j.ConsoleAppender - log4j.appender.Console.layout=org.apache.log4j.PatternLayout - log4j.appender.Console.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5p: %m%n - - # Configure the Sentry appender, overriding the logging threshold to the WARN level - log4j.appender.Sentry=io.sentry.log4j.SentryAppender - log4j.appender.Sentry.threshold=WARN - -Alternatively, using the ``log4j.xml`` format: - -.. sourcecode:: xml - - - - - - - - - - - - - - - - - - - - - - - - - - - -Next, **you'll need to configure your DSN** (client key) and optionally other values such as -``environment`` and ``release``. :ref:`See the configuration page ` for ways you can do this. - -Additional Data ---------------- - -It's possible to add extra data to events thanks to `the MDC -`_ -and `the NDC -`_ -systems provided by Log4j 1.x. - -Mapped Tags -~~~~~~~~~~~ - -By default all MDC parameters are stored under the "Additional Data" tab in Sentry. By -specifying the ``mdctags`` option in your configuration you can -choose which MDC keys to send as tags instead, which allows them to be used as -filters within the Sentry UI. - -.. sourcecode:: java - - void logWithExtras() { - // MDC extras - MDC.put("Environment", "Development"); - MDC.put("OS", "Linux"); - - // This sends an event where the Environment and OS MDC values are set as additional data - logger.error("This is a test"); - } - -In Practice ------------ - -.. sourcecode:: java - - import org.apache.log4j.Logger; - import org.apache.log4j.MDC; - import org.apache.log4j.NDC; - - public class MyClass { - private static final Logger logger = Logger.getLogger(MyClass.class); - - void logSimpleMessage() { - // This sends a simple event to Sentry - logger.error("This is a test"); - } - - void logWithBreadcrumbs() { - // Record a breadcrumb that will be sent with the next event(s), - // by default the last 100 breadcrumbs are kept. - Sentry.record( - new BreadcrumbBuilder().setMessage("User made an action").build() - ); - - // This sends a simple event to Sentry - logger.error("This is a test"); - } - - void logWithExtras() { - // MDC extras - MDC.put("extra_key", "extra_value"); - // NDC extras are sent under 'log4J-NDC' - NDC.push("Extra_details"); - // This sends an event with extra data to Sentry - logger.error("This is a test"); - } - - void logException() { - try { - unsafeMethod(); - } catch (Exception e) { - // This sends an exception event to Sentry - logger.error("Exception caught", e); - } - } - - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call this!"); - } - } - -Asynchronous Logging --------------------- - -Sentry uses asynchronous communication by default, and so it is unnecessary -to use an `AsyncAppender -`_. diff --git a/docs/modules/log4j2.rst b/docs/modules/log4j2.rst deleted file mode 100644 index 78466f282d9..00000000000 --- a/docs/modules/log4j2.rst +++ /dev/null @@ -1,165 +0,0 @@ -Log4j 2.x -========= - -The ``sentry-log4j2`` library provides `Log4j 2.x `_ -support for Sentry via an `Appender -`_ -that sends logged exceptions to Sentry. Once this integration is configured -you can *also* use Sentry's static API, :ref:`as shown on the usage page `, -in order to do things like record breadcrumbs, set the current user, or manually send -events. - -The source can be found `on Github -`_. - -**Note:** The old ``raven-log4j2`` library is no longer maintained. It is highly recommended that -you `migrate `_ to ``sentry-log4j2`` (which this -documentation covers). `Check out the migration guide `_ -for more information. If you are still using ``raven-log4j2`` you can -`find the old documentation here `_. - -Installation ------------- - -Using Maven: - -.. sourcecode:: xml - - - io.sentry - sentry-log4j2 - 1.7.5 - - -Using Gradle: - -.. sourcecode:: groovy - - compile 'io.sentry:sentry-log4j2:1.7.5' - -Using SBT: - -.. sourcecode:: scala - - libraryDependencies += "io.sentry" % "sentry-log4j2" % "1.7.5" - -For other dependency managers see the `central Maven repository `_. - -Usage ------ - -The following example configures a ``ConsoleAppender`` that logs to standard out -at the ``INFO`` level and a ``SentryAppender`` that logs to the Sentry server at -the ``WARN`` level. The ``ConsoleAppender`` is only provided as an example of -a non-Sentry appender that is set to a different logging threshold, like one you -may already have in your project. - -Example configuration using the ``log4j2.xml`` format: - -.. sourcecode:: xml - - - - - - - - - - - - - - - - - - - - -Next, **you'll need to configure your DSN** (client key) and optionally other values such as -``environment`` and ``release``. :ref:`See the configuration page ` for ways you can do this. - -Additional Data ---------------- - -It's possible to add extra data to events thanks to `the marker system -`_ -provided by Log4j 2.x. - -Mapped Tags -~~~~~~~~~~~ - -By default all MDC parameters are stored under the "Additional Data" tab in Sentry. By -specifying the ``mdctags`` option in your configuration you can -choose which MDC keys to send as tags instead, which allows them to be used as -filters within the Sentry UI. - -.. sourcecode:: java - - void logWithExtras() { - // ThreadContext ("MDC") extras - ThreadContext.put("Environment", "Development"); - ThreadContext.put("OS", "Linux"); - - // This sends an event where the Environment and OS MDC values are set as additional data - logger.error("This is a test"); - } - -In Practice ------------ - -.. sourcecode:: java - - import org.apache.logging.log4j.LogManager; - import org.apache.logging.log4j.Logger; - import org.apache.logging.log4j.Marker; - import org.apache.logging.log4j.MarkerManager; - - public class MyClass { - private static final Logger logger = LogManager.getLogger(MyClass.class); - private static final Marker MARKER = MarkerManager.getMarker("myMarker"); - - void logSimpleMessage() { - // This sends a simple event to Sentry - logger.error("This is a test"); - } - - void logWithBreadcrumbs() { - // Record a breadcrumb that will be sent with the next event(s), - // by default the last 100 breadcrumbs are kept. - Sentry.record( - new BreadcrumbBuilder().setMessage("User made an action").build() - ); - - // This sends a simple event to Sentry - logger.error("This is a test"); - } - - void logWithTag() { - // This sends an event with a tag named 'log4j2-Marker' to Sentry - logger.error(MARKER, "This is a test"); - } - - void logWithExtras() { - // MDC extras - ThreadContext.put("extra_key", "extra_value"); - // NDC extras are sent under 'log4j2-NDC' - ThreadContext.push("Extra_details"); - // This sends an event with extra data to Sentry - logger.error("This is a test"); - } - - void logException() { - try { - unsafeMethod(); - } catch (Exception e) { - // This sends an exception event to Sentry - logger.error("Exception caught", e); - } - } - - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call this!"); - } - } diff --git a/docs/modules/logback.rst b/docs/modules/logback.rst deleted file mode 100644 index d904fa5b840..00000000000 --- a/docs/modules/logback.rst +++ /dev/null @@ -1,166 +0,0 @@ -Logback -======= - -The ``sentry-logback`` library provides `Logback `_ -support for Sentry via an `Appender -`_ -that sends logged exceptions to Sentry. Once this integration is configured -you can *also* use Sentry's static API, :ref:`as shown on the usage page `, -in order to do things like record breadcrumbs, set the current user, or manually send -events. - -The source can be found `on Github -`_. - -**Note:** The old ``raven-logback`` library is no longer maintained. It is highly recommended that -you `migrate `_ to ``sentry-logback`` (which this -documentation covers). `Check out the migration guide `_ -for more information. If you are still using ``raven-logback`` you can -`find the old documentation here `_. - -Installation ------------- - -Using Maven: - -.. sourcecode:: xml - - - io.sentry - sentry-logback - 1.7.5 - - -Using Gradle: - -.. sourcecode:: groovy - - compile 'io.sentry:sentry-logback:1.7.5' - -Using SBT: - -.. sourcecode:: scala - - libraryDependencies += "io.sentry" % "sentry-logback" % "1.7.5" - -For other dependency managers see the `central Maven repository `_. - -Usage ------ - -The following example configures a ``ConsoleAppender`` that logs to standard out -at the ``INFO`` level and a ``SentryAppender`` that logs to the Sentry server at -the ``WARN`` level. The ``ConsoleAppender`` is only provided as an example of -a non-Sentry appender that is set to a different logging threshold, like one you -may already have in your project. - -Example configuration using the ``logback.xml`` format: - -.. sourcecode:: xml - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - WARN - - - - - - - - - - -Next, **you'll need to configure your DSN** (client key) and optionally other values such as -``environment`` and ``release``. :ref:`See the configuration page ` for ways you can do this. - -Additional Data ---------------- - -It's possible to add extra data to events thanks to `the MDC system provided by Logback -`_. - -Mapped Tags -~~~~~~~~~~~ - -By default all MDC parameters are stored under the "Additional Data" tab in Sentry. By -specifying the ``mdctags`` option in your configuration you can -choose which MDC keys to send as tags instead, which allows them to be used as -filters within the Sentry UI. - -.. sourcecode:: java - - void logWithExtras() { - // MDC extras - MDC.put("Environment", "Development"); - MDC.put("OS", "Linux"); - - // This sends an event where the Environment and OS MDC values are set as additional data - logger.error("This is a test"); - } - -In Practice ------------ - -.. sourcecode:: java - - import org.slf4j.Logger; - import org.slf4j.LoggerFactory; - import org.slf4j.MDC; - import org.slf4j.MarkerFactory; - - public class MyClass { - private static final Logger logger = LoggerFactory.getLogger(MyClass.class); - private static final Marker MARKER = MarkerFactory.getMarker("myMarker"); - - void logSimpleMessage() { - // This sends a simple event to Sentry - logger.error("This is a test"); - } - - void logWithBreadcrumbs() { - // Record a breadcrumb that will be sent with the next event(s), - // by default the last 100 breadcrumbs are kept. - Sentry.record( - new BreadcrumbBuilder().setMessage("User made an action").build() - ); - - // This sends a simple event to Sentry - logger.error("This is a test"); - } - - void logWithTag() { - // This sends an event with a tag named 'logback-Marker' to Sentry - logger.error(MARKER, "This is a test"); - } - - void logWithExtras() { - // MDC extras - MDC.put("extra_key", "extra_value"); - // This sends an event with extra data to Sentry - logger.error("This is a test"); - } - - void logException() { - try { - unsafeMethod(); - } catch (Exception e) { - // This sends an exception event to Sentry - logger.error("Exception caught", e); - } - } - - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call this!"); - } - } diff --git a/docs/modules/spring.rst b/docs/modules/spring.rst deleted file mode 100644 index faed5a2eafd..00000000000 --- a/docs/modules/spring.rst +++ /dev/null @@ -1,110 +0,0 @@ -Spring -====== - -The ``sentry-spring`` library provides `Spring `_ -support for Sentry via a `HandlerExceptionResolver -`_ -that sends exceptions to Sentry. Once this integration is configured -you can *also* use Sentry's static API, :ref:`as shown on the usage page `, -in order to do things like record breadcrumbs, set the current user, or manually send -events. - -The source can be found `on Github -`_. - -Important Note About Logging Integrations ------------------------------------------ - -**Note** that you should **not** configure ``sentry-spring`` -alongside a Sentry logging integration (such as ``sentry-logback``), -or you will most likely double-report exceptions. - -A Sentry logging integration is more general and will capture errors (and -possibly warnings, depending on your configuration) that occur inside *or outside* -of a Spring controller. In most scenarios, using one of the logging integrations -instead of ``sentry-spring`` is preferred. - -Installation ------------- - -Using Maven: - -.. sourcecode:: xml - - - io.sentry - sentry-spring - 1.7.5 - - -Using Gradle: - -.. sourcecode:: groovy - - compile 'io.sentry:sentry-spring:1.7.5' - -Using SBT: - -.. sourcecode:: scala - - libraryDependencies += "io.sentry" % "sentry-spring" % "1.7.5" - -For other dependency managers see the `central Maven repository `_. - -Usage ------ - -The ``sentry-spring`` library provides two classes that can be enabled by -registering them as Beans in your Spring application. - -Recording Exceptions -~~~~~~~~~~~~~~~~~~~~ - -In order to record all exceptions thrown by your controllers, you can register -``io.sentry.spring.SentryExceptionResolver`` as a Bean in your application. Once -registered, all exceptions will be sent to Sentry and then passed on to the default -exception handlers. - -Configuration via ``web.xml``: - -.. sourcecode:: xml - - - -Or via a configuration class: - -.. sourcecode:: java - - @Bean - public HandlerExceptionResolver sentryExceptionResolver() { - return new io.sentry.spring.SentryExceptionResolver(); - } - -Next, **you'll need to configure your DSN** (client key) and optionally other values such as -``environment`` and ``release``. :ref:`See the configuration page ` for ways you can do this. - -Spring Boot HTTP Data -~~~~~~~~~~~~~~~~~~~~~ - -Spring Boot doesn't automatically load any ``javax.servlet.ServletContainerInitializer``, -which means the Sentry SDK doesn't have an opportunity to hook into the request cycle -to collect information about the HTTP request. In order to add HTTP request data to -your Sentry events in Spring Boot, you need to register the -``io.sentry.spring.SentryServletContextInitializer`` class as a Bean in your application. - -Configuration via ``web.xml``: - -.. sourcecode:: xml - - - -Or via a configuration class: - -.. sourcecode:: java - - @Bean - public ServletContextInitializer sentryServletContextInitializer() { - return new io.sentry.spring.SentryServletContextInitializer(); - } - -After that, your Sentry events should contain information such as HTTP request headers. diff --git a/docs/sentry-doc-config.json b/docs/sentry-doc-config.json deleted file mode 100644 index 2cbb23ccfc5..00000000000 --- a/docs/sentry-doc-config.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "support_level": "production", - "platforms": { - "java": { - "name": "Java", - "type": "language", - "doc_link": "", - "wizard": [ - "usage#installation", - "usage#capture-an-error" - ] - }, - "java.logging": { - "name": "java.util.logging", - "type": "framework", - "doc_link": "modules/jul/", - "wizard": [ - "modules/jul#installation", - "modules/jul#usage" - ] - }, - "java.log4j": { - "name": "Log4j 1.x", - "type": "framework", - "doc_link": "modules/log4j/", - "wizard": [ - "modules/log4j#installation", - "modules/log4j#usage" - ] - }, - "java.log4j2": { - "name": "Log4j 2.x", - "type": "framework", - "doc_link": "modules/log4j2/", - "wizard": [ - "modules/log4j2#installation", - "modules/log4j2#usage" - ] - }, - "java.logback": { - "name": "Logback", - "type": "framework", - "doc_link": "modules/logback/", - "wizard": [ - "modules/logback#installation", - "modules/logback#usage" - ] - }, - "java.appengine": { - "name": "Google App Engine", - "type": "framework", - "doc_link": "modules/appengine/", - "wizard": [ - "modules/appengine#installation", - "modules/appengine#usage" - ] - }, - "java.android": { - "name": "Android", - "type": "framework", - "doc_link": "modules/android/", - "wizard": [ - "modules/android#installation", - "modules/android#usage" - ] - } - } -} diff --git a/docs/usage.rst b/docs/usage.rst deleted file mode 100644 index 4747e44509c..00000000000 --- a/docs/usage.rst +++ /dev/null @@ -1,237 +0,0 @@ -Manual Usage -============ - -**Note:** The following page provides examples on how to configure and use -Sentry directly. It is **highly recommended** that you use one of the -:ref:`provided integrations ` if possible. Once the integration -is configured you can *also* use Sentry's static API, as shown below, -in order to do things like record breadcrumbs, set the current user, or manually -send events. - -Installation ------------- - -Using Maven: - -.. sourcecode:: xml - - - io.sentry - sentry - 1.7.5 - - -Using Gradle: - -.. sourcecode:: groovy - - compile 'io.sentry:sentry:1.7.5' - -Using SBT: - -.. sourcecode:: scala - - libraryDependencies += "io.sentry" % "sentry" % "1.7.5" - -For other dependency managers see the `central Maven repository `_. - -Capture an Error ----------------- - -.. _usage_example: - -To report an event manually you need to initialize a ``SentryClient``. It is recommended -that you use the static API via the ``Sentry`` class, but you can also construct and manage -your own ``SentryClient`` instance. An example of each style is shown below: - -.. sourcecode:: java - - import io.sentry.context.Context; - import io.sentry.event.BreadcrumbBuilder; - import io.sentry.event.UserBuilder; - - public class MyClass { - private static SentryClient sentry; - - public static void main(String... args) { - /* - It is recommended that you use the DSN detection system, which - will check the environment variable "SENTRY_DSN", the Java - System Property "sentry.dsn", or the "sentry.properties" file - in your classpath. This makes it easier to provide and adjust - your DSN without needing to change your code. See the configuration - page for more information. - */ - Sentry.init(); - - // You can also manually provide the DSN to the ``init`` method. - String dsn = args[0]; - Sentry.init(dsn); - - /* - It is possible to go around the static ``Sentry`` API, which means - you are responsible for making the SentryClient instance available - to your code. - */ - sentry = SentryClientFactory.sentryClient(); - - MyClass myClass = new MyClass(); - myClass.logWithStaticAPI(); - myClass.logWithInstanceAPI(); - } - - /** - * An example method that throws an exception. - */ - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call this!"); - } - - /** - * Examples using the (recommended) static API. - */ - void logWithStaticAPI() { - // Note that all fields set on the context are optional. Context data is copied onto - // all future events in the current context (until the context is cleared). - - // Record a breadcrumb in the current context. By default the last 100 breadcrumbs are kept. - Sentry.getContext().recordBreadcrumb( - new BreadcrumbBuilder().setMessage("User made an action").build() - ); - - // Set the user in the current context. - Sentry.getContext().setUser( - new UserBuilder().setEmail("hello@sentry.io").build() - ); - - // Add extra data to future events in this context. - Sentry.getContext().addExtra("extra", "thing"); - - // Add an additional tag to future events in this context. - Sentry.getContext().addTag("tagName", "tagValue"); - - /* - This sends a simple event to Sentry using the statically stored instance - that was created in the ``main`` method. - */ - Sentry.capture("This is a test"); - - try { - unsafeMethod(); - } catch (Exception e) { - // This sends an exception event to Sentry using the statically stored instance - // that was created in the ``main`` method. - Sentry.capture(e); - } - } - - /** - * Examples that use the SentryClient instance directly. - */ - void logWithInstanceAPI() { - // Retrieve the current context. - Context context = sentry.getContext(); - - // Record a breadcrumb in the current context. By default the last 100 breadcrumbs are kept. - context.recordBreadcrumb(new BreadcrumbBuilder().setMessage("User made an action").build()); - - // Set the user in the current context. - context.setUser(new UserBuilder().setEmail("hello@sentry.io").build()); - - // This sends a simple event to Sentry. - sentry.sendMessage("This is a test"); - - try { - unsafeMethod(); - } catch (Exception e) { - // This sends an exception event to Sentry. - sentry.sendException(e); - } - } - } - -Building More Complex Events -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -For more complex messages, you'll need to build an ``Event`` with the -``EventBuilder`` class: - -.. sourcecode:: java - - import io.sentry.Sentry; - import io.sentry.event.Event; - import io.sentry.event.EventBuilder; - import io.sentry.event.interfaces.ExceptionInterface; - - public class MyClass { - public static void main(String... args) { - Sentry.init(); - } - - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call this!"); - } - - void logSimpleMessage() { - // This sends an event to Sentry. - EventBuilder eventBuilder = new EventBuilder() - .withMessage("This is a test") - .withLevel(Event.Level.INFO) - .withLogger(MyClass.class.getName()); - - // Note that the *unbuilt* EventBuilder instance is passed in so that - // EventBuilderHelpers are run to add extra information to your event. - Sentry.capture(eventBuilder); - } - - void logException() { - try { - unsafeMethod(); - } catch (Exception e) { - // This sends an exception event to Sentry. - EventBuilder eventBuilder = new EventBuilder() - .withMessage("Exception caught") - .withLevel(Event.Level.ERROR) - .withLogger(MyClass.class.getName()) - .withSentryInterface(new ExceptionInterface(e)); - - // Note that the *unbuilt* EventBuilder instance is passed in so that - // EventBuilderHelpers are run to add extra information to your event. - Sentry.capture(eventBuilder); - } - } - } - -Automatically Enhancing Events -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can also implement an ``EventBuilderHelper`` that is able to automatically -enhance outgoing events. - -.. sourcecode:: java - - import io.sentry.Sentry; - import io.sentry.SentryClient; - import io.sentry.event.EventBuilder; - import io.sentry.event.helper.EventBuilderHelper; - - public class MyClass { - public void myMethod() { - SentryClient client = Sentry.getStoredClient(); - - EventBuilderHelper myEventBuilderHelper = new EventBuilderHelper() { - @Override - public void helpBuildingEvent(EventBuilder eventBuilder) { - eventBuilder.withMessage("Overwritten by myEventBuilderHelper!"); - } - }; - - // Add an ``EventBuilderHelper`` to the current client instance. Note that - // this helper will process *all* future events. - client.addBuilderHelper(myEventBuilderHelper); - - // Send an event to Sentry. During construction of the event the message - // body will be overwritten by ``myEventBuilderHelper``. - Sentry.capture("Hello, world!"); - } - } From 33f8b7edb41879a9e669aa863a7abfbb79247aa0 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 30 Aug 2018 09:13:28 -0500 Subject: [PATCH 1925/2152] Fix JDK7 SSL for Maven Central. (#604) --- .travis.yml | 8 +++++--- Makefile | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 941bfbfa77f..6776a94fbf5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,14 +11,16 @@ matrix: - language: java dist: precise jdk: oraclejdk7 - script: env | sort && mvn verify + install: make install + script: env | sort && make verify cache: directories: - $HOME/.m2 - language: java dist: precise jdk: oraclejdk8 - script: env | sort && mvn verify + install: make install + script: env | sort && make verify cache: directories: - $HOME/.m2 @@ -44,4 +46,4 @@ deploy: script: .ci/agent-deploy.sh skip_cleanup: true on: - tags: true \ No newline at end of file + tags: true diff --git a/Makefile b/Makefile index 4b1ce2d1b33..917e2905803 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ .PHONY: checkstyle compile test install clean prepare prepareMvn prepareChanges perform verify # TODO: Fix to work between macOS and Linux -MVN=mvn -e +MVN=mvn -e -Dhttps.protocols=TLSv1.2 ECHO=echo SED=sed @@ -26,7 +26,7 @@ verify: test: verify install: - $(MVN) source:jar install -Dcheckstyle.skip=true -DskipTests + $(MVN) source:jar install -Dcheckstyle.skip=true -DskipTests -Dmaven.javadoc.skip=true -B -V clean: $(MVN) clean From c5cbdb6423efbaf4806e4e25280047872a10f8dd Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 4 Sep 2018 09:19:29 -0500 Subject: [PATCH 1926/2152] Fix CONTRIBUTING. --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d1195971e79..bf76825124e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ We love pull requests from everyone. -The test suite currently requires you run JDK version `1.7.5`. +The test suite currently requires you run JDK version `1.7.0_80`. See [#487](https://github.com/getsentry/sentry-java/issues/478) for more information. @@ -14,4 +14,4 @@ make test Tests are automatically run against branches and pull requests via TravisCI, so you can also depend on that if you'd rather not -deal with installing an older JDK. \ No newline at end of file +deal with installing an older JDK. From e9eb49629647e08ae506a895b9c4660e08df5ef0 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 4 Sep 2018 13:12:40 -0500 Subject: [PATCH 1927/2152] Handle exceptions in `toString` calls when marshalling arbitrary objects to JSON. --- CHANGES | 2 +- .../io/sentry/marshaller/json/SentryJsonGenerator.java | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index fd57684afb0..424478f22b6 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.7.6 ------------- -- +- Handle exceptions in `toString` calls when marshalling arbitrary objects to JSON. Version 1.7.5 ------------- diff --git a/sentry/src/main/java/io/sentry/marshaller/json/SentryJsonGenerator.java b/sentry/src/main/java/io/sentry/marshaller/json/SentryJsonGenerator.java index 0b409b54c68..0352627d351 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/SentryJsonGenerator.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/SentryJsonGenerator.java @@ -112,8 +112,12 @@ private void writeObject(Object value, int recursionLevel) throws IOException { generator.writeObject(value); } catch (IllegalStateException e) { logger.debug("Couldn't marshal '{}' of type '{}', had to be converted into a String", - value, value.getClass()); - generator.writeString(Util.trimString(value.toString(), maxLengthString)); + value, value.getClass()); + try { + generator.writeString(Util.trimString(value.toString(), maxLengthString)); + } catch (Exception innerE) { + generator.writeString(""); + } } } } From a62b55ff6dd6e33f23313cd347cf120dac463781 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 4 Sep 2018 13:13:15 -0500 Subject: [PATCH 1928/2152] Bump sentry-cli. --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 7dfc121b2e0..1fb285fb049 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.32.0 +VERSION=1.35.4 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* From d50743297eaef422b822d6c39f9e216dea51aa04 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 4 Sep 2018 13:21:31 -0500 Subject: [PATCH 1929/2152] [maven-release-plugin] prepare release v1.7.7 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 7cec153a3cd..a9b36be8189 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.6-SNAPSHOT + 1.7.7 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.7 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 84c8fc3f11c..6dd93e9ba1c 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.6-SNAPSHOT + 1.7.7 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index cd35e316cbe..baadd2ab702 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.6-SNAPSHOT + 1.7.7 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 5c0aa2b80f1..a29cdd67ab4 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.6-SNAPSHOT + 1.7.7 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index fdc3ad73c51..61542a261f8 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.6-SNAPSHOT + 1.7.7 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 97171bc9a70..f793d03a320 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.6-SNAPSHOT + 1.7.7 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index d4a2130b42f..bdcd575b7cc 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.6-SNAPSHOT + 1.7.7 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 9658e4d1b82..2f320780896 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.6-SNAPSHOT + 1.7.7 sentry From c82bbbbd2c7169d780f6363ac76a5a3df40ca1c3 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 4 Sep 2018 13:21:31 -0500 Subject: [PATCH 1930/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index a9b36be8189..708abb14c1d 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.7 + 1.7.8-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.7 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 6dd93e9ba1c..94851dfbe30 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.7 + 1.7.8-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index baadd2ab702..a5bccbc1c68 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.7 + 1.7.8-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index a29cdd67ab4..167197ed015 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.7 + 1.7.8-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 61542a261f8..84bf90ed6a4 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.7 + 1.7.8-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index f793d03a320..048dce9942a 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.7 + 1.7.8-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index bdcd575b7cc..811be21108c 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.7 + 1.7.8-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 2f320780896..4744dcb1625 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.7 + 1.7.8-SNAPSHOT sentry From 511745ecf099b928b5984bd5c865fdee8cd67c60 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 4 Sep 2018 13:21:33 -0500 Subject: [PATCH 1931/2152] Bump CHANGES to 1.7.8 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 424478f22b6..78a81912f98 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.8 +------------- + +- + Version 1.7.6 ------------- From f8864c00ab125ce7204d2ae3c71e771bfc8baf67 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 4 Sep 2018 13:26:45 -0500 Subject: [PATCH 1932/2152] Fix CHANGES. --- CHANGES | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 78a81912f98..f030f7fad2b 100644 --- a/CHANGES +++ b/CHANGES @@ -3,11 +3,16 @@ Version 1.7.8 - -Version 1.7.6 +Version 1.7.7 ------------- - Handle exceptions in `toString` calls when marshalling arbitrary objects to JSON. +Version 1.7.6 +------------- + +*Built with the incorrect version of Java, please skip this release* + Version 1.7.5 ------------- From ffd9fb6fc1f56815bdb11418d522b2599de34c27 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 4 Sep 2018 13:31:27 -0500 Subject: [PATCH 1933/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 090212926f3..2c947a8b32d 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.6-SNAPSHOT +version = 1.7.8-SNAPSHOT From a396eb0d3359d76219dbd4b5d1f958ed29e1ba71 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 10 Sep 2018 09:10:42 -0500 Subject: [PATCH 1934/2152] Fix Android Gradle Plugin for 3.3.0+ (#608) --- sentry-android-gradle-plugin/.gitignore | 4 ++++ sentry-android-gradle-plugin/build.gradle | 3 +++ .../main/groovy/io/sentry/android/gradle/SentryPlugin.groovy | 4 ++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/sentry-android-gradle-plugin/.gitignore b/sentry-android-gradle-plugin/.gitignore index 26cc88223fb..429a909cf3b 100644 --- a/sentry-android-gradle-plugin/.gitignore +++ b/sentry-android-gradle-plugin/.gitignore @@ -2,3 +2,7 @@ .idea/ build/ out/ +.classpath +.project +.settings/ +bin/ diff --git a/sentry-android-gradle-plugin/build.gradle b/sentry-android-gradle-plugin/build.gradle index 4a805baea1a..e7ef01d15a1 100644 --- a/sentry-android-gradle-plugin/build.gradle +++ b/sentry-android-gradle-plugin/build.gradle @@ -7,6 +7,9 @@ apply plugin: 'maven-publish' repositories { jcenter() + mavenLocal() + mavenCentral() + google() } compileGroovy { diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index a77080eecdc..41ed95889c4 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -152,12 +152,12 @@ class SentryPlugin implements Plugin { } catch (Exception ignored) { // Android Gradle Plugin >= 3.0.0 manifestPath = new File( - variantOutput.processManifest.manifestOutputDirectory, + variantOutput.processManifest.manifestOutputDirectory.toString(), "AndroidManifest.xml") if (!manifestPath.isFile()) { manifestPath = new File( new File( - variantOutput.processManifest.manifestOutputDirectory, + variantOutput.processManifest.manifestOutputDirectory.toString(), variantOutput.dirName), "AndroidManifest.xml") } From be6465e5624f715a36aa33c5496282b82fced3fa Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 10 Sep 2018 09:12:26 -0500 Subject: [PATCH 1935/2152] Update CHANGES. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index f030f7fad2b..aaea5dfdf8f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.7.8 ------------- -- +- Fix Android Gradle Plugin for >=3.3.0. Version 1.7.7 ------------- From eb378c32ee2b2bfccccc46cb196d9634a5691677 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 10 Sep 2018 09:14:05 -0500 Subject: [PATCH 1936/2152] [maven-release-plugin] prepare release v1.7.8 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 708abb14c1d..110cb93d16e 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.8-SNAPSHOT + 1.7.8 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.8 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 94851dfbe30..83dd719027d 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.8-SNAPSHOT + 1.7.8 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index a5bccbc1c68..5a48e78f9b8 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.8-SNAPSHOT + 1.7.8 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 167197ed015..9884200f507 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.8-SNAPSHOT + 1.7.8 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 84bf90ed6a4..c38526c9352 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.8-SNAPSHOT + 1.7.8 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 048dce9942a..b977025dc45 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.8-SNAPSHOT + 1.7.8 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 811be21108c..453eab15758 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.8-SNAPSHOT + 1.7.8 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 4744dcb1625..bf7c41fa7bb 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.8-SNAPSHOT + 1.7.8 sentry From 0b9fb4b16e82733469acb2db465b7049b23ac3fb Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 10 Sep 2018 09:14:05 -0500 Subject: [PATCH 1937/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 110cb93d16e..a71d7663bdf 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.8 + 1.7.9-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.8 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 83dd719027d..57ef1b0bbd5 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.8 + 1.7.9-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 5a48e78f9b8..6e0648dbf56 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.8 + 1.7.9-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 9884200f507..6cda61416d0 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.8 + 1.7.9-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index c38526c9352..bc1f49bd827 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.8 + 1.7.9-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index b977025dc45..cb774d31965 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.8 + 1.7.9-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 453eab15758..8337f5e30a2 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.8 + 1.7.9-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index bf7c41fa7bb..f89310b6c37 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.8 + 1.7.9-SNAPSHOT sentry From e6494bf13266a583d876b98d0a0b3569e151f511 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 10 Sep 2018 09:14:07 -0500 Subject: [PATCH 1938/2152] Bump CHANGES to 1.7.9 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index aaea5dfdf8f..c6ca114a5df 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.9 +------------- + +- + Version 1.7.8 ------------- From 27d5f6b8ec51bcf97f8d066abcb80e072652573b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 10 Sep 2018 09:35:41 -0500 Subject: [PATCH 1939/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 1fb285fb049..9bfd345ad5f 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.35.4 +VERSION=1.35.5 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 2c947a8b32d..5bea4d3ba65 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.8-SNAPSHOT +version = 1.7.9-SNAPSHOT From f5367934dc9946141d62ff287f9dd6e534d3b0fd Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 12 Sep 2018 11:03:38 -0500 Subject: [PATCH 1940/2152] =?UTF-8?q?Encode=20SDK=5FVERSION=20in=20a=20Jav?= =?UTF-8?q?a=20class=20instead=20of=20looking=20up=20a=20ResouceBun?= =?UTF-8?q?=E2=80=A6=20(#609)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES | 2 +- sentry/pom.xml | 13 +++++++++++++ .../io/sentry/environment/Version.java | 9 +++++++++ .../io/sentry/environment/SentryEnvironment.java | 3 +-- sentry/src/test/java/io/sentry/SentryTest.java | 5 +++-- .../sentry/connection/AbstractConnectionTest.java | 3 ++- src/checkstyle/suppressions.xml | 1 + 7 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 sentry/src/main/java-templates/io/sentry/environment/Version.java diff --git a/CHANGES b/CHANGES index c6ca114a5df..621d087fc09 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.7.9 ------------- -- +- Encode SDK_VERSION in a Java class instead of looking up a ResouceBundle. Version 1.7.8 ------------- diff --git a/sentry/pom.xml b/sentry/pom.xml index f89310b6c37..14962da063e 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -98,6 +98,19 @@ + + org.codehaus.mojo + templating-maven-plugin + 1.0.0 + + + filtering-java-templates + + filter-sources + + + + org.codehaus.mojo buildnumber-maven-plugin diff --git a/sentry/src/main/java-templates/io/sentry/environment/Version.java b/sentry/src/main/java-templates/io/sentry/environment/Version.java new file mode 100644 index 00000000000..f27c7458cc0 --- /dev/null +++ b/sentry/src/main/java-templates/io/sentry/environment/Version.java @@ -0,0 +1,9 @@ +package io.sentry.environment; + +public final class Version { + public static final String SDK_VERSION = "${buildNumber}"; + + private Version() { + + } +} diff --git a/sentry/src/main/java/io/sentry/environment/SentryEnvironment.java b/sentry/src/main/java/io/sentry/environment/SentryEnvironment.java index b406d8fc8fb..1e73b836bfc 100644 --- a/sentry/src/main/java/io/sentry/environment/SentryEnvironment.java +++ b/sentry/src/main/java/io/sentry/environment/SentryEnvironment.java @@ -3,7 +3,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ResourceBundle; import java.util.concurrent.atomic.AtomicInteger; /** @@ -20,7 +19,7 @@ public final class SentryEnvironment { /** * Version of this SDK. */ - public static final String SDK_VERSION = ResourceBundle.getBundle("sentry-build").getString("build.name"); + public static final String SDK_VERSION = Version.SDK_VERSION; /** * Indicates whether the current thread is managed by sentry or not. */ diff --git a/sentry/src/test/java/io/sentry/SentryTest.java b/sentry/src/test/java/io/sentry/SentryTest.java index 462c6edf48e..3b828ec77e3 100644 --- a/sentry/src/test/java/io/sentry/SentryTest.java +++ b/sentry/src/test/java/io/sentry/SentryTest.java @@ -6,6 +6,7 @@ import io.sentry.context.ContextManager; import io.sentry.context.SingletonContextManager; import io.sentry.dsn.Dsn; +import io.sentry.environment.Version; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.helper.EventBuilderHelper; @@ -141,7 +142,7 @@ public void testInitStringDsn() throws Exception { assertThat(sentryUrl.getPath(), equalTo("/api/1/store/")); String authHeader = getField(connection, "authHeader"); - assertThat(authHeader, equalTo("Sentry sentry_version=6,sentry_client=sentry-java/test,sentry_key=public,sentry_secret=private")); + assertThat(authHeader, equalTo("Sentry sentry_version=6,sentry_client=sentry-java/" + Version.SDK_VERSION + ",sentry_key=public,sentry_secret=private")); } @Test @@ -157,7 +158,7 @@ public void testInitStringDsnAndFactory() throws Exception { assertThat(sentryUrl.getPath(), equalTo("/api/1/store/")); String authHeader = getField(connection, "authHeader"); - assertThat(authHeader, equalTo("Sentry sentry_version=6,sentry_client=sentry-java/test,sentry_key=public,sentry_secret=private")); + assertThat(authHeader, equalTo("Sentry sentry_version=6,sentry_client=sentry-java/" + Version.SDK_VERSION + ",sentry_key=public,sentry_secret=private")); } @Test diff --git a/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java b/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java index 1d5ac9b8983..ff0e15a1a06 100644 --- a/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java @@ -1,6 +1,7 @@ package io.sentry.connection; import io.sentry.BaseTest; +import io.sentry.environment.Version; import io.sentry.time.FixedClock; import mockit.*; import io.sentry.event.Event; @@ -40,7 +41,7 @@ public void testAuthHeader() throws Exception { String authHeader = abstractConnection.getAuthHeader(); assertThat(authHeader, is("Sentry sentry_version=6," - + "sentry_client=sentry-java/test," + + "sentry_client=sentry-java/" + Version.SDK_VERSION + "," + "sentry_key=" + publicKey + "," + "sentry_secret=" + secretKey)); } diff --git a/src/checkstyle/suppressions.xml b/src/checkstyle/suppressions.xml index 8fabea49981..864cfc83f49 100644 --- a/src/checkstyle/suppressions.xml +++ b/src/checkstyle/suppressions.xml @@ -5,4 +5,5 @@ + From 4b3bdb3031451a83924a2582b13c53e991c1c28f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 12 Sep 2018 11:04:56 -0500 Subject: [PATCH 1941/2152] [maven-release-plugin] prepare release v1.7.9 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index a71d7663bdf..477a7ed5bb8 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.9-SNAPSHOT + 1.7.9 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.9 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 57ef1b0bbd5..e7bbf594203 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.9-SNAPSHOT + 1.7.9 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 6e0648dbf56..88fb498858c 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.9-SNAPSHOT + 1.7.9 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 6cda61416d0..9db9b01923b 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.9-SNAPSHOT + 1.7.9 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index bc1f49bd827..962a6943fd0 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.9-SNAPSHOT + 1.7.9 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index cb774d31965..a11fa0993bd 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.9-SNAPSHOT + 1.7.9 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 8337f5e30a2..e01fc3575f3 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.9-SNAPSHOT + 1.7.9 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 14962da063e..8d031e7e59b 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.9-SNAPSHOT + 1.7.9 sentry From 34cf13dee2f84964617a4e65e22498828f1746f3 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 12 Sep 2018 11:04:56 -0500 Subject: [PATCH 1942/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 477a7ed5bb8..8589d86c94b 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.9 + 1.7.10-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.9 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index e7bbf594203..b3b01bf618c 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.9 + 1.7.10-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 88fb498858c..54d7f6b3329 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.9 + 1.7.10-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 9db9b01923b..295a952b27d 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.9 + 1.7.10-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 962a6943fd0..c68b92b87ec 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.9 + 1.7.10-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index a11fa0993bd..03611e644a4 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.9 + 1.7.10-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index e01fc3575f3..040edfe9267 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.9 + 1.7.10-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 8d031e7e59b..15f7f0d4f5e 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.9 + 1.7.10-SNAPSHOT sentry From b2ef3e244f26e68487c7034e623a0e040e1d4427 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 12 Sep 2018 11:04:58 -0500 Subject: [PATCH 1943/2152] Bump CHANGES to 1.7.10 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 621d087fc09..51f60223890 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.10 +-------------- + +- + Version 1.7.9 ------------- From 9133ad6f96b64136d6e12f4808b2c63df1ce9836 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 12 Sep 2018 11:20:18 -0500 Subject: [PATCH 1944/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 5bea4d3ba65..902c0d5b6f8 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.9-SNAPSHOT +version = 1.7.10-SNAPSHOT From 784b6fb1a98c4ac57af34d1c34024b097d58b202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Nilsson=20=C3=96hrn?= Date: Tue, 18 Sep 2018 16:33:47 +0200 Subject: [PATCH 1945/2152] Android Plugin: Look for prop file directly under flavor dir (#612) --- .../main/groovy/io/sentry/android/gradle/SentryPlugin.groovy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 41ed95889c4..3ce2697c2f3 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -199,9 +199,11 @@ class SentryPlugin implements Plugin { def propName = "sentry.properties" def possibleProps = [ "${project.projectDir}/src/${variantName}/${propName}", + "${project.projectDir}/src/${flavorName}/${propName}", "${project.projectDir}/src/${variantName}/${flavorName}/${propName}", "${project.projectDir}/src/${flavorName}/${variantName}/${propName}", "${project.rootDir.toPath()}/src/${variantName}/${propName}", + "${project.rootDir.toPath()}/src/${flavorName}/${propName}", "${project.rootDir.toPath()}/src/${variantName}/${flavorName}/${propName}", "${project.rootDir.toPath()}/src/${flavorName}/${variantName}/${propName}", "${project.rootDir.toPath()}/${propName}" From 20e3e0b1d1e468cca7ac6d0506fde8d4b7550bae Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 18 Sep 2018 09:34:33 -0500 Subject: [PATCH 1946/2152] Update CHANGES. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 51f60223890..9b09ab9da75 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.7.10 -------------- -- +- Android Plugin: Look for properties file directly under flavor directory. (thanks biggestT) Version 1.7.9 ------------- From b5286b0cf280d61fe1fed36f4fc314b4402eadde Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 19 Sep 2018 13:39:35 -0500 Subject: [PATCH 1947/2152] Drop events when the server returns an HTTP 429. (#613) --- CHANGES | 1 + .../main/java/io/sentry/connection/BufferedConnection.java | 4 ++-- .../java/io/sentry/connection/BufferedConnectionTest.java | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 9b09ab9da75..af2914afe7f 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 1.7.10 -------------- - Android Plugin: Look for properties file directly under flavor directory. (thanks biggestT) +- Drop events when the server returns an HTTP 429. Version 1.7.9 ------------- diff --git a/sentry/src/main/java/io/sentry/connection/BufferedConnection.java b/sentry/src/main/java/io/sentry/connection/BufferedConnection.java index d904c36b0fe..0cd19a41a79 100644 --- a/sentry/src/main/java/io/sentry/connection/BufferedConnection.java +++ b/sentry/src/main/java/io/sentry/connection/BufferedConnection.java @@ -107,10 +107,10 @@ public void send(Event event) { boolean notSerializable = e.getCause() instanceof NotSerializableException; Integer responseCode = e.getResponseCode(); - if (notSerializable || (responseCode != null && responseCode != HttpConnection.HTTP_TOO_MANY_REQUESTS)) { + if (notSerializable || (responseCode != null)) { // don't retry events (discard from the buffer) if: // 1. they aren't serializable - // 2. the connection is up (valid response code was returned) and it's not an HTTP 429 + // 2. the connection is up (valid response code was returned) buffer.discard(event); } diff --git a/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java b/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java index 46d8a6fa08f..e0cc4e27f0f 100644 --- a/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java @@ -156,7 +156,7 @@ public void test500NotBuffered() throws Exception { } @Test - public void test429IsBuffered() throws Exception { + public void test429IsNotBuffered() throws Exception { Event event = new EventBuilder().build(); connectionException = new ConnectionException("429", new IOException(), null, HttpConnection.HTTP_TOO_MANY_REQUESTS); try { @@ -164,7 +164,7 @@ public void test429IsBuffered() throws Exception { } catch (Exception e) { } - assertThat(bufferedEvents.size(), equalTo(1)); + assertThat(bufferedEvents.size(), equalTo(0)); } @Test From 598d48327f69a53c86665aa227147da9daa89883 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 19 Sep 2018 13:42:48 -0500 Subject: [PATCH 1948/2152] [maven-release-plugin] prepare release v1.7.10 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 8589d86c94b..ad35d642c17 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.10-SNAPSHOT + 1.7.10 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.10 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index b3b01bf618c..f5c3564a13d 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.10-SNAPSHOT + 1.7.10 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 54d7f6b3329..338932260d5 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.10-SNAPSHOT + 1.7.10 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 295a952b27d..d592a603ed3 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.10-SNAPSHOT + 1.7.10 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index c68b92b87ec..d7dff3a37fe 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.10-SNAPSHOT + 1.7.10 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 03611e644a4..30ff9354933 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.10-SNAPSHOT + 1.7.10 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 040edfe9267..e5f6f65f212 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.10-SNAPSHOT + 1.7.10 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 15f7f0d4f5e..a701094ff3e 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.10-SNAPSHOT + 1.7.10 sentry From 4e5e011266d497d439b671c120e5b5f1238ee6be Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 19 Sep 2018 13:42:48 -0500 Subject: [PATCH 1949/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index ad35d642c17..38bb47ae304 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.10 + 1.7.11-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.10 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index f5c3564a13d..66eab64ea2c 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.10 + 1.7.11-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 338932260d5..6aeff9b79e6 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.10 + 1.7.11-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index d592a603ed3..8c8d7e32f18 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.10 + 1.7.11-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index d7dff3a37fe..befad1d2d5a 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.10 + 1.7.11-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 30ff9354933..e209ad99ffe 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.10 + 1.7.11-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index e5f6f65f212..0faca221a90 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.10 + 1.7.11-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index a701094ff3e..d803133145d 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.10 + 1.7.11-SNAPSHOT sentry From c6c9e20a322d70ad1ecadafce0c21a002bc8034c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 19 Sep 2018 13:42:50 -0500 Subject: [PATCH 1950/2152] Bump CHANGES to 1.7.11 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index af2914afe7f..f3d16cf2bdb 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.11 +-------------- + +- + Version 1.7.10 -------------- From 9293b44e02ee7c735e20398a717c6de7d0971ed6 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 19 Sep 2018 13:49:28 -0500 Subject: [PATCH 1951/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 902c0d5b6f8..a458009201f 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.10-SNAPSHOT +version = 1.7.11-SNAPSHOT From c4aa8f643ec2153acb16b48d96fb304542b7dd36 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 27 Sep 2018 10:38:29 -0500 Subject: [PATCH 1952/2152] Only set `release` and `dist` on Android events if the user didn't provide their own. Fixes GH-615. --- CHANGES | 2 +- .../android/event/helper/AndroidEventBuilderHelper.java | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index f3d16cf2bdb..8f7b217a80a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.7.11 -------------- -- +- Only set `release` and `dist` on Android events if the user didn't provide their own. Version 1.7.10 -------------- diff --git a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java index a6cca54723f..4cda4eeea89 100644 --- a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java +++ b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java @@ -56,8 +56,12 @@ public void helpBuildingEvent(EventBuilder eventBuilder) { eventBuilder.withSdkIntegration("android"); PackageInfo packageInfo = getPackageInfo(ctx); if (packageInfo != null) { - eventBuilder.withRelease(packageInfo.packageName + "-" + packageInfo.versionName); - eventBuilder.withDist(Integer.toString(packageInfo.versionCode)); + if (eventBuilder.getEvent().getRelease() == null) { + eventBuilder.withRelease(packageInfo.packageName + "-" + packageInfo.versionName); + } + if (eventBuilder.getEvent().getDist() == null) { + eventBuilder.withDist(Integer.toString(packageInfo.versionCode)); + } } String androidId = Settings.Secure.getString(ctx.getContentResolver(), Settings.Secure.ANDROID_ID); From c5002e9b58fa3312eaabef76c81f8aeefa249b50 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 2 Oct 2018 14:42:10 -0500 Subject: [PATCH 1953/2152] Add example picture to Agent README. (#619) --- agent/README.md | 4 +++- agent/example.png | Bin 0 -> 84125 bytes 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 agent/example.png diff --git a/agent/README.md b/agent/README.md index df658a1db02..92ddab8b58a 100644 --- a/agent/README.md +++ b/agent/README.md @@ -1,9 +1,11 @@ # sentry-java-agent The Sentry Java Agent collects and stores local variable information for each stack frame -when an exception is created. If local variable information is available the Sentry Java +when an exception is created. If local variable information is available the Sentry Java SDK will send the variable information along with events. Build: `cmake CMakeLists.txt && make` Run: `java -agentpath:libsentry_agent{.dylib,.so} ...` + +![Example of local variable state in the Sentry UI](/agent/example.png) diff --git a/agent/example.png b/agent/example.png new file mode 100644 index 0000000000000000000000000000000000000000..2fa43a5adcf9e839e1cb877f30a7b8536bf3c8f6 GIT binary patch literal 84125 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 literal 0 HcmV?d00001 From 17cbb8250ffca80eb006821c7adb1c2467796129 Mon Sep 17 00:00:00 2001 From: Nikos Linakis Date: Wed, 10 Oct 2018 16:59:53 +0300 Subject: [PATCH 1954/2152] Allow sentry to be included in library projects (#625) --- .../main/groovy/io/sentry/android/gradle/SentryPlugin.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 3ce2697c2f3..46a4d43c2c5 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -1,6 +1,7 @@ package io.sentry.android.gradle import com.android.build.gradle.AppPlugin +import com.android.build.gradle.LibraryPlugin import com.android.build.gradle.api.ApplicationVariant import org.apache.commons.compress.utils.IOUtils import org.gradle.api.Plugin @@ -139,7 +140,7 @@ class SentryPlugin implements Plugin { project.extensions.create("sentry", SentryPluginExtension) project.afterEvaluate { - if(!project.plugins.hasPlugin(AppPlugin)) { + if(!project.plugins.hasPlugin(AppPlugin) && !project.getPlugins().hasPlugin(LibraryPlugin)) { throw new IllegalStateException('Must apply \'com.android.application\' first!') } From 09644b57b88dc1a5664f71ebd254235d20ecdf22 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 10 Oct 2018 09:01:16 -0500 Subject: [PATCH 1955/2152] Update CHANGES. --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 8f7b217a80a..7af36e7f288 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 1.7.11 -------------- - Only set `release` and `dist` on Android events if the user didn't provide their own. +- Allow Gradle plugin to be included in library projects. (thanks linakis) Version 1.7.10 -------------- From 130812c795308584da3410c4271c5b5c4b59dcc8 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 10 Oct 2018 09:04:57 -0500 Subject: [PATCH 1956/2152] [maven-release-plugin] prepare release v1.7.11 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 38bb47ae304..55ee16b3538 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.11-SNAPSHOT + 1.7.11 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.11 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 66eab64ea2c..a13fe1aa04c 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.11-SNAPSHOT + 1.7.11 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 6aeff9b79e6..598332c8931 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.11-SNAPSHOT + 1.7.11 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 8c8d7e32f18..428b9ebecc7 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.11-SNAPSHOT + 1.7.11 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index befad1d2d5a..277524140da 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.11-SNAPSHOT + 1.7.11 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index e209ad99ffe..19102560844 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.11-SNAPSHOT + 1.7.11 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 0faca221a90..9fab0640ca6 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.11-SNAPSHOT + 1.7.11 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index d803133145d..5513a5f72f8 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.11-SNAPSHOT + 1.7.11 sentry From ae8a02750398ee14ab03109483e9d7ec0effee7a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 10 Oct 2018 09:04:58 -0500 Subject: [PATCH 1957/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 55ee16b3538..068397a693f 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.11 + 1.7.12-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.11 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index a13fe1aa04c..c31930f73a2 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.11 + 1.7.12-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 598332c8931..a428d2f1586 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.11 + 1.7.12-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 428b9ebecc7..95b7b072ebe 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.11 + 1.7.12-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 277524140da..5e34279fa66 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.11 + 1.7.12-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 19102560844..127c26b95da 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.11 + 1.7.12-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 9fab0640ca6..cf1fec6d104 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.11 + 1.7.12-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 5513a5f72f8..2f5ccfa5cdb 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.11 + 1.7.12-SNAPSHOT sentry From 0b03dd2c7bf1139c527449b66e8d740d42307caa Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 10 Oct 2018 09:04:59 -0500 Subject: [PATCH 1958/2152] Bump CHANGES to 1.7.12 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 7af36e7f288..484c45f8bfc 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.12 +-------------- + +- + Version 1.7.11 -------------- From c5210bdccae7a76c5782886f4bf260480d344290 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 10 Oct 2018 09:13:59 -0500 Subject: [PATCH 1959/2152] Update CHANGES. --- CHANGES | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 484c45f8bfc..1cb0e8bfe2f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,13 +1,13 @@ Version 1.7.12 -------------- -- +- Only set `release` and `dist` on Android events if the user didn't provide their own. +- Allow Gradle plugin to be included in library projects. (thanks linakis) Version 1.7.11 -------------- -- Only set `release` and `dist` on Android events if the user didn't provide their own. -- Allow Gradle plugin to be included in library projects. (thanks linakis) +*Accidentally republished 1.7.10, please skip this release* Version 1.7.10 -------------- From e03ba08f7263c5755c7fc343d7ba918a750e0d4c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 10 Oct 2018 09:14:56 -0500 Subject: [PATCH 1960/2152] [maven-release-plugin] prepare release v1.7.12 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 068397a693f..4ebc69acc44 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.12-SNAPSHOT + 1.7.12 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.12 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index c31930f73a2..6b8c0d14bd6 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.12-SNAPSHOT + 1.7.12 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index a428d2f1586..c98ecc2cdbe 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.12-SNAPSHOT + 1.7.12 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 95b7b072ebe..49ac279dcca 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.12-SNAPSHOT + 1.7.12 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 5e34279fa66..34c8a340c14 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.12-SNAPSHOT + 1.7.12 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 127c26b95da..6ab7cf5594c 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.12-SNAPSHOT + 1.7.12 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index cf1fec6d104..94bc368b3eb 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.12-SNAPSHOT + 1.7.12 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 2f5ccfa5cdb..678a965059a 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.12-SNAPSHOT + 1.7.12 sentry From 4852165923276813e918122518cf813b5c7f58d0 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 10 Oct 2018 09:14:56 -0500 Subject: [PATCH 1961/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 4ebc69acc44..4ef6244f0f1 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.12 + 1.7.13-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.12 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 6b8c0d14bd6..148fffc2d1c 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.12 + 1.7.13-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index c98ecc2cdbe..18ca086303f 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.12 + 1.7.13-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 49ac279dcca..83d593c1aaa 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.12 + 1.7.13-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 34c8a340c14..2a239ac33ce 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.12 + 1.7.13-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 6ab7cf5594c..10b0d09eafe 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.12 + 1.7.13-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 94bc368b3eb..251b7a8af90 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.12 + 1.7.13-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 678a965059a..56f70f132d8 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.12 + 1.7.13-SNAPSHOT sentry From 3e7fca26b0825b2eaf320954c109853f74f7f511 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 10 Oct 2018 09:14:58 -0500 Subject: [PATCH 1962/2152] Bump CHANGES to 1.7.13 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 1cb0e8bfe2f..b941126f022 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.13 +-------------- + +- + Version 1.7.12 -------------- From 2a24a67a14815ef75da673386710ff2554c53d76 Mon Sep 17 00:00:00 2001 From: Cezary Butler Date: Wed, 17 Oct 2018 13:35:53 +0200 Subject: [PATCH 1963/2152] Fix compatibility with Gradle 4.4+ (#621) (thanks cezary-butler) Without the fix android project build with sentry-android-gradle-plugin-enabled produces error: No such property: autoProguardConfig for class: org.codehaus.groovy.runtime.GStringImpl --- .../groovy/io/sentry/android/gradle/SentryPlugin.groovy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 46a4d43c2c5..640c5db0a78 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -137,7 +137,7 @@ class SentryPlugin implements Plugin { } void apply(Project project) { - project.extensions.create("sentry", SentryPluginExtension) + SentryPluginExtension extension = project.extensions.create("sentry", SentryPluginExtension) project.afterEvaluate { if(!project.plugins.hasPlugin(AppPlugin) && !project.getPlugins().hasPlugin(LibraryPlugin)) { @@ -173,7 +173,7 @@ class SentryPlugin implements Plugin { } // create a task to configure proguard automatically unless the user disabled it. - if (project.sentry.autoProguardConfig) { + if (extension.autoProguardConfig) { def addProguardSettingsTaskName = "addSentryProguardSettingsFor${variant.name.capitalize()}" if (!project.tasks.findByName(addProguardSettingsTaskName)) { SentryProguardConfigTask proguardConfigTask = project.tasks.create( @@ -231,7 +231,7 @@ class SentryPlugin implements Plugin { mappingFile ] - if (!project.sentry.autoUpload) { + if (!extension.autoUpload) { args.push("--no-upload") } From 0626077a0a5316cef31d1f367a0c5fe3b6b02149 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 19 Oct 2018 12:33:31 -0500 Subject: [PATCH 1964/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 9bfd345ad5f..044aa99d1db 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.35.5 +VERSION=1.36.1 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index a458009201f..80ba35c8312 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.11-SNAPSHOT +version = 1.7.13-SNAPSHOT From ecdb03ec6ff5ce96dc74328e189fc53e5fa20832 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 23 Oct 2018 08:48:39 -0700 Subject: [PATCH 1965/2152] Bump sentry-cli --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 044aa99d1db..7a243fd9f7e 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.36.1 +VERSION=1.36.3 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* From 1c5c96b6d3669fd32e005211223cd1364ddc3f63 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 23 Oct 2018 08:49:18 -0700 Subject: [PATCH 1966/2152] Update CHANGES. --- CHANGES | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index b941126f022..060a7a9f435 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,8 @@ Version 1.7.13 -------------- -- +- Fix compatibility with Gradle 4.4+ (#621) (thanks cezary-butler) +- Bump Sentry Gradle Plugin embedded sentry-cli to 1.36.3 Version 1.7.12 -------------- From 1c2691446d8b809e9f8057fd49a8fb31c988f026 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 23 Oct 2018 08:52:38 -0700 Subject: [PATCH 1967/2152] [maven-release-plugin] prepare release v1.7.13 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 4ef6244f0f1..00f106c638e 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.13-SNAPSHOT + 1.7.13 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.13 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 148fffc2d1c..febe0d27bc6 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.13-SNAPSHOT + 1.7.13 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 18ca086303f..eb523c38750 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.13-SNAPSHOT + 1.7.13 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 83d593c1aaa..db028774c99 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.13-SNAPSHOT + 1.7.13 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 2a239ac33ce..88756c29b9f 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.13-SNAPSHOT + 1.7.13 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 10b0d09eafe..8aebb75e8a9 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.13-SNAPSHOT + 1.7.13 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 251b7a8af90..9da08699e74 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.13-SNAPSHOT + 1.7.13 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 56f70f132d8..cffdb09deb2 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.13-SNAPSHOT + 1.7.13 sentry From 8bbfe34262ee8b63acb35e52a2b5cea6fff8f7e1 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 23 Oct 2018 08:52:38 -0700 Subject: [PATCH 1968/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 00f106c638e..69b26d5f2d1 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.13 + 1.7.14-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.13 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index febe0d27bc6..17fd79ae2ce 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.13 + 1.7.14-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index eb523c38750..f25293fd267 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.13 + 1.7.14-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index db028774c99..b45ee7c1ea8 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.13 + 1.7.14-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 88756c29b9f..4b2b040f8ba 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.13 + 1.7.14-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 8aebb75e8a9..665edbc8462 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.13 + 1.7.14-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 9da08699e74..9d9fa369379 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.13 + 1.7.14-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index cffdb09deb2..de934731f73 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.13 + 1.7.14-SNAPSHOT sentry From ff4c5553b6f8f0a3cff44387f767a0c5cd4b2c60 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 23 Oct 2018 08:52:40 -0700 Subject: [PATCH 1969/2152] Bump CHANGES to 1.7.14 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 060a7a9f435..aa3fda32331 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.14 +-------------- + +- + Version 1.7.13 -------------- From fdee75b2682c74935d947940006e4f3c21c2e91c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 23 Oct 2018 09:09:18 -0700 Subject: [PATCH 1970/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 80ba35c8312..937577527cf 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.13-SNAPSHOT +version = 1.7.14-SNAPSHOT From 5be007328d2ae0352aeddd61e146d27a4fab184d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 1 Nov 2018 09:51:16 -0500 Subject: [PATCH 1971/2152] Fix Sentry Gradle Plugin asset directory so Proguard debug information is written to the correct location when using newer versions of the Android Gradle Plugin. (#632) --- CHANGES | 4 +++- .../main/groovy/io/sentry/android/gradle/SentryPlugin.groovy | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index aa3fda32331..871be319aad 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,9 @@ Version 1.7.14 -------------- -- +- Fix Sentry Gradle Plugin asset directory so Proguard debug information + is written to the correct location when using newer versions of the + Android Gradle Plugin. Version 1.7.13 -------------- diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 640c5db0a78..5132e343f51 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -133,7 +133,7 @@ class SentryPlugin implements Plugin { * @return */ static String getDebugMetaPropPath(Project project, ApplicationVariant variant) { - return "${project.buildDir}/intermediates/assets/${variant.dirName}/sentry-debug-meta.properties" + return "${variant.mergeAssets.outputDir}/sentry-debug-meta.properties" } void apply(Project project) { From 5cd48a225b8e521452f4d4903da9011ff57afbd9 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 1 Nov 2018 09:51:45 -0500 Subject: [PATCH 1972/2152] Bump sentry-cli. --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 7a243fd9f7e..32653e71416 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.36.3 +VERSION=1.36.4 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* From 548f5659c8acacdde087f115e74e05d406768677 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 1 Nov 2018 10:00:52 -0500 Subject: [PATCH 1973/2152] [maven-release-plugin] prepare release v1.7.14 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 69b26d5f2d1..b8d71f31516 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.14-SNAPSHOT + 1.7.14 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.14 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 17fd79ae2ce..26910b257ac 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.14-SNAPSHOT + 1.7.14 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index f25293fd267..1fc47c231f8 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.14-SNAPSHOT + 1.7.14 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index b45ee7c1ea8..16b2a758f00 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.14-SNAPSHOT + 1.7.14 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 4b2b040f8ba..8fc199d3a05 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.14-SNAPSHOT + 1.7.14 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 665edbc8462..4c72c5c2c85 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.14-SNAPSHOT + 1.7.14 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 9d9fa369379..7aaf4ede08d 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.14-SNAPSHOT + 1.7.14 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index de934731f73..5f198937d57 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.14-SNAPSHOT + 1.7.14 sentry From feb5a1154281db41a53cce6bd21131e2ae48923b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 1 Nov 2018 10:00:52 -0500 Subject: [PATCH 1974/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index b8d71f31516..be454411a8a 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.14 + 1.7.15-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.14 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 26910b257ac..e0163124ce1 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.14 + 1.7.15-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 1fc47c231f8..52fb1057e46 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.14 + 1.7.15-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 16b2a758f00..8675f99fede 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.14 + 1.7.15-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 8fc199d3a05..109d205e85d 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.14 + 1.7.15-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 4c72c5c2c85..d19a5834fd6 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.14 + 1.7.15-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 7aaf4ede08d..8867105a337 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.14 + 1.7.15-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 5f198937d57..d015c521b04 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.14 + 1.7.15-SNAPSHOT sentry From 05089b4a01a24062caf444df03e5ca38bff46b84 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 1 Nov 2018 10:00:54 -0500 Subject: [PATCH 1975/2152] Bump CHANGES to 1.7.15 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 871be319aad..b08d3f77d7b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.15 +-------------- + +- + Version 1.7.14 -------------- From c58cbdedee5b5cfbeb28cfd4cf590a17aeab5c25 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 1 Nov 2018 10:12:58 -0500 Subject: [PATCH 1976/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 937577527cf..6f2f66d8653 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.14-SNAPSHOT +version = 1.7.15-SNAPSHOT From b30dab49299ca98108f0e5f3c11c9128bbcef972 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 14 Nov 2018 14:52:54 -0600 Subject: [PATCH 1977/2152] Allow users to provide a custom in the Sentry Gradle Plugin. (#638) --- CHANGES | 2 +- .../sentry/android/gradle/SentryPlugin.groovy | 28 ++++++++++--------- .../gradle/SentryPluginExtension.groovy | 1 + 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/CHANGES b/CHANGES index b08d3f77d7b..40920beca2d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.7.15 -------------- -- +- Allow users to provide a custom `manifestPath` in the Sentry Gradle Plugin. Version 1.7.14 -------------- diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 5132e343f51..a768f676cc8 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -146,21 +146,23 @@ class SentryPlugin implements Plugin { project.android.applicationVariants.all { ApplicationVariant variant -> variant.outputs.each { variantOutput -> - def manifestPath - try { - // Android Gradle Plugin < 3.0.0 - manifestPath = variantOutput.processManifest.manifestOutputFile - } catch (Exception ignored) { - // Android Gradle Plugin >= 3.0.0 - manifestPath = new File( - variantOutput.processManifest.manifestOutputDirectory.toString(), - "AndroidManifest.xml") - if (!manifestPath.isFile()) { + def manifestPath = extension.manifestPath + if (manifestPath == null) { + try { + // Android Gradle Plugin < 3.0.0 + manifestPath = variantOutput.processManifest.manifestOutputFile + } catch (Exception ignored) { + // Android Gradle Plugin >= 3.0.0 manifestPath = new File( - new File( - variantOutput.processManifest.manifestOutputDirectory.toString(), - variantOutput.dirName), + variantOutput.processManifest.manifestOutputDirectory.toString(), "AndroidManifest.xml") + if (!manifestPath.isFile()) { + manifestPath = new File( + new File( + variantOutput.processManifest.manifestOutputDirectory.toString(), + variantOutput.dirName), + "AndroidManifest.xml") + } } } diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy index 11100be887e..ee38db335e1 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy @@ -3,4 +3,5 @@ package io.sentry.android.gradle class SentryPluginExtension { def boolean autoProguardConfig = true; def boolean autoUpload = true; + def String manifestPath = null; } From 5e08a27b762e12e6c00fb3f0a37800c8c51729b8 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 14 Nov 2018 14:56:14 -0600 Subject: [PATCH 1978/2152] [maven-release-plugin] prepare release v1.7.15 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index be454411a8a..db59ee30887 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.15-SNAPSHOT + 1.7.15 pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.15 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index e0163124ce1..796004f3673 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.15-SNAPSHOT + 1.7.15 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 52fb1057e46..3941c2dcb1f 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.15-SNAPSHOT + 1.7.15 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 8675f99fede..655047669a7 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.15-SNAPSHOT + 1.7.15 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 109d205e85d..00e6423bd8f 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.15-SNAPSHOT + 1.7.15 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index d19a5834fd6..42d1139120a 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.15-SNAPSHOT + 1.7.15 sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 8867105a337..1cc81238d77 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.15-SNAPSHOT + 1.7.15 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index d015c521b04..d7762f70534 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.15-SNAPSHOT + 1.7.15 sentry From 31f269a0598d766d1927a7766cc8d664495a5bd2 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 14 Nov 2018 14:56:14 -0600 Subject: [PATCH 1979/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index db59ee30887..9423909cd06 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.15 + 1.7.16-SNAPSHOT pom Sentry-Java @@ -86,7 +86,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.15 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 796004f3673..e76b11692b6 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.15 + 1.7.16-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 3941c2dcb1f..ce796b08ea3 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.15 + 1.7.16-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 655047669a7..c42e2419bed 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.15 + 1.7.16-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 00e6423bd8f..e530390fb5e 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.15 + 1.7.16-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 42d1139120a..9eedf7b6b13 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.15 + 1.7.16-SNAPSHOT sentry-logback diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 1cc81238d77..dc86d34070b 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.15 + 1.7.16-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index d7762f70534..f0f6252c6d2 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.15 + 1.7.16-SNAPSHOT sentry From 707c1f46156a05a1aec6f49d31e66b3df41681f3 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 14 Nov 2018 14:56:17 -0600 Subject: [PATCH 1980/2152] Bump CHANGES to 1.7.16 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 40920beca2d..093f0e8768a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.16 +-------------- + +- + Version 1.7.15 -------------- From 9b4b92321e9d23bab45e0646aa81e2d840b74a10 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 14 Nov 2018 15:04:42 -0600 Subject: [PATCH 1981/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 6f2f66d8653..c976e69a979 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.15-SNAPSHOT +version = 1.7.16-SNAPSHOT From 16977570ca8f312c7559cfdfa414dea634868af8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez=20Gonzales?= Date: Fri, 16 Nov 2018 12:30:47 -0500 Subject: [PATCH 1982/2152] Add new spring boot starter module (#636) This new module add all the configuration needed to integrate web projects with sentry. --- pom.xml | 1 + sentry-spring-boot-starter/pom.xml | 50 ++++++++++++++ .../SentryAutoConfiguration.java | 33 ++++++++++ ...itional-spring-configuration-metadata.json | 10 +++ .../main/resources/META-INF/spring.factories | 2 + .../SentryAutoConfigurationTest.java | 65 +++++++++++++++++++ 6 files changed, 161 insertions(+) create mode 100644 sentry-spring-boot-starter/pom.xml create mode 100644 sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryAutoConfiguration.java create mode 100644 sentry-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 sentry-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 sentry-spring-boot-starter/src/test/java/io/sentry/spring/autoconfigure/SentryAutoConfigurationTest.java diff --git a/pom.xml b/pom.xml index 9423909cd06..a1b2860d658 100644 --- a/pom.xml +++ b/pom.xml @@ -80,6 +80,7 @@ sentry-logback sentry-log4j2 sentry-spring + sentry-spring-boot-starter diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml new file mode 100644 index 00000000000..3dd0b41f861 --- /dev/null +++ b/sentry-spring-boot-starter/pom.xml @@ -0,0 +1,50 @@ + + + + 4.0.0 + + + sentry-all + io.sentry + 1.7.16-SNAPSHOT + + + sentry-spring-boot-starter + jar + + + + org.springframework.boot + spring-boot-autoconfigure + + + org.springframework.boot + spring-boot-starter-web + + + io.sentry + sentry-spring + 1.7.16-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-test + + + + + + + org.springframework.boot + spring-boot-dependencies + 1.5.17.RELEASE + pom + import + + + + + diff --git a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryAutoConfiguration.java b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryAutoConfiguration.java new file mode 100644 index 00000000000..d121b790651 --- /dev/null +++ b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryAutoConfiguration.java @@ -0,0 +1,33 @@ +package io.sentry.spring.autoconfigure; + +import io.sentry.spring.SentryExceptionResolver; +import io.sentry.spring.SentryServletContextInitializer; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.web.servlet.ServletContextInitializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.HandlerExceptionResolver; + +@Configuration +@ConditionalOnClass({ HandlerExceptionResolver.class, SentryExceptionResolver.class }) +@ConditionalOnWebApplication +@ConditionalOnProperty(name = "sentry.enabled", havingValue = "true", matchIfMissing = true) +public class SentryAutoConfiguration { + + @Bean + @ConditionalOnMissingBean(SentryExceptionResolver.class) + public HandlerExceptionResolver sentryExceptionResolver() { + return new SentryExceptionResolver(); + } + + @Bean + @ConditionalOnMissingBean(SentryServletContextInitializer.class) + public ServletContextInitializer sentryServletContextInitializer() { + return new SentryServletContextInitializer(); + } + +} diff --git a/sentry-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/sentry-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 00000000000..6a21f452e32 --- /dev/null +++ b/sentry-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,10 @@ +{ + "properties": [ + { + "name": "sentry.enabled", + "type": "java.lang.Boolean", + "description": "Whether to enable sentry.", + "defaultValue": "true" + } + ] +} diff --git a/sentry-spring-boot-starter/src/main/resources/META-INF/spring.factories b/sentry-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000000..15daf449c07 --- /dev/null +++ b/sentry-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +io.sentry.spring.autoconfigure.SentryAutoConfiguration diff --git a/sentry-spring-boot-starter/src/test/java/io/sentry/spring/autoconfigure/SentryAutoConfigurationTest.java b/sentry-spring-boot-starter/src/test/java/io/sentry/spring/autoconfigure/SentryAutoConfigurationTest.java new file mode 100644 index 00000000000..758ba580c1e --- /dev/null +++ b/sentry-spring-boot-starter/src/test/java/io/sentry/spring/autoconfigure/SentryAutoConfigurationTest.java @@ -0,0 +1,65 @@ +package io.sentry.spring.autoconfigure; + +import io.sentry.spring.SentryExceptionResolver; +import io.sentry.spring.SentryServletContextInitializer; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; +import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; +import org.springframework.boot.test.util.EnvironmentTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SentryAutoConfigurationTest { + + private AnnotationConfigEmbeddedWebApplicationContext context; + + @Before + public void setup() { + if (this.context == null) { + this.context = new AnnotationConfigEmbeddedWebApplicationContext(); + } + } + + @After + public void teardown() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void testSentryExceptionResolverIsAvailable() { + load(); + this.context.refresh(); + String[] exceptionResolverBeans = this.context + .getBeanNamesForType(SentryExceptionResolver.class); + String[] servletContextInitialerBeans = this.context + .getBeanNamesForType(SentryServletContextInitializer.class); + + assertThat(exceptionResolverBeans).contains("sentryExceptionResolver"); + assertThat(servletContextInitialerBeans) + .contains("sentryServletContextInitializer"); + } + + @Test + public void testSentryAutoConfigurationIsEnabled() { + load(); + EnvironmentTestUtils.addEnvironment(this.context, "sentry.enabled:false"); + this.context.refresh(); + String[] beans = this.context.getBeanNamesForType(SentryExceptionResolver.class); + assertThat(beans).isEmpty(); + } + + private void load() { + this.context.register(EmbeddedServletContainerAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, + SentryAutoConfiguration.class); + } + +} From aae6976613d04e50b6fb56baf6c56c48691553e0 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 16 Nov 2018 11:31:49 -0600 Subject: [PATCH 1983/2152] Update CHANGES. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 093f0e8768a..51aab46bc9b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.7.16 -------------- -- +- Add `sentry-spring-boot-starter` module that autoconfigures Sentry for Spring-Boot. (thanks eddumelendez) Version 1.7.15 -------------- From 35f34664840a9cdf55a756962841cdb8a0268b96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez=20Gonzales?= Date: Sun, 18 Nov 2018 11:34:15 -0500 Subject: [PATCH 1984/2152] Add missing scope for spring-boot-starter-test dependency (#641) --- sentry-spring-boot-starter/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 3dd0b41f861..faafce1d0ac 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -32,6 +32,7 @@ org.springframework.boot spring-boot-starter-test + test From 3146ddb6995b5ef063379de97f1cb6fdca961208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez=20Gonzales?= Date: Mon, 19 Nov 2018 10:39:58 -0500 Subject: [PATCH 1985/2152] Add spring boot configuration processor dependency (#642) This dependency generate metadata needed to help Spring Boot with the startup time. --- sentry-spring-boot-starter/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index faafce1d0ac..ad50f89c44f 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -29,6 +29,12 @@ 1.7.16-SNAPSHOT + + org.springframework.boot + spring-boot-autoconfigure-processor + true + + org.springframework.boot spring-boot-starter-test From 551857aa05484f20db69195d34d16de0bf62ec3b Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 29 Nov 2018 08:34:35 -0600 Subject: [PATCH 1986/2152] Check for stored SentryClient before caching any frames. (#624) --- .gitignore | 1 + agent/agent.cpp | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index bfd95355746..4c34a505990 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ target/ infer-out/ agent/build/ +agent/.vscode/ local.properties diff --git a/agent/agent.cpp b/agent/agent.cpp index b6a9b8d0cb5..bfcafe443af 100644 --- a/agent/agent.cpp +++ b/agent/agent.cpp @@ -10,14 +10,23 @@ extern Level LOG_LEVEL; -static bool INIT_SUCCESS = false; - static void JNICALL ExceptionCallback(jvmtiEnv *jvmti, JNIEnv *env, jthread thread, jmethodID method, jlocation location, jobject exception, jmethodID catch_method, jlocation catch_location) { log(TRACE, "ExceptionCallback called."); - if (!INIT_SUCCESS) { + jclass sentry_class = env->FindClass("io/sentry/Sentry"); + if (sentry_class == nullptr) { + env->ExceptionClear(); + log(TRACE, "Unable to locate Sentry class."); + return; + } + + jfieldID storedClientId = env->GetStaticFieldID(sentry_class, "storedClient", "Lio/sentry/SentryClient;"); + jobject storedClientVal = env->GetStaticObjectField(sentry_class, storedClientId); + if (storedClientVal == nullptr) { + env->ExceptionClear(); + log(TRACE, "No stored SentryClient."); return; } @@ -125,7 +134,6 @@ JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { return JNI_ABORT; } - INIT_SUCCESS = true; log(TRACE, "OnLoad exit."); return JNI_OK; } From 10da2f6ee67a957e0be97cf17703e4614eb7c71d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 5 Dec 2018 13:35:32 -0600 Subject: [PATCH 1987/2152] Fix timestamp format locale to be what Sentry expects. --- CHANGES | 1 + .../src/main/java/io/sentry/marshaller/json/JsonMarshaller.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 51aab46bc9b..1fec4fb4125 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ Version 1.7.16 -------------- +- Fix timestamp format locale to be what Sentry expects. - Add `sentry-spring-boot-starter` module that autoconfigures Sentry for Spring-Boot. (thanks eddumelendez) Version 1.7.15 diff --git a/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java b/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java index a39d27ee0b3..146499dacac 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java @@ -114,7 +114,7 @@ public class JsonMarshaller implements Marshaller { private static final ThreadLocal ISO_FORMAT = new ThreadLocal() { @Override protected DateFormat initialValue() { - DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); return dateFormat; } From 2849dca68cf57c229624b054b1482c1361dbc53d Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Fri, 7 Dec 2018 21:45:20 +0100 Subject: [PATCH 1988/2152] Get the actual file out of the Provider type to get the correct manifest path (closes #644) (#645) --- .../sentry/android/gradle/SentryPlugin.groovy | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index a768f676cc8..6fcf6b8a6a4 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -153,16 +153,18 @@ class SentryPlugin implements Plugin { manifestPath = variantOutput.processManifest.manifestOutputFile } catch (Exception ignored) { // Android Gradle Plugin >= 3.0.0 - manifestPath = new File( - variantOutput.processManifest.manifestOutputDirectory.toString(), - "AndroidManifest.xml") - if (!manifestPath.isFile()) { - manifestPath = new File( - new File( - variantOutput.processManifest.manifestOutputDirectory.toString(), - variantOutput.dirName), - "AndroidManifest.xml") + def outputDir = variantOutput.processManifest.manifestOutputDirectory + // Gradle 4.7 introduced the lazy task API and AGP 3.3+ adopts that, + // so we apparently have a Provider here instead + // TODO: This will let us depend on the configuration of each flavor's + // manifest creation task and their transitive dependencies, which in + // turn prolongs the configuration time accordingly. Evaluate how Gradle's + // new Task Avoidance API can be used instead. + // (https://docs.gradle.org/current/userguide/task_configuration_avoidance.html) + if (!(outputDir instanceof File)) { + outputDir = outputDir.get().asFile } + manifestPath = new File(outputDir, "AndroidManifest.xml") } } From cf330821f60a45e13f7d282c60a8264c38e7284e Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 7 Dec 2018 14:46:35 -0600 Subject: [PATCH 1989/2152] Update CHANGES. --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 1fec4fb4125..fa5dcd41328 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ Version 1.7.16 - Fix timestamp format locale to be what Sentry expects. - Add `sentry-spring-boot-starter` module that autoconfigures Sentry for Spring-Boot. (thanks eddumelendez) +- Fix Sentry Gradle Plugin for AGP 3.3 and Gradle 4.10 (thanks realdadfish) Version 1.7.15 -------------- From 9b60b8f2edf8236c72a9a8f83c92cec458efba2c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 7 Dec 2018 14:48:01 -0600 Subject: [PATCH 1990/2152] [maven-release-plugin] prepare release v1.7.16 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 8 +++----- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 12 insertions(+), 14 deletions(-) diff --git a/pom.xml b/pom.xml index a1b2860d658..57b375f6068 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.16-SNAPSHOT + 1.7.16 pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.16 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index e76b11692b6..58dcbfe90d9 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.16-SNAPSHOT + 1.7.16 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index ce796b08ea3..1a3ab61e57b 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.16-SNAPSHOT + 1.7.16 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index c42e2419bed..34f5292d310 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.16-SNAPSHOT + 1.7.16 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index e530390fb5e..c49cda3d3ae 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.16-SNAPSHOT + 1.7.16 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 9eedf7b6b13..2fc25ec5684 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.16-SNAPSHOT + 1.7.16 sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index ad50f89c44f..1b89f18b77d 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -1,14 +1,12 @@ - + 4.0.0 sentry-all io.sentry - 1.7.16-SNAPSHOT + 1.7.16 sentry-spring-boot-starter @@ -26,7 +24,7 @@ io.sentry sentry-spring - 1.7.16-SNAPSHOT + 1.7.16 diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index dc86d34070b..11d91e20eb2 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.16-SNAPSHOT + 1.7.16 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index f0f6252c6d2..e9d66fcee67 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.16-SNAPSHOT + 1.7.16 sentry From abf0d2674aabc26b42ecb328d4e8701565dd165a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 7 Dec 2018 14:48:02 -0600 Subject: [PATCH 1991/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 57b375f6068..2aa8501a229 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.16 + 1.7.17-SNAPSHOT pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.16 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 58dcbfe90d9..2299aee5cfa 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.16 + 1.7.17-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 1a3ab61e57b..0e2928249e5 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.16 + 1.7.17-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 34f5292d310..66126dc0a49 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.16 + 1.7.17-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index c49cda3d3ae..7e49f866ac1 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.16 + 1.7.17-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 2fc25ec5684..b99b5abe83f 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.16 + 1.7.17-SNAPSHOT sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 1b89f18b77d..0a34c44f28a 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.16 + 1.7.17-SNAPSHOT sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.16 + 1.7.17-SNAPSHOT diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 11d91e20eb2..48311a6c0e6 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.16 + 1.7.17-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index e9d66fcee67..2136f7ae91a 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.16 + 1.7.17-SNAPSHOT sentry From b2f8e3e396f867bce3060059b96e80614ff343e1 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 7 Dec 2018 14:48:04 -0600 Subject: [PATCH 1992/2152] Bump CHANGES to 1.7.17 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index fa5dcd41328..28e3f2f0a61 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.17 +-------------- + +- + Version 1.7.16 -------------- From 7291a61d0f01dbd5508c08dc8496c34b7dc96320 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 7 Dec 2018 14:53:27 -0600 Subject: [PATCH 1993/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 32653e71416..939f0a8d668 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.36.4 +VERSION=1.37.0 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index c976e69a979..0268e1f15e1 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.16-SNAPSHOT +version = 1.7.17-SNAPSHOT From a85514ba573945b02e669f1fce50e4736a5f87d2 Mon Sep 17 00:00:00 2001 From: Andrea Di Menna Date: Wed, 9 Jan 2019 14:41:12 +0100 Subject: [PATCH 1994/2152] Removed offending DSN info from Android logs (#659) --- .../main/java/io/sentry/android/AndroidSentryClientFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java index af6fa1348f5..660dbb1960c 100644 --- a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java +++ b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java @@ -55,7 +55,7 @@ public SentryClient createSentryClient(Dsn dsn) { + " please add it to your AndroidManifest.xml"); } - Log.d(TAG, "Sentry init with ctx='" + ctx.toString() + "' and dsn='" + dsn + "'"); + Log.d(TAG, "Sentry init with ctx='" + ctx.toString() + "'"); String protocol = dsn.getProtocol(); if (protocol.equalsIgnoreCase("noop")) { From b807ba22e1c651095d20a9f6d435586ff6591e21 Mon Sep 17 00:00:00 2001 From: Nikolay Nesterov Date: Fri, 18 Jan 2019 23:29:05 +0300 Subject: [PATCH 1995/2152] Safely close after sentry.properties file reading (#664) --- .../src/main/java/io/sentry/config/Lookup.java | 6 +++++- sentry/src/main/java/io/sentry/util/Util.java | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/sentry/src/main/java/io/sentry/config/Lookup.java b/sentry/src/main/java/io/sentry/config/Lookup.java index 981f71ce3c5..716e99de1af 100644 --- a/sentry/src/main/java/io/sentry/config/Lookup.java +++ b/sentry/src/main/java/io/sentry/config/Lookup.java @@ -1,6 +1,7 @@ package io.sentry.config; import io.sentry.dsn.Dsn; +import io.sentry.util.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,8 +34,9 @@ public final class Lookup { static { String filePath = getConfigFilePath(); + InputStream input = null; try { - InputStream input = getInputStream(filePath); + input = getInputStream(filePath); if (input != null) { configProps = new Properties(); @@ -44,6 +46,8 @@ public final class Lookup { } } catch (Exception e) { logger.error("Error loading Sentry configuration file '{}': ", filePath, e); + } finally { + Util.closeQuietly(input); } } diff --git a/sentry/src/main/java/io/sentry/util/Util.java b/sentry/src/main/java/io/sentry/util/Util.java index 750cb70362f..0ad859c14d9 100644 --- a/sentry/src/main/java/io/sentry/util/Util.java +++ b/sentry/src/main/java/io/sentry/util/Util.java @@ -1,5 +1,6 @@ package io.sentry.util; +import java.io.Closeable; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -173,4 +174,19 @@ public static boolean safelyRemoveShutdownHook(Thread shutDownHook) { } return false; } + + /** + * Closes an instance of Closeable and suppress all possible raised Exceptions. + * @param closeable an instance that needs to be closed. null value is supported. + */ + public static void closeQuietly(Closeable closeable) { + // CHECKSTYLE.OFF: EmptyCatchBlock + if (closeable != null) { + try { + closeable.close(); + } catch (Exception ignored) { + } + } + // CHECKSTYLE.ON: EmptyCatchBlock + } } From e8856626b0643aa18301597878218be63365af06 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 18 Jan 2019 14:39:25 -0600 Subject: [PATCH 1996/2152] Gradle Plugin: Replace execute with finalizedBy (#650) --- .../main/groovy/io/sentry/android/gradle/SentryPlugin.groovy | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 6fcf6b8a6a4..c0e776ad69f 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -254,9 +254,7 @@ class SentryPlugin implements Plugin { if (dexTask != null) { dexTask.dependsOn persistIdsTask } else { - proguardTask.doLast { - persistIdsTask.execute() - } + proguardTask.finalizedBy(persistIdsTask) } persistIdsTask.dependsOn proguardTask } From 765ccfd744d4d6adbdfc656d635f3509a718b092 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Sat, 19 Jan 2019 12:17:51 -0600 Subject: [PATCH 1997/2152] Handle new task name 'transformClassesWithDexBuilderFor_' (#665) --- .../sentry/android/gradle/SentryPlugin.groovy | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index c0e776ad69f..77575975ef4 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -117,12 +117,20 @@ class SentryPlugin implements Plugin { * @return */ static Task getDexTask(Project project, ApplicationVariant variant) { - def name = "transformClassesWithDexFor${variant.name.capitalize()}" - def rv = project.tasks.findByName(name) - if (rv != null) { - return rv + def names = [ + "transformClassesWithDexFor${variant.name.capitalize()}", + "transformClassesWithDexBuilderFor${variant.name.capitalize()}" + ] + + def rv = null + names.each { + rv = project.tasks.findByName(it) + if (rv != null) { + return rv + } } - return project.tasks.findByName("dex${name}") + + return project.tasks.findByName("dex${names[0]}") } /** From 4b83a675ef00252d6c6389699914aa85a015139a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Sat, 19 Jan 2019 12:19:47 -0600 Subject: [PATCH 1998/2152] Update CHANGES. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 28e3f2f0a61..7ceccff5729 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.7.17 -------------- -- +- Fixes for Sentry Gradle Plugin when using Android Studio 3.3 and Gradle 5.1. Version 1.7.16 -------------- From 557caa8f7a273e93d3921559866b9e556b3915f1 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Sat, 19 Jan 2019 12:22:47 -0600 Subject: [PATCH 1999/2152] [maven-release-plugin] prepare release v1.7.17 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 2aa8501a229..002949705ea 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.17-SNAPSHOT + 1.7.17 pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.17 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 2299aee5cfa..831d8607f6d 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.17-SNAPSHOT + 1.7.17 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 0e2928249e5..43edcc7bc76 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.17-SNAPSHOT + 1.7.17 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 66126dc0a49..4278582ea36 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.17-SNAPSHOT + 1.7.17 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 7e49f866ac1..1cdca84713a 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.17-SNAPSHOT + 1.7.17 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index b99b5abe83f..d31886fe3e1 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.17-SNAPSHOT + 1.7.17 sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 0a34c44f28a..01e54a3e59a 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.17-SNAPSHOT + 1.7.17 sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.17-SNAPSHOT + 1.7.17 diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 48311a6c0e6..81bb0a2c211 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.17-SNAPSHOT + 1.7.17 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 2136f7ae91a..c78d633a214 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.17-SNAPSHOT + 1.7.17 sentry From 9badb20334ccfa27d60f5e9ff1a78eab2c8b4a02 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Sat, 19 Jan 2019 12:22:48 -0600 Subject: [PATCH 2000/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 002949705ea..b33dd0e5e5c 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.17 + 1.7.18-SNAPSHOT pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.17 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 831d8607f6d..fecbeb733c4 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.17 + 1.7.18-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 43edcc7bc76..f6239326401 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.17 + 1.7.18-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 4278582ea36..604726527f6 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.17 + 1.7.18-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 1cdca84713a..5dbc3591dbc 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.17 + 1.7.18-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index d31886fe3e1..d14dabee2bc 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.17 + 1.7.18-SNAPSHOT sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 01e54a3e59a..beda3684980 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.17 + 1.7.18-SNAPSHOT sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.17 + 1.7.18-SNAPSHOT diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 81bb0a2c211..9298701e5f8 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.17 + 1.7.18-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index c78d633a214..101520aca08 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.17 + 1.7.18-SNAPSHOT sentry From 752fda08a3ab74245511f16d455c7db59c8256f8 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Sat, 19 Jan 2019 12:22:50 -0600 Subject: [PATCH 2001/2152] Bump CHANGES to 1.7.17 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 7ceccff5729..c2f90d86e2c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.18 +-------------- + +- + Version 1.7.17 -------------- From 5626a1c38adf150402f5a43c19092788c2e96688 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 4 Feb 2019 09:41:28 -0600 Subject: [PATCH 2002/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 939f0a8d668..e2d86ee3a03 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.37.0 +VERSION=1.37.4 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 0268e1f15e1..bd2110fcce4 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.17-SNAPSHOT +version = 1.7.18-SNAPSHOT From 07c2d6534ded352f2a61988305b0d4043552b002 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 4 Feb 2019 11:40:00 -0600 Subject: [PATCH 2003/2152] Fix `--no-upload` option in Sentry Gradle Plugin under newer Groovy. --- CHANGES | 4 ++-- .../main/groovy/io/sentry/android/gradle/SentryPlugin.groovy | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index c2f90d86e2c..e1fb17abd2f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,12 +1,12 @@ Version 1.7.18 -------------- -- +- Fix `--no-upload` option in Sentry Gradle Plugin under newer Groovy. Version 1.7.17 -------------- -- Fixes for Sentry Gradle Plugin when using Android Studio 3.3 and Gradle 5.1. +- Fix for Sentry Gradle Plugin when using Android Studio 3.3 and Gradle 5.1. Version 1.7.16 -------------- diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 77575975ef4..ee99bc2eee3 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -244,7 +244,7 @@ class SentryPlugin implements Plugin { ] if (!extension.autoUpload) { - args.push("--no-upload") + args << "--no-upload" } if (Os.isFamily(Os.FAMILY_WINDOWS)) { From f153cc2c18870bec7c2a07f1d0944cb2d7e8cef3 Mon Sep 17 00:00:00 2001 From: Nikolay Nesterov Date: Mon, 4 Feb 2019 21:02:20 +0300 Subject: [PATCH 2004/2152] Allow overriding Sentry Organization and Project per buildType. (thanks nesterov-n) --- CHANGES | 1 + .../io/sentry/android/gradle/SentryPlugin.groovy | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGES b/CHANGES index e1fb17abd2f..53e4dac1f57 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 1.7.18 -------------- - Fix `--no-upload` option in Sentry Gradle Plugin under newer Groovy. +- Allow overriding Sentry Organization and Project per buildType. (thanks nesterov-n) Version 1.7.17 -------------- diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index ee99bc2eee3..8dc1c6fe6de 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -12,6 +12,8 @@ import org.apache.tools.ant.taskdefs.condition.Os class SentryPlugin implements Plugin { static final String GROUP_NAME = 'Sentry' + private static final String SENTRY_ORG_PARAMETER = "sentryOrg" + private static final String SENTRY_PROJECT_PARAMETER = "sentryProject" /** * Return the correct sentry-cli executable path to use for the given project. This @@ -247,6 +249,16 @@ class SentryPlugin implements Plugin { args << "--no-upload" } + def buildTypeProperties = variant.buildType.ext + if (buildTypeProperties.has(SENTRY_ORG_PARAMETER)) { + args.add("--org") + args.add(buildTypeProperties.get(SENTRY_ORG_PARAMETER).toString()) + } + if (buildTypeProperties.has(SENTRY_PROJECT_PARAMETER)) { + args.add("--project") + args.add(buildTypeProperties.get(SENTRY_PROJECT_PARAMETER).toString()) + } + if (Os.isFamily(Os.FAMILY_WINDOWS)) { commandLine("cmd", "/c", *args) } else { From 761916376552d0c5ffd48e818c09632ff14980e5 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 4 Feb 2019 12:18:20 -0600 Subject: [PATCH 2005/2152] [maven-release-plugin] prepare release v1.7.18 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index b33dd0e5e5c..ce060fe8707 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.18-SNAPSHOT + 1.7.18 pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.18 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index fecbeb733c4..467b9724cd7 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.18-SNAPSHOT + 1.7.18 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index f6239326401..8ea15622049 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.18-SNAPSHOT + 1.7.18 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 604726527f6..2f86a85cf7a 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.18-SNAPSHOT + 1.7.18 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 5dbc3591dbc..72909f03b56 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.18-SNAPSHOT + 1.7.18 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index d14dabee2bc..b1c2d906dac 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.18-SNAPSHOT + 1.7.18 sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index beda3684980..cf682bde0d2 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.18-SNAPSHOT + 1.7.18 sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.18-SNAPSHOT + 1.7.18 diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 9298701e5f8..f51d2a0432d 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.18-SNAPSHOT + 1.7.18 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 101520aca08..af130bf48d2 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.18-SNAPSHOT + 1.7.18 sentry From 8ca4b5ffb578237e294bd4fd8d42bf4083c6d68a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 4 Feb 2019 12:18:20 -0600 Subject: [PATCH 2006/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index ce060fe8707..18949831b58 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.18 + 1.7.19-SNAPSHOT pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.18 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 467b9724cd7..462fec980ad 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.18 + 1.7.19-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 8ea15622049..4c96683d082 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.18 + 1.7.19-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 2f86a85cf7a..9ed10433816 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.18 + 1.7.19-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 72909f03b56..e6b8da9fbb0 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.18 + 1.7.19-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index b1c2d906dac..b1980607c98 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.18 + 1.7.19-SNAPSHOT sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index cf682bde0d2..3a02d7cb608 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.18 + 1.7.19-SNAPSHOT sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.18 + 1.7.19-SNAPSHOT diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index f51d2a0432d..1f3dc6bf523 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.18 + 1.7.19-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index af130bf48d2..92d1f8a72d3 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.18 + 1.7.19-SNAPSHOT sentry From 80f67a111166d937f51e77245e2591280dd1c6fb Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 4 Feb 2019 12:18:23 -0600 Subject: [PATCH 2007/2152] Bump CHANGES to 1.7.19 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 53e4dac1f57..1ab716e95aa 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.19 +-------------- + +- + Version 1.7.18 -------------- From d333996cfc45499e0eb96e96c1cbb9e37eff1971 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 4 Feb 2019 12:24:51 -0600 Subject: [PATCH 2008/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index bd2110fcce4..ce1d52e7d6e 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.18-SNAPSHOT +version = 1.7.19-SNAPSHOT From f9ae71442b58b73975443cd0a513039dd3a120ad Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 5 Feb 2019 18:44:42 +0100 Subject: [PATCH 2009/2152] feat: Support R8 (#678) * feat: Support R8 --- .../download-sentry-cli.sh | 2 +- .../sentry/android/gradle/SentryPlugin.groovy | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index e2d86ee3a03..bc99d90ae78 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.37.4 +VERSION=1.38.0 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 8dc1c6fe6de..5b5ce286a6e 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -103,12 +103,21 @@ class SentryPlugin implements Plugin { * @return */ static Task getProguardTask(Project project, ApplicationVariant variant) { - def name = "transformClassesAndResourcesWithProguardFor${variant.name.capitalize()}" - def rv = project.tasks.findByName(name) - if (rv != null) { - return rv + def names = [ + // Android Studio 3.3 includes the R8 shrinker. + "transformClassesAndResourcesWithR8For${variant.name.capitalize()}", + "transformClassesAndResourcesWithProguardFor${variant.name.capitalize()}" + ] + + def rv = null + names.each { + rv = project.tasks.findByName(it) + if (rv != null) { + return rv + } } - return project.tasks.findByName("proguard${name}") + + return project.tasks.findByName("proguard${names[1]}") } /** From edccf3963024cf2aad253bbc3b9d3c0813582b36 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 5 Feb 2019 11:48:09 -0600 Subject: [PATCH 2010/2152] CHANGES. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 1ab716e95aa..f603af74ae6 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.7.19 -------------- -- +- Add support for Android R8 minification. Version 1.7.18 -------------- From 95c61da35b9803a505de4166da2b805a93df9954 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 5 Feb 2019 11:49:33 -0600 Subject: [PATCH 2011/2152] [maven-release-plugin] prepare release v1.7.19 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 18949831b58..c8d0e36a483 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.19-SNAPSHOT + 1.7.19 pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.19 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 462fec980ad..8f4df28402e 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.19-SNAPSHOT + 1.7.19 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 4c96683d082..041ca0ba83e 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.19-SNAPSHOT + 1.7.19 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 9ed10433816..9f13989aaff 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.19-SNAPSHOT + 1.7.19 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index e6b8da9fbb0..1512104dbfe 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.19-SNAPSHOT + 1.7.19 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index b1980607c98..563f2c186a8 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.19-SNAPSHOT + 1.7.19 sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 3a02d7cb608..bff94cfa938 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.19-SNAPSHOT + 1.7.19 sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.19-SNAPSHOT + 1.7.19 diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 1f3dc6bf523..4ad35f97ec0 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.19-SNAPSHOT + 1.7.19 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 92d1f8a72d3..7a391d0b2ac 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.19-SNAPSHOT + 1.7.19 sentry From 90d3bd91a91e806b69f015ed3f75a97dea97bbaa Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 5 Feb 2019 11:49:33 -0600 Subject: [PATCH 2012/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index c8d0e36a483..fbab94c6da3 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.19 + 1.7.20-SNAPSHOT pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.19 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 8f4df28402e..f85e14b7b47 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.19 + 1.7.20-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 041ca0ba83e..12f6e3e1f66 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.19 + 1.7.20-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 9f13989aaff..72a740a47d9 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.19 + 1.7.20-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 1512104dbfe..0fa63206156 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.19 + 1.7.20-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 563f2c186a8..e530027b1d0 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.19 + 1.7.20-SNAPSHOT sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index bff94cfa938..ae8ff63bdbc 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.19 + 1.7.20-SNAPSHOT sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.19 + 1.7.20-SNAPSHOT diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 4ad35f97ec0..d2d3d58e68d 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.19 + 1.7.20-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 7a391d0b2ac..8921aa709d1 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.19 + 1.7.20-SNAPSHOT sentry From cb02080332b3d4a764c6daba4205d66af0988839 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 5 Feb 2019 11:49:36 -0600 Subject: [PATCH 2013/2152] Bump CHANGES to 1.7.20 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index f603af74ae6..bdc2403e13f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.20 +-------------- + +- + Version 1.7.19 -------------- From 4495ea8105641fc612b85d0a0f209658d37e2de7 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 5 Feb 2019 11:58:19 -0600 Subject: [PATCH 2014/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index ce1d52e7d6e..09e6bf51a48 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.19-SNAPSHOT +version = 1.7.20-SNAPSHOT From 9f8212144986e57591db89742f305160458ff511 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 6 Feb 2019 09:52:14 -0600 Subject: [PATCH 2015/2152] Make AndroidEventBuilderHelper methods protected. --- .../helper/AndroidEventBuilderHelper.java | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java index 4cda4eeea89..8339813244b 100644 --- a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java +++ b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java @@ -83,7 +83,8 @@ public void helpBuildingEvent(EventBuilder eventBuilder) { eventBuilder.withContexts(getContexts()); } - private Map> getContexts() { + //CHECKSTYLE.OFF: JavadocMethod + protected Map> getContexts() { Map> contexts = new HashMap<>(); Map deviceMap = new HashMap<>(); Map osMap = new HashMap<>(); @@ -149,7 +150,7 @@ private Map> getContexts() { return contexts; } - private static String[] getProGuardUuids(Context ctx) { + protected static String[] getProGuardUuids(Context ctx) { if (cachedProGuardUuids != null) { return cachedProGuardUuids; } @@ -182,7 +183,7 @@ private static String[] getProGuardUuids(Context ctx) { * @param ctx Android application context * @return the Application's PackageInfo if possible, or null */ - private static PackageInfo getPackageInfo(Context ctx) { + protected static PackageInfo getPackageInfo(Context ctx) { try { return ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0); } catch (PackageManager.NameNotFoundException e) { @@ -197,7 +198,7 @@ private static PackageInfo getPackageInfo(Context ctx) { * * @return family name of the device, as best we can tell */ - private static String getFamily() { + protected static String getFamily() { try { return Build.MODEL.split(" ")[0]; } catch (Exception e) { @@ -211,7 +212,7 @@ private static String getFamily() { * * @return true if the application is running in an emulator, false otherwise */ - private static Boolean isEmulator() { + protected static Boolean isEmulator() { try { return Build.FINGERPRINT.startsWith("generic") || Build.FINGERPRINT.startsWith("unknown") @@ -233,7 +234,7 @@ private static Boolean isEmulator() { * @param ctx Android application context * @return MemoryInfo object representing the memory state of the application */ - private static ActivityManager.MemoryInfo getMemInfo(Context ctx) { + protected static ActivityManager.MemoryInfo getMemInfo(Context ctx) { try { ActivityManager actManager = (ActivityManager) ctx.getSystemService(ACTIVITY_SERVICE); ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); @@ -251,7 +252,7 @@ private static ActivityManager.MemoryInfo getMemInfo(Context ctx) { * @param ctx Android application context * @return the device's current screen orientation, or null if unknown */ - private static String getOrientation(Context ctx) { + protected static String getOrientation(Context ctx) { try { String o; switch (ctx.getResources().getConfiguration().orientation) { @@ -278,7 +279,7 @@ private static String getOrientation(Context ctx) { * @param ctx Android application context * @return the device's current battery level (as a percentage of total), or null if unknown */ - private static Float getBatteryLevel(Context ctx) { + protected static Float getBatteryLevel(Context ctx) { try { Intent intent = ctx.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); if (intent == null) { @@ -309,7 +310,7 @@ private static Float getBatteryLevel(Context ctx) { * @param ctx Android application context * @return whether or not the device is currently plugged in and charging, or null if unknown */ - private static Boolean isCharging(Context ctx) { + protected static Boolean isCharging(Context ctx) { try { Intent intent = ctx.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); if (intent == null) { @@ -330,7 +331,7 @@ private static Boolean isCharging(Context ctx) { * * @return the device's current kernel version, as a string */ - private static String getKernelVersion() { + protected static String getKernelVersion() { String errorMsg = "Exception while attempting to read kernel information"; String defaultVersion = System.getProperty("os.version"); @@ -364,7 +365,7 @@ private static String getKernelVersion() { * * @return true if heuristics show the device is probably rooted, otherwise false */ - private static Boolean isRooted() { + protected static Boolean isRooted() { if (android.os.Build.TAGS != null && android.os.Build.TAGS.contains("test-keys")) { return true; } @@ -400,7 +401,7 @@ private static Boolean isRooted() { return false; } - private static boolean isExternalStorageMounted() { + protected static boolean isExternalStorageMounted() { return Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED) && !Environment.isExternalStorageEmulated(); } @@ -410,7 +411,7 @@ private static boolean isExternalStorageMounted() { * * @return the unused amount of internal storage, in bytes */ - private static Long getUnusedInternalStorage() { + protected static Long getUnusedInternalStorage() { try { File path = Environment.getDataDirectory(); StatFs stat = new StatFs(path.getPath()); @@ -428,7 +429,7 @@ private static Long getUnusedInternalStorage() { * * @return the total amount of internal storage, in bytes */ - private static Long getTotalInternalStorage() { + protected static Long getTotalInternalStorage() { try { File path = Environment.getDataDirectory(); StatFs stat = new StatFs(path.getPath()); @@ -448,7 +449,7 @@ private static Long getTotalInternalStorage() { * @return the unused amount of external storage, in bytes, or null if no external storage * is mounted */ - private static Long getUnusedExternalStorage() { + protected static Long getUnusedExternalStorage() { try { if (isExternalStorageMounted()) { File path = Environment.getExternalStorageDirectory(); @@ -471,7 +472,7 @@ private static Long getUnusedExternalStorage() { * @return the total amount of external storage, in bytes, or null if no external storage * is mounted */ - private static Long getTotalExternalStorage() { + protected static Long getTotalExternalStorage() { try { if (isExternalStorageMounted()) { File path = Environment.getExternalStorageDirectory(); @@ -493,7 +494,7 @@ private static Long getTotalExternalStorage() { * @param ctx Android application context * @return the DisplayMetrics object for the current application */ - private static DisplayMetrics getDisplayMetrics(Context ctx) { + protected static DisplayMetrics getDisplayMetrics(Context ctx) { try { return ctx.getResources().getDisplayMetrics(); } catch (Exception e) { @@ -509,7 +510,7 @@ private static DisplayMetrics getDisplayMetrics(Context ctx) { * @param date Date to format as ISO8601 * @return String representing the provided Date in ISO8601 format */ - private static String stringifyDate(Date date) { + protected static String stringifyDate(Date date) { return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH).format(date); } @@ -519,7 +520,7 @@ private static String stringifyDate(Date date) { * @param ctx Android application context * @return Application name */ - private static String getApplicationName(Context ctx) { + protected static String getApplicationName(Context ctx) { try { ApplicationInfo applicationInfo = ctx.getApplicationInfo(); int stringId = applicationInfo.labelRes; @@ -543,11 +544,11 @@ private static String getApplicationName(Context ctx) { * @param ctx Android application context * @return true if the application has internet access */ - private static boolean isConnected(Context ctx) { + protected static boolean isConnected(Context ctx) { ConnectivityManager connectivityManager = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); return activeNetworkInfo != null && activeNetworkInfo.isConnected(); } - + //CHECKSTYLE.ON: JavadocMethod } From ef6fbcc2c5d2c97ed1e2b67049a9ca17647dfbf2 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Wed, 6 Feb 2019 17:03:39 +0100 Subject: [PATCH 2016/2152] fix: Gradle find R8 task (#679) --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- .../io/sentry/android/gradle/SentryPlugin.groovy | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index bc99d90ae78..839ab122f70 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.38.0 +VERSION=1.38.1 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 5b5ce286a6e..253f0b232cf 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -109,15 +109,7 @@ class SentryPlugin implements Plugin { "transformClassesAndResourcesWithProguardFor${variant.name.capitalize()}" ] - def rv = null - names.each { - rv = project.tasks.findByName(it) - if (rv != null) { - return rv - } - } - - return project.tasks.findByName("proguard${names[1]}") + return names.findResult { project.tasks.findByName(it) } ?: project.tasks.findByName("proguard${names[1]}") } /** From bec940f1ab80e81a124e1b101dddae4ee8d622a1 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 6 Feb 2019 10:05:20 -0600 Subject: [PATCH 2017/2152] CHANGES --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index bdc2403e13f..3cc5cfd2eb6 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.7.20 -------------- -- +- Fixes for Android R8 support. Version 1.7.19 -------------- From 5963197fabca0201706b34ef2ffe65147e9f728d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 6 Feb 2019 10:07:28 -0600 Subject: [PATCH 2018/2152] [maven-release-plugin] prepare release v1.7.20 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index fbab94c6da3..6b8381b8e46 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.20-SNAPSHOT + 1.7.20 pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.20 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index f85e14b7b47..561ae415512 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.20-SNAPSHOT + 1.7.20 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 12f6e3e1f66..e652ca1e086 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.20-SNAPSHOT + 1.7.20 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 72a740a47d9..a48b03a8b46 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.20-SNAPSHOT + 1.7.20 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 0fa63206156..ee6934b8b4b 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.20-SNAPSHOT + 1.7.20 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index e530027b1d0..11aea545b07 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.20-SNAPSHOT + 1.7.20 sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index ae8ff63bdbc..cc2055b9938 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.20-SNAPSHOT + 1.7.20 sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.20-SNAPSHOT + 1.7.20 diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index d2d3d58e68d..92d4cdc4fea 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.20-SNAPSHOT + 1.7.20 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 8921aa709d1..4a2de4a5c21 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.20-SNAPSHOT + 1.7.20 sentry From 99bba7f08b561fce338ecd8bffcb4b653116ce7e Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 6 Feb 2019 10:07:28 -0600 Subject: [PATCH 2019/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 6b8381b8e46..fcbe7e28220 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.20 + 1.7.21-SNAPSHOT pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.20 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 561ae415512..62e23a7b626 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.20 + 1.7.21-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index e652ca1e086..55f3fe057ba 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.20 + 1.7.21-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index a48b03a8b46..3ad16213658 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.20 + 1.7.21-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index ee6934b8b4b..41d1b4d7d40 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.20 + 1.7.21-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 11aea545b07..7d08ec672f1 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.20 + 1.7.21-SNAPSHOT sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index cc2055b9938..dfcc6a580d6 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.20 + 1.7.21-SNAPSHOT sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.20 + 1.7.21-SNAPSHOT diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 92d4cdc4fea..3a77c2e42fa 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.20 + 1.7.21-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 4a2de4a5c21..25a15277a3d 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.20 + 1.7.21-SNAPSHOT sentry From 635a8ae102554c042dd03ae98a550a87e4c8445f Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 6 Feb 2019 10:07:31 -0600 Subject: [PATCH 2020/2152] Bump CHANGES to 1.7.21 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 3cc5cfd2eb6..1c8a692b891 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.21 +-------------- + +- + Version 1.7.20 -------------- From c5719925abb09d798601c7560ae269c47df38671 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 6 Feb 2019 10:47:02 -0600 Subject: [PATCH 2021/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 09e6bf51a48..35460e81af4 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.20-SNAPSHOT +version = 1.7.21-SNAPSHOT From 7e09968ae09533c23612cc0f8442035e1408859d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 15 Feb 2019 08:36:12 -0600 Subject: [PATCH 2022/2152] Fixes for release annoyances. --- .ci/agent-upload-release.py | 2 +- Makefile | 2 +- pom.xml | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.ci/agent-upload-release.py b/.ci/agent-upload-release.py index 0018b40deaf..a2f72e9f775 100644 --- a/.ci/agent-upload-release.py +++ b/.ci/agent-upload-release.py @@ -51,7 +51,7 @@ def ensure_release(): return release resp = api_request('POST', 'repos/%s/releases' % REPO, json={ 'tag_name': TAG, - 'name': 'sentry-java-agent %s' % TAG, + 'name': 'sentry-java %s' % TAG, 'draft': True, }) resp.raise_for_status() diff --git a/Makefile b/Makefile index 917e2905803..be821106fed 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ # TODO: Fix to work between macOS and Linux MVN=mvn -e -Dhttps.protocols=TLSv1.2 ECHO=echo -SED=sed +SED=gsed all: checkstyle test install diff --git a/pom.xml b/pom.xml index fcbe7e28220..d000919cfe2 100644 --- a/pom.xml +++ b/pom.xml @@ -213,6 +213,11 @@ -Psonatype-oss-release + + org.apache.maven.plugins + maven-help-plugin + 3.1.1 + com.github.github site-maven-plugin From 8fad4dab318fdcd867dc2b64ba26ac0210e42248 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Fri, 15 Feb 2019 16:59:49 +0100 Subject: [PATCH 2023/2152] doc: Changelog --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 1c8a692b891..4bf1064624d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.7.21 -------------- -- +- Updating sentry-cli version in the Android gradle plugin. Version 1.7.20 -------------- From 4073e659133dcdefe5ed844b39b1b749f579a627 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Fri, 15 Feb 2019 17:26:54 +0100 Subject: [PATCH 2024/2152] [maven-release-plugin] prepare release v1.7.21 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index d000919cfe2..4a23ecc4e11 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.21-SNAPSHOT + 1.7.21 pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.21 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 62e23a7b626..99507ded0e6 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.21-SNAPSHOT + 1.7.21 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 55f3fe057ba..0462412ca7f 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.21-SNAPSHOT + 1.7.21 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 3ad16213658..fd28a9934d6 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.21-SNAPSHOT + 1.7.21 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 41d1b4d7d40..13c956c7400 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.21-SNAPSHOT + 1.7.21 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 7d08ec672f1..8f7f4e2f2f9 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.21-SNAPSHOT + 1.7.21 sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index dfcc6a580d6..8c99483f8f3 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.21-SNAPSHOT + 1.7.21 sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.21-SNAPSHOT + 1.7.21 diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 3a77c2e42fa..017661172d9 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.21-SNAPSHOT + 1.7.21 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 25a15277a3d..333290786a6 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.21-SNAPSHOT + 1.7.21 sentry From d2d9ab49728349529163ad708282d857f9967508 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Fri, 15 Feb 2019 17:26:54 +0100 Subject: [PATCH 2025/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 4a23ecc4e11..f0710875b6a 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.21 + 1.7.22-SNAPSHOT pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.21 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 99507ded0e6..d1d92c84a8d 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.21 + 1.7.22-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 0462412ca7f..b8d8355ca76 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.21 + 1.7.22-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index fd28a9934d6..6fb7962f809 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.21 + 1.7.22-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 13c956c7400..b8506f632d0 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.21 + 1.7.22-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 8f7f4e2f2f9..3868b492605 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.21 + 1.7.22-SNAPSHOT sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 8c99483f8f3..3b0e96b73b9 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.21 + 1.7.22-SNAPSHOT sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.21 + 1.7.22-SNAPSHOT diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 017661172d9..5b9feb801a5 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.21 + 1.7.22-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 333290786a6..902f8092db0 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.21 + 1.7.22-SNAPSHOT sentry From 7fcba2839a6a4a74101a31173b58565d016d1bb6 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Fri, 15 Feb 2019 17:26:58 +0100 Subject: [PATCH 2026/2152] Bump CHANGES to 1.7.22 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 4bf1064624d..04ea7129471 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.22 +-------------- + +- + Version 1.7.21 -------------- From 08f695eb7c0bd5541c592747afb245fda1f2fc40 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 15 Feb 2019 10:50:02 -0600 Subject: [PATCH 2027/2152] Bump sentry-cli. --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 839ab122f70..6c9a28901ee 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.38.1 +VERSION=1.39.1 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* From 9807197352a023900907d36a1b0b1cbc0ee4d617 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Fri, 15 Feb 2019 10:50:16 -0600 Subject: [PATCH 2028/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 35460e81af4..dd18a7572b9 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.21-SNAPSHOT +version = 1.7.22-SNAPSHOT From 1f8fe00b519d07d877abaca91cf63191d318b6ff Mon Sep 17 00:00:00 2001 From: Erhan GULLU <38051212+erhangullu@users.noreply.github.com> Date: Wed, 27 Feb 2019 18:24:25 +0100 Subject: [PATCH 2029/2152] Don't put body in nested object if parameterMap is empty (#686) Since if there is only body present without the parameters, there is no need to nest body in another object. HttpInterfaceBinding now checks if the body is present without any parameters Fixes #683 --- .../marshaller/json/HttpInterfaceBinding.java | 26 +++++----- .../json/HttpInterfaceBindingTest.java | 48 +++++++++++++++++++ .../io/sentry/marshaller/json/Http1.json | 4 +- .../io/sentry/marshaller/json/Http2.json | 31 ++++++++++++ 4 files changed, 95 insertions(+), 14 deletions(-) create mode 100644 sentry/src/test/resources/io/sentry/marshaller/json/Http2.json diff --git a/sentry/src/main/java/io/sentry/marshaller/json/HttpInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/HttpInterfaceBinding.java index 9edf9bfdd25..e380400a289 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/HttpInterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/HttpInterfaceBinding.java @@ -109,19 +109,23 @@ private void writeData(JsonGenerator generator, return; } - generator.writeStartObject(); - if (body != null) { - generator.writeStringField(BODY, Util.trimString(body, MAX_BODY_LENGTH)); - } - if (parameterMap != null) { - for (Map.Entry> parameter : parameterMap.entrySet()) { - generator.writeArrayFieldStart(parameter.getKey()); - for (String parameterValue : parameter.getValue()) { - generator.writeString(parameterValue); + if ((parameterMap == null || parameterMap.isEmpty()) && body != null) { + generator.writeString(Util.trimString(body, MAX_BODY_LENGTH)); + } else { + generator.writeStartObject(); + if (body != null) { + generator.writeStringField(BODY, Util.trimString(body, MAX_BODY_LENGTH)); + } + if (parameterMap != null) { + for (Map.Entry> parameter : parameterMap.entrySet()) { + generator.writeArrayFieldStart(parameter.getKey()); + for (String parameterValue : parameter.getValue()) { + generator.writeString(parameterValue); + } + generator.writeEndArray(); } - generator.writeEndArray(); } + generator.writeEndObject(); } - generator.writeEndObject(); } } diff --git a/sentry/src/test/java/io/sentry/marshaller/json/HttpInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/HttpInterfaceBindingTest.java index f82710b164e..285ad9b025a 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/HttpInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/HttpInterfaceBindingTest.java @@ -65,4 +65,52 @@ public void testHeaders() throws Exception { JsonNode value = jsonGeneratorParser.value(); assertThat(value, is(jsonResource("/io/sentry/marshaller/json/Http1.json"))); } + + @Test + public void testBodyWithParameters() throws Exception { + final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); + final Map> headers = new HashMap<>(); + headers.put("Header1", Lists.newArrayList("Value1")); + headers.put("Header2", Lists.newArrayList("Value1", "Value2")); + final HashMap cookies = new HashMap<>(); + cookies.put("Cookie1", "Value1"); + final HashMap> parameters = new HashMap<>(); + parameters.put("Parameter1", Lists.newArrayList("Value1")); + parameters.put("Parameter2", Lists.newArrayList("Value2", "Value3")); + new NonStrictExpectations() {{ + mockMessageInterface.getHeaders(); + result = headers; + mockMessageInterface.getRequestUrl(); + result = "http://host/url"; + mockMessageInterface.getMethod(); + result = "GET"; + mockMessageInterface.getQueryString(); + result = "query"; + mockMessageInterface.getCookies(); + result = cookies; + mockMessageInterface.getRemoteAddr(); + result = "1.2.3.4"; + mockMessageInterface.getServerName(); + result = "server-name"; + mockMessageInterface.getServerPort(); + result = 1234; + mockMessageInterface.getLocalPort(); + result = 5678; + mockMessageInterface.getProtocol(); + result = "HTTP"; + mockMessageInterface.getLocalAddr(); + result = "5.6.7.8"; + mockMessageInterface.getLocalName(); + result = "local-name"; + mockMessageInterface.getBody(); + result = "body"; + mockMessageInterface.getParameters(); + result = parameters; + }}; + + interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockMessageInterface); + + JsonNode value = jsonGeneratorParser.value(); + assertThat(value, is(jsonResource("/io/sentry/marshaller/json/Http2.json"))); + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/Http1.json b/sentry/src/test/resources/io/sentry/marshaller/json/Http1.json index 54eb1822034..ac684e28a37 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/Http1.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/Http1.json @@ -1,9 +1,7 @@ { "url": "http://host/url", "method": "GET", - "data": { - "body": "body" - }, + "data": "body", "query_string": "query", "cookies": { "Cookie1": "Value1" diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/Http2.json b/sentry/src/test/resources/io/sentry/marshaller/json/Http2.json new file mode 100644 index 00000000000..cab082cf618 --- /dev/null +++ b/sentry/src/test/resources/io/sentry/marshaller/json/Http2.json @@ -0,0 +1,31 @@ +{ + "url": "http://host/url", + "method": "GET", + "data": { + "body": "body", + "Parameter1": ["Value1"], + "Parameter2": ["Value2","Value3"] + }, + "query_string": "query", + "cookies": { + "Cookie1": "Value1" + }, + "headers": [ + ["Header1","Value1"], + ["Header2","Value1"], + ["Header2","Value2"] + ], + "env": { + "REMOTE_ADDR": "1.2.3.4", + "SERVER_NAME": "server-name", + "SERVER_PORT": 1234, + "LOCAL_ADDR": "5.6.7.8", + "LOCAL_NAME": "local-name", + "LOCAL_PORT": 5678, + "SERVER_PROTOCOL": "HTTP", + "REQUEST_SECURE": false, + "REQUEST_ASYNC": false, + "AUTH_TYPE": null, + "REMOTE_USER": null + } +} From 6d6b8788ea93bf28d7b087c3e90c900d76a2e9cb Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 27 Feb 2019 11:25:02 -0600 Subject: [PATCH 2030/2152] CHANGES. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 04ea7129471..eae545f4f32 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ Version 1.7.22 -------------- -- +- Don't put body in nested object if parameterMap is empty. (thanks erhangullu) Version 1.7.21 -------------- From 08014a0c7c47a2d2a0c18491ad33bfdee9f911fd Mon Sep 17 00:00:00 2001 From: mpp-anasa <38040056+mpp-anasa@users.noreply.github.com> Date: Wed, 27 Feb 2019 22:53:43 +0200 Subject: [PATCH 2031/2152] feat: Support DexGuard (#684) * Support for Dexguard's pre-dex step * Add dex task for DexGuard * Reorder tasks for consistency --- .../io/sentry/android/gradle/SentryPlugin.groovy | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 253f0b232cf..ded44c3120e 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -104,12 +104,14 @@ class SentryPlugin implements Plugin { */ static Task getProguardTask(Project project, ApplicationVariant variant) { def names = [ + "transformClassesAndResourcesWithProguardFor${variant.name.capitalize()}", // Android Studio 3.3 includes the R8 shrinker. "transformClassesAndResourcesWithR8For${variant.name.capitalize()}", - "transformClassesAndResourcesWithProguardFor${variant.name.capitalize()}" + //Dexguard's task + "transformDexWithDexFor${variant.name.capitalize()}", ] - return names.findResult { project.tasks.findByName(it) } ?: project.tasks.findByName("proguard${names[1]}") + return names.findResult { project.tasks.findByName(it) } ?: project.tasks.findByName("proguard${names[0]}") } /** @@ -122,7 +124,9 @@ class SentryPlugin implements Plugin { static Task getDexTask(Project project, ApplicationVariant variant) { def names = [ "transformClassesWithDexFor${variant.name.capitalize()}", - "transformClassesWithDexBuilderFor${variant.name.capitalize()}" + "transformClassesWithDexBuilderFor${variant.name.capitalize()}", + //Pre-dex step for DexGuard + "transformClassesWithPreDexFor${variant.name.capitalize()}" ] def rv = null @@ -133,7 +137,7 @@ class SentryPlugin implements Plugin { } } - return project.tasks.findByName("dex${names[0]}") + return names.findResult { project.tasks.findByName(it) } ?: project.tasks.findByName("dex${names[0]}") } /** From b587395cc7c1a23c2f1662d1bf579759eee1569d Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Wed, 27 Feb 2019 14:54:23 -0600 Subject: [PATCH 2032/2152] CHANGES. --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index eae545f4f32..1648caa04c4 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Version 1.7.22 -------------- - Don't put body in nested object if parameterMap is empty. (thanks erhangullu) +- Support DexGuard in Android Gradle Plugin. (thanks mpp-anasa) Version 1.7.21 -------------- From 4331bfc2c0a02a689054812c324a46e954e02b58 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 28 Feb 2019 09:12:51 -0600 Subject: [PATCH 2033/2152] Revert "feat: Support DexGuard (#684)" This reverts commit 08014a0c7c47a2d2a0c18491ad33bfdee9f911fd. --- .../io/sentry/android/gradle/SentryPlugin.groovy | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index ded44c3120e..253f0b232cf 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -104,14 +104,12 @@ class SentryPlugin implements Plugin { */ static Task getProguardTask(Project project, ApplicationVariant variant) { def names = [ - "transformClassesAndResourcesWithProguardFor${variant.name.capitalize()}", // Android Studio 3.3 includes the R8 shrinker. "transformClassesAndResourcesWithR8For${variant.name.capitalize()}", - //Dexguard's task - "transformDexWithDexFor${variant.name.capitalize()}", + "transformClassesAndResourcesWithProguardFor${variant.name.capitalize()}" ] - return names.findResult { project.tasks.findByName(it) } ?: project.tasks.findByName("proguard${names[0]}") + return names.findResult { project.tasks.findByName(it) } ?: project.tasks.findByName("proguard${names[1]}") } /** @@ -124,9 +122,7 @@ class SentryPlugin implements Plugin { static Task getDexTask(Project project, ApplicationVariant variant) { def names = [ "transformClassesWithDexFor${variant.name.capitalize()}", - "transformClassesWithDexBuilderFor${variant.name.capitalize()}", - //Pre-dex step for DexGuard - "transformClassesWithPreDexFor${variant.name.capitalize()}" + "transformClassesWithDexBuilderFor${variant.name.capitalize()}" ] def rv = null @@ -137,7 +133,7 @@ class SentryPlugin implements Plugin { } } - return names.findResult { project.tasks.findByName(it) } ?: project.tasks.findByName("dex${names[0]}") + return project.tasks.findByName("dex${names[0]}") } /** From 5a2b534876fa68fd74421da91f5266f82144bb2c Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Thu, 28 Feb 2019 09:12:54 -0600 Subject: [PATCH 2034/2152] Revert "CHANGES." This reverts commit b587395cc7c1a23c2f1662d1bf579759eee1569d. --- CHANGES | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGES b/CHANGES index 1648caa04c4..eae545f4f32 100644 --- a/CHANGES +++ b/CHANGES @@ -2,7 +2,6 @@ Version 1.7.22 -------------- - Don't put body in nested object if parameterMap is empty. (thanks erhangullu) -- Support DexGuard in Android Gradle Plugin. (thanks mpp-anasa) Version 1.7.21 -------------- From fe173cf7c3a8ffaca1ff2fe1c234a7c186571095 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Mon, 4 Mar 2019 13:33:40 -0500 Subject: [PATCH 2035/2152] Allow the creation of the HttpInterface object from just primitive objects. (#690) Add HttpInterface to the context and contextbuilderhelper, allowing integration with other web frameworks that are not Servlets --- .../main/java/io/sentry/context/Context.java | 34 ++++++++++ .../event/helper/ContextBuilderHelper.java | 4 ++ .../event/interfaces/HttpInterface.java | 63 +++++++++++++++++++ 3 files changed, 101 insertions(+) diff --git a/sentry/src/main/java/io/sentry/context/Context.java b/sentry/src/main/java/io/sentry/context/Context.java index 3acdd9cd266..9f67bd4cf85 100644 --- a/sentry/src/main/java/io/sentry/context/Context.java +++ b/sentry/src/main/java/io/sentry/context/Context.java @@ -2,6 +2,7 @@ import io.sentry.event.Breadcrumb; import io.sentry.event.User; +import io.sentry.event.interfaces.HttpInterface; import io.sentry.util.CircularFifoQueue; import java.io.Serializable; @@ -42,6 +43,12 @@ public class Context implements Serializable { */ private volatile Map extra; + /** + * HTTP data to add to any future {@link io.sentry.event.Event}s. + */ + private volatile HttpInterface http; + + /** * Create a new (empty) Context object with the default Breadcrumb limit. */ @@ -67,6 +74,7 @@ public synchronized void clear() { clearUser(); clearTags(); clearExtra(); + clearHttp(); } /** @@ -178,6 +186,32 @@ public synchronized void clearExtra() { extra = null; } + + /** + * Store the http information in the context. + * + * @param http Http data to store in context. + */ + public synchronized void setHttp(HttpInterface http) { + this.http = http; + } + + /** + * Gets the http information from the context. + * + * @return HttpInterface currently stored in context. + */ + public synchronized HttpInterface getHttp() { + return http; + } + + /** + * Clear the http data from this context. + */ + public synchronized void clearHttp() { + http = null; + } + /** * Record a single {@link Breadcrumb} into this context. * diff --git a/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java b/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java index fc654f5e4e5..d302817f8b1 100644 --- a/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java +++ b/sentry/src/main/java/io/sentry/event/helper/ContextBuilderHelper.java @@ -39,6 +39,10 @@ public void helpBuildingEvent(EventBuilder eventBuilder) { eventBuilder.withBreadcrumbs(breadcrumbs); } + if (context.getHttp() != null) { + eventBuilder.withSentryInterface(context.getHttp()); + } + if (context.getUser() != null) { eventBuilder.withSentryInterface(fromUser(context.getUser())); } diff --git a/sentry/src/main/java/io/sentry/event/interfaces/HttpInterface.java b/sentry/src/main/java/io/sentry/event/interfaces/HttpInterface.java index a2957c17646..a378e742e69 100644 --- a/sentry/src/main/java/io/sentry/event/interfaces/HttpInterface.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/HttpInterface.java @@ -94,6 +94,69 @@ public HttpInterface(HttpServletRequest request, RemoteAddressResolver remoteAdd this.body = body; } + // CHECKSTYLE.OFF: ParameterNumber + + /** + * Creates an HTTP element for an {@link io.sentry.event.Event}. + * + * @param requestUrl The full url including the scheme, host, path and query parameters + * @param method The HTTP method + * @param parameters Extra request parameters from either the query string or posted form data. + * @param queryString The query string + * @param cookies Collection of values for each cookie + * @param remoteAddr The remote ip address + * @param serverName The server's hostname + * @param serverPort The port from the server's hostname + * @param localAddr The IP that received the request + * @param localName The hostname of the IP that received the request + * @param localPort The port that received the request + * @param protocol The protocol name and version + * @param secure True if the request was made using a secure channel + * @param asyncStarted Servlet specific + * @param authType Servlet specific + * @param remoteUser The login of the user making the request + * @param headers The headers sent with the request + * @param body The request body + */ + public HttpInterface(String requestUrl, + String method, + Map> parameters, + String queryString, Map cookies, + String remoteAddr, + String serverName, + int serverPort, + String localAddr, + String localName, + int localPort, + String protocol, + boolean secure, + boolean asyncStarted, + String authType, + String remoteUser, + Map> headers, + String body) { + + this.requestUrl = requestUrl; + this.method = method; + this.parameters = parameters; + this.queryString = queryString; + this.cookies = cookies; + this.remoteAddr = remoteAddr; + this.serverName = serverName; + this.serverPort = serverPort; + this.localAddr = localAddr; + this.localName = localName; + this.localPort = localPort; + this.protocol = protocol; + this.secure = secure; + this.asyncStarted = asyncStarted; + this.authType = authType; + this.remoteUser = remoteUser; + this.headers = headers; + this.body = body; + } + // CHECKSTYLE.ON: ParameterNumber + @Override public String getInterfaceName() { return HTTP_INTERFACE; From 8d8be4975a000db15399f7d3d1f011f388cc427e Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 4 Mar 2019 12:34:37 -0600 Subject: [PATCH 2036/2152] CHANGES. --- CHANGES | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index eae545f4f32..84344616eb6 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,8 @@ Version 1.7.22 -------------- - + - Don't put body in nested object if parameterMap is empty. (thanks erhangullu) +- Allow creating and setting an `HttpInterface` in the Context. (thanks chris-smith-zocdoc) Version 1.7.21 -------------- From 2a267ad44d82f7364fd2c99e7f6808a00062abd8 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 12 Mar 2019 15:46:39 -0500 Subject: [PATCH 2037/2152] [maven-release-plugin] prepare release v1.7.22 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index f0710875b6a..25dd782fb49 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.22-SNAPSHOT + 1.7.22 pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.22 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index d1d92c84a8d..c31d50fd8fb 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.22-SNAPSHOT + 1.7.22 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index b8d8355ca76..0420c85fb0c 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.22-SNAPSHOT + 1.7.22 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 6fb7962f809..9d608ef617b 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.22-SNAPSHOT + 1.7.22 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index b8506f632d0..43a7f2db274 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.22-SNAPSHOT + 1.7.22 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 3868b492605..f53fb801809 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.22-SNAPSHOT + 1.7.22 sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 3b0e96b73b9..73e3e51011c 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.22-SNAPSHOT + 1.7.22 sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.22-SNAPSHOT + 1.7.22 diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 5b9feb801a5..ed0c88cfa9b 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.22-SNAPSHOT + 1.7.22 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 902f8092db0..018aa979e0d 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.22-SNAPSHOT + 1.7.22 sentry From 7fe25b899b8a83f64f1745d23d4af8804b4034d5 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 12 Mar 2019 15:46:39 -0500 Subject: [PATCH 2038/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 25dd782fb49..31407c56851 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.22 + 1.7.23-SNAPSHOT pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.22 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index c31d50fd8fb..6d8b4585f41 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.22 + 1.7.23-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 0420c85fb0c..f9f9668a3a6 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.22 + 1.7.23-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 9d608ef617b..3cd16446223 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.22 + 1.7.23-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 43a7f2db274..e1ef94a554b 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.22 + 1.7.23-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index f53fb801809..4064b6c0951 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.22 + 1.7.23-SNAPSHOT sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 73e3e51011c..384922b115e 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.22 + 1.7.23-SNAPSHOT sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.22 + 1.7.23-SNAPSHOT diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index ed0c88cfa9b..d95c67f66d2 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.22 + 1.7.23-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 018aa979e0d..93e4021af35 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.22 + 1.7.23-SNAPSHOT sentry From ee8e8299b95d918b9ae9e6dfad8cf1f731c1d8c9 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 12 Mar 2019 15:46:42 -0500 Subject: [PATCH 2039/2152] Bump CHANGES to 1.7.23 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 84344616eb6..64315881404 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +Version 1.7.23 +-------------- + +- + Version 1.7.22 -------------- From b598c2b126f2688700bf0d33fb5b974d7bc5df9a Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 12 Mar 2019 15:50:43 -0500 Subject: [PATCH 2040/2152] Bump sentry-cli. --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 6c9a28901ee..5a4c6fdb2ee 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.39.1 +VERSION=1.40.0 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* From 2e01461775cbd5951ff82b566069c349146d8aca Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Tue, 12 Mar 2019 15:53:29 -0500 Subject: [PATCH 2041/2152] Bump Android Gradle Plugin version. --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index dd18a7572b9..8a86417a6b8 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.22-SNAPSHOT +version = 1.7.23-SNAPSHOT From 94862776b8825bad12a94fdeacb654ba7b2ac0aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ferreira?= Date: Thu, 9 May 2019 23:13:17 -0400 Subject: [PATCH 2042/2152] [LOG4J2] Remove usage of deprecated constructors (#710) Use the builder pattern for tests instead of deprecated constructors --- .gitignore | 1 + .../SentryAppenderEventBuildingTest.java | 132 ++++++++++++------ 2 files changed, 93 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index 4c34a505990..a2cf89a51a7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ infer-out/ agent/build/ agent/.vscode/ local.properties +.idea diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java index 02ac9d39892..d2b42f07f1f 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java @@ -3,20 +3,22 @@ import io.sentry.BaseTest; import io.sentry.Sentry; import io.sentry.SentryClient; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; import io.sentry.event.interfaces.*; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; import mockit.Verifications; -import io.sentry.event.Event; -import io.sentry.event.EventBuilder; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.impl.ContextDataFactory; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.FormattedMessage; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.spi.DefaultThreadContextStack; +import org.apache.logging.log4j.util.StringMap; import org.hamcrest.Matchers; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; @@ -37,7 +39,7 @@ public class SentryAppenderEventBuildingTest extends BaseTest { private Set extraTags; @BeforeMethod - public void setUp() throws Exception { + public void setUp() { Sentry.setStoredClient(mockSentryClient); sentryAppender = new SentryAppender(); mockUpErrorHandler = new MockUpErrorHandler(); @@ -46,19 +48,26 @@ public void setUp() throws Exception { extraTags.add(mockExtraTag); } - private void assertNoErrorsInErrorHandler() throws Exception { + private void assertNoErrorsInErrorHandler() { assertThat(mockUpErrorHandler.getErrorCount(), is(0)); } @Test - public void testSimpleMessageLogging() throws Exception { + public void testSimpleMessageLogging() { final String loggerName = "0a05c9ff-45ef-45cf-9595-9307b0729a0d"; final String message = "6ff10df4-2e27-43f5-b4e9-a957f8678176"; final String threadName = "f891f3c4-c619-4441-9c47-f5c8564d3c0a"; final Date date = new Date(1373883196416L); - sentryAppender.append(new Log4jLogEvent(loggerName, null, null, Level.INFO, new SimpleMessage(message), - null, null, null, threadName, null, date.getTime())); + Log4jLogEvent event = Log4jLogEvent.newBuilder() + .setLoggerName(loggerName) + .setThreadName(threadName) + .setTimeMillis(date.getTime()) + .setLevel(Level.INFO) + .setMessage(new SimpleMessage(message)) + .build(); + + sentryAppender.append(event); new Verifications() {{ EventBuilder eventBuilder; @@ -76,17 +85,23 @@ public void testSimpleMessageLogging() throws Exception { @DataProvider(name = "levels") private Object[][] levelConversions() { return new Object[][]{ - {Event.Level.DEBUG, Level.TRACE}, - {Event.Level.DEBUG, Level.DEBUG}, - {Event.Level.INFO, Level.INFO}, - {Event.Level.WARNING, Level.WARN}, - {Event.Level.ERROR, Level.ERROR}, - {Event.Level.FATAL, Level.FATAL}}; + {Event.Level.DEBUG, Level.TRACE}, + {Event.Level.DEBUG, Level.DEBUG}, + {Event.Level.INFO, Level.INFO}, + {Event.Level.WARNING, Level.WARN}, + {Event.Level.ERROR, Level.ERROR}, + {Event.Level.FATAL, Level.FATAL}}; } @Test(dataProvider = "levels") public void testLevelConversion(final Event.Level expectedLevel, Level level) throws Exception { - sentryAppender.append(new Log4jLogEvent(null, null, null, level, new SimpleMessage(""), null)); + + Log4jLogEvent event = Log4jLogEvent.newBuilder() + .setLevel(level) + .setMessage(new SimpleMessage("")) + .build(); + + sentryAppender.append(event); new Verifications() {{ EventBuilder eventBuilder; @@ -98,7 +113,7 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th } @Test - public void testExceptionLogging() throws Exception { + public void testExceptionLogging() { final Exception exception = new Exception("d0d1b31f-e885-42e3-aac6-48c500f10ed1"); sentryAppender.append(new Log4jLogEvent(null, null, null, Level.ERROR, new SimpleMessage(""), exception)); @@ -108,7 +123,7 @@ public void testExceptionLogging() throws Exception { mockSentryClient.sendEvent(eventBuilder = withCapture()); Event event = eventBuilder.build(); ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() - .get(ExceptionInterface.EXCEPTION_INTERFACE); + .get(ExceptionInterface.EXCEPTION_INTERFACE); SentryException sentryException = exceptionInterface.getExceptions().getFirst(); assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); assertThat(sentryException.getStackTraceInterface().getStackTrace(), @@ -118,33 +133,42 @@ public void testExceptionLogging() throws Exception { } @Test - public void testLogParametrisedMessage() throws Exception { + public void testLogParametrisedMessage() { final String messagePattern = "Formatted message {} {} {}"; final Object[] parameters = {"first parameter", new Object[0], null}; - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, - new FormattedMessage(messagePattern, parameters), null)); + Log4jLogEvent event = Log4jLogEvent.newBuilder() + .setMessage(new FormattedMessage(messagePattern, parameters)) + .setLevel(Level.INFO) + .build(); + + sentryAppender.append(event); new Verifications() {{ EventBuilder eventBuilder; mockSentryClient.sendEvent(eventBuilder = withCapture()); Event event = eventBuilder.build(); MessageInterface messageInterface = (MessageInterface) event.getSentryInterfaces() - .get(MessageInterface.MESSAGE_INTERFACE); + .get(MessageInterface.MESSAGE_INTERFACE); assertThat(event.getMessage(), is("Formatted message first parameter [] null")); assertThat(messageInterface.getMessage(), is(messagePattern)); assertThat(messageInterface.getParameters(), - is(Arrays.asList(parameters[0].toString(), parameters[1].toString(), null))); + is(Arrays.asList(parameters[0].toString(), parameters[1].toString(), null))); }}; assertNoErrorsInErrorHandler(); } @Test - public void testMarkerAddedToTag() throws Exception { + public void testMarkerAddedToTag() { final String markerName = "c97e1fc0-9fff-41b3-8d0d-c24b54c670bb"; - sentryAppender.append(new Log4jLogEvent(null, MarkerManager.getMarker(markerName), null, Level.INFO, - new SimpleMessage(""), null)); + Log4jLogEvent event = Log4jLogEvent.newBuilder() + .setMarker(MarkerManager.getMarker(markerName)) + .setLevel(Level.INFO) + .setMessage(new SimpleMessage("")) + .build(); + + sentryAppender.append(event); new Verifications() {{ EventBuilder eventBuilder; @@ -156,12 +180,21 @@ public void testMarkerAddedToTag() throws Exception { } @Test - public void testMdcAddedToExtra() throws Exception { + public void testMdcAddedToExtra() { final String extraKey = "a4ce2632-8d9c-471d-8b06-1744be2ae8e9"; final String extraValue = "6dbeb494-197e-4f57-939a-613e2c16607d"; - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, - Collections.singletonMap(extraKey, extraValue), null, null, null, 0)); + StringMap mdc = ContextDataFactory.createContextData(); + mdc.putValue(extraKey, extraValue); + + Log4jLogEvent event = Log4jLogEvent.newBuilder() + .setContextData(mdc) + .setLevel(Level.INFO) + .setMessage(new SimpleMessage("")) + .setTimeMillis(0) + .build(); + + sentryAppender.append(event); new Verifications() {{ EventBuilder eventBuilder; @@ -174,14 +207,20 @@ public void testMdcAddedToExtra() throws Exception { @Test @SuppressWarnings("unchecked") - public void testNdcAddedToExtra() throws Exception { + public void testNdcAddedToExtra() { final ThreadContext.ContextStack contextStack = new DefaultThreadContextStack(true); contextStack.push("444af01f-fb80-414f-b035-15bdb91cb8b2"); contextStack.push("a1cb5e08-480a-4b32-b675-212f00c44e05"); contextStack.push("0aa5db14-1579-46ef-aae2-350d974e7fb8"); - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, - contextStack, null, null, 0)); + Log4jLogEvent event = Log4jLogEvent.newBuilder() + .setContextStack(contextStack) + .setLevel(Level.INFO) + .setMessage(new SimpleMessage("")) + .setTimeMillis(0L) + .build(); + + sentryAppender.append(event); new Verifications() {{ EventBuilder eventBuilder; @@ -193,19 +232,25 @@ public void testNdcAddedToExtra() throws Exception { } @Test - public void testSourceUsedAsStacktrace() throws Exception { + public void testSourceUsedAsStacktrace() { final StackTraceElement location = new StackTraceElement("7039c1f7-21e3-4134-8ced-524281633224", - "c68f3af9-1618-4d80-ad1b-ea0701568153", "f87a8821-1c70-44b8-81c3-271d454e4b08", 42); + "c68f3af9-1618-4d80-ad1b-ea0701568153", "f87a8821-1c70-44b8-81c3-271d454e4b08", 42); + + Log4jLogEvent event = Log4jLogEvent.newBuilder() + .setSource(location) + .setLevel(Level.INFO) + .setMessage(new SimpleMessage("")) + .setTimeMillis(0L) + .build(); - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, null, null, - null, location, 0)); + sentryAppender.append(event); new Verifications() {{ EventBuilder eventBuilder; mockSentryClient.sendEvent(eventBuilder = withCapture()); Event event = eventBuilder.build(); StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() - .get(StackTraceInterface.STACKTRACE_INTERFACE); + .get(StackTraceInterface.STACKTRACE_INTERFACE); assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); assertThat(stackTraceInterface.getStackTrace()[0], is(SentryStackTraceElement.fromStackTraceElement(location))); }}; @@ -213,17 +258,24 @@ public void testSourceUsedAsStacktrace() throws Exception { } @Test - public void testExtraTagObtainedFromMdc() throws Exception { - Map mdc = new HashMap<>(); - mdc.put(mockExtraTag, "565940d2-f4a4-42f6-9496-42e3c7c85c43"); - mdc.put("other_property", "395856e8-fa1d-474f-8fa9-c062b4886527"); + public void testExtraTagObtainedFromMdc() { + StringMap mdc = ContextDataFactory.createContextData(); + mdc.putValue(mockExtraTag, "565940d2-f4a4-42f6-9496-42e3c7c85c43"); + mdc.putValue("other_property", "395856e8-fa1d-474f-8fa9-c062b4886527"); new NonStrictExpectations() {{ mockSentryClient.getMdcTags(); result = extraTags; }}; - sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null, mdc, null, null, null, 0)); + Log4jLogEvent event = Log4jLogEvent.newBuilder() + .setContextData(mdc) + .setLevel(Level.INFO) + .setMessage(new SimpleMessage("")) + .setTimeMillis(0) + .build(); + + sentryAppender.append(event); new Verifications() {{ EventBuilder eventBuilder; From 95d97f16827563446329d74f63c14f458f0e42cb Mon Sep 17 00:00:00 2001 From: Sergey Tselovalnikov Date: Fri, 24 May 2019 12:49:27 +1000 Subject: [PATCH 2043/2152] [LOGBACK] Change appender to the unsynchronized version (#701) --- .../src/main/java/io/sentry/logback/SentryAppender.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index f2328654a95..7c9f6d6cd59 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -4,7 +4,7 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.IThrowableProxy; import ch.qos.logback.classic.spi.StackTraceElementProxy; -import ch.qos.logback.core.AppenderBase; +import ch.qos.logback.core.UnsynchronizedAppenderBase; import ch.qos.logback.core.filter.Filter; import ch.qos.logback.core.spi.FilterReply; import io.sentry.Sentry; @@ -28,7 +28,7 @@ /** * Appender for logback in charge of sending the logged events to a Sentry server. */ -public class SentryAppender extends AppenderBase { +public class SentryAppender extends UnsynchronizedAppenderBase { /** * Name of the {@link Event#extra} property containing Maker details. @@ -44,7 +44,7 @@ public class SentryAppender extends AppenderBase { * @deprecated use logback filters. */ @Deprecated - protected Level minLevel; + protected volatile Level minLevel; /** * Creates an instance of SentryAppender. From a779329a6b7caa2143659043755f204a8942ba4a Mon Sep 17 00:00:00 2001 From: mayt017 <47799388+mayt017@users.noreply.github.com> Date: Thu, 23 May 2019 19:54:23 -0700 Subject: [PATCH 2044/2152] [DSN] When a secret key is not set, don't throw NPE (#696) --- sentry/src/main/java/io/sentry/dsn/Dsn.java | 2 +- sentry/src/main/java/io/sentry/util/Util.java | 20 +++++++++++++++++++ .../src/test/java/io/sentry/dsn/DsnTest.java | 7 +++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/sentry/src/main/java/io/sentry/dsn/Dsn.java b/sentry/src/main/java/io/sentry/dsn/Dsn.java index 71801cbd88b..7ecd0b22ea8 100644 --- a/sentry/src/main/java/io/sentry/dsn/Dsn.java +++ b/sentry/src/main/java/io/sentry/dsn/Dsn.java @@ -290,7 +290,7 @@ public boolean equals(Object o) { if (!publicKey.equals(dsn.publicKey)) { return false; } - if (!secretKey.equals(dsn.secretKey)) { + if (!Util.equals(secretKey, dsn.secretKey)) { return false; } diff --git a/sentry/src/main/java/io/sentry/util/Util.java b/sentry/src/main/java/io/sentry/util/Util.java index 0ad859c14d9..7215aa2b958 100644 --- a/sentry/src/main/java/io/sentry/util/Util.java +++ b/sentry/src/main/java/io/sentry/util/Util.java @@ -17,6 +17,26 @@ private Util() { } + /** + * Returns {@code true} if the arguments are equal to each other + * and {@code false} otherwise. + * Consequently, if both arguments are {@code null}, {@code true} + * is returned and if exactly one argument is {@code null}, {@code + * false} is returned. Otherwise, equality is determined by using + * the {@link Object#equals equals} method of the first + * argument. + * + * @param a an object + * @param b an object to be compared with {@code a} for equality + * @return {@code true} if the arguments are equal to each other + * and {@code false} otherwise + * @see Object#equals(Object) + */ + public static boolean equals(final Object a, final Object b) { + //noinspection EqualsReplaceableByObjectsCall + return (a == b) || (a != null && a.equals(b)); + } + /** * Returns {@code true} if the given string is null or is the empty string. * diff --git a/sentry/src/test/java/io/sentry/dsn/DsnTest.java b/sentry/src/test/java/io/sentry/dsn/DsnTest.java index 6bdc8201822..45a6f6fc262 100644 --- a/sentry/src/test/java/io/sentry/dsn/DsnTest.java +++ b/sentry/src/test/java/io/sentry/dsn/DsnTest.java @@ -185,4 +185,11 @@ public void testProtocolSettingsImmutable() throws Exception { dsn.getProtocolSettings().add("test"); } + + @Test + public void testDsnEqualsMethodWithNoSecretKey() { + final Dsn dsn1 = new Dsn("http://publicKey@host/9"); + final Dsn dsn2 = new Dsn("http://publicKey@host/9"); + assertThat(dsn1, equalTo(dsn2)); + } } From e4ca5a90744c4de7e21636b9c961739fb9bd129e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ferreira?= Date: Thu, 23 May 2019 23:08:23 -0400 Subject: [PATCH 2045/2152] [JACKSON] Upgrade to version 2.9.9 (#717) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 31407c56851..e722d1c2305 100644 --- a/pom.xml +++ b/pom.xml @@ -119,7 +119,7 @@ 1.7.24 - 2.8.7 + 2.9.9 1.14 6.9.10 1.3 From 294f6c858912b61acb1e57cc5424a0d705f37b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ferreira?= Date: Fri, 24 May 2019 01:20:42 -0400 Subject: [PATCH 2046/2152] [EXCEPTION] Don't erase inner class names from stack trace interface when it's of type exception (#720) --- .../event/interfaces/SentryException.java | 15 ++++++++--- .../event/interfaces/SentryExceptionTest.java | 27 +++++++++++++++++-- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/sentry/src/main/java/io/sentry/event/interfaces/SentryException.java b/sentry/src/main/java/io/sentry/event/interfaces/SentryException.java index f23ceb0832a..f3865570f23 100644 --- a/sentry/src/main/java/io/sentry/event/interfaces/SentryException.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/SentryException.java @@ -1,7 +1,6 @@ package io.sentry.event.interfaces; import io.sentry.jvmti.FrameCache; - import java.io.Serializable; import java.util.ArrayDeque; import java.util.Deque; @@ -31,10 +30,18 @@ public final class SentryException implements Serializable { * @param childExceptionStackTrace StackTrace of the exception caused by {@code throwable}. */ public SentryException(Throwable throwable, StackTraceElement[] childExceptionStackTrace) { - this.exceptionMessage = throwable.getMessage(); - this.exceptionClassName = throwable.getClass().getSimpleName(); Package exceptionPackage = throwable.getClass().getPackage(); - this.exceptionPackageName = exceptionPackage != null ? exceptionPackage.getName() : null; + String fullClassName = throwable.getClass().getName(); + + this.exceptionMessage = throwable.getMessage(); + this.exceptionClassName = exceptionPackage != null + ? fullClassName.replace(exceptionPackage.getName() + ".", "") + : fullClassName; + + this.exceptionPackageName = exceptionPackage != null + ? exceptionPackage.getName() + : null; + this.stackTraceInterface = new StackTraceInterface( throwable.getStackTrace(), childExceptionStackTrace, diff --git a/sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java b/sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java index 07b01428533..ac3a65dc695 100644 --- a/sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java @@ -1,20 +1,23 @@ package io.sentry.event.interfaces; import io.sentry.BaseTest; +import java.util.Deque; import mockit.Delegate; import mockit.Injectable; import mockit.NonStrictExpectations; import org.testng.annotations.Test; -import java.util.Deque; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; public class SentryExceptionTest extends BaseTest { + @Injectable private Throwable mockThrowable = null; + @Injectable + private InnerClassThrowable mockInnerClassThrowable = null; + @Test public void ensureConversionToQueueKeepsOrder(@Injectable final Throwable mockCause) throws Exception { final String exceptionMessage = "208ea34a-9c99-42d6-a399-59a4c85900dc"; @@ -38,4 +41,24 @@ public Throwable getCause() { assertThat(exceptions.getFirst().getExceptionMessage(), is(exceptionMessage)); assertThat(exceptions.getLast().getExceptionMessage(), is(causeMessage)); } + + @Test + public void ensureInnerClassesAreRepresentedCorrectly(@Injectable final InnerClassThrowable mockCause) throws Exception { + new NonStrictExpectations() {{ + mockInnerClassThrowable.getCause(); + result = new Delegate() { + @SuppressWarnings("unused") + public Throwable getCause() { + return mockCause; + } + }; + }}; + + Deque exceptions = SentryException.extractExceptionQueue(mockInnerClassThrowable); + assertThat(exceptions.getFirst().getExceptionClassName(), is("SentryExceptionTest$InnerClassThrowable")); + } + + private static final class InnerClassThrowable extends Throwable { + + } } From 2ccdfd2e175343154787f2e5f2f4b305c334a70a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ferreira?= Date: Fri, 24 May 2019 01:21:05 -0400 Subject: [PATCH 2047/2152] [STACKTRACE] Add more classes to blacklist and optimize parsing (#719) * [JSON] Correctly format JSON resources * [STACKTRACE] Add more classes to blacklist and optimize parsing * [STACKTRACE] Fix checkstyle * [STACKTRACE] Fix declaration order --- .../json/StackTraceInterfaceBinding.java | 33 +++++----- .../json/StackTraceInterfaceBindingTest.java | 43 +++++++------ .../io/sentry/marshaller/json/Exception1.json | 12 ++-- .../io/sentry/marshaller/json/Exception2.json | 12 ++-- .../io/sentry/marshaller/json/Exception3.json | 24 ++++---- .../io/sentry/marshaller/json/Http1.json | 17 ++++-- .../io/sentry/marshaller/json/Http2.json | 26 ++++++-- .../io/sentry/marshaller/json/Message1.json | 7 ++- .../io/sentry/marshaller/json/Proguard.json | 12 ++-- .../sentry/marshaller/json/StackTrace1.json | 16 ++--- .../sentry/marshaller/json/StackTrace2.json | 26 ++++---- .../sentry/marshaller/json/StackTrace3.json | 26 ++++---- .../marshaller/json/StackTraceBlacklist.json | 46 +++++++++----- .../io/sentry/marshaller/json/User1.json | 18 +++--- .../jsonmarshallertest/testBreadcrumbs.json | 60 +++++++++++-------- .../json/jsonmarshallertest/testChecksum.json | 38 ++++++------ .../json/jsonmarshallertest/testContexts.json | 27 ++++----- .../json/jsonmarshallertest/testCulprit.json | 38 ++++++------ .../json/jsonmarshallertest/testEventId.json | 38 ++++++------ .../jsonmarshallertest/testExtraArray.json | 52 ++++++++-------- .../jsonmarshallertest/testExtraBoolean.json | 42 ++++++------- .../testExtraCustomValue.json | 42 ++++++------- .../jsonmarshallertest/testExtraIterable.json | 52 ++++++++-------- .../json/jsonmarshallertest/testExtraMap.json | 44 +++++++------- .../jsonmarshallertest/testExtraNull.json | 42 ++++++------- .../testExtraNullKeyMap.json | 44 +++++++------- .../jsonmarshallertest/testExtraNumber.json | 42 ++++++------- .../testExtraObjectKeyMap.json | 44 +++++++------- .../testExtraRecursiveArray.json | 56 ++++++++--------- .../testExtraRecursiveMap.json | 54 ++++++++--------- .../jsonmarshallertest/testExtraString.json | 42 ++++++------- .../jsonmarshallertest/testFingerprint.json | 46 +++++++------- .../testInterfaceBinding.json | 40 ++++++------- .../jsonmarshallertest/testLevelDebug.json | 38 ++++++------ .../jsonmarshallertest/testLevelError.json | 38 ++++++------ .../jsonmarshallertest/testLevelFatal.json | 38 ++++++------ .../jsonmarshallertest/testLevelInfo.json | 38 ++++++------ .../jsonmarshallertest/testLevelWarning.json | 38 ++++++------ .../json/jsonmarshallertest/testLogger.json | 38 ++++++------ .../json/jsonmarshallertest/testMessage.json | 38 ++++++------ .../json/jsonmarshallertest/testPlatform.json | 38 ++++++------ .../json/jsonmarshallertest/testRelease.json | 38 ++++++------ .../json/jsonmarshallertest/testSdk.json | 5 +- .../jsonmarshallertest/testServerName.json | 38 ++++++------ .../json/jsonmarshallertest/testTags.json | 42 ++++++------- .../jsonmarshallertest/testTimestamp.json | 38 ++++++------ .../jsonobjectmarshallertest/testByte.json | 4 +- .../testByteArray.json | 8 ++- .../jsonobjectmarshallertest/testCycle.json | 10 +++- .../testIntArray.json | 8 ++- .../testIterable.json | 16 ++++- .../jsonobjectmarshallertest/testMap.json | 13 +++- .../jsonobjectmarshallertest/testNull.json | 4 +- .../testObjectArray.json | 8 ++- .../jsonobjectmarshallertest/testPath.json | 4 +- 55 files changed, 909 insertions(+), 792 deletions(-) diff --git a/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java index 378dba7edf5..4c491ed34db 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java @@ -25,16 +25,14 @@ public class StackTraceInterfaceBinding implements InterfaceBinding inAppBlacklistRegexps = new ArrayList<>(); + private static final Pattern IN_APP_BLACKLIST = Pattern.compile("\\$+" + // match $ (one or more) + "(?:" + // start outer group + "(?:(?:FastClass|Enhancer)[a-zA-Z]*CGLIB)" + // Match Spring's FastClass and Enhancer classes + "|(?:HibernateProxy)" + // match Hibernate proxies + ")\\$+"); // end outer group and match $ (one or more) private Collection inAppFrames = Collections.emptyList(); private boolean removeCommonFramesWithEnclosing = true; - static { - // skip CGLIB generated classes like Foo$$FastClassBySpringCGLIB$$4ed8b6b - inAppBlacklistRegexps.add(Pattern.compile("\\$\\$FastClass[a-zA-Z]*CGLIB\\$\\$")); - inAppBlacklistRegexps.add(Pattern.compile("\\$\\$Enhancer[a-zA-Z]*CGLIB\\$\\$")); - } - /** * Writes a single frame based on a {@code StackTraceElement}. * @@ -76,25 +74,22 @@ private void writeFrame(JsonGenerator generator, SentryStackTraceElement stackTr } private boolean isFrameInApp(SentryStackTraceElement stackTraceElement) { - // TODO: A linear search is not efficient here, a Trie could be a better solution. + String className = stackTraceElement.getModule(); + if (classIsBlacklisted(className)) { + return false; + } + for (String inAppFrame : inAppFrames) { - String className = stackTraceElement.getModule(); - if (className.startsWith(inAppFrame) && !isBlacklistedFromInApp(className)) { + if (className.startsWith(inAppFrame)) { return true; } } return false; } - private boolean isBlacklistedFromInApp(String className) { - for (Pattern inAppBlacklistRegexp : inAppBlacklistRegexps) { - boolean found = inAppBlacklistRegexp.matcher(className).find(); - if (found) { - return true; - } - } - - return false; + private boolean classIsBlacklisted(String className) { + return (className.contains("CGLIB") || className.contains("Hibernate")) + && IN_APP_BLACKLIST.matcher(className).find(); } @Override diff --git a/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java index 02abb0bcbd7..d4f961342ac 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java @@ -2,22 +2,24 @@ import io.sentry.BaseTest; import io.sentry.event.interfaces.SentryStackTraceElement; +import io.sentry.event.interfaces.StackTraceInterface; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; -import io.sentry.event.interfaces.StackTraceInterface; import org.testng.annotations.Test; -import java.util.ArrayList; -import java.util.List; - import static io.sentry.marshaller.json.JsonComparisonUtil.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; public class StackTraceInterfaceBindingTest extends BaseTest { + @Tested private StackTraceInterfaceBinding interfaceBinding = null; + @Injectable private StackTraceInterface mockStackTraceInterface = null; @@ -26,7 +28,7 @@ public void testSingleSentryStackFrame() throws Exception { final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final SentryStackTraceElement sentryStackTraceElement = new SentryStackTraceElement( "", "throwError", "index.js", 100, 10, - "http://localhost","javascript", null); + "http://localhost", "javascript", null); new NonStrictExpectations() {{ mockStackTraceInterface.getStackTrace(); result = new SentryStackTraceElement[]{sentryStackTraceElement}; @@ -94,21 +96,27 @@ public void testFramesCommonWithEnclosingDisabled() throws Exception { public void testInAppFrames() throws Exception { final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); - final SentryStackTraceElement stackTraceElement1 = new SentryStackTraceElement( - "inAppModule.foo", "inAppMethod", - "File.java", 1, null, null, null, null); - - final SentryStackTraceElement stackTraceElement2 = new SentryStackTraceElement( - "notInAppModule.bar", "notInAppMethod", - "File.java", 2, null, null, null, null); - - final SentryStackTraceElement stackTraceElement3 = new SentryStackTraceElement( - "inAppModule.Blacklisted$$FastClassBySpringCGLIB$$", "blacklisted", - "File.java", 3, null, null, null, null); + final List sentryStackTraceElements = Arrays.asList( + new SentryStackTraceElement( + "inAppModule.foo", "inAppMethod", "File.java", + 1, null, null, null, null), + new SentryStackTraceElement( + "notInAppModule.bar", "notInAppMethod", + "File.java", 2, null, null, null, null), + new SentryStackTraceElement( + "inAppModule.Blacklisted$$FastClassBySpringCGLIB$$", "blacklisted", + "File.java", 3, null, null, null, null), + new SentryStackTraceElement( + "inAppModule.Blacklisted$HibernateProxy$", "blacklisted", + "File.java", 4, null, null, null, null), + new SentryStackTraceElement( + "inAppModule.Blacklisted$$EnhancerBySpringCGLIB$$", "blacklisted", + "File.java", 5, null, null, null, null) + ); new NonStrictExpectations() {{ mockStackTraceInterface.getStackTrace(); - result = new SentryStackTraceElement[]{stackTraceElement1, stackTraceElement2, stackTraceElement3}; + result = sentryStackTraceElements.toArray(new SentryStackTraceElement[4]); }}; List inAppModules = new ArrayList<>(); @@ -118,5 +126,4 @@ public void testInAppFrames() throws Exception { assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/StackTraceBlacklist.json"))); } - } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/Exception1.json b/sentry/src/test/resources/io/sentry/marshaller/json/Exception1.json index 8fa4ba89290..a149cf83f5a 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/Exception1.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/Exception1.json @@ -1,8 +1,8 @@ [ - { - "type": "IllegalStateException", - "value": "6e65f60d-9f22-495a-9556-7a61eeea2a14", - "module": "java.lang", - "stacktrace": {} - } + { + "type": "IllegalStateException", + "value": "6e65f60d-9f22-495a-9556-7a61eeea2a14", + "module": "java.lang", + "stacktrace": {} + } ] diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/Exception2.json b/sentry/src/test/resources/io/sentry/marshaller/json/Exception2.json index d5f64015573..d0797e5e780 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/Exception2.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/Exception2.json @@ -1,8 +1,8 @@ [ - { - "type": "DefaultPackageException", - "value": null, - "module": "(default)", - "stacktrace": {} - } + { + "type": "DefaultPackageException", + "value": null, + "module": "(default)", + "stacktrace": {} + } ] diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/Exception3.json b/sentry/src/test/resources/io/sentry/marshaller/json/Exception3.json index 0c5d993bbd7..2ef634c8dc8 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/Exception3.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/Exception3.json @@ -1,14 +1,14 @@ [ - { - "type": "IllegalStateException", - "value": "a71e6132-9867-457d-8b04-5021cd7a251f", - "module": "java.lang", - "stacktrace": {} - }, - { - "type": "IllegalStateException", - "value": "f1296959-5b86-45f7-853a-cdc25196710b", - "module": "java.lang", - "stacktrace": {} - } + { + "type": "IllegalStateException", + "value": "a71e6132-9867-457d-8b04-5021cd7a251f", + "module": "java.lang", + "stacktrace": {} + }, + { + "type": "IllegalStateException", + "value": "f1296959-5b86-45f7-853a-cdc25196710b", + "module": "java.lang", + "stacktrace": {} + } ] diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/Http1.json b/sentry/src/test/resources/io/sentry/marshaller/json/Http1.json index ac684e28a37..7d9dd6a481e 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/Http1.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/Http1.json @@ -6,10 +6,19 @@ "cookies": { "Cookie1": "Value1" }, - "headers": [ - ["Header1","Value1"], - ["Header2","Value1"], - ["Header2","Value2"] + "headers": [ + [ + "Header1", + "Value1" + ], + [ + "Header2", + "Value1" + ], + [ + "Header2", + "Value2" + ] ], "env": { "REMOTE_ADDR": "1.2.3.4", diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/Http2.json b/sentry/src/test/resources/io/sentry/marshaller/json/Http2.json index cab082cf618..8b3bcadee57 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/Http2.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/Http2.json @@ -3,17 +3,31 @@ "method": "GET", "data": { "body": "body", - "Parameter1": ["Value1"], - "Parameter2": ["Value2","Value3"] + "Parameter1": [ + "Value1" + ], + "Parameter2": [ + "Value2", + "Value3" + ] }, "query_string": "query", "cookies": { "Cookie1": "Value1" }, - "headers": [ - ["Header1","Value1"], - ["Header2","Value1"], - ["Header2","Value2"] + "headers": [ + [ + "Header1", + "Value1" + ], + [ + "Header2", + "Value1" + ], + [ + "Header2", + "Value2" + ] ], "env": { "REMOTE_ADDR": "1.2.3.4", diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/Message1.json b/sentry/src/test/resources/io/sentry/marshaller/json/Message1.json index 352dd3234b1..3912828b594 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/Message1.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/Message1.json @@ -1,4 +1,7 @@ { - "message": "550ee459-cbb5-438e-91d2-b0bbdefab670", - "params": ["33ed929b-d803-46b6-a57b-9c0feab1f468", "5fc10379-6392-470d-9de5-e4cb805ab78c"] + "message": "550ee459-cbb5-438e-91d2-b0bbdefab670", + "params": [ + "33ed929b-d803-46b6-a57b-9c0feab1f468", + "5fc10379-6392-470d-9de5-e4cb805ab78c" + ] } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/Proguard.json b/sentry/src/test/resources/io/sentry/marshaller/json/Proguard.json index 546efa0221c..7bf07762be0 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/Proguard.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/Proguard.json @@ -1,8 +1,8 @@ { - "images": [ - { - "uuid": "abcd", - "type": "proguard" - } - ] + "images": [ + { + "uuid": "abcd", + "type": "proguard" + } + ] } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/StackTrace1.json b/sentry/src/test/resources/io/sentry/marshaller/json/StackTrace1.json index 12311ce1368..985eb163e60 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/StackTrace1.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/StackTrace1.json @@ -1,9 +1,11 @@ -{"frames": [ +{ + "frames": [ { - "module": "31b26f01-9b97-442b-9f36-8a317f94ad76", - "in_app": false, - "function": "0cce55c9-478f-4386-8ede-4b6f000da3e6", - "lineno": 1, - "filename": "File.java" + "module": "31b26f01-9b97-442b-9f36-8a317f94ad76", + "in_app": false, + "function": "0cce55c9-478f-4386-8ede-4b6f000da3e6", + "lineno": 1, + "filename": "File.java" } -]} + ] +} diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/StackTrace2.json b/sentry/src/test/resources/io/sentry/marshaller/json/StackTrace2.json index 2b1d621fa76..8f70fafa75b 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/StackTrace2.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/StackTrace2.json @@ -1,16 +1,18 @@ -{"frames": [ +{ + "frames": [ { - "module": "", - "in_app": false, - "function": "", - "filename": "File.java", - "lineno": 0 + "module": "", + "in_app": false, + "function": "", + "filename": "File.java", + "lineno": 0 }, { - "module": "", - "in_app": false, - "function": "", - "filename": "File.java", - "lineno": 0 + "module": "", + "in_app": false, + "function": "", + "filename": "File.java", + "lineno": 0 } -]} + ] +} diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/StackTrace3.json b/sentry/src/test/resources/io/sentry/marshaller/json/StackTrace3.json index 2b1d621fa76..8f70fafa75b 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/StackTrace3.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/StackTrace3.json @@ -1,16 +1,18 @@ -{"frames": [ +{ + "frames": [ { - "module": "", - "in_app": false, - "function": "", - "filename": "File.java", - "lineno": 0 + "module": "", + "in_app": false, + "function": "", + "filename": "File.java", + "lineno": 0 }, { - "module": "", - "in_app": false, - "function": "", - "filename": "File.java", - "lineno": 0 + "module": "", + "in_app": false, + "function": "", + "filename": "File.java", + "lineno": 0 } -]} + ] +} diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/StackTraceBlacklist.json b/sentry/src/test/resources/io/sentry/marshaller/json/StackTraceBlacklist.json index 436870fb2ed..da15e286c3d 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/StackTraceBlacklist.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/StackTraceBlacklist.json @@ -1,25 +1,39 @@ { - "frames":[ + "frames": [ { - "filename":"File.java", - "module":"inAppModule.Blacklisted$$FastClassBySpringCGLIB$$", - "in_app":false, - "function":"blacklisted", - "lineno":3 + "filename": "File.java", + "module": "inAppModule.Blacklisted$$EnhancerBySpringCGLIB$$", + "in_app": false, + "function": "blacklisted", + "lineno": 5 }, { - "filename":"File.java", - "module":"notInAppModule.bar", - "in_app":false, - "function":"notInAppMethod", - "lineno":2 + "filename": "File.java", + "module": "inAppModule.Blacklisted$HibernateProxy$", + "in_app": false, + "function": "blacklisted", + "lineno": 4 }, { - "filename":"File.java", - "module":"inAppModule.foo", - "in_app":true, - "function":"inAppMethod", - "lineno":1 + "filename": "File.java", + "module": "inAppModule.Blacklisted$$FastClassBySpringCGLIB$$", + "in_app": false, + "function": "blacklisted", + "lineno": 3 + }, + { + "filename": "File.java", + "module": "notInAppModule.bar", + "in_app": false, + "function": "notInAppMethod", + "lineno": 2 + }, + { + "filename": "File.java", + "module": "inAppModule.foo", + "in_app": true, + "function": "inAppMethod", + "lineno": 1 } ] } \ No newline at end of file diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/User1.json b/sentry/src/test/resources/io/sentry/marshaller/json/User1.json index f17890284ba..ec660279c32 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/User1.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/User1.json @@ -1,11 +1,11 @@ { - "id": "970e9df6-6e0b-4a27-b2ee-0faf0f368354", - "username": "3eaa555a-e813-4778-9852-7c1880bf0fd7", - "email": "9bcade34-a58c-4616-9de7-bc8b456c96de", - "ip_address": "9a1a658b-6f74-43ae-9e45-0f89f4c5fcb4", - "data": { - "foo": "bar", - "baz": 2, - "qux": null - } + "id": "970e9df6-6e0b-4a27-b2ee-0faf0f368354", + "username": "3eaa555a-e813-4778-9852-7c1880bf0fd7", + "email": "9bcade34-a58c-4616-9de7-bc8b456c96de", + "ip_address": "9a1a658b-6f74-43ae-9e45-0f89f4c5fcb4", + "data": { + "foo": "bar", + "baz": 2, + "qux": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json index 629db9efa97..237e982b23e 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json @@ -1,27 +1,37 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": {}, - "checksum": null, - "breadcrumbs": { - "values": [ - {"timestamp":1463169342,"level":"info","message":"test1","category":"foo"}, - {"timestamp":1463169343,"level":"info","message":"test2","category":"foo"} - ] - }, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": {}, + "checksum": null, + "breadcrumbs": { + "values": [ + { + "timestamp": 1463169342, + "level": "info", + "message": "test1", + "category": "foo" + }, + { + "timestamp": 1463169343, + "level": "info", + "message": "test2", + "category": "foo" + } + ] + }, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testChecksum.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testChecksum.json index 73de4060db1..62c6ff0a982 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testChecksum.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testChecksum.json @@ -1,21 +1,21 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": {}, - "checksum": "1234567890abcdef", - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": {}, + "checksum": "1234567890abcdef", + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testContexts.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testContexts.json index 55933e2bad4..6d1cfcdfa1a 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testContexts.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testContexts.json @@ -14,21 +14,20 @@ "environment": null, "extra": {}, "checksum": null, - "contexts": - { - "context1": { - "context1key1": "context1value1", - "context1key2": 12, - "context1key3": 1.3, - "context1key4": true - }, - "context2": { - "context2key1": "context2value1", - "context2key2": 22, - "context2key3": 2.3, - "context2key4": false - } + "contexts": { + "context1": { + "context1key1": "context1value1", + "context1key2": 12, + "context1key3": 1.3, + "context1key4": true }, + "context2": { + "context2key1": "context2value1", + "context2key2": 22, + "context2key3": 2.3, + "context2key4": false + } + }, "sdk": { "name": null, "version": null diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCulprit.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCulprit.json index 35074544405..d0ddb073262 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCulprit.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testCulprit.json @@ -1,21 +1,21 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": "culprit", - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": {}, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": "culprit", + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": {}, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEventId.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEventId.json index 48f8e51ed66..aa1f7ff0aff 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEventId.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testEventId.json @@ -1,21 +1,21 @@ { - "event_id": "3b71fba5413e4022ae985f0b80a155a5", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": {}, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "3b71fba5413e4022ae985f0b80a155a5", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": {}, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraArray.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraArray.json index 2b96ab2965c..477fe0b364b 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraArray.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraArray.json @@ -1,28 +1,28 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": { - "key": [ - "string", - 1, - null, - true - ] - }, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": { + "key": [ + "string", + 1, + null, + true + ] + }, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraBoolean.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraBoolean.json index 808243eb0c3..305692a42c4 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraBoolean.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraBoolean.json @@ -1,23 +1,23 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": { - "key": true - }, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": { + "key": true + }, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraCustomValue.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraCustomValue.json index c2b117509b6..21fc7bd5a21 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraCustomValue.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraCustomValue.json @@ -1,23 +1,23 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": { - "key": "test" - }, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": { + "key": "test" + }, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraIterable.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraIterable.json index 7a70a6fbd14..975367ae56b 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraIterable.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraIterable.json @@ -1,28 +1,28 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": { - "key": [ - true, - null, - 1, - "string" - ] - }, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": { + "key": [ + true, + null, + 1, + "string" + ] + }, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraMap.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraMap.json index 5eb00cc9492..fccbe40c9b6 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraMap.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraMap.json @@ -1,25 +1,25 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": { - "key": { - "key": "value" - } - }, - "checksum": null, - "sdk": { - "name": null, - "version": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": { + "key": { + "key": "value" } + }, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNull.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNull.json index 35232470250..5cee9a34e02 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNull.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNull.json @@ -1,23 +1,23 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": { - "key": null - }, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": { + "key": null + }, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json index 04bff4d8d8f..6ce38ab6f21 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNullKeyMap.json @@ -1,25 +1,25 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": { - "key": { - "null": "value" - } - }, - "checksum": null, - "sdk": { - "name": null, - "version": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": { + "key": { + "null": "value" } + }, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNumber.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNumber.json index 02b89e27388..2f0d0f1b628 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNumber.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraNumber.json @@ -1,23 +1,23 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": { - "key": 1 - }, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": { + "key": 1 + }, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json index 150eb30e9ee..96afd05e325 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraObjectKeyMap.json @@ -1,25 +1,25 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": { - "key": { - "true": "value" - } - }, - "checksum": null, - "sdk": { - "name": null, - "version": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": { + "key": { + "true": "value" } + }, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json index 310b646031e..b381de42887 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveArray.json @@ -1,30 +1,30 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": { - "key": [ - [ - "string", - 1, - null, - true - ] - ] - }, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": { + "key": [ + [ + "string", + 1, + null, + true + ] + ] + }, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json index d6a1695c63a..41954fe34ee 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraRecursiveMap.json @@ -1,30 +1,30 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": { - "key": { - "key": [ - "string", - 1, - true, - null - ] - } - }, - "checksum": null, - "sdk": { - "name": null, - "version": null + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": { + "key": { + "key": [ + "string", + 1, + true, + null + ] } + }, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraString.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraString.json index 66f1fce9af0..d78ab088a95 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraString.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testExtraString.json @@ -1,23 +1,23 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": { - "key": "string" - }, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": { + "key": "string" + }, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testFingerprint.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testFingerprint.json index f1b48e0e7ab..57faccaa53a 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testFingerprint.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testFingerprint.json @@ -1,25 +1,25 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": {}, - "fingerprint": [ - "fingerprint1", - "fingerprint2" - ], - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": {}, + "fingerprint": [ + "fingerprint1", + "fingerprint2" + ], + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testInterfaceBinding.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testInterfaceBinding.json index 5ee24105c09..3d53a9d9d1f 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testInterfaceBinding.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testInterfaceBinding.json @@ -1,22 +1,22 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": {}, - "checksum": null, - "interfaceKey": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": {}, + "checksum": null, + "interfaceKey": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelDebug.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelDebug.json index c25e44359af..8b3aed7ec6c 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelDebug.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelDebug.json @@ -1,21 +1,21 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": "debug", - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": {}, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": "debug", + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": {}, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelError.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelError.json index cae444d8bdf..83595fc51cf 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelError.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelError.json @@ -1,21 +1,21 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": "error", - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": {}, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": "error", + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": {}, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelFatal.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelFatal.json index 1e4f902c51a..c305bc691fb 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelFatal.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelFatal.json @@ -1,21 +1,21 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": "fatal", - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": {}, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": "fatal", + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": {}, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelInfo.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelInfo.json index 4290f0b6375..51b8b468076 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelInfo.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelInfo.json @@ -1,21 +1,21 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": "info", - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": {}, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": "info", + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": {}, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelWarning.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelWarning.json index e29c0fd897b..63040498655 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelWarning.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLevelWarning.json @@ -1,21 +1,21 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": "warning", - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": {}, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": "warning", + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": {}, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLogger.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLogger.json index 0e33d1f80af..db065120842 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLogger.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testLogger.json @@ -1,21 +1,21 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": "logger", - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": {}, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": "logger", + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": {}, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testMessage.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testMessage.json index bd39eff3655..81b9137f906 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testMessage.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testMessage.json @@ -1,21 +1,21 @@ { - "event_id": "00000000000000000000000000000000", - "message": "message", - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": {}, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": "message", + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": {}, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testPlatform.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testPlatform.json index 8f728df9dc7..d524bc87d5b 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testPlatform.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testPlatform.json @@ -1,21 +1,21 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": "platform", - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": {}, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": "platform", + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": {}, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testRelease.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testRelease.json index 52b2bf77956..678cd2b1689 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testRelease.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testRelease.json @@ -1,21 +1,21 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": "release", - "dist": null, - "environment": null, - "extra": {}, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": "release", + "dist": null, + "environment": null, + "extra": {}, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json index 6641a444064..9a5361ba883 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testSdk.json @@ -17,6 +17,9 @@ "sdk": { "name": "sdkName", "version": "sdkVersion", - "integrations": ["integration2", "integration1"] + "integrations": [ + "integration2", + "integration1" + ] } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testServerName.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testServerName.json index 19d34f501a5..d81bb0aa973 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testServerName.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testServerName.json @@ -1,21 +1,21 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": "serverName", - "release": null, - "dist": null, - "environment": null, - "extra": {}, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": "serverName", + "release": null, + "dist": null, + "environment": null, + "extra": {}, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTags.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTags.json index dd6f244819c..3d4aced8966 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTags.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTags.json @@ -1,23 +1,23 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "1970-01-01T00:00:00", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": { - "tagName": "tagValue" - }, - "server_name": null, - "release": null, - "dist": null, - "environment": null, - "extra": {}, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "1970-01-01T00:00:00", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": { + "tagName": "tagValue" + }, + "server_name": null, + "release": null, + "dist": null, + "environment": null, + "extra": {}, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTimestamp.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTimestamp.json index 08ad26aca15..80c00536a6b 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTimestamp.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testTimestamp.json @@ -1,21 +1,21 @@ { - "event_id": "00000000000000000000000000000000", - "message": null, - "timestamp": "2013-11-24T04:11:35", - "level": null, - "logger": null, - "platform": null, - "culprit": null, - "transaction": null, - "tags": {}, - "server_name": null, - "release": null, - "dist": null, - "extra": {}, - "environment": null, - "checksum": null, - "sdk": { - "name": null, - "version": null - } + "event_id": "00000000000000000000000000000000", + "message": null, + "timestamp": "2013-11-24T04:11:35", + "level": null, + "logger": null, + "platform": null, + "culprit": null, + "transaction": null, + "tags": {}, + "server_name": null, + "release": null, + "dist": null, + "extra": {}, + "environment": null, + "checksum": null, + "sdk": { + "name": null, + "version": null + } } diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testByte.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testByte.json index 26b66c9b416..3077122453e 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testByte.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testByte.json @@ -1 +1,3 @@ -{"output": 127} \ No newline at end of file +{ + "output": 127 +} \ No newline at end of file diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testByteArray.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testByteArray.json index 30a24e7c750..ac086faaabb 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testByteArray.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testByteArray.json @@ -1 +1,7 @@ -{"output": [3,4,"..."]} \ No newline at end of file +{ + "output": [ + 3, + 4, + "..." + ] +} \ No newline at end of file diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testCycle.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testCycle.json index b7e6f62f2b7..f18878aac4e 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testCycle.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testCycle.json @@ -1 +1,9 @@ -{"output":{"cycle!":{"cycle!":{"cycle!":""}}}} \ No newline at end of file +{ + "output": { + "cycle!": { + "cycle!": { + "cycle!": "" + } + } + } +} \ No newline at end of file diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testIntArray.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testIntArray.json index 66d31fb59b1..5a9f5975fba 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testIntArray.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testIntArray.json @@ -1 +1,7 @@ -{"output": [1,2,"..."]} \ No newline at end of file +{ + "output": [ + 1, + 2, + "..." + ] +} \ No newline at end of file diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testIterable.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testIterable.json index 5f44373683a..4ea752f3bd5 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testIterable.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testIterable.json @@ -1 +1,15 @@ -{"output":[["1","2","..."],["4","5","..."],"..."]} \ No newline at end of file +{ + "output": [ + [ + "1", + "2", + "..." + ], + [ + "4", + "5", + "..." + ], + "..." + ] +} \ No newline at end of file diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testMap.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testMap.json index 8c05321fb8c..8b83d786c31 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testMap.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testMap.json @@ -1 +1,12 @@ -{"output":{"map1":{"one ver...":1,"two ver...":2},"map2":{"four ve...":4,"five ve...":5}}} \ No newline at end of file +{ + "output": { + "map1": { + "one ver...": 1, + "two ver...": 2 + }, + "map2": { + "four ve...": 4, + "five ve...": 5 + } + } +} \ No newline at end of file diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testNull.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testNull.json index 06cbf7ad610..cb9da8e360f 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testNull.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testNull.json @@ -1 +1,3 @@ -{"output": null} \ No newline at end of file +{ + "output": null +} \ No newline at end of file diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testObjectArray.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testObjectArray.json index b73bca3724b..cb23ab4bff5 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testObjectArray.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testObjectArray.json @@ -1 +1,7 @@ -{"output": ["obj1", "obj2","..."]} \ No newline at end of file +{ + "output": [ + "obj1", + "obj2", + "..." + ] +} \ No newline at end of file diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testPath.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testPath.json index b70d2d96ac6..686365fc123 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testPath.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonobjectmarshallertest/testPath.json @@ -1 +1,3 @@ -{"output": "/home/u..."} \ No newline at end of file +{ + "output": "/home/u..." +} \ No newline at end of file From 83244bf0f99c76c2ec11a49dce428d82ae3189e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ferreira?= Date: Fri, 24 May 2019 18:01:30 -0400 Subject: [PATCH 2048/2152] [STACKTRACE] Fix checkstyle (#721) --- .../marshaller/json/StackTraceInterfaceBinding.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java index 4c491ed34db..1ed507d45a9 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java @@ -25,11 +25,11 @@ public class StackTraceInterfaceBinding implements InterfaceBinding inAppFrames = Collections.emptyList(); private boolean removeCommonFramesWithEnclosing = true; From c3db9f3de85814c53d1c096dd045907ef905bbc9 Mon Sep 17 00:00:00 2001 From: JoakimNil <24590972+JoakimNil@users.noreply.github.com> Date: Sun, 2 Jun 2019 20:54:39 +0200 Subject: [PATCH 2049/2152] [LOG4J2] Include log4j marker parent(s) in log4j marker tag (#691) Before this change, information about marker parents was not logged at all by sentry. With this change, the Sentry appender will include marker & marker parent information in the same way as built-in log4j appenders (for example ConsoleAppender). --- .../src/main/java/io/sentry/log4j2/SentryAppender.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java index f6d2893c576..cbf7daab4e5 100644 --- a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -186,7 +186,7 @@ protected EventBuilder createEventBuilder(LogEvent event) { } if (event.getMarker() != null) { - eventBuilder.withTag(LOG4J_MARKER, event.getMarker().getName()); + eventBuilder.withTag(LOG4J_MARKER, event.getMarker().toString()); } return eventBuilder; From 488600c56b7087bf87bedbb9cbb591a5a296d8a8 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 4 Jun 2019 13:55:46 +0200 Subject: [PATCH 2050/2152] chore: Ignore intelij project files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a2cf89a51a7..1067965b001 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ agent/build/ agent/.vscode/ local.properties .idea +*.iml \ No newline at end of file From a534a185038036ab7266956d44aed5209a8b3570 Mon Sep 17 00:00:00 2001 From: Christoph Dreis Date: Fri, 14 Jun 2019 11:20:29 +0200 Subject: [PATCH 2051/2152] Allow read timeout to be configured (#726) --- .../io/sentry/DefaultSentryClientFactory.java | 29 ++++++++++++++++--- .../io/sentry/connection/HttpConnection.java | 4 +-- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index 56bd1daaaef..ce421d7d9f4 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -46,13 +46,21 @@ public class DefaultSentryClientFactory extends SentryClientFactory { */ public static final String MAX_MESSAGE_LENGTH_OPTION = "maxmessagelength"; /** - * Option to set a timeout for requests to the Sentry server, in milliseconds. + * Option to set a timeout for HTTP connections to the Sentry server, in milliseconds. */ - public static final String TIMEOUT_OPTION = "timeout"; + public static final String CONNECTION_TIMEOUT_OPTION = "timeout"; /** * Default timeout of an HTTP connection to Sentry. */ - public static final int TIMEOUT_DEFAULT = (int) TimeUnit.SECONDS.toMillis(1); + public static final int CONNECTION_TIMEOUT_DEFAULT = (int) TimeUnit.SECONDS.toMillis(1); + /** + * Option to set a read timeout for requests to the Sentry server, in milliseconds. + */ + public static final String READ_TIMEOUT_OPTION = "readtimeout"; + /** + * Default read timeout of an HTTP request to Sentry. + */ + public static final int READ_TIMEOUT_DEFAULT = (int) TimeUnit.SECONDS.toMillis(5); /** * Option to enable or disable Event buffering. A buffering directory is also required. * This setting is mostly useful on Android where a buffering directory is set by default. @@ -430,6 +438,9 @@ protected Connection createHttpConnection(Dsn dsn) { int timeout = getTimeout(dsn); httpConnection.setConnectionTimeout(timeout); + int readTimeout = getReadTimeout(dsn); + httpConnection.setReadTimeout(readTimeout); + boolean bypassSecurityEnabled = getBypassSecurityEnabled(dsn); httpConnection.setBypassSecurity(bypassSecurityEnabled); @@ -845,7 +856,17 @@ protected int getMaxMessageLength(Dsn dsn) { * @return Timeout for requests to the Sentry server, in milliseconds. */ protected int getTimeout(Dsn dsn) { - return Util.parseInteger(Lookup.lookup(TIMEOUT_OPTION, dsn), TIMEOUT_DEFAULT); + return Util.parseInteger(Lookup.lookup(CONNECTION_TIMEOUT_OPTION, dsn), CONNECTION_TIMEOUT_DEFAULT); + } + + /** + * Read timeout for requests to the Sentry server, in milliseconds. + * + * @param dsn Sentry server DSN which may contain options. + * @return Read timeout for requests to the Sentry server, in milliseconds. + */ + protected int getReadTimeout(Dsn dsn) { + return Util.parseInteger(Lookup.lookup(READ_TIMEOUT_OPTION, dsn), READ_TIMEOUT_DEFAULT); } /** diff --git a/sentry/src/main/java/io/sentry/connection/HttpConnection.java b/sentry/src/main/java/io/sentry/connection/HttpConnection.java index 276cab7ef26..0bc3242fdc4 100644 --- a/sentry/src/main/java/io/sentry/connection/HttpConnection.java +++ b/sentry/src/main/java/io/sentry/connection/HttpConnection.java @@ -253,7 +253,7 @@ public void setTimeout(int timeout) { /** * This will set the timeout that is used in establishing a connection to the url. - * By default this is set to 5 second. + * By default this is set to 1 second. * * @param timeout New timeout to set. If 0 is used (java default) wait forever. */ @@ -263,7 +263,7 @@ public void setConnectionTimeout(int timeout) { /** * This will set the timeout that is used in reading data on an already established connection. - * By default this is set to 1 seconds. + * By default this is set to 5 seconds. * * @param timeout New timeout to set. If 0 is used (java default) wait forever. */ From fe14db21909d9ade4f689d27b7f3cdc32e1b19c0 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 17 Jun 2019 15:32:42 +0200 Subject: [PATCH 2052/2152] fix(gradle): gradle plugin on AGP version 3.4 (#725) --- sentry-android-gradle-plugin/build.gradle | 6 +-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../sentry/android/gradle/SentryPlugin.groovy | 51 ++++++++++++------- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/sentry-android-gradle-plugin/build.gradle b/sentry-android-gradle-plugin/build.gradle index e7ef01d15a1..5ae88e84413 100644 --- a/sentry-android-gradle-plugin/build.gradle +++ b/sentry-android-gradle-plugin/build.gradle @@ -18,9 +18,9 @@ compileGroovy { } dependencies { - compile gradleApi() - compile localGroovy() - compile 'com.android.tools.build:gradle:2.3.3' + compileOnly gradleApi() + compileOnly localGroovy() + compileOnly 'com.android.tools.build:gradle:3.4.0' } publishing { diff --git a/sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.properties index a595f8ae4aa..659279ee3a0 100644 --- a/sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 253f0b232cf..affbf763634 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -3,6 +3,7 @@ package io.sentry.android.gradle import com.android.build.gradle.AppPlugin import com.android.build.gradle.LibraryPlugin import com.android.build.gradle.api.ApplicationVariant +import com.android.build.gradle.api.BaseVariantOutput import org.apache.commons.compress.utils.IOUtils import org.gradle.api.Plugin import org.gradle.api.Project @@ -144,7 +145,12 @@ class SentryPlugin implements Plugin { * @return */ static String getDebugMetaPropPath(Project project, ApplicationVariant variant) { - return "${variant.mergeAssets.outputDir}/sentry-debug-meta.properties" + try { + return variant.mergeAssets.outputDir.get().file("sentry-debug-meta.properties").getAsFile().path + } catch (Throwable ignored) { + return "${variant.mergeAssets.outputDir}/sentry-debug-meta.properties" + } + } void apply(Project project) { @@ -158,25 +164,10 @@ class SentryPlugin implements Plugin { project.android.applicationVariants.all { ApplicationVariant variant -> variant.outputs.each { variantOutput -> def manifestPath = extension.manifestPath + if (manifestPath == null) { - try { - // Android Gradle Plugin < 3.0.0 - manifestPath = variantOutput.processManifest.manifestOutputFile - } catch (Exception ignored) { - // Android Gradle Plugin >= 3.0.0 - def outputDir = variantOutput.processManifest.manifestOutputDirectory - // Gradle 4.7 introduced the lazy task API and AGP 3.3+ adopts that, - // so we apparently have a Provider here instead - // TODO: This will let us depend on the configuration of each flavor's - // manifest creation task and their transitive dependencies, which in - // turn prolongs the configuration time accordingly. Evaluate how Gradle's - // new Task Avoidance API can be used instead. - // (https://docs.gradle.org/current/userguide/task_configuration_avoidance.html) - if (!(outputDir instanceof File)) { - outputDir = outputDir.get().asFile - } - manifestPath = new File(outputDir, "AndroidManifest.xml") - } + def dir = findAndroidManifestFileDir(variantOutput) + manifestPath = new File(dir, "AndroidManifest.xml") } def mappingFile = variant.getMappingFile() @@ -282,4 +273,26 @@ class SentryPlugin implements Plugin { } } } + + static File findAndroidManifestFileDir(BaseVariantOutput variantOutput) { + // Gradle 4.7 introduced the lazy task API and AGP 3.3+ adopts that, + // so we apparently have a Provider here instead + // TODO: This will let us depend on the configuration of each flavor's + // manifest creation task and their transitive dependencies, which in + // turn prolongs the configuration time accordingly. Evaluate how Gradle's + // new Task Avoidance API can be used instead. + // (https://docs.gradle.org/current/userguide/task_configuration_avoidance.html) + + try { // Android Gradle Plugin >= 3.3.0 + return variantOutput.processManifestProvider.get().manifestOutputDirectory.get().asFile + } catch (Exception ignored) {} + + try { // Android Gradle Plugin >= 3.0.0 + return variantOutput.processManifest.manifestOutputDirectory.get().asFile + } catch (Exception ignored) {} + + try { // Android Gradle Plugin < 3.0.0 + return new File(variantOutput.processManifest.manifestOutputFile).parentFile + } catch (Exception ignored) {} + } } From 0c75fb0a816e04d24621c350904c492dc605514e Mon Sep 17 00:00:00 2001 From: Niko Date: Mon, 17 Jun 2019 06:42:25 -0700 Subject: [PATCH 2053/2152] find dex task when shrinking resources (#722) --- .../main/groovy/io/sentry/android/gradle/SentryPlugin.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index affbf763634..7ec7229a1b0 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -123,7 +123,8 @@ class SentryPlugin implements Plugin { static Task getDexTask(Project project, ApplicationVariant variant) { def names = [ "transformClassesWithDexFor${variant.name.capitalize()}", - "transformClassesWithDexBuilderFor${variant.name.capitalize()}" + "transformClassesWithDexBuilderFor${variant.name.capitalize()}", + "transformClassesAndDexWithShrinkResFor${variant.name.capitalize()}" ] def rv = null From 3878a8bbd21d67b8895e2ad7c848677be22bdb2e Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 17 Jun 2019 17:12:49 +0200 Subject: [PATCH 2054/2152] doc: Add changes for release 1.7.23 --- CHANGES | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 64315881404..4c36e65b0e5 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,16 @@ Version 1.7.23 -------------- -- +- Remove usage of deprecated constructors in log4j2 (thanks justacodefan) +- Change logback appender to the unsynchronized version (thanks SerCeMan) +- When a secret key is not set, don't throw NPE (thanks mayt017) +- Upgrade Jackson to version 2.9.9 (thanks justacodefan) +- Don't erase inner class names from stack trace interface when it's of type exception (thanks justacodefan) +- Add more classes to blacklist and optimize parsing (thanks justacodefan) +- Include log4j marker parent(s) in log4j marker tag (thanks JoakimNil) +- Allow read timeout to be configured (thanks mdreis2211) +- Gradle plugin on AGP version 3.4 (thanks justacodefan, ninniuz, remcomokveld, realdadfish) +- Find dex task when shrinking resources (thanks codeniko) Version 1.7.22 -------------- From 1d154ada4654320a974dd329686ee21bb9e82f3d Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 17 Jun 2019 21:05:03 +0200 Subject: [PATCH 2055/2152] [maven-release-plugin] prepare release v1.7.23 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index e722d1c2305..98a4dcbbbfe 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.23-SNAPSHOT + 1.7.23 pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.23 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 6d8b4585f41..e5cd165b027 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.23-SNAPSHOT + 1.7.23 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index f9f9668a3a6..4379ee3e1fb 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.23-SNAPSHOT + 1.7.23 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 3cd16446223..370d7a360d7 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.23-SNAPSHOT + 1.7.23 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index e1ef94a554b..c83573b9694 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.23-SNAPSHOT + 1.7.23 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 4064b6c0951..055053aeb73 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.23-SNAPSHOT + 1.7.23 sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 384922b115e..526efff41a6 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.23-SNAPSHOT + 1.7.23 sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.23-SNAPSHOT + 1.7.23 diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index d95c67f66d2..d1d0a2c1191 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.23-SNAPSHOT + 1.7.23 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 93e4021af35..fa22d590b69 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.23-SNAPSHOT + 1.7.23 sentry From 19a0f4a5599b89869a7d1e2ed64d5e8ae9a809e5 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 17 Jun 2019 21:05:04 +0200 Subject: [PATCH 2056/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 98a4dcbbbfe..2b7af2c8554 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.23 + 1.7.24-SNAPSHOT pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.23 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index e5cd165b027..91874c23d66 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.23 + 1.7.24-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 4379ee3e1fb..868fd0f843d 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.23 + 1.7.24-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 370d7a360d7..86f67dd8a06 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.23 + 1.7.24-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index c83573b9694..98a52a80a00 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.23 + 1.7.24-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 055053aeb73..0d46fbf2d87 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.23 + 1.7.24-SNAPSHOT sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 526efff41a6..f7273bd175e 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.23 + 1.7.24-SNAPSHOT sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.23 + 1.7.24-SNAPSHOT diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index d1d0a2c1191..b22f1701d0b 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.23 + 1.7.24-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index fa22d590b69..e8d40419197 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.23 + 1.7.24-SNAPSHOT sentry From 46bc093c0ec821a1fb841721d793b10a2d4640eb Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 17 Jun 2019 21:05:06 +0200 Subject: [PATCH 2057/2152] Bump CHANGES to 1.7.24 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 4c36e65b0e5..95e863e2043 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +-e Version 1.7.24 +-------------- + +- + Version 1.7.23 -------------- From d3938d62d71243aaecd9f80e46c71edea31e8d4f Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 18 Jun 2019 18:58:12 +0200 Subject: [PATCH 2058/2152] Bump gradle plugin version --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 8a86417a6b8..3b8ce9de43a 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.23-SNAPSHOT +version = 1.7.24-SNAPSHOT From 83fa534bb9872ad1dd01af7b7f923ec72c6567c2 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Thu, 20 Jun 2019 15:47:06 +0200 Subject: [PATCH 2059/2152] doc: Link to CI --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 15a9246060b..1a50dc09a13 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@

    sentry-java - Sentry SDK for Java

    +[![Travis](https://travis-ci.org/getsentry/sentry-java.svg?branch=master)](https://travis-ci.org/getsentry/sentry-java) + This is the Java SDK for [Sentry](https://sentry.io/). It provides out-of-the-box support for many popular JVM based frameworks and libraries, including Android, Log4j, Logback, and more. From 9e508388bd3d9935dc2d1e3e1f3a630f3423c2c7 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Thu, 20 Jun 2019 16:53:10 +0200 Subject: [PATCH 2060/2152] fix: Cached frame might miss a name (#729) * Resolves #728 --- .../io/sentry/event/interfaces/SentryStackTraceElement.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java b/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java index 34dfde1a084..5f2d2dfef6a 100644 --- a/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/SentryStackTraceElement.java @@ -144,7 +144,7 @@ index in stackTraceElements (thus skipping elements). In addition, the only info if (cachedFrames != null) { // step through cachedFrames until we hit a match on the method in the stackTraceElement while (j < cachedFrames.length - && !cachedFrames[j].getMethod().getName().equals(stackTraceElement.getMethodName())) { + && !stackTraceElement.getMethodName().equals(cachedFrames[j].getMethod().getName())) { j++; } From 3cccb1d36324df8c8a60ee46915b1c7d012a3d11 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Thu, 4 Jul 2019 10:38:54 +0200 Subject: [PATCH 2061/2152] fix: make stylesheet (#733) --- .../SentryAutoConfiguration.java | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryAutoConfiguration.java b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryAutoConfiguration.java index d121b790651..336f54163a4 100644 --- a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryAutoConfiguration.java +++ b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryAutoConfiguration.java @@ -12,22 +12,33 @@ import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.HandlerExceptionResolver; +/** + * Spring Auto Configuration for Sentry. + */ @Configuration @ConditionalOnClass({ HandlerExceptionResolver.class, SentryExceptionResolver.class }) @ConditionalOnWebApplication @ConditionalOnProperty(name = "sentry.enabled", havingValue = "true", matchIfMissing = true) public class SentryAutoConfiguration { - @Bean - @ConditionalOnMissingBean(SentryExceptionResolver.class) - public HandlerExceptionResolver sentryExceptionResolver() { - return new SentryExceptionResolver(); - } + /** + * Resolves a {@link HandlerExceptionResolver}. + * @return a new instance of {@link SentryAutoConfiguration}. + */ + @Bean + @ConditionalOnMissingBean(SentryExceptionResolver.class) + public HandlerExceptionResolver sentryExceptionResolver() { + return new SentryExceptionResolver(); + } - @Bean - @ConditionalOnMissingBean(SentryServletContextInitializer.class) - public ServletContextInitializer sentryServletContextInitializer() { - return new SentryServletContextInitializer(); - } + /** + * Initializes a {@link ServletContextInitializer}. + * @return a new instance of {@link SentryServletContextInitializer}. + */ + @Bean + @ConditionalOnMissingBean(SentryServletContextInitializer.class) + public ServletContextInitializer sentryServletContextInitializer() { + return new SentryServletContextInitializer(); + } } From 5ffab36bbf61a37f84eab26f521a3268c3f44231 Mon Sep 17 00:00:00 2001 From: Stephen Johnson Date: Thu, 11 Jul 2019 09:13:23 +0100 Subject: [PATCH 2062/2152] Update Sentry Cli Version (#735) --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 5a4c6fdb2ee..8cd2b67a29a 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.40.0 +VERSION=1.46.0 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* From a07b90a319726b844ff9e7c7fa7252081f681cb7 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Fri, 12 Jul 2019 16:46:13 +0200 Subject: [PATCH 2063/2152] fix: Look for sentry.properties on flavor folder (#737) * fix: Look for sentry.properties on flavor folder * ref: Check build types before flavor name --- .../sentry/android/gradle/SentryPlugin.groovy | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 7ec7229a1b0..42484131c43 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -202,25 +202,38 @@ class SentryPlugin implements Plugin { description "Write references to proguard UUIDs to the android assets." workingDir project.rootDir - def variantName = variant.buildType.name + def buildTypeName = variant.buildType.name def flavorName = variant.flavorName + // When flavor is used in combination with dimensions, variant.flavorName will be a concatenation + // of flavors of different dimensions def propName = "sentry.properties" - def possibleProps = [ - "${project.projectDir}/src/${variantName}/${propName}", + // current flavor name takes priority + def possibleProps = [] + variant.productFlavors.each { + // flavors used with dimension come in second + possibleProps.push("${project.projectDir}/src/${it.name}/${propName}") + } + + possibleProps = [ + "${project.projectDir}/src/${buildTypeName}/${propName}", + "${project.projectDir}/src/${buildTypeName}/${flavorName}/${propName}", + "${project.projectDir}/src/${flavorName}/${buildTypeName}/${propName}", "${project.projectDir}/src/${flavorName}/${propName}", - "${project.projectDir}/src/${variantName}/${flavorName}/${propName}", - "${project.projectDir}/src/${flavorName}/${variantName}/${propName}", - "${project.rootDir.toPath()}/src/${variantName}/${propName}", "${project.rootDir.toPath()}/src/${flavorName}/${propName}", - "${project.rootDir.toPath()}/src/${variantName}/${flavorName}/${propName}", - "${project.rootDir.toPath()}/src/${flavorName}/${variantName}/${propName}", + ] + possibleProps + [ + "${project.rootDir.toPath()}/src/${buildTypeName}/${propName}", + "${project.rootDir.toPath()}/src/${buildTypeName}/${flavorName}/${propName}", + "${project.rootDir.toPath()}/src/${flavorName}/${buildTypeName}/${propName}", + // Root sentry.properties is the last to be looked up "${project.rootDir.toPath()}/${propName}" ] def propsFile = null possibleProps.each { + project.logger.info("Looking for Sentry properties at: $it") if (propsFile == null && new File(it).isFile()) { propsFile = it + project.logger.info("Found Sentry properties in: $it") } } From 611088dbb81484b10f972185e99f479ddf7e217c Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Fri, 12 Jul 2019 17:12:37 +0200 Subject: [PATCH 2064/2152] rel: Changes for 1.7.24 --- CHANGES | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 95e863e2043..7d8f9c5b5f0 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,8 @@ --e Version 1.7.24 +Version 1.7.24 -------------- -- +- Android Gradle plugin: Look for sentry.properties on flavor directory. +- Android Gradle plugin: Bumped sentry-cli version. Version 1.7.23 -------------- From 00300758e1e1a98b26a8f7bd2385763beab7d5b8 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Fri, 12 Jul 2019 17:29:29 +0200 Subject: [PATCH 2065/2152] [maven-release-plugin] prepare release v1.7.24 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 2b7af2c8554..fa02cd2cd40 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.24-SNAPSHOT + 1.7.24 pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.24 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 91874c23d66..565f3728949 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.24-SNAPSHOT + 1.7.24 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 868fd0f843d..778fbf6e819 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.24-SNAPSHOT + 1.7.24 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 86f67dd8a06..7f4816d9d31 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.24-SNAPSHOT + 1.7.24 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 98a52a80a00..17dc0d90f70 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.24-SNAPSHOT + 1.7.24 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 0d46fbf2d87..a49019bd046 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.24-SNAPSHOT + 1.7.24 sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index f7273bd175e..2b2897f9391 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.24-SNAPSHOT + 1.7.24 sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.24-SNAPSHOT + 1.7.24 diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index b22f1701d0b..7e790373bbf 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.24-SNAPSHOT + 1.7.24 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index e8d40419197..1038ff40c0c 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.24-SNAPSHOT + 1.7.24 sentry From bb45b0bfe3a015c6ead3eeb35165c435dab3aba3 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Fri, 12 Jul 2019 17:29:30 +0200 Subject: [PATCH 2066/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index fa02cd2cd40..b7436564dda 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.24 + 1.7.25-SNAPSHOT pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.24 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 565f3728949..c1f92483224 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.24 + 1.7.25-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 778fbf6e819..be1f8ec830a 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.24 + 1.7.25-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 7f4816d9d31..fc09eac37a7 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.24 + 1.7.25-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 17dc0d90f70..ff25d150714 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.24 + 1.7.25-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index a49019bd046..2a58f60db44 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.24 + 1.7.25-SNAPSHOT sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 2b2897f9391..8f62b9d443d 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.24 + 1.7.25-SNAPSHOT sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.24 + 1.7.25-SNAPSHOT diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 7e790373bbf..0074490d305 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.24 + 1.7.25-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 1038ff40c0c..bab5383fc5f 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.24 + 1.7.25-SNAPSHOT sentry From 21856e4d8331959e7ae668277a90bb207f1d5c82 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Fri, 12 Jul 2019 17:29:32 +0200 Subject: [PATCH 2067/2152] Bump CHANGES to 1.7.25 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 7d8f9c5b5f0..8d09a879a1f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +-e Version 1.7.25 +-------------- + +- + Version 1.7.24 -------------- From 52d7836f0d642b079238a3e4736dd96143d3f9d5 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Fri, 12 Jul 2019 18:03:48 +0200 Subject: [PATCH 2068/2152] chore: Prepare next version --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 3b8ce9de43a..a061c9ed179 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.24-SNAPSHOT +version = 1.7.25-SNAPSHOT From dd5613b863ab31a838a058878ae2e206166024bd Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 16 Jul 2019 18:16:21 +0200 Subject: [PATCH 2069/2152] fix: AGP splits (#738) --- .../main/groovy/io/sentry/android/gradle/SentryPlugin.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 42484131c43..477882d4613 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -168,7 +168,7 @@ class SentryPlugin implements Plugin { if (manifestPath == null) { def dir = findAndroidManifestFileDir(variantOutput) - manifestPath = new File(dir, "AndroidManifest.xml") + manifestPath = new File(new File(dir, variantOutput.dirName), "AndroidManifest.xml") } def mappingFile = variant.getMappingFile() From 89695062a08bd125dbbdb2f1b4d5abd8e7f1089c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ferreira?= Date: Thu, 18 Jul 2019 11:36:23 -0400 Subject: [PATCH 2070/2152] [ANDROID] Support for passing in an Application directly to the AndroidSentryClientFactory (#714) --- .../android/AndroidSentryClientFactory.java | 25 +++++++++-- .../io/sentry/android/SentryAndroidIT.java | 24 ++++++++++- .../SentryITActivityUsingApplication.java | 42 +++++++++++++++++++ ... => SentryITActivityUsingBaseContext.java} | 12 +++--- 4 files changed, 91 insertions(+), 12 deletions(-) create mode 100644 sentry-android/src/test/java/io/sentry/android/SentryITActivityUsingApplication.java rename sentry-android/src/test/java/io/sentry/android/{SentryITActivity.java => SentryITActivityUsingBaseContext.java} (78%) diff --git a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java index 660dbb1960c..fa04b2d3095 100644 --- a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java +++ b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java @@ -1,11 +1,13 @@ package io.sentry.android; import android.Manifest; +import android.app.Application; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.util.Log; -import io.sentry.*; +import io.sentry.DefaultSentryClientFactory; +import io.sentry.SentryClient; import io.sentry.android.event.helper.AndroidEventBuilderHelper; import io.sentry.buffer.Buffer; import io.sentry.buffer.DiskBuffer; @@ -14,7 +16,6 @@ import io.sentry.context.SingletonContextManager; import io.sentry.dsn.Dsn; import io.sentry.util.Util; - import java.io.File; import java.util.ArrayList; import java.util.Collection; @@ -30,6 +31,7 @@ public class AndroidSentryClientFactory extends DefaultSentryClientFactory { * Logger tag. */ public static final String TAG = AndroidSentryClientFactory.class.getName(); + /** * Default Buffer directory name. */ @@ -37,17 +39,32 @@ public class AndroidSentryClientFactory extends DefaultSentryClientFactory { private Context ctx; + /** + * Construct an AndroidSentryClientFactory using the base Context from the specified Android Application. + * + * @param app Android Application + */ + public AndroidSentryClientFactory(Application app) { + Log.d(TAG, "Construction of Android Sentry from Android Application."); + + this.ctx = app.getApplicationContext(); + } + /** * Construct an AndroidSentryClientFactory using the specified Android Context. * * @param ctx Android Context. */ public AndroidSentryClientFactory(Context ctx) { - Log.d(TAG, "Construction of Android Sentry."); + Log.d(TAG, "Construction of Android Sentry from Android Context."); this.ctx = ctx.getApplicationContext(); + if (this.ctx == null) { + this.ctx = ctx; + } } + @Override public SentryClient createSentryClient(Dsn dsn) { if (!checkPermission(Manifest.permission.INTERNET)) { @@ -123,11 +140,11 @@ protected ContextManager getContextManager(Dsn dsn) { * Check whether the application has been granted a certain permission. * * @param permission Permission as a string + * * @return true if permissions is granted */ private boolean checkPermission(String permission) { int res = ctx.checkCallingOrSelfPermission(permission); return (res == PackageManager.PERMISSION_GRANTED); } - } diff --git a/sentry-android/src/test/java/io/sentry/android/SentryAndroidIT.java b/sentry-android/src/test/java/io/sentry/android/SentryAndroidIT.java index 26367e72326..e3d02275f21 100644 --- a/sentry-android/src/test/java/io/sentry/android/SentryAndroidIT.java +++ b/sentry-android/src/test/java/io/sentry/android/SentryAndroidIT.java @@ -12,11 +12,31 @@ public class SentryAndroidIT extends AndroidTest { @Test - public void test() throws Exception { + public void testInitializingUsingBaseContext() throws Exception { verifyProject1PostRequestCount(0); verifyStoredEventCount(0); - SentryITActivity activity = Robolectric.setupActivity(SentryITActivity.class); + SentryITActivityUsingBaseContext activity = Robolectric.setupActivity(SentryITActivityUsingBaseContext.class); + Assert.assertEquals(activity.getCustomFactoryUsed(), true); + + activity.sendEvent(); + waitUntilTrue(1000, new Callable() { + @Override + public Boolean call() throws Exception { + return getStoredEvents().size() == 1; + } + }); + + verifyProject1PostRequestCount(1); + verifyStoredEventCount(1); + } + + @Test + public void testInitializingUsingApplication() throws Exception { + verifyProject1PostRequestCount(0); + verifyStoredEventCount(0); + + SentryITActivityUsingApplication activity = Robolectric.setupActivity(SentryITActivityUsingApplication.class); Assert.assertEquals(activity.getCustomFactoryUsed(), true); activity.sendEvent(); diff --git a/sentry-android/src/test/java/io/sentry/android/SentryITActivityUsingApplication.java b/sentry-android/src/test/java/io/sentry/android/SentryITActivityUsingApplication.java new file mode 100644 index 00000000000..f6579a2ff7b --- /dev/null +++ b/sentry-android/src/test/java/io/sentry/android/SentryITActivityUsingApplication.java @@ -0,0 +1,42 @@ +package io.sentry.android; + +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.os.Bundle; +import io.sentry.Sentry; +import java.util.concurrent.atomic.AtomicBoolean; + +public class SentryITActivityUsingApplication extends Activity { + + private AtomicBoolean customFactoryUsed = new AtomicBoolean(false); + + class CustomAndroidSentryClientFactory extends AndroidSentryClientFactory { + + /** + * Construct an AndroidSentryClientFactory using the specified Android Context. + * + * @param app Android Application + */ + CustomAndroidSentryClientFactory(Application app) { + super(app); + customFactoryUsed.set(true); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Sentry.init( + "http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1", + new CustomAndroidSentryClientFactory(getApplication())); + } + + public void sendEvent() { + Sentry.capture("sendEvent()"); + } + + public boolean getCustomFactoryUsed() { + return customFactoryUsed.get(); + } +} diff --git a/sentry-android/src/test/java/io/sentry/android/SentryITActivity.java b/sentry-android/src/test/java/io/sentry/android/SentryITActivityUsingBaseContext.java similarity index 78% rename from sentry-android/src/test/java/io/sentry/android/SentryITActivity.java rename to sentry-android/src/test/java/io/sentry/android/SentryITActivityUsingBaseContext.java index d009c975012..67495692830 100644 --- a/sentry-android/src/test/java/io/sentry/android/SentryITActivity.java +++ b/sentry-android/src/test/java/io/sentry/android/SentryITActivityUsingBaseContext.java @@ -1,23 +1,24 @@ package io.sentry.android; import android.app.Activity; +import android.app.Application; import android.content.Context; import android.os.Bundle; import io.sentry.Sentry; - import java.util.concurrent.atomic.AtomicBoolean; -public class SentryITActivity extends Activity { +public class SentryITActivityUsingBaseContext extends Activity { private AtomicBoolean customFactoryUsed = new AtomicBoolean(false); class CustomAndroidSentryClientFactory extends AndroidSentryClientFactory { + /** * Construct an AndroidSentryClientFactory using the specified Android Context. * - * @param ctx Android Context. + * @param ctx Android Context */ - public CustomAndroidSentryClientFactory(Context ctx) { + CustomAndroidSentryClientFactory(Context ctx) { super(ctx); customFactoryUsed.set(true); } @@ -28,7 +29,7 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Sentry.init( "http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1", - new CustomAndroidSentryClientFactory(getApplicationContext())); + new CustomAndroidSentryClientFactory(getBaseContext())); } public void sendEvent() { @@ -38,5 +39,4 @@ public void sendEvent() { public boolean getCustomFactoryUsed() { return customFactoryUsed.get(); } - } From e41ced3a48d1976fec76583dc5725755a8726cc9 Mon Sep 17 00:00:00 2001 From: Takao Chiba Date: Mon, 22 Jul 2019 18:22:13 +0900 Subject: [PATCH 2071/2152] Add task dependency to adapt App Bundle (#707) --- .../io/sentry/android/gradle/SentryPlugin.groovy | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 477882d4613..d0acd1310b9 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -138,6 +138,17 @@ class SentryPlugin implements Plugin { return project.tasks.findByName("dex${names[0]}") } + /** + * Returns the bundle task for the given project and variant. + * + * @param project + * @param variant + * @return + */ + static Task getBundleTask(Project project, ApplicationVariant variant) { + return project.tasks.findByName("build${variant.name.capitalize()}PreBundle") + } + /** * Returns the path to the debug meta properties file for the given variant. * @@ -174,6 +185,7 @@ class SentryPlugin implements Plugin { def mappingFile = variant.getMappingFile() def proguardTask = getProguardTask(project, variant) def dexTask = getDexTask(project, variant) + def bundleTask = getBundleTask(project, variant) if (proguardTask == null) { return @@ -282,6 +294,10 @@ class SentryPlugin implements Plugin { } else { proguardTask.finalizedBy(persistIdsTask) } + // To include proguard uuid file into aab, run before bundle task. + if (bundleTask != null) { + bundleTask.dependsOn persistIdsTask + } persistIdsTask.dependsOn proguardTask } } From 073966c78440e767789659255e18942204203751 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 22 Jul 2019 13:14:18 +0200 Subject: [PATCH 2072/2152] chore: Changelist for 1.7.25 --- CHANGES | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 8d09a879a1f..72cf8e7eb00 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,9 @@ --e Version 1.7.25 + Version 1.7.25 -------------- -- +- Android Gradle Plugin: Fixed bug when using ABI splits #667 +- Android Gradle Plugin: Support Android App Bundle #707 +- Android SDK: Support providing the whole Application Context #713 Version 1.7.24 -------------- From aad53fff208a7d3cbc3be8a79fe887bcbec4d46e Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 22 Jul 2019 13:30:46 +0200 Subject: [PATCH 2073/2152] [maven-release-plugin] prepare release v1.7.25 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index b7436564dda..f9bdf5dc141 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.25-SNAPSHOT + 1.7.25 pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.25 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index c1f92483224..8f6d07bbf5b 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.25-SNAPSHOT + 1.7.25 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index be1f8ec830a..cac10fb08ed 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.25-SNAPSHOT + 1.7.25 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index fc09eac37a7..825e7d2622b 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.25-SNAPSHOT + 1.7.25 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index ff25d150714..436bc099ffb 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.25-SNAPSHOT + 1.7.25 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 2a58f60db44..dfbe841a2fd 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.25-SNAPSHOT + 1.7.25 sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 8f62b9d443d..ae58c8fe269 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.25-SNAPSHOT + 1.7.25 sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.25-SNAPSHOT + 1.7.25 diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 0074490d305..faa032c80af 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.25-SNAPSHOT + 1.7.25 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index bab5383fc5f..9c6070b6801 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.25-SNAPSHOT + 1.7.25 sentry From 2bd84db8d4e929cffadddee3dc12ed3aaba8e52c Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 22 Jul 2019 13:30:46 +0200 Subject: [PATCH 2074/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index f9bdf5dc141..720e615e657 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.25 + 1.7.26-SNAPSHOT pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.25 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 8f6d07bbf5b..29c03766ec4 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.25 + 1.7.26-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index cac10fb08ed..2655000e4f8 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.25 + 1.7.26-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 825e7d2622b..3281b0ba207 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.25 + 1.7.26-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 436bc099ffb..82db917348e 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.25 + 1.7.26-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index dfbe841a2fd..5f24558b85d 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.25 + 1.7.26-SNAPSHOT sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index ae58c8fe269..4f486ac067c 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.25 + 1.7.26-SNAPSHOT sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.25 + 1.7.26-SNAPSHOT diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index faa032c80af..f585ed785f0 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.25 + 1.7.26-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 9c6070b6801..75983ca34c6 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.25 + 1.7.26-SNAPSHOT sentry From 4188ea60438097f74caa19101213ca3d480cceb5 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 22 Jul 2019 13:30:49 +0200 Subject: [PATCH 2075/2152] Bump CHANGES to 1.7.26 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 72cf8e7eb00..af161261bab 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +-e Version 1.7.26 +-------------- + +- + Version 1.7.25 -------------- From 82027109ae53b28d0d75180a8212f7b058bd22b6 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 22 Jul 2019 14:58:35 +0200 Subject: [PATCH 2076/2152] chore: version bump --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 8cd2b67a29a..cd53b55c8f9 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.46.0 +VERSION=1.47.0 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* From c32bfd99388bdf96350ca09a7698b3add911d246 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Wed, 24 Jul 2019 14:27:42 +0200 Subject: [PATCH 2077/2152] chore: Release version bumb --- CHANGES | 4 ++-- sentry-android-gradle-plugin/gradle.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index af161261bab..9c698ea176c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,9 +1,9 @@ --e Version 1.7.26 +Version 1.7.26 -------------- - - Version 1.7.25 +Version 1.7.25 -------------- - Android Gradle Plugin: Fixed bug when using ABI splits #667 diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index a061c9ed179..edeff62b690 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.25-SNAPSHOT +version = 1.7.26-SNAPSHOT From 6dcc2f4c30464cf26a45b38bf335b004c75999ae Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Thu, 1 Aug 2019 10:04:12 +0200 Subject: [PATCH 2078/2152] fix: find dex (#743) --- .../io/sentry/android/gradle/SentryPlugin.groovy | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index d0acd1310b9..a526a141a98 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -127,15 +127,7 @@ class SentryPlugin implements Plugin { "transformClassesAndDexWithShrinkResFor${variant.name.capitalize()}" ] - def rv = null - names.each { - rv = project.tasks.findByName(it) - if (rv != null) { - return rv - } - } - - return project.tasks.findByName("dex${names[0]}") + return names.findResult { project.tasks.findByName(it) } ?: project.tasks.findByName("dex${names[0]}") } /** From 2b1d342b1d125f858fd5ec809a69f2c4090fe8dc Mon Sep 17 00:00:00 2001 From: Andrea Di Menna Date: Thu, 1 Aug 2019 13:22:58 +0200 Subject: [PATCH 2079/2152] Issue #708: add a resource loader interface to target Android slow loading of jar files (#739) The Lookup class will try to load Sentry properties from a classpath resource file by default. This method is VERY slow on Android (on some older devices specifically). --- .../android/AndroidAssetsResourceLoader.java | 47 +++++++++++++++++++ .../android/AndroidSentryClientFactory.java | 1 - sentry/src/main/java/io/sentry/Sentry.java | 28 +++++++++++ .../main/java/io/sentry/config/Lookup.java | 9 +++- .../java/io/sentry/config/ResourceLoader.java | 17 +++++++ 5 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 sentry-android/src/main/java/io/sentry/android/AndroidAssetsResourceLoader.java create mode 100644 sentry/src/main/java/io/sentry/config/ResourceLoader.java diff --git a/sentry-android/src/main/java/io/sentry/android/AndroidAssetsResourceLoader.java b/sentry-android/src/main/java/io/sentry/android/AndroidAssetsResourceLoader.java new file mode 100644 index 00000000000..e0759c7c60b --- /dev/null +++ b/sentry-android/src/main/java/io/sentry/android/AndroidAssetsResourceLoader.java @@ -0,0 +1,47 @@ +package io.sentry.android; + +import android.content.Context; +import android.util.Log; +import io.sentry.config.ResourceLoader; + +import java.io.IOException; +import java.io.InputStream; + +/** + * ResourceLoader specific for Android that loads files from assets. + */ +@SuppressWarnings("unused") +public class AndroidAssetsResourceLoader implements ResourceLoader { + + /** + * Logger tag. + */ + private static final String TAG = AndroidAssetsResourceLoader.class.getName(); + + private Context context; + + /** + * Construct an AndroidAssetsResourceLoader using an Application Context from the provided context + * or the latter if the application context is null. + * + * @param context Android context + */ + public AndroidAssetsResourceLoader(Context context) { + this.context = context.getApplicationContext(); + + if (this.context == null) { + this.context = context; + } + } + + @Override + public InputStream getInputStream(String filepath) { + try { + return context.getAssets().open(filepath); + } catch (IOException e) { + Log.e(TAG, "Cannot open stream from file: " + filepath, e); + } + + return null; + } +} diff --git a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java index fa04b2d3095..c49d2ef8025 100644 --- a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java +++ b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java @@ -64,7 +64,6 @@ public AndroidSentryClientFactory(Context ctx) { } } - @Override public SentryClient createSentryClient(Dsn dsn) { if (!checkPermission(Manifest.permission.INTERNET)) { diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 9629dbe1e3c..75f86cb5877 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -1,5 +1,6 @@ package io.sentry; +import io.sentry.config.ResourceLoader; import io.sentry.context.Context; import io.sentry.dsn.Dsn; import io.sentry.event.Breadcrumb; @@ -27,6 +28,8 @@ public final class Sentry { */ private static AtomicBoolean autoInitAttempted = new AtomicBoolean(false); + private static ResourceLoader resourceLoader = null; + /** * Hide constructor. */ @@ -34,6 +37,14 @@ private Sentry() { } + public static ResourceLoader getResourceLoader() { + return resourceLoader; + } + + private static void setResourceLoader(ResourceLoader resourceLoader) { + Sentry.resourceLoader = resourceLoader; + } + /** * Initialize and statically store a {@link SentryClient} by looking up * a {@link Dsn} and automatically choosing a {@link SentryClientFactory}. @@ -83,6 +94,23 @@ public static SentryClient init(String dsn, SentryClientFactory sentryClientFact return sentryClient; } + /** + * Initialize and statically store a {@link SentryClient} by using the provided + * {@link Dsn}, {@link SentryClientFactory} and {@link ResourceLoader}. + *

    + * Note that the Dsn, SentryClientFactory or ResourceLoader may be null, at which a best effort attempt + * is made to look up or choose the best value(s). + * + * @param dsn Data Source Name of the Sentry server. + * @param sentryClientFactory SentryClientFactory to use. + * @param resLoader ResourceLoader to use to retrieve Sentry options. + * @return SentryClient + */ + public static SentryClient init(String dsn, SentryClientFactory sentryClientFactory, ResourceLoader resLoader) { + setResourceLoader(resLoader); + return init(dsn, sentryClientFactory); + } + /** * Returns the last statically stored {@link SentryClient} instance. If no instance * is already stored, the {@link #init()} method will be called one time in an attempt to diff --git a/sentry/src/main/java/io/sentry/config/Lookup.java b/sentry/src/main/java/io/sentry/config/Lookup.java index 716e99de1af..fd2861c445c 100644 --- a/sentry/src/main/java/io/sentry/config/Lookup.java +++ b/sentry/src/main/java/io/sentry/config/Lookup.java @@ -1,5 +1,6 @@ package io.sentry.config; +import io.sentry.Sentry; import io.sentry.dsn.Dsn; import io.sentry.util.Util; import org.slf4j.Logger; @@ -78,8 +79,12 @@ private static InputStream getInputStream(String filePath) throws FileNotFoundEx return new FileInputStream(file); } - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - return classLoader.getResourceAsStream(filePath); + if (Sentry.getResourceLoader() != null) { + return Sentry.getResourceLoader().getInputStream(filePath); + } else { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + return classLoader.getResourceAsStream(filePath); + } } /** diff --git a/sentry/src/main/java/io/sentry/config/ResourceLoader.java b/sentry/src/main/java/io/sentry/config/ResourceLoader.java new file mode 100644 index 00000000000..fb446017ccb --- /dev/null +++ b/sentry/src/main/java/io/sentry/config/ResourceLoader.java @@ -0,0 +1,17 @@ +package io.sentry.config; + +import java.io.InputStream; + +/** + * Interface for platform-specific resource loaders. + */ +public interface ResourceLoader { + /** + * Returns an InputStream from the resource at {@code filepath} or null, if it was not possible + * to open the resource. + * + * @param filepath Path of the resource to open + * @return Resource's input stream or null + */ + InputStream getInputStream(String filepath); +} From 66aac3cff991baa2675473d4b7d7f4f1b3206113 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Thu, 1 Aug 2019 16:32:38 +0200 Subject: [PATCH 2080/2152] feat: introduce SentryOptions (#745) --- sentry/src/main/java/io/sentry/Sentry.java | 45 ++++++++++---- .../main/java/io/sentry/SentryOptions.java | 60 +++++++++++++++++++ .../main/java/io/sentry/config/Lookup.java | 5 +- 3 files changed, 98 insertions(+), 12 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/SentryOptions.java diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 75f86cb5877..8db3890ba97 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -28,6 +28,9 @@ public final class Sentry { */ private static AtomicBoolean autoInitAttempted = new AtomicBoolean(false); + /** + * Optional override for the default resource loader used to look for properties. + */ private static ResourceLoader resourceLoader = null; /** @@ -37,14 +40,6 @@ private Sentry() { } - public static ResourceLoader getResourceLoader() { - return resourceLoader; - } - - private static void setResourceLoader(ResourceLoader resourceLoader) { - Sentry.resourceLoader = resourceLoader; - } - /** * Initialize and statically store a {@link SentryClient} by looking up * a {@link Dsn} and automatically choosing a {@link SentryClientFactory}. @@ -89,7 +84,30 @@ public static SentryClient init(String dsn) { * @return SentryClient */ public static SentryClient init(String dsn, SentryClientFactory sentryClientFactory) { - SentryClient sentryClient = SentryClientFactory.sentryClient(dsn, sentryClientFactory); + SentryOptions sentryOptions = new SentryOptions(); + sentryOptions.setDsn(dsn); + sentryOptions.setSentryClientFactory(sentryClientFactory); + return init(sentryOptions); + } + + /** + * Initialize and statically store a {@link SentryClient} by using the provided + * {@link SentryOptions}. + *

    x + * Note that the Dsn or SentryClientFactory may be null, at which a best effort attempt + * is made to look up or choose the best value(s). + * + * @param sentryOptions SentryOptions to take DSN and other options from. + * @return SentryClient + */ + public static SentryClient init(SentryOptions sentryOptions) { + SentryClient sentryClient = SentryClientFactory.sentryClient( + sentryOptions.getDsn(), + sentryOptions.getSentryClientFactory()); + // Hack to allow Lookup.java access to a different resource locator before its static initializer runs. + // v2: Lookup won't be static and this hack will be removed. + // ResourceLocator will ba passed to Lookup upon instantiation + Sentry.resourceLoader = sentryOptions.getResourceLoader(); setStoredClient(sentryClient); return sentryClient; } @@ -107,7 +125,6 @@ public static SentryClient init(String dsn, SentryClientFactory sentryClientFact * @return SentryClient */ public static SentryClient init(String dsn, SentryClientFactory sentryClientFactory, ResourceLoader resLoader) { - setResourceLoader(resLoader); return init(dsn, sentryClientFactory); } @@ -134,6 +151,14 @@ public static SentryClient getStoredClient() { return storedClient; } + /** + * The {@link ResourceLoader} used to lookup properties. + * @return {link ResourceLoader}. + */ + public static ResourceLoader getResourceLoader() { + return resourceLoader; + } + /** * Returns the {@link Context} on the statically stored {@link SentryClient}. * diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java new file mode 100644 index 00000000000..634b6bcfe61 --- /dev/null +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -0,0 +1,60 @@ +package io.sentry; + +import io.sentry.config.ResourceLoader; + +/** + * SentryOptions used to configure the Sentry SDK. + */ +public class SentryOptions { + private SentryClientFactory sentryClientFactory; + private String dsn; + private ResourceLoader resourceLoader; + + /** + * Gets the optionally set {@Link SentryClientFactory}. + * @return {@link SentryClientFactory} + */ + public SentryClientFactory getSentryClientFactory() { + return sentryClientFactory; + } + + /** + * Sets the {@link SentryClientFactory} to be used when initializing the SDK. + * @param sentryClientFactory Factory used to create a {@link SentryClient}. + */ + public void setSentryClientFactory(SentryClientFactory sentryClientFactory) { + this.sentryClientFactory = sentryClientFactory; + } + + /** + * Gets the DSN. If not DSN was set, the SDK will attempt to find one in the environment. + * @return Data Source Name. + */ + public String getDsn() { + return dsn; + } + + /** + * Sets the DSN to be used by the {@link SentryClient}. + * @param dsn Sentry Data Source Name. + */ + public void setDsn(String dsn) { + this.dsn = dsn; + } + + /** + * Gets the {@link ResourceLoader} to be used when looking for properties. + * @return {@link ResourceLoader} + */ + public ResourceLoader getResourceLoader() { + return resourceLoader; + } + + /** + * Sets the {@link ResourceLoader} to be used when looking for properties. + * @param resourceLoader Optional {@link ResourceLoader} used to lookup properties. + */ + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } +} diff --git a/sentry/src/main/java/io/sentry/config/Lookup.java b/sentry/src/main/java/io/sentry/config/Lookup.java index fd2861c445c..4c7ae388cfa 100644 --- a/sentry/src/main/java/io/sentry/config/Lookup.java +++ b/sentry/src/main/java/io/sentry/config/Lookup.java @@ -79,8 +79,9 @@ private static InputStream getInputStream(String filePath) throws FileNotFoundEx return new FileInputStream(file); } - if (Sentry.getResourceLoader() != null) { - return Sentry.getResourceLoader().getInputStream(filePath); + ResourceLoader resourceLoader = Sentry.getResourceLoader(); + if (resourceLoader != null) { + return resourceLoader.getInputStream(filePath); } else { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); return classLoader.getResourceAsStream(filePath); From 2a3267eca7b9bd27ef1683f5d36c5e958b820bb1 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Thu, 1 Aug 2019 16:43:15 +0200 Subject: [PATCH 2081/2152] ref: Set resource loader via SentryOptions (#747) --- sentry/src/main/java/io/sentry/Sentry.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 8db3890ba97..956412da504 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -112,22 +112,6 @@ public static SentryClient init(SentryOptions sentryOptions) { return sentryClient; } - /** - * Initialize and statically store a {@link SentryClient} by using the provided - * {@link Dsn}, {@link SentryClientFactory} and {@link ResourceLoader}. - *

    - * Note that the Dsn, SentryClientFactory or ResourceLoader may be null, at which a best effort attempt - * is made to look up or choose the best value(s). - * - * @param dsn Data Source Name of the Sentry server. - * @param sentryClientFactory SentryClientFactory to use. - * @param resLoader ResourceLoader to use to retrieve Sentry options. - * @return SentryClient - */ - public static SentryClient init(String dsn, SentryClientFactory sentryClientFactory, ResourceLoader resLoader) { - return init(dsn, sentryClientFactory); - } - /** * Returns the last statically stored {@link SentryClient} instance. If no instance * is already stored, the {@link #init()} method will be called one time in an attempt to From 6874067746d825624852be7d70d2b3c2dfed5898 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Wed, 7 Aug 2019 11:39:53 +0200 Subject: [PATCH 2082/2152] fix: SentryClient overrides event with default data (#751) Resolves #736 --- .../src/main/java/io/sentry/SentryClient.java | 35 ++++-- .../test/java/io/sentry/SentryClientTest.java | 119 ++++++++++++++++++ 2 files changed, 147 insertions(+), 7 deletions(-) diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index b9d510af93d..9ed05649e42 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -148,32 +148,53 @@ public void sendEvent(Event event) { * @param eventBuilder {@link EventBuilder} to send to Sentry. */ public void sendEvent(EventBuilder eventBuilder) { - if (!Util.isNullOrEmpty(release)) { + Event event = buildEvent(eventBuilder); + sendEvent(event); + } + + /** + * Adds the SentryClient data into the builder without overriding existent data and builds the event. + * @param eventBuilder The event builder to potentially modify and to build the event. + * @return The event. + */ + Event buildEvent(EventBuilder eventBuilder) { + Event workingEvent = eventBuilder.getEvent(); + + if (!Util.isNullOrEmpty(release) && workingEvent.getRelease() == null) { eventBuilder.withRelease(release.trim()); if (!Util.isNullOrEmpty(dist)) { eventBuilder.withDist(dist.trim()); } } - if (!Util.isNullOrEmpty(environment)) { + if (!Util.isNullOrEmpty(environment) && workingEvent.getEnvironment() == null) { eventBuilder.withEnvironment(environment.trim()); } - if (!Util.isNullOrEmpty(serverName)) { + if (!Util.isNullOrEmpty(serverName) && workingEvent.getServerName() == null) { eventBuilder.withServerName(serverName.trim()); } for (Map.Entry tagEntry : tags.entrySet()) { - eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue()); + Map workingTags = workingEvent.getTags(); + String currentValue = workingTags.put(tagEntry.getKey(), tagEntry.getValue()); + // Default tags should not override event-specific tags: + if (currentValue != null) { + workingTags.put(tagEntry.getKey(), currentValue); + } } for (Map.Entry extraEntry : extra.entrySet()) { - eventBuilder.withExtra(extraEntry.getKey(), extraEntry.getValue()); + Map workingExtra = workingEvent.getExtra(); + Object currentValue = workingExtra.put(extraEntry.getKey(), extraEntry.getValue()); + // Default extra should not override event-specific extra: + if (currentValue != null) { + workingExtra.put(extraEntry.getKey(), currentValue); + } } runBuilderHelpers(eventBuilder); - Event event = eventBuilder.build(); - sendEvent(event); + return eventBuilder.build(); } /** diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.java b/sentry/src/test/java/io/sentry/SentryClientTest.java index 6f51adc7b7b..6d8b9d6e610 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.java +++ b/sentry/src/test/java/io/sentry/SentryClientTest.java @@ -233,4 +233,123 @@ public boolean shouldSend(Event event) { assertThat(called.get(), is(true)); }}; } + + @Test + public void testDefaultTagDoesntOverrideEvent() { + final String key = "key"; + final String expectedValue = "expected"; + sentryClient.addTag(key, "default"); + EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.withTag(key, expectedValue); + + Event event = sentryClient.buildEvent(eventBuilder); + + assertThat(event.getTags().get(key), equalTo(expectedValue)); + } + + @Test + public void testDefaultTagSetToEvent() { + final String key = "key"; + final String expectedValue = "expected"; + sentryClient.addTag(key, expectedValue); + EventBuilder eventBuilder = new EventBuilder(); + + Event event = sentryClient.buildEvent(eventBuilder); + + assertThat(event.getTags().get(key), equalTo(expectedValue)); + } + + @Test + public void testDefaultExtraDoesntOverrideEvent() { + final String key = "key"; + final Object expectedValue = "expected"; + sentryClient.addExtra(key, (Object)"default"); + EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.withExtra(key, expectedValue); + + Event event = sentryClient.buildEvent(eventBuilder); + + assertThat(event.getExtra().get(key), equalTo(expectedValue)); + } + + @Test + public void testDefaultExtraSetToEvent() { + final String key = "key"; + final Object expectedValue = "expected"; + sentryClient.addExtra(key, expectedValue); + EventBuilder eventBuilder = new EventBuilder(); + + Event event = sentryClient.buildEvent(eventBuilder); + + assertThat(event.getExtra().get(key), equalTo(expectedValue)); + } + + @Test + public void testDefaultReleaseDoesntOverrideEvent() { + final String expectedValue = "release"; + sentryClient.setRelease("default"); + EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.withRelease(expectedValue); + + Event event = sentryClient.buildEvent(eventBuilder); + + assertThat(event.getRelease(), equalTo(expectedValue)); + } + + @Test + public void testDefaultReleaseSetToEvent() { + final String expectedValue = "release"; + sentryClient.setRelease(expectedValue); + EventBuilder eventBuilder = new EventBuilder(); + + Event event = sentryClient.buildEvent(eventBuilder); + + assertThat(event.getRelease(), equalTo(expectedValue)); + } + + @Test + public void testDefaultEnvironmentDoesntOverrideEvent() { + final String expectedValue = "env"; + sentryClient.setEnvironment("default"); + EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.withEnvironment(expectedValue); + + Event event = sentryClient.buildEvent(eventBuilder); + + assertThat(event.getEnvironment(), equalTo(expectedValue)); + } + + @Test + public void testDefaultEnvironmentSetToEvent() { + final String expectedValue = "env"; + sentryClient.setEnvironment(expectedValue); + EventBuilder eventBuilder = new EventBuilder(); + + Event event = sentryClient.buildEvent(eventBuilder); + + assertThat(event.getEnvironment(), equalTo(expectedValue)); + } + + @Test + public void testDefaultServerNameDoesntOverrideEvent() { + final String expectedValue = "srv"; + sentryClient.setServerName("default"); + EventBuilder eventBuilder = new EventBuilder(); + eventBuilder.withServerName(expectedValue); + + Event event = sentryClient.buildEvent(eventBuilder); + + assertThat(event.getServerName(), equalTo(expectedValue)); + } + + @Test + public void testDefaultServerNameSetToEvent() { + final String expectedValue = "srv"; + sentryClient.setServerName(expectedValue); + EventBuilder eventBuilder = new EventBuilder(); + + Event event = sentryClient.buildEvent(eventBuilder); + + assertThat(event.getServerName(), equalTo(expectedValue)); + } } From c5287f6ce5639f8f6f4621067a9049be96974192 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Thu, 8 Aug 2019 15:06:00 +0200 Subject: [PATCH 2083/2152] chore: Changes for 1.7.26 --- CHANGES | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 9c698ea176c..2aecebfe707 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,9 @@ Version 1.7.26 -------------- -- +- Introduce SentryOptions #751 +- Android Gradle Plugin: Allow replacing ResourceLoader #739 +- Android Gradle Plugin: Fix find dex task #743 Version 1.7.25 -------------- From 2f84db83c06736033fb13135c8bbe8c6ef411d52 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Thu, 8 Aug 2019 15:26:39 +0200 Subject: [PATCH 2084/2152] [maven-release-plugin] prepare release v1.7.26 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 720e615e657..fe57d963272 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.26-SNAPSHOT + 1.7.26 pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.26 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 29c03766ec4..0f240cb717e 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.26-SNAPSHOT + 1.7.26 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 2655000e4f8..f9093675e96 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.26-SNAPSHOT + 1.7.26 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 3281b0ba207..3853336995b 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.26-SNAPSHOT + 1.7.26 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 82db917348e..12fc7c9f749 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.26-SNAPSHOT + 1.7.26 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 5f24558b85d..4c75858ee97 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.26-SNAPSHOT + 1.7.26 sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 4f486ac067c..d521378d917 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.26-SNAPSHOT + 1.7.26 sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.26-SNAPSHOT + 1.7.26 diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index f585ed785f0..e45ef59232c 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.26-SNAPSHOT + 1.7.26 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 75983ca34c6..488bd20aa50 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.26-SNAPSHOT + 1.7.26 sentry From f9b4a6fedac00549d2dc4c99842257d89eecb96c Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Thu, 8 Aug 2019 15:26:39 +0200 Subject: [PATCH 2085/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index fe57d963272..e8959906c0f 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.26 + 1.7.27-SNAPSHOT pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.26 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 0f240cb717e..dc8c771e869 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.26 + 1.7.27-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index f9093675e96..b42dcc82abc 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.26 + 1.7.27-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 3853336995b..ea04e54e8d3 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.26 + 1.7.27-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 12fc7c9f749..39b4087c954 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.26 + 1.7.27-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 4c75858ee97..4801c6bd845 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.26 + 1.7.27-SNAPSHOT sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index d521378d917..606c22b9f4f 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.26 + 1.7.27-SNAPSHOT sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.26 + 1.7.27-SNAPSHOT diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index e45ef59232c..bf3812c4a9f 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.26 + 1.7.27-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 488bd20aa50..d541073b64b 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.26 + 1.7.27-SNAPSHOT sentry From 325069a682d5ad0269959b7d1321a387bd0dff2c Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Thu, 8 Aug 2019 15:26:42 +0200 Subject: [PATCH 2086/2152] Bump CHANGES to 1.7.27 --- CHANGES | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES b/CHANGES index 2aecebfe707..9b90d238568 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,12 @@ +-e Version 1.7.27 +-------------- + +- + Version 1.7.26 -------------- +- SentryClient fixed override of event data with default data #751 - Introduce SentryOptions #751 - Android Gradle Plugin: Allow replacing ResourceLoader #739 - Android Gradle Plugin: Fix find dex task #743 From d611e86a6c587824680c75d5b80bf430f6f36c64 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Thu, 8 Aug 2019 16:24:49 +0200 Subject: [PATCH 2087/2152] chore: Prepare release --- CHANGES | 2 +- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 9b90d238568..5333e71249d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,4 @@ --e Version 1.7.27 +Version 1.7.27 -------------- - diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index cd53b55c8f9..42e60c80f30 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.47.0 +VERSION=1.47.1 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index edeff62b690..6c18075795c 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.26-SNAPSHOT +version = 1.7.27-SNAPSHOT From 94af1248f92710292a59c5fa1913ed7c941c8ce5 Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Wed, 14 Aug 2019 10:51:57 +0200 Subject: [PATCH 2088/2152] Make Lookup instantiatable with a default constructor retaining the original behavior (#750) * Make Lookup instantiatable with a default constructor retaining the original behavior * Make the names of the env var, system property or default config file location configurable. * Also made the prefixes of env vars, system properties and JNDI names containing the individual configuration properties configurable. * Use older version of Mockito to be compatible with Java7. --- pom.xml | 15 +- sentry/pom.xml | 5 + .../sentry/config/CompoundResourceLoader.java | 43 ++++ .../ContextClassLoaderResourceLoader.java | 15 ++ .../io/sentry/config/FileResourceLoader.java | 36 +++ .../java/io/sentry/config/JndiLookup.java | 3 + .../main/java/io/sentry/config/Lookup.java | 231 ++++++++++-------- .../java/io/sentry/config/ResourceLoader.java | 4 +- .../location/CompoundResourceLocator.java | 39 +++ .../ConfigurationResourceLocator.java | 17 ++ .../location/EnvironmentBasedLocator.java | 37 +++ .../config/location/StaticFileLocator.java | 34 +++ .../SystemPropertiesBasedLocator.java | 37 +++ .../CompoundConfigurationProvider.java | 34 +++ .../provider/ConfigurationProvider.java | 17 ++ .../EnvironmentConfigurationProvider.java | 47 ++++ .../provider/JndiConfigurationProvider.java | 87 +++++++ .../sentry/config/provider/JndiSupport.java | 29 +++ .../LocatorBasedConfigurationProvider.java | 26 ++ .../ResourceLoaderConfigurationProvider.java | 55 +++++ ...SystemPropertiesConfigurationProvider.java | 48 ++++ .../main/java/io/sentry/util/Nullable.java | 13 + .../io/sentry/SentryClientFactoryTest.java | 2 +- .../src/test/java/io/sentry/TestFactory.java | 3 +- .../java/io/sentry/config/LookupTest.java | 69 ++++++ .../location/CompoundResourceLocatorTest.java | 37 +++ .../CompoundConfigurationProviderTest.java | 40 +++ .../JndiConfigurationProviderTest.java | 48 ++++ ...sourceLoaderConfigurationProviderTest.java | 53 ++++ .../src/test/java/io/sentry/dsn/DsnTest.java | 1 + 30 files changed, 1013 insertions(+), 112 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/config/CompoundResourceLoader.java create mode 100644 sentry/src/main/java/io/sentry/config/ContextClassLoaderResourceLoader.java create mode 100644 sentry/src/main/java/io/sentry/config/FileResourceLoader.java create mode 100644 sentry/src/main/java/io/sentry/config/location/CompoundResourceLocator.java create mode 100644 sentry/src/main/java/io/sentry/config/location/ConfigurationResourceLocator.java create mode 100644 sentry/src/main/java/io/sentry/config/location/EnvironmentBasedLocator.java create mode 100644 sentry/src/main/java/io/sentry/config/location/StaticFileLocator.java create mode 100644 sentry/src/main/java/io/sentry/config/location/SystemPropertiesBasedLocator.java create mode 100644 sentry/src/main/java/io/sentry/config/provider/CompoundConfigurationProvider.java create mode 100644 sentry/src/main/java/io/sentry/config/provider/ConfigurationProvider.java create mode 100644 sentry/src/main/java/io/sentry/config/provider/EnvironmentConfigurationProvider.java create mode 100644 sentry/src/main/java/io/sentry/config/provider/JndiConfigurationProvider.java create mode 100644 sentry/src/main/java/io/sentry/config/provider/JndiSupport.java create mode 100644 sentry/src/main/java/io/sentry/config/provider/LocatorBasedConfigurationProvider.java create mode 100644 sentry/src/main/java/io/sentry/config/provider/ResourceLoaderConfigurationProvider.java create mode 100644 sentry/src/main/java/io/sentry/config/provider/SystemPropertiesConfigurationProvider.java create mode 100644 sentry/src/main/java/io/sentry/util/Nullable.java create mode 100644 sentry/src/test/java/io/sentry/config/LookupTest.java create mode 100644 sentry/src/test/java/io/sentry/config/location/CompoundResourceLocatorTest.java create mode 100644 sentry/src/test/java/io/sentry/config/provider/CompoundConfigurationProviderTest.java create mode 100644 sentry/src/test/java/io/sentry/config/provider/JndiConfigurationProviderTest.java create mode 100644 sentry/src/test/java/io/sentry/config/provider/ResourceLoaderConfigurationProviderTest.java diff --git a/pom.xml b/pom.xml index e8959906c0f..4cd43652323 100644 --- a/pom.xml +++ b/pom.xml @@ -125,6 +125,7 @@ 1.3 4.12 2.5.1 + 2.28.2 @@ -191,6 +192,12 @@ wiremock-standalone ${wiremock.version} + + org.mockito + mockito-core + ${mockito.version} + test + @@ -272,12 +279,12 @@ org.apache.maven.plugins maven-surefire-plugin - 2.19.1 + 2.22.2 org.apache.maven.plugins maven-failsafe-plugin - 2.19.1 + 2.22.2 integration-tests @@ -341,7 +348,7 @@ maven-compiler-plugin - 3.6.1 + 3.8.1 maven-deploy-plugin @@ -386,7 +393,7 @@ org.apache.maven.plugins maven-surefire-report-plugin - 2.19.1 + 2.22.2 org.apache.maven.plugins diff --git a/sentry/pom.xml b/sentry/pom.xml index d541073b64b..0f2c16f9c32 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -82,6 +82,11 @@ wiremock-standalone test + + org.mockito + mockito-core + test + diff --git a/sentry/src/main/java/io/sentry/config/CompoundResourceLoader.java b/sentry/src/main/java/io/sentry/config/CompoundResourceLoader.java new file mode 100644 index 00000000000..fd7011479ad --- /dev/null +++ b/sentry/src/main/java/io/sentry/config/CompoundResourceLoader.java @@ -0,0 +1,43 @@ +package io.sentry.config; + +import java.io.InputStream; +import java.util.Collection; + +import io.sentry.util.Nullable; + +/** + * A compound resource loader that returns the first non-null stream of the list of the loaders provided at + * the instantiation time. + */ +public class CompoundResourceLoader implements ResourceLoader { + private final Collection loaders; + + /** + * Instantiates new resource loader using the provided loaders. + * + * @param loaders the loaders to wrap + */ + public CompoundResourceLoader(Collection loaders) { + this.loaders = loaders; + } + + /** + * This goes through the wrapped resource loaders in the iteration order and returns the first non-null input stream + * obtained from the wrapped resource loaders. + * + * @param filepath Path of the resource to open + * @return the input stream with the contents of the resource on given path or null if none could be found + */ + @Nullable + @Override + public InputStream getInputStream(String filepath) { + for (ResourceLoader l : loaders) { + InputStream is = l.getInputStream(filepath); + if (is != null) { + return is; + } + } + + return null; + } +} diff --git a/sentry/src/main/java/io/sentry/config/ContextClassLoaderResourceLoader.java b/sentry/src/main/java/io/sentry/config/ContextClassLoaderResourceLoader.java new file mode 100644 index 00000000000..c13ae28499c --- /dev/null +++ b/sentry/src/main/java/io/sentry/config/ContextClassLoaderResourceLoader.java @@ -0,0 +1,15 @@ +package io.sentry.config; + +import java.io.InputStream; + +/** + * A {@link ResourceLoader} that considers the paths to be resource locations in the context classloader of the current + * thread. + */ +public class ContextClassLoaderResourceLoader implements ResourceLoader { + @Override + public InputStream getInputStream(String filepath) { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + return classLoader.getResourceAsStream(filepath); + } +} diff --git a/sentry/src/main/java/io/sentry/config/FileResourceLoader.java b/sentry/src/main/java/io/sentry/config/FileResourceLoader.java new file mode 100644 index 00000000000..c0bcc671120 --- /dev/null +++ b/sentry/src/main/java/io/sentry/config/FileResourceLoader.java @@ -0,0 +1,36 @@ +package io.sentry.config; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + +import io.sentry.util.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A {@link ResourceLoader} that considers the paths to be filesystem locations. + */ +public class FileResourceLoader implements ResourceLoader { + private static final Logger logger = LoggerFactory.getLogger(FileResourceLoader.class); + + @Nullable + @Override + public InputStream getInputStream(String filepath) { + File f = new File(filepath); + if (f.isFile() && f.canRead()) { + try { + return new FileInputStream(f); + } catch (FileNotFoundException e) { + logger.debug("Configuration file {} could not be found even though we just checked it can be read...", + filepath); + } + } else { + logger.debug("The configuration file {} (which resolves to absolute path {}) doesn't exist, is not a file" + + " or is not readable.", f, f.getAbsolutePath()); + } + + return null; + } +} diff --git a/sentry/src/main/java/io/sentry/config/JndiLookup.java b/sentry/src/main/java/io/sentry/config/JndiLookup.java index d60e86b5d4b..75e13996358 100644 --- a/sentry/src/main/java/io/sentry/config/JndiLookup.java +++ b/sentry/src/main/java/io/sentry/config/JndiLookup.java @@ -10,7 +10,10 @@ /** * Handles JNDI lookup of a provided key within the Sentry namespace. + * + * @deprecated Do not use this. Superseded by {@link io.sentry.config.provider.JndiConfigurationProvider} */ +@Deprecated public final class JndiLookup { /** * Lookup prefix for Sentry configuration in JNDI. diff --git a/sentry/src/main/java/io/sentry/config/Lookup.java b/sentry/src/main/java/io/sentry/config/Lookup.java index 4c7ae388cfa..2ebd6e728fc 100644 --- a/sentry/src/main/java/io/sentry/config/Lookup.java +++ b/sentry/src/main/java/io/sentry/config/Lookup.java @@ -1,90 +1,123 @@ package io.sentry.config; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + import io.sentry.Sentry; +import io.sentry.config.location.CompoundResourceLocator; +import io.sentry.config.location.ConfigurationResourceLocator; +import io.sentry.config.location.StaticFileLocator; +import io.sentry.config.location.EnvironmentBasedLocator; +import io.sentry.config.location.SystemPropertiesBasedLocator; +import io.sentry.config.provider.CompoundConfigurationProvider; +import io.sentry.config.provider.ConfigurationProvider; +import io.sentry.config.provider.EnvironmentConfigurationProvider; +import io.sentry.config.provider.JndiConfigurationProvider; +import io.sentry.config.provider.JndiSupport; +import io.sentry.config.provider.LocatorBasedConfigurationProvider; +import io.sentry.config.provider.SystemPropertiesConfigurationProvider; import io.sentry.dsn.Dsn; -import io.sentry.util.Util; +import io.sentry.util.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.util.Properties; - /** * Handle lookup of configuration keys by trying JNDI, System Environment, and Java System Properties. + * + * By default (when instantiated using the default constructor), the sources from which the configuration + * properties are consulted in the following order: + * + * 1. JNDI, if available + * 2. Java System Properties + * 3. System Environment Variables + * 4. DSN options, if a non-null DSN is provided + * 5. Sentry properties file found in resources */ public final class Lookup { private static final Logger logger = LoggerFactory.getLogger(Lookup.class); + private static final Object INSTANCE_LOCK = new Object(); + private static Lookup instance; + + private final ConfigurationProvider highPriorityProvider; + private final ConfigurationProvider lowPriorityProvider; /** - * The filename of the Sentry configuration file. - */ - private static final String CONFIG_FILE_NAME = "sentry.properties"; - /** - * Properties loaded from the Sentry configuration file, or null if no file was - * found or it failed to parse. - */ - private static Properties configProps; - /** - * Whether or not to check the JNDI system. Exists so that JNDI checks can be disabled - * the first time the class is not found. + * Constructs a new instance of Lookup with the default configuration providers. */ - private static boolean checkJndi = true; - - static { - String filePath = getConfigFilePath(); - InputStream input = null; - try { - input = getInputStream(filePath); - - if (input != null) { - configProps = new Properties(); - configProps.load(input); - } else { - logger.debug("Sentry configuration file not found in filesystem or classpath: '{}'.", filePath); - } - } catch (Exception e) { - logger.error("Error loading Sentry configuration file '{}': ", filePath, e); - } finally { - Util.closeQuietly(input); - } + public Lookup() { + this(new CompoundConfigurationProvider(getDefaultHighPriorityConfigurationProviders()), + new CompoundConfigurationProvider(getDefaultLowPriorityConfigurationProviders())); } /** - * Hidden constructor for static utility class. + * Constructs a new Lookup instance. + * + * The two configuration providers are used before and after an attempt to load the configuration property from + * the DSN in the {@link #get(String, Dsn)} method. + * + * @param highPriorityProvider the configuration provider that is consulted before the check in DSN + * @param lowPriorityProvider the configuration provider that is consulted only after the high priority one and + * the DSN */ - private Lookup() { + public Lookup(ConfigurationProvider highPriorityProvider, ConfigurationProvider lowPriorityProvider) { + this.highPriorityProvider = highPriorityProvider; + this.lowPriorityProvider = lowPriorityProvider; + } + private static List getDefaultResourceLocators() { + return asList(new SystemPropertiesBasedLocator(), new EnvironmentBasedLocator(), new StaticFileLocator()); } - private static String getConfigFilePath() { - String filePath = System.getProperty("sentry.properties.file"); + private static List getDefaultHighPriorityConfigurationProviders() { + boolean jndiPresent = JndiSupport.isAvailable(); - if (filePath == null) { - filePath = System.getenv("SENTRY_PROPERTIES_FILE"); - } + @SuppressWarnings("checkstyle:MagicNumber") + List providers = new ArrayList<>(jndiPresent ? 3 : 2); - if (filePath == null) { - filePath = CONFIG_FILE_NAME; + if (jndiPresent) { + providers.add(new JndiConfigurationProvider()); } - return filePath; + providers.add(new SystemPropertiesConfigurationProvider()); + providers.add(new EnvironmentConfigurationProvider()); + + return providers; } - private static InputStream getInputStream(String filePath) throws FileNotFoundException { - File file = new File(filePath); - if (file.isFile() && file.canRead()) { - return new FileInputStream(file); + private static List getDefaultResourceLoaders() { + ResourceLoader sentryLoader = Sentry.getResourceLoader(); + + return sentryLoader == null + ? Arrays.asList(new FileResourceLoader(), new ContextClassLoaderResourceLoader()) + : Arrays.asList(new FileResourceLoader(), sentryLoader, new ContextClassLoaderResourceLoader()); + } + + private static List getDefaultLowPriorityConfigurationProviders() { + try { + return singletonList((ConfigurationProvider) + new LocatorBasedConfigurationProvider(new CompoundResourceLoader(getDefaultResourceLoaders()), + new CompoundResourceLocator(getDefaultResourceLocators()), Charset.defaultCharset())); + } catch (IOException e) { + logger.debug("Failed to instantiate resource locator-based configuration provider.", e); + return emptyList(); } + } - ResourceLoader resourceLoader = Sentry.getResourceLoader(); - if (resourceLoader != null) { - return resourceLoader.getInputStream(filePath); - } else { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - return classLoader.getResourceAsStream(filePath); + // for use in the deprecated methods + private static Lookup getDeprecatedInstance() { + synchronized (INSTANCE_LOCK) { + if (instance == null) { + instance = new Lookup(); + } + + return instance; } } @@ -93,7 +126,10 @@ private static InputStream getInputStream(String filePath) throws FileNotFoundEx * * @param key name of configuration key, e.g. "dsn" * @return value of configuration key, if found, otherwise null + * + * @deprecated obtain an instance of this class and use {@link #get(String)} */ + @Deprecated public static String lookup(String key) { return lookup(key, null); } @@ -110,62 +146,47 @@ public static String lookup(String key) { * @param key name of configuration key, e.g. "dsn" * @param dsn an optional DSN to retrieve options from * @return value of configuration key, if found, otherwise null + * + * @deprecated obtain an instance of this class and use {@link #get(String, Dsn)} */ + @Deprecated public static String lookup(String key, Dsn dsn) { - String value = null; - - if (checkJndi) { - // Try to obtain from JNDI - try { - // Check that JNDI is available (not available on Android) by loading InitialContext - Class.forName("javax.naming.InitialContext", false, Dsn.class.getClassLoader()); - value = JndiLookup.jndiLookup(key); - if (value != null) { - logger.debug("Found {}={} in JNDI.", key, value); - } - } catch (ClassNotFoundException | NoClassDefFoundError e) { - logger.trace("JNDI is not available: " + e.getMessage()); - checkJndi = false; - } - } - - // Try to obtain from a Java System Property - if (value == null) { - value = System.getProperty("sentry." + key.toLowerCase()); - if (value != null) { - logger.debug("Found {}={} in Java System Properties.", key, value); - } - } + return getDeprecatedInstance().get(key, dsn); + } - // Try to obtain from a System Environment Variable - if (value == null) { - value = System.getenv("SENTRY_" + key.replace(".", "_").toUpperCase()); - if (value != null) { - logger.debug("Found {}={} in System Environment Variables.", key, value); - } - } + /** + * Attempt to lookup a configuration key, without checking any DSN options. + * + * @param key name of configuration key, e.g. "dsn" + * @return value of configuration key, if found, otherwise null + */ + @Nullable + public String get(String key) { + return get(key, null); + } - // Try to obtain from the provided DSN, if set - if (value == null && dsn != null) { - value = dsn.getOptions().get(key); - if (value != null) { - logger.debug("Found {}={} in DSN.", key, value); + /** + * Attempt to lookup a configuration key using either some internal means or from the DSN. + * + * @param key name of configuration key, e.g. "dsn" + * @param dsn an optional DSN to retrieve options from + * @return value of configuration key, if found, otherwise null + */ + @Nullable + public String get(String key, Dsn dsn) { + String val = highPriorityProvider.getProperty(key); + + if (val == null && dsn != null) { + val = dsn.getOptions().get(key); + if (val != null) { + logger.debug("Found {}={} in DSN.", key, val); } } - // Try to obtain from config file - if (value == null && configProps != null) { - value = configProps.getProperty(key); - if (value != null) { - logger.debug("Found {}={} in {}.", key, value, CONFIG_FILE_NAME); - } + if (val == null) { + val = lowPriorityProvider.getProperty(key); } - if (value != null) { - return value.trim(); - } else { - return null; - } + return val == null ? null : val.trim(); } - } diff --git a/sentry/src/main/java/io/sentry/config/ResourceLoader.java b/sentry/src/main/java/io/sentry/config/ResourceLoader.java index fb446017ccb..7afa59e8677 100644 --- a/sentry/src/main/java/io/sentry/config/ResourceLoader.java +++ b/sentry/src/main/java/io/sentry/config/ResourceLoader.java @@ -2,6 +2,8 @@ import java.io.InputStream; +import io.sentry.util.Nullable; + /** * Interface for platform-specific resource loaders. */ @@ -13,5 +15,5 @@ public interface ResourceLoader { * @param filepath Path of the resource to open * @return Resource's input stream or null */ - InputStream getInputStream(String filepath); + @Nullable InputStream getInputStream(String filepath); } diff --git a/sentry/src/main/java/io/sentry/config/location/CompoundResourceLocator.java b/sentry/src/main/java/io/sentry/config/location/CompoundResourceLocator.java new file mode 100644 index 00000000000..c0fc61802ce --- /dev/null +++ b/sentry/src/main/java/io/sentry/config/location/CompoundResourceLocator.java @@ -0,0 +1,39 @@ +package io.sentry.config.location; + +import java.util.Collection; + +import io.sentry.util.Nullable; + +/** + * Wraps multiple resource locators and returns the first non-null configuration file path. + */ +public class CompoundResourceLocator implements ConfigurationResourceLocator { + private final Collection locators; + + /** + * Instantiates a new compound configuration resource locator. + * @param locators the locators to iterate through + */ + public CompoundResourceLocator(Collection locators) { + this.locators = locators; + } + + /** + * Tries to find the location of the resource containing the Sentry configuration file. + * + * @return the first non-null configuration file path (in the iteration order of the collection provided to + * the constructor) or null if none such exists. + */ + @Override + @Nullable + public String getConfigurationResourcePath() { + for (ConfigurationResourceLocator l : locators) { + String path = l.getConfigurationResourcePath(); + if (path != null) { + return path; + } + } + + return null; + } +} diff --git a/sentry/src/main/java/io/sentry/config/location/ConfigurationResourceLocator.java b/sentry/src/main/java/io/sentry/config/location/ConfigurationResourceLocator.java new file mode 100644 index 00000000000..ba1b8cd97fd --- /dev/null +++ b/sentry/src/main/java/io/sentry/config/location/ConfigurationResourceLocator.java @@ -0,0 +1,17 @@ +package io.sentry.config.location; + +import io.sentry.config.ResourceLoader; +import io.sentry.util.Nullable; + +/** + * Tries to find the Sentry configuration file. + */ +public interface ConfigurationResourceLocator { + /** + * Tries to find the location of the resource containing the Sentry configuration file. + * + * @return the location on which some {@link ResourceLoader} can find the configuration file or null if this + * locator could not find any. + */ + @Nullable String getConfigurationResourcePath(); +} diff --git a/sentry/src/main/java/io/sentry/config/location/EnvironmentBasedLocator.java b/sentry/src/main/java/io/sentry/config/location/EnvironmentBasedLocator.java new file mode 100644 index 00000000000..3eab95d12a4 --- /dev/null +++ b/sentry/src/main/java/io/sentry/config/location/EnvironmentBasedLocator.java @@ -0,0 +1,37 @@ +package io.sentry.config.location; + +import io.sentry.util.Nullable; + +/** + * Tries to find the location of the Sentry configuration file in some environment variable. + */ +public class EnvironmentBasedLocator implements ConfigurationResourceLocator { + /** + * The default environment variable to use for obtaining the location of the Sentry configuration file. + */ + public static final String DEFAULT_ENV_VAR_NAME = "SENTRY_PROPERTIES_FILE"; + + private final String envVarName; + + /** + * Constructs a new instance that will use the environment variable defined in {@link #DEFAULT_ENV_VAR_NAME}. + */ + public EnvironmentBasedLocator() { + this(DEFAULT_ENV_VAR_NAME); + } + + /** + * Constructs a new instance that will use the provided environment variable. + * + * @param envVarName the name of the environment variable to use + */ + public EnvironmentBasedLocator(String envVarName) { + this.envVarName = envVarName; + } + + @Override + @Nullable + public String getConfigurationResourcePath() { + return System.getenv(envVarName); + } +} diff --git a/sentry/src/main/java/io/sentry/config/location/StaticFileLocator.java b/sentry/src/main/java/io/sentry/config/location/StaticFileLocator.java new file mode 100644 index 00000000000..8bf6f3a69e8 --- /dev/null +++ b/sentry/src/main/java/io/sentry/config/location/StaticFileLocator.java @@ -0,0 +1,34 @@ +package io.sentry.config.location; + +/** + * Provides the configuration file location using a file name provided at instantiation time. + */ +public class StaticFileLocator implements ConfigurationResourceLocator { + /** + * The default file name of the Sentry configuration file. + */ + public static final String DEFAULT_FILE_PATH = "sentry.properties"; + + private final String path; + + /** + * Constructs a new instance using the {@link #DEFAULT_FILE_PATH}. + */ + public StaticFileLocator() { + this(DEFAULT_FILE_PATH); + } + + /** + * Constructs a new instance that will return the provided path as the path of the Sentry configuration. + * + * @param filePath the path to the Sentry configuration + */ + public StaticFileLocator(String filePath) { + this.path = filePath; + } + + @Override + public String getConfigurationResourcePath() { + return path; + } +} diff --git a/sentry/src/main/java/io/sentry/config/location/SystemPropertiesBasedLocator.java b/sentry/src/main/java/io/sentry/config/location/SystemPropertiesBasedLocator.java new file mode 100644 index 00000000000..3ebe6d805c2 --- /dev/null +++ b/sentry/src/main/java/io/sentry/config/location/SystemPropertiesBasedLocator.java @@ -0,0 +1,37 @@ +package io.sentry.config.location; + +import io.sentry.util.Nullable; + +/** + * Tries to find the location of the Sentry configuration file using the value of some system property. + */ +public class SystemPropertiesBasedLocator implements ConfigurationResourceLocator { + /** + * The default system property to use for obtaining the location of the Sentry configuration file. + */ + public static final String DEFAULT_PROPERTY_NAME = "sentry.properties.file"; + + private final String propertyName; + + /** + * Constructs a new instance that will use the {@link #DEFAULT_PROPERTY_NAME}. + */ + public SystemPropertiesBasedLocator() { + this(DEFAULT_PROPERTY_NAME); + } + + /** + * Constructs a new instance that will use the provided system property name. + * + * @param propertyName the name of the property to load the location of the Sentry configuration file from + */ + public SystemPropertiesBasedLocator(String propertyName) { + this.propertyName = propertyName; + } + + @Override + @Nullable + public String getConfigurationResourcePath() { + return System.getProperty(propertyName); + } +} diff --git a/sentry/src/main/java/io/sentry/config/provider/CompoundConfigurationProvider.java b/sentry/src/main/java/io/sentry/config/provider/CompoundConfigurationProvider.java new file mode 100644 index 00000000000..2d42cbe42de --- /dev/null +++ b/sentry/src/main/java/io/sentry/config/provider/CompoundConfigurationProvider.java @@ -0,0 +1,34 @@ +package io.sentry.config.provider; + +import java.util.Collection; + +import io.sentry.util.Nullable; + +/** + * Wraps a couple of other configuration providers to act as one, returning the first non-null value for given + * configuration key, in the iteration order of the wrapped providers. + */ +public class CompoundConfigurationProvider implements ConfigurationProvider { + private final Collection providers; + + /** + * Instantiates the new compound provider by wrapping the provided collection of providers. + * @param providers the providers to wrap + */ + public CompoundConfigurationProvider(Collection providers) { + this.providers = providers; + } + + @Nullable + @Override + public String getProperty(String key) { + for (ConfigurationProvider p : providers) { + String val = p.getProperty(key); + if (val != null) { + return val; + } + } + + return null; + } +} diff --git a/sentry/src/main/java/io/sentry/config/provider/ConfigurationProvider.java b/sentry/src/main/java/io/sentry/config/provider/ConfigurationProvider.java new file mode 100644 index 00000000000..21276a96fe9 --- /dev/null +++ b/sentry/src/main/java/io/sentry/config/provider/ConfigurationProvider.java @@ -0,0 +1,17 @@ +package io.sentry.config.provider; + +import io.sentry.util.Nullable; + +/** + * Sentry is able to load configuration from various sources. This interface is implemented by each one of them. + */ +public interface ConfigurationProvider { + + /** + * Returns the value of the configuration property with the provided key. + * + * @param key the name of the configuration property + * @return the value of the property as found in this provider or null if none found + */ + @Nullable String getProperty(String key); +} diff --git a/sentry/src/main/java/io/sentry/config/provider/EnvironmentConfigurationProvider.java b/sentry/src/main/java/io/sentry/config/provider/EnvironmentConfigurationProvider.java new file mode 100644 index 00000000000..4f094601ff1 --- /dev/null +++ b/sentry/src/main/java/io/sentry/config/provider/EnvironmentConfigurationProvider.java @@ -0,0 +1,47 @@ +package io.sentry.config.provider; + +import io.sentry.util.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Tries to find the configuration properties in the environment. + */ +public class EnvironmentConfigurationProvider implements ConfigurationProvider { + /** + * The default prefix of the environment variables holding Sentry configuration. + */ + public static final String DEFAULT_ENV_VAR_PREFIX = "SENTRY_"; + + private static final Logger logger = LoggerFactory.getLogger(EnvironmentConfigurationProvider.class); + + private final String prefix; + + /** + * Creates a new instance with environment variables assumed to have the {@link #DEFAULT_ENV_VAR_PREFIX}. + */ + public EnvironmentConfigurationProvider() { + this(DEFAULT_ENV_VAR_PREFIX); + } + + /** + * Creates a new instance that will look for env variables having the provided prefix. + * + * @param envVarPrefix the prefix of the environment variables + */ + public EnvironmentConfigurationProvider(String envVarPrefix) { + this.prefix = envVarPrefix; + } + + @Nullable + @Override + public String getProperty(String key) { + String ret = System.getenv(prefix + key.replace(".", "_").toUpperCase()); + + if (ret != null) { + logger.debug("Found {}={} in System Environment Variables.", key, ret); + } + + return ret; + } +} diff --git a/sentry/src/main/java/io/sentry/config/provider/JndiConfigurationProvider.java b/sentry/src/main/java/io/sentry/config/provider/JndiConfigurationProvider.java new file mode 100644 index 00000000000..0a0fc5e3323 --- /dev/null +++ b/sentry/src/main/java/io/sentry/config/provider/JndiConfigurationProvider.java @@ -0,0 +1,87 @@ +package io.sentry.config.provider; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.naming.NoInitialContextException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A configuration provider that looks up the configuration in the JNDI registry. + * + * @see JndiSupport to check whether Jndi is available in the current JVM without needing to load this class + */ +public class JndiConfigurationProvider implements ConfigurationProvider { + /** + * The default prefix of the JNDI names of Sentry configuration. This, concatenated with the configuration key name + * provided to the {@link #getProperty(String)} method, must form a valid JNDI name. + */ + public static final String DEFAULT_JNDI_PREFIX = "java:comp/env/sentry/"; + + private static final Logger logger = LoggerFactory.getLogger(JndiConfigurationProvider.class); + + private final String prefix; + private final JndiContextProvider contextProvider; + + /** + * Constructs a new instance using the {@link #DEFAULT_JNDI_PREFIX} and {@link JndiContextProvider} that returns + * a new {@link InitialContext} each time it is asked for a context. This ensures that any changes in the JNDI + * environment are taken into account on the next configuration property lookup. + */ + public JndiConfigurationProvider() { + this(DEFAULT_JNDI_PREFIX, new JndiContextProvider() { + @Override + public Context getContext() throws NamingException { + return new InitialContext(); + } + }); + } + + /** + * Constructs a new instance that will look up the Sentry configuration keys using the provided prefix and using + * the context obtained from the JNDI context provider. Because the JNDI environment is dynamic, Sentry uses the + * JNDI context provider to obtain a fresh copy of the environment (if the provider chooses to return such). + * + * @param jndiNamePrefix The prefix of the JNDI names of Sentry configuration. This, concatenated with + * the configuration key name provided to the {@link #getProperty(String)} method, must form + * a valid JNDI name. + * @param contextProvider an object able to provide instances of the JNDI context + */ + public JndiConfigurationProvider(String jndiNamePrefix, JndiContextProvider contextProvider) { + this.prefix = jndiNamePrefix; + this.contextProvider = contextProvider; + } + + @Override + public String getProperty(String key) { + String value = null; + try { + Context ctx = contextProvider.getContext(); + value = (String) ctx.lookup(prefix + key); + } catch (NoInitialContextException e) { + logger.trace("JNDI not configured for Sentry (NoInitialContextEx)"); + } catch (NamingException e) { + logger.trace("No " + prefix + key + " in JNDI"); + } catch (RuntimeException e) { + logger.warn("Odd RuntimeException while testing for JNDI", e); + } + return value; + } + + /** + * A helper interface to be able to obtain application-specific JNDI context during Sentry configuration lookup. + */ + public interface JndiContextProvider { + /** + * Returns the context to use when looking for a property in JNDI. Note that it is supposed that this method + * returns a new context each time, but that might not be required or even correct depending on that + * requirements of the supplier of the implementation of this interface. + * + * @return a possibly new instance of the JNDI context + * @throws NamingException on JNDI error + */ + Context getContext() throws NamingException; + } +} diff --git a/sentry/src/main/java/io/sentry/config/provider/JndiSupport.java b/sentry/src/main/java/io/sentry/config/provider/JndiSupport.java new file mode 100644 index 00000000000..bf76dcb1192 --- /dev/null +++ b/sentry/src/main/java/io/sentry/config/provider/JndiSupport.java @@ -0,0 +1,29 @@ +package io.sentry.config.provider; + +import io.sentry.dsn.Dsn; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A helper class to check whether JNDI is available in the current JVM. + */ +public final class JndiSupport { + private static final Logger logger = LoggerFactory.getLogger(JndiSupport.class); + + private JndiSupport() { + } + + /** + * Checks whether JNDI is available. + * @return true if JNDI is available, false otherwise + */ + public static boolean isAvailable() { + try { + Class.forName("javax.naming.InitialContext", false, Dsn.class.getClassLoader()); + return true; + } catch (ClassNotFoundException | NoClassDefFoundError e) { + logger.trace("JNDI is not available: " + e.getMessage()); + return false; + } + } +} diff --git a/sentry/src/main/java/io/sentry/config/provider/LocatorBasedConfigurationProvider.java b/sentry/src/main/java/io/sentry/config/provider/LocatorBasedConfigurationProvider.java new file mode 100644 index 00000000000..a5ca506e819 --- /dev/null +++ b/sentry/src/main/java/io/sentry/config/provider/LocatorBasedConfigurationProvider.java @@ -0,0 +1,26 @@ +package io.sentry.config.provider; + +import java.io.IOException; +import java.nio.charset.Charset; + +import io.sentry.config.ResourceLoader; +import io.sentry.config.location.ConfigurationResourceLocator; + +/** + * Similar to {@link ResourceLoaderConfigurationProvider} but uses a {@link ConfigurationResourceLocator} to find + * the path to the configuration file. + */ +public class LocatorBasedConfigurationProvider extends ResourceLoaderConfigurationProvider { + /** + * Instantiates a new configuration provider using the parameters. + * + * @param rl the resource loader to load the contents of the configuration file with + * @param locator the locator to find the configuration file with + * @param charset the charset of the configuration file + * @throws IOException on failure to process the configuration file + */ + public LocatorBasedConfigurationProvider(ResourceLoader rl, ConfigurationResourceLocator locator, Charset charset) + throws IOException { + super(rl, locator.getConfigurationResourcePath(), charset); + } +} diff --git a/sentry/src/main/java/io/sentry/config/provider/ResourceLoaderConfigurationProvider.java b/sentry/src/main/java/io/sentry/config/provider/ResourceLoaderConfigurationProvider.java new file mode 100644 index 00000000000..1b74996a07a --- /dev/null +++ b/sentry/src/main/java/io/sentry/config/provider/ResourceLoaderConfigurationProvider.java @@ -0,0 +1,55 @@ +package io.sentry.config.provider; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.Properties; + +import io.sentry.config.ResourceLoader; +import io.sentry.util.Nullable; + +/** + * A configuration provider that loads the properties from a {@link ResourceLoader}. + */ +public class ResourceLoaderConfigurationProvider implements ConfigurationProvider { + @Nullable + private final Properties properties; + + /** + * Instantiates a new resource loader based configuration provider. + * @param rl the resource loader used to load the configuration file + * @param filePath the path to the configuration file as understood by the resource loader + * @param charset the charset of the configuration file + * @throws IOException on failure to process the configuration file contents + */ + public ResourceLoaderConfigurationProvider(ResourceLoader rl, @Nullable String filePath, Charset charset) + throws IOException { + properties = loadProperties(rl, filePath, charset); + } + + @Nullable + private static Properties loadProperties(ResourceLoader rl, @Nullable String filePath, Charset charset) + throws IOException { + if (filePath == null) { + return null; + } + + InputStream is = rl.getInputStream(filePath); + + if (is == null) { + return null; + } + + try (InputStreamReader rdr = new InputStreamReader(is, charset)) { + Properties props = new Properties(); + props.load(rdr); + return props; + } + } + + @Override + public String getProperty(String key) { + return properties == null ? null : properties.getProperty(key); + } +} diff --git a/sentry/src/main/java/io/sentry/config/provider/SystemPropertiesConfigurationProvider.java b/sentry/src/main/java/io/sentry/config/provider/SystemPropertiesConfigurationProvider.java new file mode 100644 index 00000000000..39f5f922639 --- /dev/null +++ b/sentry/src/main/java/io/sentry/config/provider/SystemPropertiesConfigurationProvider.java @@ -0,0 +1,48 @@ +package io.sentry.config.provider; + +import io.sentry.util.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A configuration provider that loads the configuration from the system properties. + */ +public class SystemPropertiesConfigurationProvider implements ConfigurationProvider { + /** + * The default prefix of the system properties that should be considered Sentry configuration. + */ + public static final String DEFAULT_SYSTEM_PROPERTY_PREFIX = "sentry."; + + private static final Logger logger = LoggerFactory.getLogger(SystemPropertiesConfigurationProvider.class); + + private final String prefix; + + /** + * Constructs a new instance using the {@link #DEFAULT_SYSTEM_PROPERTY_PREFIX}. + */ + public SystemPropertiesConfigurationProvider() { + this(DEFAULT_SYSTEM_PROPERTY_PREFIX); + } + + /** + * Constructs a new instance that will locate the configuration properties from the system properties having + * the provided prefix. + * + * @param systemPropertyPrefix the prefix of the system properties that should be considered Sentry configuration + */ + public SystemPropertiesConfigurationProvider(String systemPropertyPrefix) { + this.prefix = systemPropertyPrefix; + } + + @Nullable + @Override + public String getProperty(String key) { + String ret = System.getProperty(prefix + key.toLowerCase()); + + if (ret != null) { + logger.debug("Found {}={} in Java System Properties.", key, ret); + } + + return ret; + } +} diff --git a/sentry/src/main/java/io/sentry/util/Nullable.java b/sentry/src/main/java/io/sentry/util/Nullable.java new file mode 100644 index 00000000000..213b8db76e6 --- /dev/null +++ b/sentry/src/main/java/io/sentry/util/Nullable.java @@ -0,0 +1,13 @@ +package io.sentry.util; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Indicates that an instance on the given position can be null. + */ +@Documented +@Retention(RetentionPolicy.CLASS) +public @interface Nullable { +} diff --git a/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java b/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java index 27dcdd74e76..3623b40c119 100644 --- a/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java +++ b/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java @@ -22,7 +22,7 @@ public class SentryClientFactoryTest extends BaseTest { public void testSentryClientForFactoryNameSucceedsIfFactoryFound() throws Exception { String dsn = "noop://localhost/1?factory=io.sentry.TestFactory"; SentryClient sentryClient = SentryClientFactory.sentryClient(dsn); - assertThat(sentryClient.getRelease(), is("312407214120")); + assertThat(sentryClient.getRelease(), is(TestFactory.RELEASE)); } @Test diff --git a/sentry/src/test/java/io/sentry/TestFactory.java b/sentry/src/test/java/io/sentry/TestFactory.java index 82881a9e567..96b215a19fd 100644 --- a/sentry/src/test/java/io/sentry/TestFactory.java +++ b/sentry/src/test/java/io/sentry/TestFactory.java @@ -3,9 +3,10 @@ import io.sentry.dsn.Dsn; public class TestFactory extends DefaultSentryClientFactory { + static final String RELEASE = "312407214120"; @Override protected SentryClient configureSentryClient(SentryClient sentryClient, Dsn dsn) { - sentryClient.setRelease("312407214120"); + sentryClient.setRelease(RELEASE); return super.configureSentryClient(sentryClient, dsn); } } diff --git a/sentry/src/test/java/io/sentry/config/LookupTest.java b/sentry/src/test/java/io/sentry/config/LookupTest.java new file mode 100644 index 00000000000..d32c8691075 --- /dev/null +++ b/sentry/src/test/java/io/sentry/config/LookupTest.java @@ -0,0 +1,69 @@ +package io.sentry.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.sentry.config.provider.ConfigurationProvider; +import io.sentry.dsn.Dsn; +import org.junit.Test; + +public class LookupTest { + + private final Dsn testDsn = new Dsn("sentry://user:pass@host/42?prop1=val1&prop2=val2"); + + @Test + public void testUsesHighPriorityConfigurationProvider() throws Exception { + // given + ConfigurationProvider highPrioProvider = mock(ConfigurationProvider.class); + when(highPrioProvider.getProperty("prop1")).thenReturn("highPrio"); + + Lookup lookup = new Lookup(highPrioProvider, mock(ConfigurationProvider.class)); + + // when + String prop1 = lookup.get("prop1", testDsn); + String prop2 = lookup.get("prop2", testDsn); + String prop3 = lookup.get("prop3", testDsn); + + // then + assertEquals("highPrio", prop1); + assertEquals("val2", prop2); + assertNull(prop3); + } + + @Test + public void testusesDsnOptions() throws Exception { + // given + Lookup lookup = new Lookup(mock(ConfigurationProvider.class), mock(ConfigurationProvider.class)); + + // when + String prop1 = lookup.get("prop1", testDsn); + String prop2 = lookup.get("prop2", testDsn); + String prop3 = lookup.get("prop3", testDsn); + + // then + assertEquals("val1", prop1); + assertEquals("val2", prop2); + assertNull(prop3); + } + + @Test + public void testUsesLowPriorityConfigurationProvider() throws Exception { + // given + ConfigurationProvider lowPrioProvider = mock(ConfigurationProvider.class); + when(lowPrioProvider.getProperty("prop3")).thenReturn("lowPrio"); + + Lookup lookup = new Lookup(mock(ConfigurationProvider.class), lowPrioProvider); + + // when + String prop1 = lookup.get("prop1", testDsn); + String prop2 = lookup.get("prop2", testDsn); + String prop3 = lookup.get("prop3", testDsn); + + // then + assertEquals("val1", prop1); + assertEquals("val2", prop2); + assertEquals("lowPrio", prop3); + } +} diff --git a/sentry/src/test/java/io/sentry/config/location/CompoundResourceLocatorTest.java b/sentry/src/test/java/io/sentry/config/location/CompoundResourceLocatorTest.java new file mode 100644 index 00000000000..c5dd62400ba --- /dev/null +++ b/sentry/src/test/java/io/sentry/config/location/CompoundResourceLocatorTest.java @@ -0,0 +1,37 @@ +package io.sentry.config.location; + +import static java.util.Arrays.asList; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Test; + +public class CompoundResourceLocatorTest { + + @Test + public void testReturnsFirstNonNullValue() { + // given + ConfigurationResourceLocator first = mock(ConfigurationResourceLocator.class); + ConfigurationResourceLocator second = mock(ConfigurationResourceLocator.class); + ConfigurationResourceLocator third = mock(ConfigurationResourceLocator.class); + + when(second.getConfigurationResourcePath()).thenReturn("non-null"); + + CompoundResourceLocator compoundLocator = new CompoundResourceLocator(asList(first, second, third)); + + // when + String val = compoundLocator.getConfigurationResourcePath(); + + // then + assertEquals("non-null", val); + + verify(first, times(1)).getConfigurationResourcePath(); + verify(second, times(1)).getConfigurationResourcePath(); + verify(third, never()).getConfigurationResourcePath(); + } +} diff --git a/sentry/src/test/java/io/sentry/config/provider/CompoundConfigurationProviderTest.java b/sentry/src/test/java/io/sentry/config/provider/CompoundConfigurationProviderTest.java new file mode 100644 index 00000000000..9333740af55 --- /dev/null +++ b/sentry/src/test/java/io/sentry/config/provider/CompoundConfigurationProviderTest.java @@ -0,0 +1,40 @@ +package io.sentry.config.provider; + +import static java.util.Arrays.asList; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Test; + + +public class CompoundConfigurationProviderTest { + + @Test + public void testReturnsFirstNonNullValue() { + // given + ConfigurationProvider first = mock(ConfigurationProvider.class); + ConfigurationProvider second = mock(ConfigurationProvider.class); + ConfigurationProvider third = mock(ConfigurationProvider.class); + + when(second.getProperty(anyString())).thenReturn("non-null"); + + CompoundConfigurationProvider compoundLocator = new CompoundConfigurationProvider(asList(first, second, third)); + + // when + String val = compoundLocator.getProperty("prop"); + + // then + assertEquals("non-null", val); + + verify(first, times(1)).getProperty(eq("prop")); + verify(second, times(1)).getProperty(eq("prop")); + verify(third, never()).getProperty(anyString()); + } +} diff --git a/sentry/src/test/java/io/sentry/config/provider/JndiConfigurationProviderTest.java b/sentry/src/test/java/io/sentry/config/provider/JndiConfigurationProviderTest.java new file mode 100644 index 00000000000..48f1fa68d90 --- /dev/null +++ b/sentry/src/test/java/io/sentry/config/provider/JndiConfigurationProviderTest.java @@ -0,0 +1,48 @@ +package io.sentry.config.provider; + +import static io.sentry.config.provider.JndiConfigurationProvider.DEFAULT_JNDI_PREFIX; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import javax.naming.Context; + +import io.sentry.config.provider.JndiConfigurationProvider.JndiContextProvider; +import org.junit.Test; + +public class JndiConfigurationProviderTest { + + @Test + public void testUsesJndiContextProvider() throws Exception { + // given + JndiContextProvider jndiProvider = mock(JndiContextProvider.class); + Context jndiContext = mock(Context.class); + when(jndiProvider.getContext()).thenReturn(jndiContext); + when(jndiContext.lookup("java:comp/env/sentry/prop")).thenReturn("val"); + + JndiConfigurationProvider provider = new JndiConfigurationProvider(DEFAULT_JNDI_PREFIX, jndiProvider); + + // when + String val = provider.getProperty("prop"); + + // then + assertEquals("val", val); + } + + @Test + public void testUsesCustomJNDIRootPath() throws Exception { + // given + JndiContextProvider jndiProvider = mock(JndiContextProvider.class); + Context jndiContext = mock(Context.class); + when(jndiProvider.getContext()).thenReturn(jndiContext); + when(jndiContext.lookup("totally-random-jndi-prefix/prop")).thenReturn("val"); + + JndiConfigurationProvider provider = new JndiConfigurationProvider("totally-random-jndi-prefix/", jndiProvider); + + // when + String val = provider.getProperty("prop"); + + // then + assertEquals("val", val); + } +} diff --git a/sentry/src/test/java/io/sentry/config/provider/ResourceLoaderConfigurationProviderTest.java b/sentry/src/test/java/io/sentry/config/provider/ResourceLoaderConfigurationProviderTest.java new file mode 100644 index 00000000000..feee3231c79 --- /dev/null +++ b/sentry/src/test/java/io/sentry/config/provider/ResourceLoaderConfigurationProviderTest.java @@ -0,0 +1,53 @@ +package io.sentry.config.provider; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; + +import io.sentry.config.ResourceLoader; +import org.junit.Test; + +public class ResourceLoaderConfigurationProviderTest { + + @Test + public void testLoadsPropertiesFromResourceLoader() throws Exception { + // given + ResourceLoader resourceLoader = mock(ResourceLoader.class); + + byte[] data = UTF_8.encode("prop1=value1\nprop2=süß_žluťoučký_лошадь\n").array(); + when(resourceLoader.getInputStream("config.file")).thenReturn(new ByteArrayInputStream(data)); + + ResourceLoaderConfigurationProvider provider = new ResourceLoaderConfigurationProvider(resourceLoader, + "config.file", UTF_8); + + // when + String val1 = provider.getProperty("prop1"); + String val2 = provider.getProperty("prop2"); + String val3 = provider.getProperty("unknown"); + + // then + assertEquals("value1", val1); + assertEquals("süß_žluťoučký_лошадь", val2); + assertNull(val3); + } + + @Test + public void testUnknownFileSuppliesNullPropertyValues() throws Exception { + // given + ResourceLoader resourceLoader = mock(ResourceLoader.class); + + // when + ResourceLoaderConfigurationProvider provider = new ResourceLoaderConfigurationProvider(resourceLoader, + "unknown", UTF_8); + + // then + assertNull(provider.getProperty("prop1")); + assertNull(provider.getProperty("prop2")); + assertNull(provider.getProperty("")); + } +} diff --git a/sentry/src/test/java/io/sentry/dsn/DsnTest.java b/sentry/src/test/java/io/sentry/dsn/DsnTest.java index 45a6f6fc262..c711f9adeef 100644 --- a/sentry/src/test/java/io/sentry/dsn/DsnTest.java +++ b/sentry/src/test/java/io/sentry/dsn/DsnTest.java @@ -21,6 +21,7 @@ public class DsnTest extends BaseTest { @BeforeMethod public void setUp() throws Exception { InitialContextFactory.context = mockContext; + } @Test(expectedExceptions = InvalidDsnException.class) From fc4518035b44d153cea61128c48a700e14422d65 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 19 Aug 2019 18:53:15 +0200 Subject: [PATCH 2089/2152] fix: Set resourceLoader before calling Lookup (#753) --- sentry/src/main/java/io/sentry/Sentry.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 956412da504..eeccd701a54 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -101,13 +101,14 @@ public static SentryClient init(String dsn, SentryClientFactory sentryClientFact * @return SentryClient */ public static SentryClient init(SentryOptions sentryOptions) { - SentryClient sentryClient = SentryClientFactory.sentryClient( - sentryOptions.getDsn(), - sentryOptions.getSentryClientFactory()); // Hack to allow Lookup.java access to a different resource locator before its static initializer runs. // v2: Lookup won't be static and this hack will be removed. // ResourceLocator will ba passed to Lookup upon instantiation Sentry.resourceLoader = sentryOptions.getResourceLoader(); + + SentryClient sentryClient = SentryClientFactory.sentryClient( + sentryOptions.getDsn(), + sentryOptions.getSentryClientFactory()); setStoredClient(sentryClient); return sentryClient; } From 06575c771f5f0539d8ae1188ef202a09dab964af Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 20 Aug 2019 11:40:04 +0200 Subject: [PATCH 2090/2152] ANR support for Android (#752) --- LICENSE | 1 + .../java/io/sentry/android/ANRWatchDog.java | 120 ++++++++++++++++++ .../android/AndroidSentryClientFactory.java | 35 +++++ .../android/ApplicationNotResponding.java | 40 ++++++ .../event/interfaces/ExceptionMechanism.java | 71 +++++++++++ .../ExceptionMechanismThrowable.java | 28 ++++ .../event/interfaces/SentryException.java | 69 +++++++++- .../json/ExceptionInterfaceBinding.java | 11 ++ .../event/interfaces/SentryExceptionTest.java | 63 +++++++++ .../json/ExceptionInterfaceBindingTest.java | 29 +++++ .../json/ExceptionWithMechanism.json | 12 ++ src/checkstyle/suppressions.xml | 2 + 12 files changed, 479 insertions(+), 2 deletions(-) create mode 100644 sentry-android/src/main/java/io/sentry/android/ANRWatchDog.java create mode 100644 sentry-android/src/main/java/io/sentry/android/ApplicationNotResponding.java create mode 100644 sentry/src/main/java/io/sentry/event/interfaces/ExceptionMechanism.java create mode 100644 sentry/src/main/java/io/sentry/event/interfaces/ExceptionMechanismThrowable.java create mode 100644 sentry/target/test-classes/io/sentry/marshaller/json/ExceptionWithMechanism.json diff --git a/LICENSE b/LICENSE index 02802804714..634183ef97d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,6 @@ Copyright (c) 2016 Functional Software, Inc. Copyright (c) 2012 Ken Cochrane and individual contributors. +Copyright (c) 2015 Salomon BRYS for Android ANRWatchDog All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/sentry-android/src/main/java/io/sentry/android/ANRWatchDog.java b/sentry-android/src/main/java/io/sentry/android/ANRWatchDog.java new file mode 100644 index 00000000000..ab9f9e66f14 --- /dev/null +++ b/sentry-android/src/main/java/io/sentry/android/ANRWatchDog.java @@ -0,0 +1,120 @@ +// https://github.com/SalomonBrys/ANR-WatchDog/blob/1969075f75f5980e9000eaffbaa13b0daf282dcb/anr-watchdog/src/main/java/com/github/anrwatchdog/ANRWatchDog.java +// Based on the class above. The API unnecessary here was removed. +package io.sentry.android; + +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 Salomon BRYS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import android.os.Debug; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +/** + * A watchdog timer thread that detects when the UI thread has frozen. + */ +@SuppressWarnings("UnusedReturnValue") +class ANRWatchDog extends Thread { + + public interface ANRListener { + /** + * Called when an ANR is detected. + * + * @param error The error describing the ANR. + */ + void onAppNotResponding(ApplicationNotResponding error); + } + + public interface InterruptionListener { + void onInterrupted(InterruptedException exception); + } + + private static final String TAG = ANRWatchDog.class.getName(); + + private ANRListener _anrListener = null; + private final Handler _uiHandler = new Handler(Looper.getMainLooper()); + private final int _timeoutInterval; + + private volatile long _tick = 0; + private volatile boolean _reported = false; + + private final Runnable _ticker = new Runnable() { + @Override public void run() { + _tick = 0; + _reported = false; + } + }; + + /** + * Constructs a watchdog that checks the ui thread every given interval + * + * @param timeoutInterval The interval, in milliseconds, between to checks of the UI thread. + * It is therefore the maximum time the UI may freeze before being reported as ANR. + * @param listener The new listener or null + */ + public ANRWatchDog(int timeoutInterval, ANRListener listener) { + super(); + _anrListener = listener; + _timeoutInterval = timeoutInterval; + } + + @SuppressWarnings("NonAtomicOperationOnVolatileField") + @Override + public void run() { + setName("|ANR-WatchDog|"); + + long interval = _timeoutInterval; + while (!isInterrupted()) { + boolean needPost = _tick == 0; + _tick += interval; + if (needPost) { + _uiHandler.post(_ticker); + } + + try { + Thread.sleep(interval); + } catch (InterruptedException e) { + Log.w(TAG, "Interrupted: " + e.getMessage()); + return; + } + + // If the main thread has not handled _ticker, it is blocked. ANR. + if (_tick != 0 && !_reported) { + //noinspection ConstantConditions + if (Debug.isDebuggerConnected() || Debug.waitingForDebugger()) { + Log.d(TAG, "An ANR was detected but ignored because the debugger is connected."); + _reported = true; + continue; + } + + Log.d(TAG, "Raising ANR"); + final String message ="Application Not Responding for at least " + _timeoutInterval + " ms."; + + final ApplicationNotResponding error = new ApplicationNotResponding(message); + _anrListener.onAppNotResponding(error); + interval = _timeoutInterval; + _reported = true; + } + } + } +} diff --git a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java index c49d2ef8025..3fd69765c3f 100644 --- a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java +++ b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java @@ -11,11 +11,16 @@ import io.sentry.android.event.helper.AndroidEventBuilderHelper; import io.sentry.buffer.Buffer; import io.sentry.buffer.DiskBuffer; +import io.sentry.event.EventBuilder; +import io.sentry.Sentry; import io.sentry.config.Lookup; import io.sentry.context.ContextManager; import io.sentry.context.SingletonContextManager; import io.sentry.dsn.Dsn; import io.sentry.util.Util; +import io.sentry.event.interfaces.ExceptionMechanism; +import io.sentry.event.interfaces.ExceptionInterface; +import io.sentry.event.interfaces.ExceptionMechanismThrowable; import java.io.File; import java.util.ArrayList; import java.util.Collection; @@ -37,6 +42,8 @@ public class AndroidSentryClientFactory extends DefaultSentryClientFactory { */ private static final String DEFAULT_BUFFER_DIR = "sentry-buffered-events"; + private static volatile ANRWatchDog anrWatchDog; + private Context ctx; /** @@ -91,6 +98,34 @@ public SentryClient createSentryClient(Dsn dsn) { SentryClient sentryClient = super.createSentryClient(dsn); sentryClient.addBuilderHelper(new AndroidEventBuilderHelper(ctx)); + boolean enableAnrTracking = "true".equalsIgnoreCase(Lookup.lookup("anr.enable", dsn)); + Log.d(TAG, "ANR is='" + String.valueOf(enableAnrTracking) + "'"); + if (enableAnrTracking && anrWatchDog == null) { + String timeIntervalMsConfig = Lookup.lookup("anr.timeoutIntervalMs", dsn); + int timeoutIntervalMs = timeIntervalMsConfig != null + ? Integer.parseInt(timeIntervalMsConfig) + //CHECKSTYLE.OFF: MagicNumber + : 5000; + //CHECKSTYLE.ON: MagicNumber + + Log.d(TAG, "ANR timeoutIntervalMs is='" + String.valueOf(timeoutIntervalMs) + "'"); + + anrWatchDog = new ANRWatchDog(timeoutIntervalMs, new ANRWatchDog.ANRListener() { + @Override public void onAppNotResponding(ApplicationNotResponding error) { + Log.d(TAG, "ANR triggered='" + error.getMessage() + "'"); + + EventBuilder builder = new EventBuilder(); + builder.withTag("thread_state", error.getState().toString()); + ExceptionMechanism mechanism = new ExceptionMechanism("anr", false); + Throwable throwable = new ExceptionMechanismThrowable(mechanism, error); + builder.withSentryInterface(new ExceptionInterface(throwable)); + + Sentry.capture(builder); + } + }); + anrWatchDog.start(); + } + return sentryClient; } diff --git a/sentry-android/src/main/java/io/sentry/android/ApplicationNotResponding.java b/sentry-android/src/main/java/io/sentry/android/ApplicationNotResponding.java new file mode 100644 index 00000000000..6cdca4fd7ef --- /dev/null +++ b/sentry-android/src/main/java/io/sentry/android/ApplicationNotResponding.java @@ -0,0 +1,40 @@ +// https://github.com/SalomonBrys/ANR-WatchDog/blob/1969075f75f5980e9000eaffbaa13b0daf282dcb/anr-watchdog/src/main/java/com/github/anrwatchdog/ANRError.java +// Based on the class above. The API unnecessary here was removed. +package io.sentry.android; + +import android.os.Looper; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.Map; +import java.util.TreeMap; +import java.lang.Thread.State; + +/** + * Error thrown by ANRWatchDog when an ANR is detected. + * Contains the stack trace of the frozen UI thread. + *

    + * It is important to notice that, in an ApplicationNotResponding, all the "Caused by" are not really the cause + * of the exception. Each "Caused by" is the stack trace of a running thread. Note that the main + * thread always comes first. + */ +class ApplicationNotResponding extends Throwable { + + private Thread.State state; + + public ApplicationNotResponding(String message) { + super(message); + } + + public Thread.State getState() { + return state; + } + + @Override + public Throwable fillInStackTrace() { + final Thread mainThread = Looper.getMainLooper().getThread(); + state = mainThread.getState(); + setStackTrace(mainThread.getStackTrace()); + return this; + } +} \ No newline at end of file diff --git a/sentry/src/main/java/io/sentry/event/interfaces/ExceptionMechanism.java b/sentry/src/main/java/io/sentry/event/interfaces/ExceptionMechanism.java new file mode 100644 index 00000000000..1eed04901fb --- /dev/null +++ b/sentry/src/main/java/io/sentry/event/interfaces/ExceptionMechanism.java @@ -0,0 +1,71 @@ +package io.sentry.event.interfaces; + +import java.io.Serializable; + +/** + * Class describing an exception's mechanism. + */ +public final class ExceptionMechanism implements Serializable { + + private final String type; + private final boolean handled; + + /** + * The exception mechanism used in an exception. + * @param type The type of the mechanism. + * @param handled Whether the exception was handled or not. + */ + public ExceptionMechanism(String type, boolean handled) { + this.type = type; + this.handled = handled; + } + + /** + * The type of the mechanism. + * @return The type of the mechanism. + */ + public String getType() { + return type; + } + + /** + * Whether the exception was handled or not. + * @return True if the exception was handled, otherwise false. + */ + public boolean isHandled() { + return handled; + } + + @Override + public String toString() { + return "ExceptionMechanism{" + + "type='" + type + '\'' + + ", handled=" + handled + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ExceptionMechanism that = (ExceptionMechanism) o; + + if (type != null ? !type.equals(that.type) : that.type != null) { + return false; + } + + return handled == that.handled; + } + + @Override + public int hashCode() { + int result = type != null ? type.hashCode() : 0; + result = 31 * result + (handled ? 1231 : 1237); + return result; + } +} diff --git a/sentry/src/main/java/io/sentry/event/interfaces/ExceptionMechanismThrowable.java b/sentry/src/main/java/io/sentry/event/interfaces/ExceptionMechanismThrowable.java new file mode 100644 index 00000000000..f2733102a31 --- /dev/null +++ b/sentry/src/main/java/io/sentry/event/interfaces/ExceptionMechanismThrowable.java @@ -0,0 +1,28 @@ +package io.sentry.event.interfaces; + +/** + * A throwable decorator that holds an {@link ExceptionMechanism} related to the decorated {@link Throwable}. + */ +public final class ExceptionMechanismThrowable extends Throwable { + + private final ExceptionMechanism exceptionMechanism; + private final Throwable throwable; + + /** + * A {@link Throwable} that decorates another with a Sentry {@link ExceptionMechanism}. + * @param mechanism The {@link ExceptionMechanism}. + * @param throwable The {@link java.lang.Throwable}. + */ + public ExceptionMechanismThrowable(ExceptionMechanism mechanism, Throwable throwable) { + this.exceptionMechanism = mechanism; + this.throwable = throwable; + } + + public ExceptionMechanism getExceptionMechanism() { + return exceptionMechanism; + } + + public Throwable getThrowable() { + return throwable; + } +} diff --git a/sentry/src/main/java/io/sentry/event/interfaces/SentryException.java b/sentry/src/main/java/io/sentry/event/interfaces/SentryException.java index f3865570f23..77542c4a49d 100644 --- a/sentry/src/main/java/io/sentry/event/interfaces/SentryException.java +++ b/sentry/src/main/java/io/sentry/event/interfaces/SentryException.java @@ -19,6 +19,7 @@ public final class SentryException implements Serializable { private final String exceptionClassName; private final String exceptionPackageName; private final StackTraceInterface stackTraceInterface; + private final ExceptionMechanism exceptionMechanism; /** * Creates a Sentry exception based on a Java Throwable. @@ -30,6 +31,25 @@ public final class SentryException implements Serializable { * @param childExceptionStackTrace StackTrace of the exception caused by {@code throwable}. */ public SentryException(Throwable throwable, StackTraceElement[] childExceptionStackTrace) { + this(throwable, childExceptionStackTrace, null); + } + + /** + * Creates a Sentry exception based on a Java Throwable. + *

    + * The {@code childExceptionStackTrace} parameter is used to define the common frames with the child exception + * (Exception caused by {@code throwable}). + * + * @param throwable Java exception to send to Sentry. + * @param childExceptionStackTrace StackTrace of the exception caused by {@code throwable}. + * @param exceptionMechanism The optional {@link ExceptionMechanism} of the {@code throwable}. + * Or null if none exist. + */ + public SentryException( + Throwable throwable, + StackTraceElement[] childExceptionStackTrace, + ExceptionMechanism exceptionMechanism) { + Package exceptionPackage = throwable.getClass().getPackage(); String fullClassName = throwable.getClass().getName(); @@ -46,6 +66,8 @@ public SentryException(Throwable throwable, StackTraceElement[] childExceptionSt throwable.getStackTrace(), childExceptionStackTrace, FrameCache.get(throwable)); + + this.exceptionMechanism = exceptionMechanism; } /** @@ -60,10 +82,35 @@ public SentryException(String exceptionMessage, String exceptionClassName, String exceptionPackageName, StackTraceInterface stackTraceInterface) { + + this(exceptionMessage, + exceptionClassName, + exceptionPackageName, + stackTraceInterface, + null); + } + + /** + * Creates a Sentry exception. + * + * @param exceptionMessage message of the exception. + * @param exceptionClassName exception's class name (simple name). + * @param exceptionPackageName exception's package name. + * @param stackTraceInterface {@code StackTraceInterface} holding the StackTrace information of the exception. + * @param exceptionMechanism The optional {@link ExceptionMechanism} of the {@code throwable}. + * Or null if none exist. + */ + public SentryException(String exceptionMessage, + String exceptionClassName, + String exceptionPackageName, + StackTraceInterface stackTraceInterface, + ExceptionMechanism exceptionMechanism) { + this.exceptionMessage = exceptionMessage; this.exceptionClassName = exceptionClassName; this.exceptionPackageName = exceptionPackageName; this.stackTraceInterface = stackTraceInterface; + this.exceptionMechanism = exceptionMechanism; } /** @@ -78,10 +125,19 @@ public static Deque extractExceptionQueue(Throwable throwable) Deque exceptions = new ArrayDeque<>(); Set circularityDetector = new HashSet<>(); StackTraceElement[] childExceptionStackTrace = new StackTraceElement[0]; + ExceptionMechanism exceptionMechanism = null; //Stack the exceptions to send them in the reverse order while (throwable != null && circularityDetector.add(throwable)) { - exceptions.add(new SentryException(throwable, childExceptionStackTrace)); + if (throwable instanceof ExceptionMechanismThrowable) { + ExceptionMechanismThrowable exceptionMechanismThrowable = (ExceptionMechanismThrowable) throwable; + exceptionMechanism = exceptionMechanismThrowable.getExceptionMechanism(); + throwable = exceptionMechanismThrowable.getThrowable(); + } else { + exceptionMechanism = null; + } + + exceptions.add(new SentryException(throwable, childExceptionStackTrace, exceptionMechanism)); childExceptionStackTrace = throwable.getStackTrace(); throwable = throwable.getCause(); } @@ -112,12 +168,17 @@ public StackTraceInterface getStackTraceInterface() { return stackTraceInterface; } + public ExceptionMechanism getExceptionMechanism() { + return exceptionMechanism; + } + @Override public String toString() { return "SentryException{" + "exceptionMessage='" + exceptionMessage + '\'' + ", exceptionClassName='" + exceptionClassName + '\'' + ", exceptionPackageName='" + exceptionPackageName + '\'' + + ", exceptionMechanism='" + exceptionMechanism + '\'' + ", stackTraceInterface=" + stackTraceInterface + '}'; } @@ -141,7 +202,11 @@ public boolean equals(Object o) { return false; } if (exceptionPackageName != null ? !exceptionPackageName.equals(that.exceptionPackageName) - : that.exceptionPackageName != null) { + : that.exceptionPackageName != null) { + return false; + } + if (exceptionMechanism != null ? !exceptionMechanism.equals(that.exceptionMechanism) + : that.exceptionMechanism != null) { return false; } diff --git a/sentry/src/main/java/io/sentry/marshaller/json/ExceptionInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/ExceptionInterfaceBinding.java index 37cd1dc8768..4eb14079577 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/ExceptionInterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/ExceptionInterfaceBinding.java @@ -4,6 +4,7 @@ import io.sentry.event.interfaces.ExceptionInterface; import io.sentry.event.interfaces.SentryException; import io.sentry.event.interfaces.StackTraceInterface; +import io.sentry.event.interfaces.ExceptionMechanism; import java.io.IOException; import java.util.Deque; @@ -55,6 +56,16 @@ private void writeException(JsonGenerator generator, SentryException sentryExcep generator.writeStringField(TYPE_PARAMETER, sentryException.getExceptionClassName()); generator.writeStringField(VALUE_PARAMETER, sentryException.getExceptionMessage()); generator.writeStringField(MODULE_PARAMETER, sentryException.getExceptionPackageName()); + + ExceptionMechanism exceptionMechanism = sentryException.getExceptionMechanism(); + if (exceptionMechanism != null) { + generator.writeFieldName("mechanism"); + generator.writeStartObject(); + generator.writeStringField("type", exceptionMechanism.getType()); + generator.writeBooleanField("handled", exceptionMechanism.isHandled()); + generator.writeEndObject(); + } + generator.writeFieldName(STACKTRACE_PARAMETER); stackTraceInterfaceBinding.writeInterface(generator, sentryException.getStackTraceInterface()); generator.writeEndObject(); diff --git a/sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java b/sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java index ac3a65dc695..bbb5148a50a 100644 --- a/sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java @@ -10,6 +10,12 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + public class SentryExceptionTest extends BaseTest { @Injectable @@ -58,6 +64,63 @@ public Throwable getCause() { assertThat(exceptions.getFirst().getExceptionClassName(), is("SentryExceptionTest$InnerClassThrowable")); } + @Test + public void exceptionMechanismThrowerIsUnwrappedViaExtractExceptionQueue() throws Exception { + + Throwable throwableMock = mock(Throwable.class); + String expectedMessage = "message"; + when(throwableMock.getMessage()).thenReturn(expectedMessage); + StackTraceElement expectedStackTraceElement = new StackTraceElement("c", "m", "f", 1); + StackTraceElement[] stackTrace = new StackTraceElement[] { expectedStackTraceElement }; + when(throwableMock.getStackTrace()).thenReturn(stackTrace); + ExceptionMechanism mechanism = new ExceptionMechanism("type", true); + ExceptionMechanismThrowable wrapper = new ExceptionMechanismThrowable(mechanism, throwableMock); + + // Act + Deque exceptionDeque = SentryException.extractExceptionQueue(wrapper); + SentryException target = exceptionDeque.getFirst(); + + assertEquals(expectedStackTraceElement.getFileName(), target.getStackTraceInterface().getStackTrace()[0].getFileName()); + assertEquals(expectedMessage, target.getExceptionMessage()); + assertSame(mechanism, target.getExceptionMechanism()); + } + + @Test + public void exceptionMechanismThrowerIsUnwrappedInInnerException() throws Exception { + + Throwable firstThrowableMock = mock(Throwable.class); + String expectedFirstMessage = "first"; + when(firstThrowableMock.getMessage()).thenReturn(expectedFirstMessage); + StackTraceElement expectedFirstStackTraceElement = new StackTraceElement("c", "m", "f", 1); + StackTraceElement[] firstStackTrace = new StackTraceElement[] { expectedFirstStackTraceElement }; + when(firstThrowableMock.getStackTrace()).thenReturn(firstStackTrace); + + Throwable secondThrowableMock = mock(Throwable.class); + String expectedSecondMessage = "second"; + when(secondThrowableMock.getMessage()).thenReturn(expectedSecondMessage); + StackTraceElement expectedSecondStackTraceElement = new StackTraceElement("d", "n", "g", 1); + StackTraceElement[] secondStackTrace = new StackTraceElement[] { expectedSecondStackTraceElement }; + when(secondThrowableMock.getStackTrace()).thenReturn(secondStackTrace); + + when(secondThrowableMock.getCause()).thenReturn(firstThrowableMock); + + ExceptionMechanism mechanism = new ExceptionMechanism("type", true); + ExceptionMechanismThrowable wrapper = new ExceptionMechanismThrowable(mechanism, secondThrowableMock); + + // Act + Deque exceptionDeque = SentryException.extractExceptionQueue(wrapper); + SentryException second = exceptionDeque.removeFirst(); + SentryException first = exceptionDeque.removeFirst(); + + assertEquals(expectedFirstStackTraceElement.getFileName(), first.getStackTraceInterface().getStackTrace()[0].getFileName()); + assertEquals(expectedFirstMessage, first.getExceptionMessage()); + assertNull(first.getExceptionMechanism()); + + assertEquals(expectedSecondStackTraceElement.getFileName(), second.getStackTraceInterface().getStackTrace()[0].getFileName()); + assertEquals(expectedSecondMessage, second.getExceptionMessage()); + assertSame(mechanism, second.getExceptionMechanism()); + } + private static final class InnerClassThrowable extends Throwable { } diff --git a/sentry/src/test/java/io/sentry/marshaller/json/ExceptionInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/ExceptionInterfaceBindingTest.java index eab694c55db..c8b0f30313f 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/ExceptionInterfaceBindingTest.java @@ -6,9 +6,12 @@ import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; +import io.sentry.event.EventBuilder; import io.sentry.event.interfaces.ExceptionInterface; import io.sentry.event.interfaces.SentryException; import io.sentry.event.interfaces.StackTraceInterface; +import io.sentry.event.interfaces.ExceptionMechanism; +import io.sentry.event.interfaces.ExceptionMechanismThrowable; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -105,6 +108,32 @@ public Deque getExceptions() { assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/Exception3.json"))); } + @Test + public void testExceptionWithMechanism() throws Exception { + final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); + final String message = "6e65f60d-9f22-495a-9556-7a61eeea2a14"; + final Throwable originalThrowable = new IllegalStateException(message); + + EventBuilder builder = new EventBuilder(); + ExceptionMechanism mechanism = new ExceptionMechanism("the type", true); + final Throwable throwable = new ExceptionMechanismThrowable(mechanism, originalThrowable); + builder.withSentryInterface(new ExceptionInterface(throwable)); + + new NonStrictExpectations() {{ + mockExceptionInterface.getExceptions(); + result = new Delegate>() { + @SuppressWarnings("unused") + public Deque getExceptions() { + return SentryException.extractExceptionQueue(throwable); + } + }; + }}; + + interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockExceptionInterface); + + assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/ExceptionWithMechanism.json"))); + } + } /** diff --git a/sentry/target/test-classes/io/sentry/marshaller/json/ExceptionWithMechanism.json b/sentry/target/test-classes/io/sentry/marshaller/json/ExceptionWithMechanism.json new file mode 100644 index 00000000000..d292c854ec0 --- /dev/null +++ b/sentry/target/test-classes/io/sentry/marshaller/json/ExceptionWithMechanism.json @@ -0,0 +1,12 @@ +[ + { + "type": "IllegalStateException", + "value": "6e65f60d-9f22-495a-9556-7a61eeea2a14", + "module": "java.lang", + "mechanism": { + "type": "the type", + "handled": true + }, + "stacktrace": {} + } +] diff --git a/src/checkstyle/suppressions.xml b/src/checkstyle/suppressions.xml index 864cfc83f49..4b3134e888c 100644 --- a/src/checkstyle/suppressions.xml +++ b/src/checkstyle/suppressions.xml @@ -6,4 +6,6 @@ + + From 15ee700f800c436781a2545b7df117e88b3611aa Mon Sep 17 00:00:00 2001 From: Patrick Jiang Date: Tue, 20 Aug 2019 18:37:06 +0800 Subject: [PATCH 2091/2152] fix: some threads dont have contextClassLoader (#754) --- .../io/sentry/config/ContextClassLoaderResourceLoader.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sentry/src/main/java/io/sentry/config/ContextClassLoaderResourceLoader.java b/sentry/src/main/java/io/sentry/config/ContextClassLoaderResourceLoader.java index c13ae28499c..cd686cd951d 100644 --- a/sentry/src/main/java/io/sentry/config/ContextClassLoaderResourceLoader.java +++ b/sentry/src/main/java/io/sentry/config/ContextClassLoaderResourceLoader.java @@ -10,6 +10,9 @@ public class ContextClassLoaderResourceLoader implements ResourceLoader { @Override public InputStream getInputStream(String filepath) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if (classLoader == null) { + classLoader = ClassLoader.getSystemClassLoader(); + } return classLoader.getResourceAsStream(filepath); } } From 52fc59e0137626903ce7aa6ec520310e8e531756 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 20 Aug 2019 12:40:29 +0200 Subject: [PATCH 2092/2152] chore: changelog --- CHANGES | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 5333e71249d..0747888a28f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,9 @@ Version 1.7.27 -------------- -- +- Android: Fix setResourceLoader order #753 +- Fix classLoader lookup #754 +- Android ANR support: #752 Version 1.7.26 -------------- From 75b56857363bb13854f0dd9565d20667cc652090 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 20 Aug 2019 12:52:56 +0200 Subject: [PATCH 2093/2152] fix: location of snapshot test data --- .../io/sentry/marshaller/json/ExceptionWithMechanism.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename sentry/{target/test-classes => src/test/resources}/io/sentry/marshaller/json/ExceptionWithMechanism.json (100%) diff --git a/sentry/target/test-classes/io/sentry/marshaller/json/ExceptionWithMechanism.json b/sentry/src/test/resources/io/sentry/marshaller/json/ExceptionWithMechanism.json similarity index 100% rename from sentry/target/test-classes/io/sentry/marshaller/json/ExceptionWithMechanism.json rename to sentry/src/test/resources/io/sentry/marshaller/json/ExceptionWithMechanism.json From f636602097097a437ea405731391fd93d3d9144e Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 20 Aug 2019 13:01:30 +0200 Subject: [PATCH 2094/2152] [maven-release-plugin] prepare release v1.7.27 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 4cd43652323..8db2bd86b9a 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.27-SNAPSHOT + 1.7.27 pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.27 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index dc8c771e869..8e44b588fb1 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.27-SNAPSHOT + 1.7.27 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index b42dcc82abc..ab8ebf53075 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.27-SNAPSHOT + 1.7.27 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index ea04e54e8d3..95005803e9e 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.27-SNAPSHOT + 1.7.27 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 39b4087c954..6dfa721ae27 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.27-SNAPSHOT + 1.7.27 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 4801c6bd845..45191cd7050 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.27-SNAPSHOT + 1.7.27 sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 606c22b9f4f..f1e565d052f 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.27-SNAPSHOT + 1.7.27 sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.27-SNAPSHOT + 1.7.27 diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index bf3812c4a9f..623bf4b4c9a 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.27-SNAPSHOT + 1.7.27 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 0f2c16f9c32..4be963216eb 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.27-SNAPSHOT + 1.7.27 sentry From d645e1b0a0377cb7fbf6026638fadd5bbec7b851 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 20 Aug 2019 13:01:31 +0200 Subject: [PATCH 2095/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 8db2bd86b9a..9e70918b453 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.27 + 1.7.28-SNAPSHOT pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.27 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 8e44b588fb1..012746d9760 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.27 + 1.7.28-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index ab8ebf53075..0fab85ab536 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.27 + 1.7.28-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 95005803e9e..b55ec1f08ab 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.27 + 1.7.28-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 6dfa721ae27..f47d10e3d80 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.27 + 1.7.28-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 45191cd7050..88b897605d4 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.27 + 1.7.28-SNAPSHOT sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index f1e565d052f..a023cb3031e 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.27 + 1.7.28-SNAPSHOT sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.27 + 1.7.28-SNAPSHOT diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 623bf4b4c9a..476a06c2a5e 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.27 + 1.7.28-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 4be963216eb..b6cafd8e2cd 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.27 + 1.7.28-SNAPSHOT sentry From 4a5910fa29fca28655d5448b3e6e9b474691d3eb Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 20 Aug 2019 13:01:33 +0200 Subject: [PATCH 2096/2152] Bump CHANGES to 1.7.28 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 0747888a28f..99e77559100 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +-e Version 1.7.28 +-------------- + +- + Version 1.7.27 -------------- From 7f2d2d6393624d553a790c8c6bddde8285bc96f0 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 20 Aug 2019 13:14:00 +0200 Subject: [PATCH 2097/2152] chore: Bump gradle plugin version --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 99e77559100..5aea395af22 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,4 @@ --e Version 1.7.28 +Version 1.7.28 -------------- - From 8aa5a918dc05380c0fd747d45f977a2de6590d27 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 20 Aug 2019 13:14:34 +0200 Subject: [PATCH 2098/2152] chore: Bump gradle plugin version --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 6c18075795c..ddba3f74f6a 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.27-SNAPSHOT +version = 1.7.28-SNAPSHOT From dae5e1326a512f229488e382899ee63b204739b3 Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Wed, 4 Sep 2019 21:53:06 +0200 Subject: [PATCH 2099/2152] Propagate the Lookup instance during the Sentry.init() and other initialization workflows. (#755) * Propagate the Lookup instance during the Sentry.init() and other initialization workflows. * Restore binary compatibility and minimize the amount of change. * Restore the SentryOptions.(get|set)SentryClientFactory method accidentally renamed to (get|set)ClientFactory. * Remove the SentryOptions.createClient method that didn't really belong there. * Use the new SentryClientFactory.createClient(String) which is more versatile than the old SentryClientFactory.createSentryClient(Dsn) * Remove the deprecations from the "old" methods. The only deprecation kept is on Sentry.resourceLoader and SentryOptions.(get|set)ResourceLoader which really should not be used with the new mechanism in place. --- .../android/AndroidSentryClientFactory.java | 37 +++- .../SentryITActivityUsingApplication.java | 13 +- .../SentryITActivityUsingBaseContext.java | 13 +- .../AppEngineSentryClientFactory.java | 23 +- .../AppEngineSentryClientFactoryTest.java | 7 +- .../io/sentry/DefaultSentryClientFactory.java | 87 +++++--- sentry/src/main/java/io/sentry/Sentry.java | 141 +++++++----- .../src/main/java/io/sentry/SentryClient.java | 2 +- .../java/io/sentry/SentryClientFactory.java | 160 +++++++++++--- .../main/java/io/sentry/SentryOptions.java | 200 ++++++++++++++++-- .../main/java/io/sentry/config/Lookup.java | 56 +++-- sentry/src/main/java/io/sentry/dsn/Dsn.java | 17 +- .../DefaultSentryClientFactoryTest.java | 8 +- .../io/sentry/SentryClientFactoryTest.java | 14 +- sentry/src/test/java/io/sentry/SentryIT.java | 7 + .../src/test/java/io/sentry/SentryTest.java | 4 +- .../src/test/java/io/sentry/TestFactory.java | 6 + .../src/test/java/io/sentry/dsn/DsnTest.java | 1 + 18 files changed, 593 insertions(+), 203 deletions(-) diff --git a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java index 3fd69765c3f..b4e3b06c0bc 100644 --- a/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java +++ b/sentry-android/src/main/java/io/sentry/android/AndroidSentryClientFactory.java @@ -46,12 +46,27 @@ public class AndroidSentryClientFactory extends DefaultSentryClientFactory { private Context ctx; + /** * Construct an AndroidSentryClientFactory using the base Context from the specified Android Application. + *

    + * This uses a default lookup instance, use {@link #AndroidSentryClientFactory(Application, Lookup)} if + * you need to pass a specially configured lookup. * * @param app Android Application */ public AndroidSentryClientFactory(Application app) { + this(app, Lookup.getDefault()); + } + + /** + * Construct an AndroidSentryClientFactory using the base Context from the specified Android Application. + * + * @param app Android Application + * @param lookup the lookup for locating the configuration + */ + public AndroidSentryClientFactory(Application app, Lookup lookup) { + super(lookup); Log.d(TAG, "Construction of Android Sentry from Android Application."); this.ctx = app.getApplicationContext(); @@ -59,10 +74,24 @@ public AndroidSentryClientFactory(Application app) { /** * Construct an AndroidSentryClientFactory using the specified Android Context. + *

    + * This uses a default lookup instance, use {@link #AndroidSentryClientFactory(Context, Lookup)} if + * you need to pass a specially configured lookup. * * @param ctx Android Context. */ public AndroidSentryClientFactory(Context ctx) { + this(ctx, Lookup.getDefault()); + } + + /** + * Construct an AndroidSentryClientFactory using the specified Android Context. + * + * @param ctx Android Context. + * @param lookup the lookup for locating the configuration + */ + public AndroidSentryClientFactory(Context ctx, Lookup lookup) { + super(lookup); Log.d(TAG, "Construction of Android Sentry from Android Context."); this.ctx = ctx.getApplicationContext(); @@ -85,7 +114,7 @@ public SentryClient createSentryClient(Dsn dsn) { Log.w(TAG, "*** Couldn't find a suitable DSN, Sentry operations will do nothing!" + " See documentation: https://docs.sentry.io/clients/java/modules/android/ ***"); } else if (!(protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https"))) { - String async = Lookup.lookup(DefaultSentryClientFactory.ASYNC_OPTION, dsn); + String async = lookup.get(DefaultSentryClientFactory.ASYNC_OPTION, dsn); if (async != null && async.equalsIgnoreCase("false")) { throw new IllegalArgumentException("Sentry Android cannot use synchronous connections, remove '" + DefaultSentryClientFactory.ASYNC_OPTION + "=false' from your options."); @@ -98,10 +127,10 @@ public SentryClient createSentryClient(Dsn dsn) { SentryClient sentryClient = super.createSentryClient(dsn); sentryClient.addBuilderHelper(new AndroidEventBuilderHelper(ctx)); - boolean enableAnrTracking = "true".equalsIgnoreCase(Lookup.lookup("anr.enable", dsn)); + boolean enableAnrTracking = "true".equalsIgnoreCase(lookup.get("anr.enable", dsn)); Log.d(TAG, "ANR is='" + String.valueOf(enableAnrTracking) + "'"); if (enableAnrTracking && anrWatchDog == null) { - String timeIntervalMsConfig = Lookup.lookup("anr.timeoutIntervalMs", dsn); + String timeIntervalMsConfig = lookup.get("anr.timeoutIntervalMs", dsn); int timeoutIntervalMs = timeIntervalMsConfig != null ? Integer.parseInt(timeIntervalMsConfig) //CHECKSTYLE.OFF: MagicNumber @@ -154,7 +183,7 @@ protected Collection getInAppFrames(Dsn dsn) { @Override protected Buffer getBuffer(Dsn dsn) { File bufferDir; - String bufferDirOpt = Lookup.lookup(BUFFER_DIR_OPTION, dsn); + String bufferDirOpt = lookup.get(BUFFER_DIR_OPTION, dsn); if (bufferDirOpt != null) { bufferDir = new File(bufferDirOpt); } else { diff --git a/sentry-android/src/test/java/io/sentry/android/SentryITActivityUsingApplication.java b/sentry-android/src/test/java/io/sentry/android/SentryITActivityUsingApplication.java index f6579a2ff7b..b96cfd72530 100644 --- a/sentry-android/src/test/java/io/sentry/android/SentryITActivityUsingApplication.java +++ b/sentry-android/src/test/java/io/sentry/android/SentryITActivityUsingApplication.java @@ -2,9 +2,12 @@ import android.app.Activity; import android.app.Application; -import android.content.Context; import android.os.Bundle; import io.sentry.Sentry; +import io.sentry.SentryOptions; +import io.sentry.config.Lookup; +import io.sentry.dsn.Dsn; + import java.util.concurrent.atomic.AtomicBoolean; public class SentryITActivityUsingApplication extends Activity { @@ -18,8 +21,8 @@ class CustomAndroidSentryClientFactory extends AndroidSentryClientFactory { * * @param app Android Application */ - CustomAndroidSentryClientFactory(Application app) { - super(app); + CustomAndroidSentryClientFactory(Application app, Lookup lookup) { + super(app, lookup); customFactoryUsed.set(true); } } @@ -27,9 +30,9 @@ class CustomAndroidSentryClientFactory extends AndroidSentryClientFactory { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Sentry.init( + Sentry.init(SentryOptions.from(Lookup.getDefault(), "http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1", - new CustomAndroidSentryClientFactory(getApplication())); + new CustomAndroidSentryClientFactory(getApplication(), Lookup.getDefault()))); } public void sendEvent() { diff --git a/sentry-android/src/test/java/io/sentry/android/SentryITActivityUsingBaseContext.java b/sentry-android/src/test/java/io/sentry/android/SentryITActivityUsingBaseContext.java index 67495692830..2deb19a2570 100644 --- a/sentry-android/src/test/java/io/sentry/android/SentryITActivityUsingBaseContext.java +++ b/sentry-android/src/test/java/io/sentry/android/SentryITActivityUsingBaseContext.java @@ -1,10 +1,13 @@ package io.sentry.android; import android.app.Activity; -import android.app.Application; import android.content.Context; import android.os.Bundle; import io.sentry.Sentry; +import io.sentry.SentryOptions; +import io.sentry.config.Lookup; +import io.sentry.dsn.Dsn; + import java.util.concurrent.atomic.AtomicBoolean; public class SentryITActivityUsingBaseContext extends Activity { @@ -18,8 +21,8 @@ class CustomAndroidSentryClientFactory extends AndroidSentryClientFactory { * * @param ctx Android Context */ - CustomAndroidSentryClientFactory(Context ctx) { - super(ctx); + CustomAndroidSentryClientFactory(Context ctx, Lookup lookup) { + super(ctx, lookup); customFactoryUsed.set(true); } } @@ -27,9 +30,9 @@ class CustomAndroidSentryClientFactory extends AndroidSentryClientFactory { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Sentry.init( + Sentry.init(new SentryOptions(Lookup.getDefault(), "http://8292bf61d620417282e68a72ae03154a:e3908e05ad874b24b7a168992bfa3577@localhost:8080/1", - new CustomAndroidSentryClientFactory(getBaseContext())); + new CustomAndroidSentryClientFactory(getBaseContext(), Lookup.getDefault()))); } public void sendEvent() { diff --git a/sentry-appengine/src/main/java/io/sentry/appengine/AppEngineSentryClientFactory.java b/sentry-appengine/src/main/java/io/sentry/appengine/AppEngineSentryClientFactory.java index 98afbb2be56..e5770e8a75c 100644 --- a/sentry-appengine/src/main/java/io/sentry/appengine/AppEngineSentryClientFactory.java +++ b/sentry-appengine/src/main/java/io/sentry/appengine/AppEngineSentryClientFactory.java @@ -30,6 +30,25 @@ public class AppEngineSentryClientFactory extends DefaultSentryClientFactory { */ public static final String CONNECTION_IDENTIFIER = "sentry.async.gae.connectionid"; + /** + * This is provided for backwards compatibility but doesn't support custom lookup injection. + *

    + * This uses a default lookup instance, use {@link #AppEngineSentryClientFactory(Lookup)} if you need to + * pass a specially configured lookup. + */ + public AppEngineSentryClientFactory() { + this(Lookup.getDefault()); + } + + /** + * Creates a new instance configured using the provided lookup instance. + * + * @param lookup the lookup instance to load configuration from + */ + public AppEngineSentryClientFactory(Lookup lookup) { + super(lookup); + } + @Override public SentryClient createSentryClient(Dsn dsn) { SentryClient sentryClientInstance = super.createSentryClient(dsn); @@ -47,7 +66,7 @@ public SentryClient createSentryClient(Dsn dsn) { */ @Override protected Connection createAsyncConnection(Dsn dsn, Connection connection) { - String connectionIdentifier = Lookup.lookup(CONNECTION_IDENTIFIER, dsn); + String connectionIdentifier = lookup.get(CONNECTION_IDENTIFIER, dsn); if (connectionIdentifier == null) { connectionIdentifier = AppEngineSentryClientFactory.class.getCanonicalName() + dsn + SystemProperty.version.get(); @@ -55,7 +74,7 @@ protected Connection createAsyncConnection(Dsn dsn, Connection connection) { AppEngineAsyncConnection asyncConnection = new AppEngineAsyncConnection(connectionIdentifier, connection); - String queueName = Lookup.lookup(QUEUE_NAME, dsn); + String queueName = lookup.get(QUEUE_NAME, dsn); if (queueName != null) { asyncConnection.setQueue(queueName); } diff --git a/sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryClientFactoryTest.java b/sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryClientFactoryTest.java index 4d9cdb92f35..8f2095ae8c5 100644 --- a/sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryClientFactoryTest.java +++ b/sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryClientFactoryTest.java @@ -1,5 +1,6 @@ package io.sentry.appengine; +import io.sentry.config.Lookup; import mockit.*; import io.sentry.appengine.connection.AppEngineAsyncConnection; import io.sentry.connection.Connection; @@ -19,6 +20,8 @@ public class AppEngineSentryClientFactoryTest { private Connection mockConnection; @Injectable private Dsn mockDsn; + @Injectable + private Lookup mockLookup; @Test public void asyncConnectionCreatedByAppEngineSentryClientFactoryIsForAppEngine() throws Exception { @@ -74,8 +77,8 @@ public void asyncConnectionWithQueueNameSetsQueue( @Mocked final AppEngineAsyncConnection mockAppEngineAsyncConnection, @Injectable("queueName") final String mockQueueName) throws Exception { new NonStrictExpectations() {{ - mockDsn.getOptions(); - result = Collections.singletonMap(AppEngineSentryClientFactory.QUEUE_NAME, mockQueueName); + mockLookup.get(AppEngineSentryClientFactory.QUEUE_NAME, (Dsn) any); + result = mockQueueName; }}; appEngineSentryClientFactory.createAsyncConnection(mockDsn, mockConnection); diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index ce421d7d9f4..74ed125aae7 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -238,6 +238,27 @@ public class DefaultSentryClientFactory extends SentryClientFactory { REJECT_EXECUTION_HANDLERS.put(ASYNC_QUEUE_DISCARDOLD, new ThreadPoolExecutor.DiscardOldestPolicy()); } + /** + * Provided only for backwards compatibility. Do not use this because it cannot take advantage of the customizable + * lookup configuration. + *

    + * This uses a default lookup instance, use {@link #DefaultSentryClientFactory(Lookup)} if you need to pass + * a specially configured lookup. + * + * @see #DefaultSentryClientFactory(Lookup) + */ + public DefaultSentryClientFactory() { + this(Lookup.getDefault()); + } + + /** + * Creates a new instance using the provided lookup to locate the configuration. + * @param lookup the lookup instance + */ + public DefaultSentryClientFactory(Lookup lookup) { + super(lookup); + } + @Override public SentryClient createSentryClient(Dsn dsn) { try { @@ -528,7 +549,7 @@ protected ContextManager getContextManager(Dsn dsn) { * @return the list of package names to consider "in-app". */ protected Collection getInAppFrames(Dsn dsn) { - String inAppFramesOption = Lookup.lookup(IN_APP_FRAMES_OPTION, dsn); + String inAppFramesOption = lookup.get(IN_APP_FRAMES_OPTION, dsn); if (Util.isNullOrEmpty(inAppFramesOption)) { // Only warn if the user didn't set it at all if (inAppFramesOption == null) { @@ -556,7 +577,7 @@ protected Collection getInAppFrames(Dsn dsn) { * @return Whether or not to wrap the underlying connection in an {@link AsyncConnection}. */ protected boolean getAsyncEnabled(Dsn dsn) { - return !FALSE.equalsIgnoreCase(Lookup.lookup(ASYNC_OPTION, dsn)); + return !FALSE.equalsIgnoreCase(lookup.get(ASYNC_OPTION, dsn)); } /** @@ -567,7 +588,7 @@ protected boolean getAsyncEnabled(Dsn dsn) { */ protected RejectedExecutionHandler getRejectedExecutionHandler(Dsn dsn) { String overflowName = ASYNC_QUEUE_OVERFLOW_DEFAULT; - String asyncQueueOverflowOption = Lookup.lookup(ASYNC_QUEUE_OVERFLOW_OPTION, dsn); + String asyncQueueOverflowOption = lookup.get(ASYNC_QUEUE_OVERFLOW_OPTION, dsn); if (!Util.isNullOrEmpty(asyncQueueOverflowOption)) { overflowName = asyncQueueOverflowOption.toLowerCase(); } @@ -589,7 +610,7 @@ protected RejectedExecutionHandler getRejectedExecutionHandler(Dsn dsn) { * @return Maximum time to wait for {@link BufferedConnection} shutdown when closed, in milliseconds. */ protected long getBufferedConnectionShutdownTimeout(Dsn dsn) { - return Util.parseLong(Lookup.lookup(BUFFER_SHUTDOWN_TIMEOUT_OPTION, dsn), BUFFER_SHUTDOWN_TIMEOUT_DEFAULT); + return Util.parseLong(lookup.get(BUFFER_SHUTDOWN_TIMEOUT_OPTION, dsn), BUFFER_SHUTDOWN_TIMEOUT_DEFAULT); } /** @@ -599,7 +620,7 @@ protected long getBufferedConnectionShutdownTimeout(Dsn dsn) { * @return Whether or not to attempt a graceful shutdown of the {@link BufferedConnection} upon close. */ protected boolean getBufferedConnectionGracefulShutdownEnabled(Dsn dsn) { - return !FALSE.equalsIgnoreCase(Lookup.lookup(BUFFER_GRACEFUL_SHUTDOWN_OPTION, dsn)); + return !FALSE.equalsIgnoreCase(lookup.get(BUFFER_GRACEFUL_SHUTDOWN_OPTION, dsn)); } /** @@ -609,7 +630,7 @@ protected boolean getBufferedConnectionGracefulShutdownEnabled(Dsn dsn) { * @return ow long to wait between attempts to flush the disk buffer, in milliseconds. */ protected long getBufferFlushtime(Dsn dsn) { - return Util.parseLong(Lookup.lookup(BUFFER_FLUSHTIME_OPTION, dsn), BUFFER_FLUSHTIME_DEFAULT); + return Util.parseLong(lookup.get(BUFFER_FLUSHTIME_OPTION, dsn), BUFFER_FLUSHTIME_DEFAULT); } /** @@ -619,7 +640,7 @@ protected long getBufferFlushtime(Dsn dsn) { * @return The graceful shutdown timeout of the async executor, in milliseconds. */ protected long getAsyncShutdownTimeout(Dsn dsn) { - return Util.parseLong(Lookup.lookup(ASYNC_SHUTDOWN_TIMEOUT_OPTION, dsn), ASYNC_SHUTDOWN_TIMEOUT_DEFAULT); + return Util.parseLong(lookup.get(ASYNC_SHUTDOWN_TIMEOUT_OPTION, dsn), ASYNC_SHUTDOWN_TIMEOUT_DEFAULT); } /** @@ -629,7 +650,7 @@ protected long getAsyncShutdownTimeout(Dsn dsn) { * @return Whether or not to attempt the graceful shutdown of the {@link AsyncConnection} upon close. */ protected boolean getAsyncGracefulShutdownEnabled(Dsn dsn) { - return !FALSE.equalsIgnoreCase(Lookup.lookup(ASYNC_GRACEFUL_SHUTDOWN_OPTION, dsn)); + return !FALSE.equalsIgnoreCase(lookup.get(ASYNC_GRACEFUL_SHUTDOWN_OPTION, dsn)); } /** @@ -639,7 +660,7 @@ protected boolean getAsyncGracefulShutdownEnabled(Dsn dsn) { * @return Maximum size of the async send queue. */ protected int getAsyncQueueSize(Dsn dsn) { - return Util.parseInteger(Lookup.lookup(ASYNC_QUEUE_SIZE_OPTION, dsn), QUEUE_SIZE_DEFAULT); + return Util.parseInteger(lookup.get(ASYNC_QUEUE_SIZE_OPTION, dsn), QUEUE_SIZE_DEFAULT); } /** @@ -649,7 +670,7 @@ protected int getAsyncQueueSize(Dsn dsn) { * @return Priority of threads used for the async connection. */ protected int getAsyncPriority(Dsn dsn) { - return Util.parseInteger(Lookup.lookup(ASYNC_PRIORITY_OPTION, dsn), Thread.MIN_PRIORITY); + return Util.parseInteger(lookup.get(ASYNC_PRIORITY_OPTION, dsn), Thread.MIN_PRIORITY); } /** @@ -659,7 +680,7 @@ protected int getAsyncPriority(Dsn dsn) { * @return The number of threads used for the async connection. */ protected int getAsyncThreads(Dsn dsn) { - return Util.parseInteger(Lookup.lookup(ASYNC_THREADS_OPTION, dsn), + return Util.parseInteger(lookup.get(ASYNC_THREADS_OPTION, dsn), Runtime.getRuntime().availableProcessors()); } @@ -680,7 +701,7 @@ protected boolean getBypassSecurityEnabled(Dsn dsn) { * @return The ratio of events to allow through to server, or null if sampling is disabled. */ protected Double getSampleRate(Dsn dsn) { - return Util.parseDouble(Lookup.lookup(SAMPLE_RATE_OPTION, dsn), null); + return Util.parseDouble(lookup.get(SAMPLE_RATE_OPTION, dsn), null); } /** @@ -690,7 +711,7 @@ protected Double getSampleRate(Dsn dsn) { * @return HTTP proxy port for Sentry connections. */ protected int getProxyPort(Dsn dsn) { - return Util.parseInteger(Lookup.lookup(HTTP_PROXY_PORT_OPTION, dsn), HTTP_PROXY_PORT_DEFAULT); + return Util.parseInteger(lookup.get(HTTP_PROXY_PORT_OPTION, dsn), HTTP_PROXY_PORT_DEFAULT); } /** @@ -700,7 +721,7 @@ protected int getProxyPort(Dsn dsn) { * @return HTTP proxy hostname for Sentry connections. */ protected String getProxyHost(Dsn dsn) { - return Lookup.lookup(HTTP_PROXY_HOST_OPTION, dsn); + return lookup.get(HTTP_PROXY_HOST_OPTION, dsn); } /** @@ -710,7 +731,7 @@ protected String getProxyHost(Dsn dsn) { * @return HTTP proxy username for Sentry connections. */ protected String getProxyUser(Dsn dsn) { - return Lookup.lookup(HTTP_PROXY_USER_OPTION, dsn); + return lookup.get(HTTP_PROXY_USER_OPTION, dsn); } /** @@ -720,7 +741,7 @@ protected String getProxyUser(Dsn dsn) { * @return HTTP proxy password for Sentry connections. */ protected String getProxyPass(Dsn dsn) { - return Lookup.lookup(HTTP_PROXY_PASS_OPTION, dsn); + return lookup.get(HTTP_PROXY_PASS_OPTION, dsn); } /** @@ -731,7 +752,7 @@ protected String getProxyPass(Dsn dsn) { * @return Application version to send with {@link io.sentry.event.Event}s. */ protected String getRelease(Dsn dsn) { - return Lookup.lookup(RELEASE_OPTION, dsn); + return lookup.get(RELEASE_OPTION, dsn); } /** @@ -742,7 +763,7 @@ protected String getRelease(Dsn dsn) { * @return Application version to send with {@link io.sentry.event.Event}s. */ protected String getDist(Dsn dsn) { - return Lookup.lookup(DIST_OPTION, dsn); + return lookup.get(DIST_OPTION, dsn); } /** @@ -753,7 +774,7 @@ protected String getDist(Dsn dsn) { * @return Application version to send with {@link io.sentry.event.Event}s. */ protected String getEnvironment(Dsn dsn) { - return Lookup.lookup(ENVIRONMENT_OPTION, dsn); + return lookup.get(ENVIRONMENT_OPTION, dsn); } /** @@ -764,7 +785,7 @@ protected String getEnvironment(Dsn dsn) { * @return Server name to send with {@link io.sentry.event.Event}s. */ protected String getServerName(Dsn dsn) { - return Lookup.lookup(SERVERNAME_OPTION, dsn); + return lookup.get(SERVERNAME_OPTION, dsn); } /** @@ -774,7 +795,7 @@ protected String getServerName(Dsn dsn) { * @return Additional tags to send with {@link io.sentry.event.Event}s. */ protected Map getTags(Dsn dsn) { - return Util.parseTags(Lookup.lookup(TAGS_OPTION, dsn)); + return Util.parseTags(lookup.get(TAGS_OPTION, dsn)); } /** @@ -796,9 +817,9 @@ protected Set getExtraTags(Dsn dsn) { * @return Tags to extract from the MDC system and set on {@link io.sentry.event.Event}s, where applicable. */ protected Set getMdcTags(Dsn dsn) { - String val = Lookup.lookup(MDCTAGS_OPTION, dsn); + String val = lookup.get(MDCTAGS_OPTION, dsn); if (Util.isNullOrEmpty(val)) { - val = Lookup.lookup(EXTRATAGS_OPTION, dsn); + val = lookup.get(EXTRATAGS_OPTION, dsn); if (!Util.isNullOrEmpty(val)) { logger.warn("The '" + EXTRATAGS_OPTION + "' option is deprecated, please use" + " the '" + MDCTAGS_OPTION + "' option instead."); @@ -815,7 +836,7 @@ protected Set getMdcTags(Dsn dsn) { * @return Extra data to send with {@link io.sentry.event.Event}s. */ protected Map getExtra(Dsn dsn) { - return Util.parseExtra(Lookup.lookup(EXTRA_OPTION, dsn)); + return Util.parseExtra(lookup.get(EXTRA_OPTION, dsn)); } /** @@ -825,7 +846,7 @@ protected Map getExtra(Dsn dsn) { * @return Whether to compress requests sent to the Sentry Server. */ protected boolean getCompressionEnabled(Dsn dsn) { - return !FALSE.equalsIgnoreCase(Lookup.lookup(COMPRESSION_OPTION, dsn)); + return !FALSE.equalsIgnoreCase(lookup.get(COMPRESSION_OPTION, dsn)); } /** @@ -835,7 +856,7 @@ protected boolean getCompressionEnabled(Dsn dsn) { * @return Whether to hide common stackframes with enclosing exceptions. */ protected boolean getHideCommonFramesEnabled(Dsn dsn) { - return !FALSE.equalsIgnoreCase(Lookup.lookup(HIDE_COMMON_FRAMES_OPTION, dsn)); + return !FALSE.equalsIgnoreCase(lookup.get(HIDE_COMMON_FRAMES_OPTION, dsn)); } /** @@ -846,7 +867,7 @@ protected boolean getHideCommonFramesEnabled(Dsn dsn) { */ protected int getMaxMessageLength(Dsn dsn) { return Util.parseInteger( - Lookup.lookup(MAX_MESSAGE_LENGTH_OPTION, dsn), JsonMarshaller.DEFAULT_MAX_MESSAGE_LENGTH); + lookup.get(MAX_MESSAGE_LENGTH_OPTION, dsn), JsonMarshaller.DEFAULT_MAX_MESSAGE_LENGTH); } /** @@ -856,7 +877,7 @@ protected int getMaxMessageLength(Dsn dsn) { * @return Timeout for requests to the Sentry server, in milliseconds. */ protected int getTimeout(Dsn dsn) { - return Util.parseInteger(Lookup.lookup(CONNECTION_TIMEOUT_OPTION, dsn), CONNECTION_TIMEOUT_DEFAULT); + return Util.parseInteger(lookup.get(CONNECTION_TIMEOUT_OPTION, dsn), CONNECTION_TIMEOUT_DEFAULT); } /** @@ -866,7 +887,7 @@ protected int getTimeout(Dsn dsn) { * @return Read timeout for requests to the Sentry server, in milliseconds. */ protected int getReadTimeout(Dsn dsn) { - return Util.parseInteger(Lookup.lookup(READ_TIMEOUT_OPTION, dsn), READ_TIMEOUT_DEFAULT); + return Util.parseInteger(lookup.get(READ_TIMEOUT_OPTION, dsn), READ_TIMEOUT_DEFAULT); } /** @@ -876,7 +897,7 @@ protected int getReadTimeout(Dsn dsn) { * @return Whether or not buffering is enabled. */ protected boolean getBufferEnabled(Dsn dsn) { - String bufferEnabled = Lookup.lookup(BUFFER_ENABLED_OPTION, dsn); + String bufferEnabled = lookup.get(BUFFER_ENABLED_OPTION, dsn); if (bufferEnabled != null) { return Boolean.parseBoolean(bufferEnabled); } @@ -890,7 +911,7 @@ protected boolean getBufferEnabled(Dsn dsn) { * @return the {@link Buffer} where events are stored when network is down. */ protected Buffer getBuffer(Dsn dsn) { - String bufferDir = Lookup.lookup(BUFFER_DIR_OPTION, dsn); + String bufferDir = lookup.get(BUFFER_DIR_OPTION, dsn); if (bufferDir != null) { return new DiskBuffer(new File(bufferDir), getBufferSize(dsn)); } @@ -904,7 +925,7 @@ protected Buffer getBuffer(Dsn dsn) { * @return the maximum number of events to cache offline when network is down. */ protected int getBufferSize(Dsn dsn) { - return Util.parseInteger(Lookup.lookup(BUFFER_SIZE_OPTION, dsn), BUFFER_SIZE_DEFAULT); + return Util.parseInteger(lookup.get(BUFFER_SIZE_OPTION, dsn), BUFFER_SIZE_DEFAULT); } /** @@ -914,7 +935,7 @@ protected int getBufferSize(Dsn dsn) { * @return Whether or not to enable a {@link SentryUncaughtExceptionHandler}. */ protected boolean getUncaughtHandlerEnabled(Dsn dsn) { - return !FALSE.equalsIgnoreCase(Lookup.lookup(UNCAUGHT_HANDLER_ENABLED_OPTION, dsn)); + return !FALSE.equalsIgnoreCase(lookup.get(UNCAUGHT_HANDLER_ENABLED_OPTION, dsn)); } /** diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index eeccd701a54..2865c40a5d7 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -1,5 +1,6 @@ package io.sentry; +import io.sentry.config.Lookup; import io.sentry.config.ResourceLoader; import io.sentry.context.Context; import io.sentry.dsn.Dsn; @@ -7,31 +8,37 @@ import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.User; +import io.sentry.util.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.atomic.AtomicBoolean; - /** * Sentry provides easy access to a statically stored {@link SentryClient} instance. */ public final class Sentry { private static final Logger logger = LoggerFactory.getLogger(Sentry.class); + /** - * The most recently constructed {@link SentryClient} instance, used by static helper - * methods like {@link Sentry#capture(Event)}. + * A synchronization guard of the stored client. Not using the class as the guard because that could theoretically + * deadlock with 3rd party code if it also synced on the class. */ - private static volatile SentryClient storedClient = null; + private static final Object STORED_CLIENT_ACCESS = new Object(); + /** - * Tracks whether the {@link #init()} method has already been attempted automatically - * by {@link #getStoredClient()}. + * The most recently constructed {@link SentryClient} instance, used by static helper + * methods like {@link Sentry#capture(Event)}. */ - private static AtomicBoolean autoInitAttempted = new AtomicBoolean(false); + private static SentryClient storedClient = null; /** * Optional override for the default resource loader used to look for properties. + * + * @deprecated This was a hack to be able to inject the resource loader into the static Lookup initialization. + * This is no longer required due to {@link Lookup} being configurable and passed throughout the classes as an + * instance. */ - private static ResourceLoader resourceLoader = null; + @Deprecated + private static ResourceLoader resourceLoader; /** * Hide constructor. @@ -43,33 +50,45 @@ private Sentry() { /** * Initialize and statically store a {@link SentryClient} by looking up * a {@link Dsn} and automatically choosing a {@link SentryClientFactory}. + *

    + * This uses a default lookup instance, use {@link #init(SentryOptions)} if you need to pass a specially + * configured lookup. * * @return SentryClient + * @see #init(SentryOptions) */ public static SentryClient init() { - return init(null, null); + return init((String) null); } /** * Initialize and statically store a {@link SentryClient} by looking up * a {@link Dsn} and using the provided {@link SentryClientFactory}. + *

    + * This uses a default lookup instance, use {@link #init(SentryOptions)} if you need to pass a specially + * configured lookup. * * @param sentryClientFactory SentryClientFactory to use. * @return SentryClient + * @see #init(SentryOptions) */ - public static SentryClient init(SentryClientFactory sentryClientFactory) { - return init(null, sentryClientFactory); + public static SentryClient init(@Nullable SentryClientFactory sentryClientFactory) { + return init(SentryOptions.from(Lookup.getDefault(), null, sentryClientFactory)); } /** * Initialize and statically store a {@link SentryClient} by using the provided * {@link Dsn} and automatically choosing a {@link SentryClientFactory}. + *

    + * This uses a default lookup instance, use {@link #init(SentryOptions)} if you need to pass a specially + * configured lookup. * * @param dsn Data Source Name of the Sentry server. * @return SentryClient + * @see #init(SentryOptions) */ - public static SentryClient init(String dsn) { - return init(dsn, null); + public static SentryClient init(@Nullable String dsn) { + return init(SentryOptions.defaults(dsn)); } /** @@ -78,27 +97,38 @@ public static SentryClient init(String dsn) { *

    * Note that the Dsn or SentryClientFactory may be null, at which a best effort attempt * is made to look up or choose the best value(s). + *

    + * This uses a default lookup instance, use {@link #init(SentryOptions)} if you need to pass a specially + * configured lookup. * * @param dsn Data Source Name of the Sentry server. * @param sentryClientFactory SentryClientFactory to use. * @return SentryClient + * @see #init(SentryOptions) */ - public static SentryClient init(String dsn, SentryClientFactory sentryClientFactory) { - SentryOptions sentryOptions = new SentryOptions(); - sentryOptions.setDsn(dsn); - sentryOptions.setSentryClientFactory(sentryClientFactory); - return init(sentryOptions); + public static SentryClient init(@Nullable String dsn, @Nullable SentryClientFactory sentryClientFactory) { + SentryOptions options = SentryOptions.defaults(dsn); + options.setSentryClientFactory(sentryClientFactory); + return init(options); } /** - * Initialize and statically store a {@link SentryClient} by using the provided - * {@link SentryOptions}. - *

    x - * Note that the Dsn or SentryClientFactory may be null, at which a best effort attempt - * is made to look up or choose the best value(s). + * Initializes a new Sentry client from the provided context. * - * @param sentryOptions SentryOptions to take DSN and other options from. - * @return SentryClient + *

    The canonical way of using this method is: + *

    + *
    +     * {@link Lookup} lookup = ... obtain or construct the instance of this class to be able to locate Sentry config
    +     * String dsn = ... obtain the Sentry data source name or leave null for lookup in the configuration
    +     * SentryClient client =
    +     *   Sentry.init({@link SentryOptions}.{@link SentryOptions#from(Lookup, String) from(lookup, dsn))};
    +     * 
    + * If you want to rely on the default mechanisms to obtain the configuration, you can also use the + * {@link SentryOptions#defaults()} method which will use the default way of obtaining the configuration and DSN + * obtained from the configuration. + * + * @param sentryOptions the context using with to create the client + * @return the Sentry client */ public static SentryClient init(SentryOptions sentryOptions) { // Hack to allow Lookup.java access to a different resource locator before its static initializer runs. @@ -106,31 +136,27 @@ public static SentryClient init(SentryOptions sentryOptions) { // ResourceLocator will ba passed to Lookup upon instantiation Sentry.resourceLoader = sentryOptions.getResourceLoader(); - SentryClient sentryClient = SentryClientFactory.sentryClient( - sentryOptions.getDsn(), - sentryOptions.getSentryClientFactory()); - setStoredClient(sentryClient); - return sentryClient; + // make sure to use the DSN configured in the options instead of the one that the factory can find in its + // lookup + SentryClient client = sentryOptions.getSentryClientFactory().createClient(sentryOptions.getDsn()); + setStoredClient(client); + return client; } /** * Returns the last statically stored {@link SentryClient} instance. If no instance - * is already stored, the {@link #init()} method will be called one time in an attempt to - * create a {@link SentryClient}. + * is already stored, an attempt will be made to create a {@link SentryClient} from the configuration + * found in the environment. * * @return statically stored {@link SentryClient} instance, or null. */ public static SentryClient getStoredClient() { - if (storedClient != null) { - return storedClient; - } - - synchronized (Sentry.class) { - if (storedClient == null && !autoInitAttempted.get()) { - // attempt initialization by using configuration found in the environment - autoInitAttempted.set(true); - init(); + synchronized (STORED_CLIENT_ACCESS) { + if (storedClient != null) { + return storedClient; } + + init(SentryOptions.defaults()); } return storedClient; @@ -138,8 +164,12 @@ public static SentryClient getStoredClient() { /** * The {@link ResourceLoader} used to lookup properties. - * @return {link ResourceLoader}. + * + * @return {@link ResourceLoader}. + * @deprecated Using this field is discouraged in favour of using the configurable {@link Lookup} with + * {@link io.sentry.config.provider.ResourceLoaderConfigurationProvider}. */ + @Deprecated public static ResourceLoader getResourceLoader() { return resourceLoader; } @@ -166,11 +196,13 @@ public static void clearContext() { * @param client {@link SentryClient} instance to store. */ public static void setStoredClient(SentryClient client) { - if (storedClient != null) { - logger.warn("Overwriting statically stored SentryClient instance {} with {}.", - storedClient, client); + synchronized (STORED_CLIENT_ACCESS) { + if (storedClient != null) { + logger.warn("Overwriting statically stored SentryClient instance {} with {}.", + storedClient, client); + } + storedClient = client; } - storedClient = client; } /** @@ -241,15 +273,14 @@ public static void setUser(User user) { * Close the stored {@link SentryClient}'s connections and remove it from static storage. */ public static void close() { - if (storedClient == null) { - return; - } - - storedClient.closeConnection(); - storedClient = null; + synchronized (STORED_CLIENT_ACCESS) { + if (storedClient == null) { + return; + } - // Allow the client to be auto initialized on the next use. - autoInitAttempted.set(false); + storedClient.closeConnection(); + storedClient = null; + } } } diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 9ed05649e42..5ed281850e3 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -23,7 +23,7 @@ * Sentry client, for sending {@link Event}s to a Sentry server. *

    * It is recommended that you create an instance of Sentry through - * {@link SentryClientFactory#createSentryClient(io.sentry.dsn.Dsn)}, which will use the best factory available. + * {@link SentryClientFactory#createClient(String)}, which will use the best factory available. */ public class SentryClient { private static final Logger logger = LoggerFactory.getLogger(SentryClient.class); diff --git a/sentry/src/main/java/io/sentry/SentryClientFactory.java b/sentry/src/main/java/io/sentry/SentryClientFactory.java index 1716bc7804b..8a35799f982 100644 --- a/sentry/src/main/java/io/sentry/SentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/SentryClientFactory.java @@ -1,95 +1,187 @@ package io.sentry; +import static java.util.Objects.requireNonNull; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + import io.sentry.config.Lookup; import io.sentry.dsn.Dsn; +import io.sentry.util.Nullable; import io.sentry.util.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Factory in charge of creating {@link SentryClient} instances. + * Factory in charge of creating {@link SentryClient} instances. The implementations should have a constructor with a + * single parameter of type {@link Lookup}. + * + * @see SentryClientFactory#instantiateFrom(Lookup, String) */ public abstract class SentryClientFactory { private static final Logger logger = LoggerFactory.getLogger(SentryClientFactory.class); + /** + * The {@link Lookup} instance to use for obtaining configuration. + */ + protected final Lookup lookup; + + /** + * Creates a new instance using the provided lookup. + * + * @param lookup the lookup to use + */ + protected SentryClientFactory(Lookup lookup) { + this.lookup = requireNonNull(lookup); + } + + /** + * Creates a new instance with default configuration. + *

    + * This uses a default lookup instance, use {@link #SentryClientFactory(Lookup)} if you need to pass + * a specially configured lookup. + * + * @see #SentryClientFactory(Lookup) + */ + protected SentryClientFactory() { + this(Lookup.getDefault()); + } + /** * Creates an instance of Sentry by discovering the DSN. + *

    + * This uses a default lookup instance, use {@link #instantiateFrom(Lookup, String)}.{@link #createClient(String)} + * if you need to use a specially configured lookup * * @return an instance of Sentry. */ + @Nullable public static SentryClient sentryClient() { return sentryClient(null, null); } /** * Creates an instance of Sentry using the provided DSN. + *

    + * This uses a default lookup instance, use {@link #instantiateFrom(Lookup, String)}.{@link #createClient(String)} + * if you need to use a specially configured lookup * * @param dsn Data Source Name of the Sentry server. * @return an instance of Sentry. */ - public static SentryClient sentryClient(String dsn) { + @Nullable + public static SentryClient sentryClient(@Nullable String dsn) { return sentryClient(dsn, null); } /** * Creates an instance of Sentry using the provided DSN and the specified factory. + *

    + * This uses a default lookup instance, use {@link #instantiateFrom(Lookup, String)}.{@link #createClient(String)} + * if you need to use a specially configured lookup * * @param dsn Data Source Name of the Sentry server. * @param sentryClientFactory SentryClientFactory instance to use, or null to do a config lookup. * @return SentryClient instance, or null if one couldn't be constructed. */ - public static SentryClient sentryClient(String dsn, SentryClientFactory sentryClientFactory) { - Dsn realDsn = resolveDsn(dsn); - - // If the caller didn't pass a factory, try to look one up - if (sentryClientFactory == null) { - String sentryClientFactoryName = Lookup.lookup("factory", realDsn); - if (Util.isNullOrEmpty(sentryClientFactoryName)) { - // no name specified, use the default factory - sentryClientFactory = new DefaultSentryClientFactory(); - } else { - // attempt to construct the user specified factory class - Class factoryClass = null; + @Nullable + public static SentryClient sentryClient(@Nullable String dsn, @Nullable SentryClientFactory sentryClientFactory) { + Lookup lookup = Lookup.getDefault(); + String realDsn = dsnOrLookedUp(dsn, lookup); + SentryClientFactory factory = sentryClientFactory == null + ? instantiateFrom(lookup, realDsn) + : sentryClientFactory; + return factory == null ? null : factory.createClient(realDsn); + } + + /** + * Creates a new instance of the configured implementation of the Sentry client factory. + * + *

    The provided parameters (lookup and dsn) are used to get the class name of the client factory (by looking + * for the configuration parameter called "factory"). If no such configuration parameter exists an instance of + * the {@link DefaultSentryClientFactory} initialized with the provided lookup is returned. + * + *

    If such configuration parameter exists, a new instance of the configured class is created by first looking + * for a constructor accepting a single parameter of type {@link Lookup} or using a default constructor if no such + * constructor is found. If for any reason such instantiation fails, {@code null} is returned. + * + * @param lookup the lookup instance to use for reading the configuration + * @param dsn the DSN + * @return the Sentry client factory or null if not available + */ + @Nullable + public static SentryClientFactory instantiateFrom(Lookup lookup, @Nullable String dsn) { + Dsn realDsn = new Dsn(dsnOrLookedUp(dsn, lookup)); + + SentryClientFactory sentryClientFactory; + + String sentryClientFactoryName = lookup.get("factory", realDsn); + if (Util.isNullOrEmpty(sentryClientFactoryName)) { + // no name specified, use the default factory + sentryClientFactory = new DefaultSentryClientFactory(lookup); + } else { + // attempt to construct the user specified factory class + try { + Class factoryClass = Class.forName(sentryClientFactoryName); + + Constructor ctor = null; try { - factoryClass = (Class) Class.forName(sentryClientFactoryName); - sentryClientFactory = factoryClass.newInstance(); - } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { - logger.error("Error creating SentryClient using factory class: '" - + sentryClientFactoryName + "'.", e); - return null; + ctor = factoryClass.getConstructor(Lookup.class); + sentryClientFactory = (SentryClientFactory) ctor.newInstance(lookup); + } catch (NoSuchMethodException e) { + sentryClientFactory = (SentryClientFactory) factoryClass.newInstance(); + } catch (InvocationTargetException e) { + logger.warn("Failed to instantiate SentryClientFactory using " + ctor + ". Falling back to using" + + " the default constructor, if any."); + sentryClientFactory = (SentryClientFactory) factoryClass.newInstance(); } + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { + logger.error("Error creating SentryClient using factory class: '" + + sentryClientFactoryName + "'.", e); + return null; } } - return sentryClientFactory.createSentryClient(realDsn); + return sentryClientFactory; } - private static Dsn resolveDsn(String dsn) { - try { - if (Util.isNullOrEmpty(dsn)) { - dsn = Dsn.dsnLookup(); - } - - return new Dsn(dsn); - } catch (Exception e) { - logger.error("Error creating valid DSN from: '{}'.", dsn, e); - throw e; + private static String dsnOrLookedUp(@Nullable String dsn, Lookup lookup) { + if (dsn == null) { + dsn = Dsn.dsnFrom(lookup); } + + return dsn; } /** * Creates an instance of Sentry given a DSN. + *

    + * You may want to prefer the {@link #createClient(String)} method, which can accept a null DSN if the DSN from the + * lookup configuration should be used. * - * @param dsn Data Source Name of the Sentry server. + * @param dsn Data Source Name of the Sentry server to override the configuration of the factory * @return an instance of Sentry. * @throws RuntimeException when an instance couldn't be created. */ public abstract SentryClient createSentryClient(Dsn dsn); + /** + * Creates an instance of Sentry given a DSN. Uses either the explicitly provided non-null DSN or the default + * DSN found in the configuration if {@code null} is provided. + * + * @param dsn optional Data Source Name of the Sentry server to override the configuration of the factory + * @return an instance of Sentry. + * @throws RuntimeException when an instance couldn't be created. + */ + public SentryClient createClient(@Nullable String dsn) { + Dsn realDsn = new Dsn(dsn == null ? Dsn.dsnFrom(lookup) : dsn); + return createSentryClient(realDsn); + } + @Override public String toString() { return "SentryClientFactory{" - + "name='" + this.getClass().getName() + '\'' - + '}'; + + "name='" + this.getClass().getName() + '\'' + + '}'; } } diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 634b6bcfe61..8f430b6c298 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -1,17 +1,124 @@ package io.sentry; +import static java.util.Objects.requireNonNull; + +import io.sentry.config.Lookup; import io.sentry.config.ResourceLoader; +import io.sentry.dsn.Dsn; +import io.sentry.util.Nullable; +import io.sentry.util.Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * SentryOptions used to configure the Sentry SDK. */ -public class SentryOptions { +public final class SentryOptions { + private static final Logger logger = LoggerFactory.getLogger(SentryOptions.class); + + private Lookup lookup; private SentryClientFactory sentryClientFactory; private String dsn; + + /** + * The resource loader to use during {@link Lookup#getDefault()}, if an instance of {@code SentryOptions} is first + * passed to {@link Sentry#init(SentryOptions)}. + * + * @deprecated this is just in support of the hack to use a custom resource loader during the deprecated static + * lookup + */ + @Deprecated private ResourceLoader resourceLoader; /** - * Gets the optionally set {@Link SentryClientFactory}. + * Creates a new instance of the options using the provided parameters. + * + * @param lookup the lookup to locate the configuration with + * @param dsn the DSN to use or null if the DSN should be found in the lookup + * @param sentryClientFactory the client factory to use or null if the instance should be found in the lookup + * @throws NullPointerException if lookup is null + */ + public SentryOptions(Lookup lookup, @Nullable String dsn, @Nullable SentryClientFactory sentryClientFactory) { + this.lookup = requireNonNull(lookup, "lookup"); + this.dsn = resolveDsn(lookup, dsn); + this.sentryClientFactory = sentryClientFactory == null + ? SentryClientFactory.instantiateFrom(this.lookup, this.dsn) + : sentryClientFactory; + this.resourceLoader = null; + + if (this.sentryClientFactory == null) { + logger.error("Failed to find a Sentry client factory in the provided configuration. Will continue" + + " with a dummy implementation that will send no data."); + + this.sentryClientFactory = new InvalidSentryClientFactory(); + } + } + + /** + * Creates new options using the provided lookup instance. The DSN and the client factory are obtained using the + * provided lookup instance. + * + * @param lookup the lookup to locate the configuration with + * @return new instance of SentryOptions + */ + public static SentryOptions from(Lookup lookup) { + return from(lookup, null, null); + } + + /** + * Creates new options using the provided lookup instance and optional DSN. If the provided DSN is null, the DSN + * to use is looked up using the provided lookup instance. + * + * @param lookup the lookup to locate the configuration with + * @param dsn the optional dsn to use + * @return new instance of SentryOptions + */ + public static SentryOptions from(Lookup lookup, @Nullable String dsn) { + return from(lookup, dsn, null); + } + + /** + * Creates new options using the provided lookup instance and optional DSN and client factory. If the provided + * DSN or client factory is null, the DSN and client factory to use is looked up using the provided lookup instance. + * + * @param lookup the lookup to locate the configuration with + * @param dsn the optional dsn to use or null if the value should be located in the config + * @param factory the client factory to use or null if the value should be located in the config + * @return new instance of SentryOptions + */ + public static SentryOptions from(Lookup lookup, @Nullable String dsn, @Nullable SentryClientFactory factory) { + return new SentryOptions(lookup, dsn, factory); + } + + /** + * A convenience method to load the default SentryOptions using the default lookup instance. + * + * @see Lookup#getDefault() + * + * @return the default sentry options + */ + public static SentryOptions defaults() { + return defaults(null); + } + + /** + * A convenience method to load the default SentryOptions using the default lookup instance using the provided + * DSN instead of the one from the config. + * + *

    Equivalent to {@code SentryOptions.defaults().withDsn(dsn)}. + * + * @param dsn the DSN to override the configured default with + * + * @see Lookup#getDefault() + * + * @return the default sentry options with the provided DSN + */ + public static SentryOptions defaults(@Nullable String dsn) { + return from(Lookup.getDefault(), dsn, null); + } + + /** + * Gets the optionally set {@link SentryClientFactory}. * @return {@link SentryClientFactory} */ public SentryClientFactory getSentryClientFactory() { @@ -20,10 +127,12 @@ public SentryClientFactory getSentryClientFactory() { /** * Sets the {@link SentryClientFactory} to be used when initializing the SDK. - * @param sentryClientFactory Factory used to create a {@link SentryClient}. + * @param clientFactory Factory used to create a {@link SentryClient}. */ - public void setSentryClientFactory(SentryClientFactory sentryClientFactory) { - this.sentryClientFactory = sentryClientFactory; + public void setSentryClientFactory(@Nullable SentryClientFactory clientFactory) { + this.sentryClientFactory = clientFactory == null + ? SentryClientFactory.instantiateFrom(getLookup(), getDsn()) + : clientFactory; } /** @@ -38,23 +147,90 @@ public String getDsn() { * Sets the DSN to be used by the {@link SentryClient}. * @param dsn Sentry Data Source Name. */ - public void setDsn(String dsn) { - this.dsn = dsn; + public void setDsn(@Nullable String dsn) { + this.dsn = resolveDsn(getLookup(), dsn); + } + + /** + * Returns the lookup instance to use. + * @return the lookup instance + */ + public Lookup getLookup() { + return lookup; + } + + /** + * Sets the lookup instance to use. + * @param lookup the lookup to use + */ + public void setLookup(Lookup lookup) { + this.lookup = requireNonNull(lookup); } /** - * Gets the {@link ResourceLoader} to be used when looking for properties. - * @return {@link ResourceLoader} + * Gets the resource loader to be used during {@link Sentry#init(SentryOptions)} to set the + * {@link Sentry#getResourceLoader()}. This is kept for supporting the initialization of a default lookup instance + * ({@link Lookup#getDefault()}) that is used during the deprecated static lookups ({@link Lookup#lookup(String)} + * and {@link Lookup#lookup(String, Dsn)}). + * + * @deprecated don't use this method. Instead configure the {@link Lookup} instance with appropriate + * {@link io.sentry.config.provider.ResourceLoaderConfigurationProvider}s and create a new {@link SentryOptions} + * instance + * @return the resource loader */ + @Deprecated public ResourceLoader getResourceLoader() { return resourceLoader; } /** - * Sets the {@link ResourceLoader} to be used when looking for properties. - * @param resourceLoader Optional {@link ResourceLoader} used to lookup properties. + * Sets the resource loader to be used during {@link Sentry#init(SentryOptions)} to set the + * {@link Sentry#getResourceLoader()}. This is kept for supporting the initialization of a default lookup instance + * ({@link Lookup#getDefault()}) that is used during the deprecated static lookups ({@link Lookup#lookup(String)} + * and {@link Lookup#lookup(String, Dsn)}). + * + * @param resourceLoader the resource loader to use in default lookup instance + * + * @deprecated don't use this method. Instead configure the {@link Lookup} instance with appropriate + * {@link io.sentry.config.provider.ResourceLoaderConfigurationProvider}s and create a new {@link SentryOptions} + * instance */ - public void setResourceLoader(ResourceLoader resourceLoader) { + @Deprecated + public void setResourceLoader(@Nullable ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } + + private static String resolveDsn(Lookup lookup, @Nullable String dsn) { + try { + if (Util.isNullOrEmpty(dsn)) { + dsn = Dsn.dsnFrom(lookup); + } + + return dsn; + } catch (Exception e) { + logger.error("Error creating valid DSN from: '{}'.", dsn, e); + throw e; + } + } + + /** + * A dummy factory used in case of invalid configuration. + */ + private final class InvalidSentryClientFactory extends SentryClientFactory { + + private InvalidSentryClientFactory() { + super(getLookup()); + } + + /** + * Returns null. + * + * @param newDsn not used + * @return always null + */ + @Override + public SentryClient createSentryClient(Dsn newDsn) { + return null; + } + } } diff --git a/sentry/src/main/java/io/sentry/config/Lookup.java b/sentry/src/main/java/io/sentry/config/Lookup.java index 2ebd6e728fc..42f942b1bd2 100644 --- a/sentry/src/main/java/io/sentry/config/Lookup.java +++ b/sentry/src/main/java/io/sentry/config/Lookup.java @@ -13,8 +13,8 @@ import io.sentry.Sentry; import io.sentry.config.location.CompoundResourceLocator; import io.sentry.config.location.ConfigurationResourceLocator; -import io.sentry.config.location.StaticFileLocator; import io.sentry.config.location.EnvironmentBasedLocator; +import io.sentry.config.location.StaticFileLocator; import io.sentry.config.location.SystemPropertiesBasedLocator; import io.sentry.config.provider.CompoundConfigurationProvider; import io.sentry.config.provider.ConfigurationProvider; @@ -29,33 +29,16 @@ import org.slf4j.LoggerFactory; /** - * Handle lookup of configuration keys by trying JNDI, System Environment, and Java System Properties. - * - * By default (when instantiated using the default constructor), the sources from which the configuration - * properties are consulted in the following order: + * Handle lookup of configuration keys from the various sources. * - * 1. JNDI, if available - * 2. Java System Properties - * 3. System Environment Variables - * 4. DSN options, if a non-null DSN is provided - * 5. Sentry properties file found in resources + * @see #getDefault() for obtaining the default instance */ public final class Lookup { private static final Logger logger = LoggerFactory.getLogger(Lookup.class); - private static final Object INSTANCE_LOCK = new Object(); - private static Lookup instance; private final ConfigurationProvider highPriorityProvider; private final ConfigurationProvider lowPriorityProvider; - /** - * Constructs a new instance of Lookup with the default configuration providers. - */ - public Lookup() { - this(new CompoundConfigurationProvider(getDefaultHighPriorityConfigurationProviders()), - new CompoundConfigurationProvider(getDefaultLowPriorityConfigurationProviders())); - } - /** * Constructs a new Lookup instance. * @@ -71,6 +54,25 @@ public Lookup(ConfigurationProvider highPriorityProvider, ConfigurationProvider this.lowPriorityProvider = lowPriorityProvider; } + /** + * In the default lookup returned from this method, the configuration properties are looked up in the sources in the + * following order. + * + *

      + *
    1. JNDI, if available + *
    2. Java System Properties + *
    3. System Environment Variables + *
    4. DSN options, if a non-null DSN is provided + *
    5. Sentry properties file found in resources + *
    + * + * @return the default lookup instance + */ + public static Lookup getDefault() { + return new Lookup(new CompoundConfigurationProvider(getDefaultHighPriorityConfigurationProviders()), + new CompoundConfigurationProvider(getDefaultLowPriorityConfigurationProviders())); + } + private static List getDefaultResourceLocators() { return asList(new SystemPropertiesBasedLocator(), new EnvironmentBasedLocator(), new StaticFileLocator()); } @@ -110,16 +112,6 @@ private static List getDefaultLowPriorityConfigurationPro } } - // for use in the deprecated methods - private static Lookup getDeprecatedInstance() { - synchronized (INSTANCE_LOCK) { - if (instance == null) { - instance = new Lookup(); - } - - return instance; - } - } /** * Attempt to lookup a configuration key, without checking any DSN options. @@ -151,7 +143,7 @@ public static String lookup(String key) { */ @Deprecated public static String lookup(String key, Dsn dsn) { - return getDeprecatedInstance().get(key, dsn); + return getDefault().get(key, dsn); } /** @@ -173,7 +165,7 @@ public String get(String key) { * @return value of configuration key, if found, otherwise null */ @Nullable - public String get(String key, Dsn dsn) { + public String get(String key, @Nullable Dsn dsn) { String val = highPriorityProvider.getProperty(key); if (val == null && dsn != null) { diff --git a/sentry/src/main/java/io/sentry/dsn/Dsn.java b/sentry/src/main/java/io/sentry/dsn/Dsn.java index 7ecd0b22ea8..94f700d047b 100644 --- a/sentry/src/main/java/io/sentry/dsn/Dsn.java +++ b/sentry/src/main/java/io/sentry/dsn/Dsn.java @@ -74,15 +74,28 @@ public Dsn(URI dsn) throws InvalidDsnException { /** * Looks for a DSN configuration within JNDI, the System environment or Java properties. + *

    + * This uses the default lookup. If you need a specially configured lookup instance, use {@link #dsnFrom(Lookup)} + * method. * * @return a DSN configuration or null if nothing could be found. */ public static String dsnLookup() { - String dsn = Lookup.lookup("dsn"); + return dsnFrom(Lookup.getDefault()); + } + + /** + * Looks for the DSN configuration in the provided configuration lookup. + * + * @param lookup the lookup used to find the DSN configuration + * @return the DSN URI or the {@link #DEFAULT_DSN} if none found in the lookup + */ + public static String dsnFrom(Lookup lookup) { + String dsn = lookup.get("dsn"); if (Util.isNullOrEmpty(dsn)) { // check if the user accidentally set "dns" instead of "dsn" - dsn = Lookup.lookup("dns"); + dsn = lookup.get("dns"); } if (Util.isNullOrEmpty(dsn)) { diff --git a/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java b/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java index 36914baec67..74ff659b597 100644 --- a/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java +++ b/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java @@ -7,8 +7,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import io.sentry.connection.NoopConnection; - public class DefaultSentryClientFactoryTest extends BaseTest { @Test public void testFieldsFromDsn() throws Exception { @@ -23,8 +21,9 @@ public void testFieldsFromDsn() throws Exception { String dsn = String.format("https://user:pass@example.com/1?" + "release=%s&dist=%s&environment=%s&servername=%s&tags=%s&extratags=%s&extra=%s", release, dist, environment, serverName, tags, extraTags, extras); - SentryClient sentryClient = DefaultSentryClientFactory.sentryClient(dsn); + SentryClient sentryClient = SentryClientFactory.sentryClient(dsn); + assertThat(sentryClient, is(notNullValue())); assertThat(sentryClient.getRelease(), is(release)); assertThat(sentryClient.getDist(), is(dist)); assertThat(sentryClient.getEnvironment(), is(environment)); @@ -51,8 +50,9 @@ public void testBadDataInitializesWithoutException() { String badTags = "foo:"; String dsn = String.format("https://user:pass@example.com/1?tags=%s", badTags); - SentryClient sentryClient = DefaultSentryClientFactory.sentryClient(dsn); + SentryClient sentryClient = SentryClientFactory.sentryClient(dsn); + assertThat(sentryClient, is(notNullValue())); assertThat(sentryClient, isA(SentryClient.class)); assertThat(sentryClient.getContext(), notNullValue()); } diff --git a/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java b/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java index 3623b40c119..931560738b1 100644 --- a/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java +++ b/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java @@ -1,17 +1,6 @@ package io.sentry; -import mockit.*; -import io.sentry.dsn.Dsn; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.ServiceLoader; - -import static mockit.Deencapsulation.setField; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; @@ -22,6 +11,7 @@ public class SentryClientFactoryTest extends BaseTest { public void testSentryClientForFactoryNameSucceedsIfFactoryFound() throws Exception { String dsn = "noop://localhost/1?factory=io.sentry.TestFactory"; SentryClient sentryClient = SentryClientFactory.sentryClient(dsn); + assertThat(sentryClient, is(notNullValue())); assertThat(sentryClient.getRelease(), is(TestFactory.RELEASE)); } @@ -48,6 +38,7 @@ public void testAutoDetectDsnIfNotProvided() throws Exception { } } + assertThat(sentryClient, is(notNullValue())); assertThat(sentryClient.getRelease(), is("xyz")); } @@ -55,6 +46,7 @@ public void testAutoDetectDsnIfNotProvided() throws Exception { public void testCreateDsnIfStringProvided() throws Exception { final String dsn = "noop://localhost/1?release=abc"; SentryClient sentryClient = SentryClientFactory.sentryClient(dsn); + assertThat(sentryClient, is(notNullValue())); assertThat(sentryClient.getRelease(), is("abc")); } } diff --git a/sentry/src/test/java/io/sentry/SentryIT.java b/sentry/src/test/java/io/sentry/SentryIT.java index 6b6a521990f..b6cb5ba762d 100644 --- a/sentry/src/test/java/io/sentry/SentryIT.java +++ b/sentry/src/test/java/io/sentry/SentryIT.java @@ -5,6 +5,7 @@ import org.junit.Test; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static mockit.Deencapsulation.getField; @@ -18,6 +19,8 @@ public void test500DoesLockdown() throws Exception { verifyStoredEventCount(0); SentryClient client = SentryClientFactory.sentryClient(); + assertNotNull(client); + client.sendMessage("Test"); verifyProject1PostRequestCount(1); @@ -34,6 +37,8 @@ public void test403FilteredDoesNotLockdown() throws Exception { verifyStoredEventCount(0); SentryClient client = SentryClientFactory.sentryClient(); + assertNotNull(client); + client.sendMessage("Test"); verifyProject1PostRequestCount(1); @@ -50,6 +55,8 @@ public void testSuccess() throws Exception { verifyStoredEventCount(0); SentryClient client = SentryClientFactory.sentryClient(); + assertNotNull(client); + client.sendMessage("Test"); verifyProject1PostRequestCount(1); diff --git a/sentry/src/test/java/io/sentry/SentryTest.java b/sentry/src/test/java/io/sentry/SentryTest.java index 3b828ec77e3..13218a7657a 100644 --- a/sentry/src/test/java/io/sentry/SentryTest.java +++ b/sentry/src/test/java/io/sentry/SentryTest.java @@ -1,5 +1,6 @@ package io.sentry; +import io.sentry.config.Lookup; import io.sentry.connection.Connection; import io.sentry.connection.HttpConnection; import io.sentry.connection.NoopConnection; @@ -147,7 +148,8 @@ public void testInitStringDsn() throws Exception { @Test public void testInitStringDsnAndFactory() throws Exception { - SentryClient sentryClient = Sentry.init("http://public:private@localhost:4567/1?async=false", new DefaultSentryClientFactory()); + SentryClient sentryClient = Sentry.init("http://public:private@localhost:4567/1?async=false", + new DefaultSentryClientFactory()); HttpConnection connection = getField(sentryClient, "connection"); assertThat(connection, instanceOf(HttpConnection.class)); diff --git a/sentry/src/test/java/io/sentry/TestFactory.java b/sentry/src/test/java/io/sentry/TestFactory.java index 96b215a19fd..17bfede5301 100644 --- a/sentry/src/test/java/io/sentry/TestFactory.java +++ b/sentry/src/test/java/io/sentry/TestFactory.java @@ -1,9 +1,15 @@ package io.sentry; +import io.sentry.config.Lookup; import io.sentry.dsn.Dsn; public class TestFactory extends DefaultSentryClientFactory { static final String RELEASE = "312407214120"; + + public TestFactory(Lookup lookup) { + super(lookup); + } + @Override protected SentryClient configureSentryClient(SentryClient sentryClient, Dsn dsn) { sentryClient.setRelease(RELEASE); diff --git a/sentry/src/test/java/io/sentry/dsn/DsnTest.java b/sentry/src/test/java/io/sentry/dsn/DsnTest.java index c711f9adeef..954e969543f 100644 --- a/sentry/src/test/java/io/sentry/dsn/DsnTest.java +++ b/sentry/src/test/java/io/sentry/dsn/DsnTest.java @@ -2,6 +2,7 @@ import io.sentry.BaseTest; import io.sentry.config.JndiLookup; +import io.sentry.config.Lookup; import mockit.Expectations; import mockit.Mocked; import mockit.NonStrictExpectations; From 43179753c2e5d673766dfdc29af97693a3ac2104 Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Tue, 10 Sep 2019 16:14:44 +0200 Subject: [PATCH 2100/2152] Test rewrite in sentry module (#759) --- pom.xml | 8 + .../sentry/log4j/SentryAppenderCloseTest.java | 1 + sentry/pom.xml | 18 +- .../src/main/java/io/sentry/SentryClient.java | 10 + .../sentry/connection/AbstractConnection.java | 22 +- .../io/sentry/connection/AsyncConnection.java | 14 +- .../io/sentry/connection/LockdownManager.java | 5 + .../sentry/environment/SentryEnvironment.java | 2 +- .../java/io/sentry/event/EventBuilder.java | 65 ++++- .../main/java/io/sentry/jvmti/FrameCache.java | 8 + .../servlet/SentryServletRequestListener.java | 9 + .../test/java/DefaultPackageException.java | 9 + sentry/src/test/java/io/sentry/BaseTest.java | 9 +- .../src/test/java/io/sentry/ContextTest.java | 3 +- .../DefaultSentryClientFactoryTest.java | 4 +- .../io/sentry/EmptyConfigurationProvider.java | 12 + .../io/sentry/SentryClientFactoryTest.java | 3 +- .../test/java/io/sentry/SentryClientTest.java | 154 +++++----- sentry/src/test/java/io/sentry/SentryIT.java | 6 +- .../src/test/java/io/sentry/SentryTest.java | 113 +++----- .../SentryUncaughtExceptionHandlerTest.java | 12 +- .../java/io/sentry/buffer/DiskBufferTest.java | 14 +- .../EnvironmentConfigurationProviderTest.java | 37 +++ .../JndiConfigurationProviderTest.java | 36 +++ ...emPropertiesConfigurationProviderTest.java | 48 ++++ .../connection/AbstractConnectionTest.java | 143 +++++----- .../connection/AsyncConnectionTest.java | 212 +++++++------- .../connection/BufferedConnectionTest.java | 126 +++++---- .../sentry/connection/HttpConnectionTest.java | 214 ++++++-------- .../OutputStreamConnectionTest.java | 46 ++- .../connection/RandomEventSamplerTest.java | 3 +- .../src/test/java/io/sentry/dsn/DsnTest.java | 120 +++----- .../io/sentry/dsn/InitialContextFactory.java | 19 -- .../environment/SentryEnvironmentTest.java | 6 +- .../java/io/sentry/event/BreadcrumbTest.java | 42 ++- .../event/EventBuilderHostnameCacheTest.java | 114 ++++---- .../io/sentry/event/EventBuilderTest.java | 263 +++++++----------- .../test/java/io/sentry/event/EventTest.java | 37 ++- .../test/java/io/sentry/event/UserTest.java | 49 ++-- .../helper/ContextBuilderHelperTest.java | 44 +-- .../helper/HttpEventBuilderHelperTest.java | 155 ++++++----- .../interfaces/DebugMetaInterfaceTest.java | 2 +- .../event/interfaces/HttpInterfaceTest.java | 160 +++++------ .../interfaces/MessageInterfaceTest.java | 2 +- .../event/interfaces/SentryExceptionTest.java | 62 ++--- .../SentryStackTraceElementTest.java | 6 +- .../interfaces/StackTraceInterfaceTest.java | 2 +- .../event/interfaces/UserInterfaceTest.java | 2 +- .../helper/RemoteAddressResolverTest.java | 40 +-- .../jul/SentryHandlerEventBuildingTest.java | 104 +++---- .../java/io/sentry/jvmti/FrameCacheTest.java | 22 +- .../UncloseableOutputStreamTest.java | 41 ++- .../json/DebugMetaInterfaceBindingTest.java | 22 +- .../json/ExceptionInterfaceBindingTest.java | 142 +++++----- .../json/HttpInterfaceBindingTest.java | 116 +++----- .../marshaller/json/JsonComparisonUtil.java | 3 +- .../marshaller/json/JsonMarshallerTest.java | 262 ++++++++--------- .../json/MessageInterfaceBindingTest.java | 24 +- .../json/SentryJsonGeneratorTest.java | 2 +- .../json/StackTraceInterfaceBindingTest.java | 70 ++--- .../json/UserInterfaceBindingTest.java | 28 +- ...SentryServletContainerInitializerTest.java | 19 +- .../SentryServletRequestListenerTest.java | 58 ++-- .../test/java/io/sentry/time/ClockTest.java | 2 +- sentry/src/test/resources/jndi.properties | 1 - 65 files changed, 1591 insertions(+), 1816 deletions(-) create mode 100644 sentry/src/test/java/DefaultPackageException.java create mode 100644 sentry/src/test/java/io/sentry/EmptyConfigurationProvider.java create mode 100644 sentry/src/test/java/io/sentry/config/provider/EnvironmentConfigurationProviderTest.java create mode 100644 sentry/src/test/java/io/sentry/config/provider/SystemPropertiesConfigurationProviderTest.java delete mode 100644 sentry/src/test/java/io/sentry/dsn/InitialContextFactory.java delete mode 100644 sentry/src/test/resources/jndi.properties diff --git a/pom.xml b/pom.xml index 9e70918b453..84cab5dbcf8 100644 --- a/pom.xml +++ b/pom.xml @@ -126,6 +126,7 @@ 4.12 2.5.1 2.28.2 + 1.1.1 @@ -186,6 +187,13 @@ junit junit ${junit.version} + test + + + pl.pragmatists + JUnitParams + ${junitparams.version} + test com.github.tomakehurst diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java index 64dd4c3f0ca..feaa35357d6 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java @@ -25,6 +25,7 @@ public class SentryAppenderCloseTest extends BaseTest { @BeforeMethod public void setUp() throws Exception { + resetClient(); mockUpErrorHandler = new MockUpErrorHandler(); } diff --git a/sentry/pom.xml b/sentry/pom.xml index b6cafd8e2cd..f1bb71d690a 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -52,11 +52,6 @@ jackson-annotations test - - org.jmockit - jmockit - test - org.hamcrest hamcrest-core @@ -68,13 +63,13 @@ test - org.testng - testng + junit + junit test - junit - junit + pl.pragmatists + JUnitParams test @@ -178,6 +173,11 @@ ${project.build.directory}/test-classes/logging-test.properties + + + testValue1 + testValue2 + diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 5ed281850e3..6e9a30b9e01 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -426,6 +426,16 @@ public void addShouldSendEventCallback(ShouldSendEventCallback shouldSendEventCa shouldSendEventCallbacks.add(shouldSendEventCallback); } + /** + * The connection to the Sentry server used by this client. + * + * @return the connection of this client + */ + // visible for testing + Connection getConnection() { + return connection; + } + /** * Setup and store the {@link SentryUncaughtExceptionHandler} so that it can be * disabled on {@link SentryClient#closeConnection()}. diff --git a/sentry/src/main/java/io/sentry/connection/AbstractConnection.java b/sentry/src/main/java/io/sentry/connection/AbstractConnection.java index c7c091afa59..7a2692aed4d 100644 --- a/sentry/src/main/java/io/sentry/connection/AbstractConnection.java +++ b/sentry/src/main/java/io/sentry/connection/AbstractConnection.java @@ -44,7 +44,19 @@ public abstract class AbstractConnection implements Connection { * @param secretKey secret key (password) to the Sentry server. */ protected AbstractConnection(String publicKey, String secretKey) { - this.lockdownManager = new LockdownManager(); + this(publicKey, secretKey, new LockdownManager()); + } + + /** + * Creates a connection based on the public and secret keys and uses the provided lockdown manager. + * + * @param publicKey public key (identifier) to the Sentry server. + * @param secretKey secret key (password) to the Sentry server. + * @param lockdownManager the lockdown manager to use + */ + // visible for testing + AbstractConnection(String publicKey, String secretKey, LockdownManager lockdownManager) { + this.lockdownManager = lockdownManager; this.eventSendCallbacks = new HashSet<>(); this.authHeader = "Sentry sentry_version=" + SENTRY_PROTOCOL_VERSION + "," + "sentry_client=" + SentryEnvironment.getSentryName() + "," @@ -123,4 +135,12 @@ public void addEventSendCallback(EventSendCallback eventSendCallback) { eventSendCallbacks.add(eventSendCallback); } + /** + * A helper method to figure out whether the connection is locked down or not. Used primarily for testing purposes. + * + * @return true if the connection is locked down, false otherwise + */ + public boolean isLockedDown() { + return lockdownManager.isLockedDown(); + } } diff --git a/sentry/src/main/java/io/sentry/connection/AsyncConnection.java b/sentry/src/main/java/io/sentry/connection/AsyncConnection.java index 8e40b4b6268..5fbda6f1483 100644 --- a/sentry/src/main/java/io/sentry/connection/AsyncConnection.java +++ b/sentry/src/main/java/io/sentry/connection/AsyncConnection.java @@ -3,6 +3,7 @@ import io.sentry.SentryClient; import io.sentry.environment.SentryEnvironment; import io.sentry.event.Event; +import io.sentry.util.Nullable; import io.sentry.util.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,6 +27,11 @@ public class AsyncConnection implements Connection { // CHECKSTYLE.OFF: ConstantName private static final Logger lockdownLogger = LoggerFactory.getLogger(SentryClient.class.getName() + ".lockdown"); // CHECKSTYLE.ON: ConstantName + /** + * Shutdown hook used to stop the async connection properly when the JVM quits. + */ + // visible for testing + final ShutDownHook shutDownHook = new ShutDownHook(); /** * Timeout of the {@link #executorService}, in milliseconds. */ @@ -38,10 +44,6 @@ public class AsyncConnection implements Connection { * Executor service in charge of running the connection in separate threads. */ private final ExecutorService executorService; - /** - * Shutdown hook used to stop the async connection properly when the JVM quits. - */ - private final ShutDownHook shutDownHook = new ShutDownHook(); /** * Boolean that represents if graceful shutdown is enabled. */ @@ -62,8 +64,8 @@ public class AsyncConnection implements Connection { * @param gracefulShutdown Indicates whether or not the shutdown operation should be managed by a ShutdownHook. * @param shutdownTimeout timeout for graceful shutdown of the executor, in milliseconds. */ - public AsyncConnection(Connection actualConnection, ExecutorService executorService, boolean gracefulShutdown, - long shutdownTimeout) { + public AsyncConnection(Connection actualConnection, @Nullable ExecutorService executorService, + boolean gracefulShutdown, long shutdownTimeout) { this.actualConnection = actualConnection; if (executorService == null) { this.executorService = Executors.newSingleThreadExecutor(); diff --git a/sentry/src/main/java/io/sentry/connection/LockdownManager.java b/sentry/src/main/java/io/sentry/connection/LockdownManager.java index fa4d68ec795..0877f680c15 100644 --- a/sentry/src/main/java/io/sentry/connection/LockdownManager.java +++ b/sentry/src/main/java/io/sentry/connection/LockdownManager.java @@ -110,4 +110,9 @@ public synchronized void setBaseLockdownTime(long baseLockdownTime) { public synchronized void setMaxLockdownTime(long maxLockdownTime) { this.maxLockdownTime = maxLockdownTime; } + + // visible for testing + long getLockdownTime() { + return lockdownTime; + } } diff --git a/sentry/src/main/java/io/sentry/environment/SentryEnvironment.java b/sentry/src/main/java/io/sentry/environment/SentryEnvironment.java index 1e73b836bfc..188c9245a51 100644 --- a/sentry/src/main/java/io/sentry/environment/SentryEnvironment.java +++ b/sentry/src/main/java/io/sentry/environment/SentryEnvironment.java @@ -23,7 +23,7 @@ public final class SentryEnvironment { /** * Indicates whether the current thread is managed by sentry or not. */ - protected static final ThreadLocal SENTRY_THREAD = new ThreadLocal() { + static final ThreadLocal SENTRY_THREAD = new ThreadLocal() { @Override protected AtomicInteger initialValue() { return new AtomicInteger(); diff --git a/sentry/src/main/java/io/sentry/event/EventBuilder.java b/sentry/src/main/java/io/sentry/event/EventBuilder.java index d6e01bbec00..cde93f06247 100644 --- a/sentry/src/main/java/io/sentry/event/EventBuilder.java +++ b/sentry/src/main/java/io/sentry/event/EventBuilder.java @@ -1,13 +1,16 @@ package io.sentry.event; +import static java.nio.charset.StandardCharsets.UTF_8; + import io.sentry.environment.SentryEnvironment; import io.sentry.event.interfaces.SentryInterface; import io.sentry.event.interfaces.SentryStackTraceElement; +import io.sentry.time.Clock; +import io.sentry.time.SystemClock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.InetAddress; -import java.nio.charset.Charset; import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; @@ -34,8 +37,12 @@ public class EventBuilder { * @see HostnameCache */ public static final long HOSTNAME_CACHE_DURATION = TimeUnit.HOURS.toMillis(5); - private static final Charset UTF_8 = Charset.forName("UTF-8"); - private static final HostnameCache HOSTNAME_CACHE = new HostnameCache(HOSTNAME_CACHE_DURATION); + + /** + * The global hostname cache to speed up localhost hostname resolution. + */ + // visible for testing + static final HostnameCache HOSTNAME_CACHE = new HostnameCache(HOSTNAME_CACHE_DURATION); private final Event event; private boolean alreadyBuilt = false; private Set sdkIntegrations = new HashSet<>(); @@ -470,36 +477,53 @@ public String toString() { * time * defined by {@link #GET_HOSTNAME_TIMEOUT} without result. */ - private static final class HostnameCache { + // visible for testing + static final class HostnameCache { /** * Time before the get hostname operation times out (in ms). */ - public static final long GET_HOSTNAME_TIMEOUT = TimeUnit.SECONDS.toMillis(1); + static final long GET_HOSTNAME_TIMEOUT = TimeUnit.SECONDS.toMillis(1); private static final Logger logger = LoggerFactory.getLogger(HostnameCache.class); /** * Time for which the cache is kept. */ - private final long cacheDuration; + final long cacheDuration; /** * Current value for hostname (might change over time). */ - private volatile String hostname = DEFAULT_HOSTNAME; + volatile String hostname = DEFAULT_HOSTNAME; /** * Time at which the cache should expire. */ - private volatile long expirationTimestamp; + volatile long expirationTimestamp; /** * Whether a cache update thread is currently running or not. */ private AtomicBoolean updateRunning = new AtomicBoolean(false); + private final Clock clock; + private final Callable getLocalhost; + + private HostnameCache(long cacheDuration) { + this(cacheDuration, new SystemClock(), new Callable() { + @Override + public InetAddress call() throws Exception { + return InetAddress.getLocalHost(); + } + }); + } + /** * Sets up a cache for the hostname. * * @param cacheDuration cache duration in milliseconds. + * @param clock the clock to track time with - this is mostly here because of testability + * @param getLocalhost a callback to obtain the localhost address - this is mostly here because of testability */ - private HostnameCache(long cacheDuration) { + HostnameCache(long cacheDuration, Clock clock, Callable getLocalhost) { this.cacheDuration = cacheDuration; + this.clock = clock; + this.getLocalhost = getLocalhost; } /** @@ -509,8 +533,8 @@ private HostnameCache(long cacheDuration) { * * @return the hostname of the current machine. */ - public String getHostname() { - if (expirationTimestamp < System.currentTimeMillis() + String getHostname() { + if (expirationTimestamp < clock.millis() && updateRunning.compareAndSet(false, true)) { updateCache(); } @@ -521,13 +545,13 @@ public String getHostname() { /** * Force an update of the cache to get the current value of the hostname. */ - public void updateCache() { + void updateCache() { Callable hostRetriever = new Callable() { @Override public Void call() throws Exception { try { - hostname = InetAddress.getLocalHost().getCanonicalHostName(); - expirationTimestamp = System.currentTimeMillis() + cacheDuration; + hostname = getLocalhost.call().getCanonicalHostName(); + expirationTimestamp = clock.millis() + cacheDuration; } finally { updateRunning.set(false); } @@ -542,12 +566,23 @@ public Void call() throws Exception { new Thread(futureTask).start(); futureTask.get(GET_HOSTNAME_TIMEOUT, TimeUnit.MILLISECONDS); } catch (Exception e) { - expirationTimestamp = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(1); + expirationTimestamp = clock.millis() + TimeUnit.SECONDS.toMillis(1); logger.debug("Localhost hostname lookup failed, keeping the value '{}'." + " If this persists it may mean your DNS is incorrectly configured and" + " you may want to hardcode your server name: https://docs.sentry.io/clients/java/config/", hostname, e); } } + + /** + * This method is to support testing. It serves little purpose outside of it. It forgets the resolved + * hostname and sets the expiration time to the provided value. + * + * @param newExpirationTimestamp the expiration + */ + void reset(long newExpirationTimestamp) { + this.hostname = DEFAULT_HOSTNAME; + this.expirationTimestamp = newExpirationTimestamp; + } } } diff --git a/sentry/src/main/java/io/sentry/jvmti/FrameCache.java b/sentry/src/main/java/io/sentry/jvmti/FrameCache.java index b1b6c31987d..e8bf1ad1a1c 100644 --- a/sentry/src/main/java/io/sentry/jvmti/FrameCache.java +++ b/sentry/src/main/java/io/sentry/jvmti/FrameCache.java @@ -97,4 +97,12 @@ public static void addAppPackage(String newAppPackage) { appPackages.add(newAppPackage); } + /** + * This method is meant for cleaner testing. Don't attempt to use it in production. + */ + // visible for testing + static void reset() { + cache.get().clear(); + appPackages.clear(); + } } diff --git a/sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java b/sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java index 92a7b8bd098..dc8c5d2b8c8 100644 --- a/sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java +++ b/sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java @@ -45,4 +45,13 @@ public void requestInitialized(ServletRequestEvent servletRequestEvent) { THREAD_REQUEST.set((HttpServletRequest) servletRequest); } } + + /** + * Used for resetting the state of the thread-local storing the current HttpServletRequest. + *

    + * This method exists solely for testing purposes. + */ + static void reset() { + THREAD_REQUEST.remove(); + } } diff --git a/sentry/src/test/java/DefaultPackageException.java b/sentry/src/test/java/DefaultPackageException.java new file mode 100644 index 00000000000..6eb10fdfc04 --- /dev/null +++ b/sentry/src/test/java/DefaultPackageException.java @@ -0,0 +1,9 @@ +import io.sentry.marshaller.json.ExceptionInterfaceBindingTest; + +/** + * Exception used to test exceptions defined in the default package. + *

    + * This class is used by the {@link ExceptionInterfaceBindingTest} + */ +public class DefaultPackageException extends Exception { +} diff --git a/sentry/src/test/java/io/sentry/BaseTest.java b/sentry/src/test/java/io/sentry/BaseTest.java index 297533b5468..7560f796796 100644 --- a/sentry/src/test/java/io/sentry/BaseTest.java +++ b/sentry/src/test/java/io/sentry/BaseTest.java @@ -1,12 +1,13 @@ package io.sentry; -import org.testng.annotations.BeforeMethod; - import java.util.concurrent.Callable; +import org.junit.Before; + public class BaseTest { - @BeforeMethod - public void baseTestSetup() { + + @Before + public void resetClient() { Sentry.setStoredClient(null); } diff --git a/sentry/src/test/java/io/sentry/ContextTest.java b/sentry/src/test/java/io/sentry/ContextTest.java index 76a17618c2f..726770e2f10 100644 --- a/sentry/src/test/java/io/sentry/ContextTest.java +++ b/sentry/src/test/java/io/sentry/ContextTest.java @@ -5,14 +5,13 @@ import io.sentry.event.BreadcrumbBuilder; import io.sentry.event.User; import io.sentry.event.UserBuilder; -import org.testng.annotations.Test; +import org.junit.Test; import java.util.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; -@Test(singleThreaded = true) public class ContextTest extends BaseTest { @Test public void testBreadcrumbs() { diff --git a/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java b/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java index 74ff659b597..f7edcffe86e 100644 --- a/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java +++ b/sentry/src/test/java/io/sentry/DefaultSentryClientFactoryTest.java @@ -1,12 +1,12 @@ package io.sentry; -import org.testng.annotations.Test; - import java.util.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import org.junit.Test; + public class DefaultSentryClientFactoryTest extends BaseTest { @Test public void testFieldsFromDsn() throws Exception { diff --git a/sentry/src/test/java/io/sentry/EmptyConfigurationProvider.java b/sentry/src/test/java/io/sentry/EmptyConfigurationProvider.java new file mode 100644 index 00000000000..81ccd61cc04 --- /dev/null +++ b/sentry/src/test/java/io/sentry/EmptyConfigurationProvider.java @@ -0,0 +1,12 @@ +package io.sentry; + +import io.sentry.config.provider.ConfigurationProvider; +import io.sentry.util.Nullable; + +public class EmptyConfigurationProvider implements ConfigurationProvider { + @Nullable + @Override + public String getProperty(String key) { + return null; + } +} diff --git a/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java b/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java index 931560738b1..59b19f78f36 100644 --- a/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java +++ b/sentry/src/test/java/io/sentry/SentryClientFactoryTest.java @@ -1,11 +1,12 @@ package io.sentry; -import org.testng.annotations.Test; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; +import org.junit.Test; + public class SentryClientFactoryTest extends BaseTest { @Test public void testSentryClientForFactoryNameSucceedsIfFactoryFound() throws Exception { diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.java b/sentry/src/test/java/io/sentry/SentryClientTest.java index 6d8b9d6e610..395bbaf4d5f 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.java +++ b/sentry/src/test/java/io/sentry/SentryClientTest.java @@ -3,50 +3,54 @@ import io.sentry.context.ContextManager; import io.sentry.context.SingletonContextManager; import io.sentry.event.helper.ShouldSendEventCallback; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Tested; -import mockit.Verifications; import io.sentry.connection.Connection; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.helper.EventBuilderHelper; import io.sentry.event.interfaces.ExceptionInterface; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.not; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; public class SentryClientTest extends BaseTest { - @Tested private SentryClient sentryClient = null; - @Injectable private Connection mockConnection = null; - @Injectable private ContextManager contextManager = new SingletonContextManager(); - @Injectable private Event mockEvent = null; - @Injectable private EventBuilderHelper mockEventBuilderHelper = null; - @BeforeMethod + @Before public void setup() { contextManager.clear(); + mockConnection = mock(Connection.class); + mockEvent = mock(Event.class); + mockEventBuilderHelper = mock(EventBuilderHelper.class); + sentryClient = new SentryClient(mockConnection, contextManager); } @Test public void testSendEvent() throws Exception { sentryClient.sendEvent(mockEvent); - new Verifications() {{ - mockConnection.send(mockEvent); - }}; + verify(mockConnection).send(eq(mockEvent)); } @Test @@ -58,27 +62,22 @@ public void testSendEventBuilder() throws Exception { .withMessage(message) .withLevel(Event.Level.INFO)); - new Verifications() {{ - Event event; - mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); - mockConnection.send(event = withCapture()); - assertThat(event.getLevel(), equalTo(Event.Level.INFO)); - assertThat(event.getMessage(), equalTo(message)); - }}; + verify(mockEventBuilderHelper).helpBuildingEvent(any(EventBuilder.class)); + + ArgumentCaptor eventArgumentCaptor = ArgumentCaptor.forClass(Event.class); + verify(mockConnection).send(eventArgumentCaptor.capture()); + Event event = eventArgumentCaptor.getValue(); + assertThat(event.getLevel(), equalTo(Event.Level.INFO)); + assertThat(event.getMessage(), equalTo(message)); } @Test public void testSendEventFailingIsCaught() throws Exception { - new NonStrictExpectations() {{ - mockConnection.send((Event) any); - result = new RuntimeException(); - }}; + doThrow(new RuntimeException()).when(mockConnection).send(eq(mockEvent)); sentryClient.sendEvent(mockEvent); - new Verifications() {{ - mockConnection.send(mockEvent); - }}; + verify(mockConnection).send(eq(mockEvent)); } @Test @@ -88,13 +87,12 @@ public void testSendMessage() throws Exception { sentryClient.sendMessage(message); - new Verifications() {{ - Event event; - mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); - mockConnection.send(event = withCapture()); - assertThat(event.getLevel(), equalTo(Event.Level.INFO)); - assertThat(event.getMessage(), equalTo(message)); - }}; + verify(mockEventBuilderHelper).helpBuildingEvent(any(EventBuilder.class)); + ArgumentCaptor eventArgumentCaptor = ArgumentCaptor.forClass(Event.class); + verify(mockConnection).send(eventArgumentCaptor.capture()); + Event event = eventArgumentCaptor.getValue(); + assertThat(event.getLevel(), equalTo(Event.Level.INFO)); + assertThat(event.getMessage(), equalTo(message)); } @Test @@ -105,18 +103,18 @@ public void testSendException() throws Exception { sentryClient.sendException(exception); - new Verifications() {{ - Event event; - mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); - mockConnection.send(event = withCapture()); - assertThat(event.getLevel(), equalTo(Event.Level.ERROR)); - assertThat(event.getMessage(), equalTo(message)); - assertThat(event.getSentryInterfaces(), hasKey(ExceptionInterface.EXCEPTION_INTERFACE)); - }}; + verify(mockEventBuilderHelper).helpBuildingEvent(any(EventBuilder.class)); + ArgumentCaptor eventArgumentCaptor = ArgumentCaptor.forClass(Event.class); + verify(mockConnection).send(eventArgumentCaptor.capture()); + Event event = eventArgumentCaptor.getValue(); + assertThat(event.getLevel(), equalTo(Event.Level.ERROR)); + assertThat(event.getMessage(), equalTo(message)); + assertThat(event.getSentryInterfaces(), hasKey(ExceptionInterface.EXCEPTION_INTERFACE)); } @Test - public void testAddRemoveBuilderHelpers(@Injectable final EventBuilderHelper mockBuilderHelper) throws Exception { + public void testAddRemoveBuilderHelpers() throws Exception { + EventBuilderHelper mockBuilderHelper = mock(EventBuilderHelper.class); assertThat(sentryClient.getBuilderHelpers(), not(contains(mockBuilderHelper))); sentryClient.addBuilderHelper(mockBuilderHelper); @@ -125,38 +123,31 @@ public void testAddRemoveBuilderHelpers(@Injectable final EventBuilderHelper moc assertThat(sentryClient.getBuilderHelpers(), not(contains(mockBuilderHelper))); } - @Test(expectedExceptions = UnsupportedOperationException.class) - public void testCantModifyBuilderHelpersDirectly(@Injectable final EventBuilderHelper mockBuilderHelper) throws Exception { - sentryClient.getBuilderHelpers().add(mockBuilderHelper); + @Test(expected = UnsupportedOperationException.class) + public void testCantModifyBuilderHelpersDirectly() throws Exception { + sentryClient.getBuilderHelpers().add(mock(EventBuilderHelper.class)); } @Test - public void testRunBuilderHelpers(@Injectable final EventBuilderHelper mockBuilderHelper, - @Injectable final EventBuilder mockEventBuilder) throws Exception { - sentryClient.addBuilderHelper(mockBuilderHelper); + public void testRunBuilderHelpers() throws Exception { + EventBuilderHelper mockBuilderHelper = mock(EventBuilderHelper.class); + EventBuilder mockEventBuilder = mock(EventBuilder.class); + sentryClient.addBuilderHelper(mockBuilderHelper); sentryClient.runBuilderHelpers(mockEventBuilder); - new Verifications() {{ - mockBuilderHelper.helpBuildingEvent(mockEventBuilder); - }}; + verify(mockBuilderHelper).helpBuildingEvent(eq(mockEventBuilder)); } @Test public void testCloseConnectionSuccessful() throws Exception { sentryClient.closeConnection(); - - new Verifications() {{ - mockConnection.close(); - }}; + verify(mockConnection).close(); } - @Test(expectedExceptions = RuntimeException.class) + @Test(expected = RuntimeException.class) public void testCloseConnectionFailed() throws Exception { - new NonStrictExpectations() {{ - mockConnection.close(); - result = new IOException(); - }}; + doThrow(new IOException()).when(mockConnection).close(); sentryClient.closeConnection(); } @@ -182,18 +173,17 @@ public void testFields() throws Exception { sentryClient.sendMessage("message"); - new Verifications() {{ - Event event; - mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); - mockConnection.send(event = withCapture()); - assertThat(event.getLevel(), equalTo(Event.Level.INFO)); - assertThat(event.getServerName(), equalTo(serverName)); - assertThat(event.getEnvironment(), equalTo(environment)); - assertThat(event.getDist(), equalTo(dist)); - assertThat(event.getRelease(), equalTo(release)); - assertThat(event.getTags(), equalTo(tags)); - assertThat(event.getExtra(), equalTo(extras)); - }}; + verify(mockEventBuilderHelper).helpBuildingEvent(any(EventBuilder.class)); + ArgumentCaptor eventArgumentCaptor = ArgumentCaptor.forClass(Event.class); + verify(mockConnection).send(eventArgumentCaptor.capture()); + Event event = eventArgumentCaptor.getValue(); + assertThat(event.getLevel(), equalTo(Event.Level.INFO)); + assertThat(event.getServerName(), equalTo(serverName)); + assertThat(event.getEnvironment(), equalTo(environment)); + assertThat(event.getDist(), equalTo(dist)); + assertThat(event.getRelease(), equalTo(release)); + assertThat(event.getTags(), equalTo(tags)); + assertThat(event.getExtra(), equalTo(extras)); } @Test @@ -209,10 +199,8 @@ public boolean shouldSend(Event event) { sentryClient.sendEvent(mockEvent); - new Verifications() {{ - mockConnection.send(mockEvent); times = 0; - assertThat(called.get(), is(true)); - }}; + verify(mockConnection, never()).send(eq(mockEvent)); + assertThat(called.get(), is(true)); } @Test @@ -228,10 +216,8 @@ public boolean shouldSend(Event event) { sentryClient.sendEvent(mockEvent); - new Verifications() {{ - mockConnection.send(mockEvent); - assertThat(called.get(), is(true)); - }}; + verify(mockConnection).send(eq(mockEvent)); + assertThat(called.get(), is(true)); } @Test diff --git a/sentry/src/test/java/io/sentry/SentryIT.java b/sentry/src/test/java/io/sentry/SentryIT.java index b6cb5ba762d..902d199680b 100644 --- a/sentry/src/test/java/io/sentry/SentryIT.java +++ b/sentry/src/test/java/io/sentry/SentryIT.java @@ -7,7 +7,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static mockit.Deencapsulation.getField; public class SentryIT extends BaseIT { @@ -66,9 +65,8 @@ public void testSuccess() throws Exception { } private boolean isLockedDown(SentryClient client) { - AbstractConnection connection = getField(client, "connection"); - LockdownManager lockdownManager = getField(connection, "lockdownManager"); - return lockdownManager.isLockedDown(); + AbstractConnection connection = (AbstractConnection) client.getConnection(); + return connection.isLockedDown(); } } diff --git a/sentry/src/test/java/io/sentry/SentryTest.java b/sentry/src/test/java/io/sentry/SentryTest.java index 13218a7657a..72135282b48 100644 --- a/sentry/src/test/java/io/sentry/SentryTest.java +++ b/sentry/src/test/java/io/sentry/SentryTest.java @@ -1,42 +1,40 @@ package io.sentry; -import io.sentry.config.Lookup; import io.sentry.connection.Connection; import io.sentry.connection.HttpConnection; import io.sentry.connection.NoopConnection; import io.sentry.context.ContextManager; import io.sentry.context.SingletonContextManager; import io.sentry.dsn.Dsn; -import io.sentry.environment.Version; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.helper.EventBuilderHelper; import io.sentry.event.interfaces.ExceptionInterface; -import mockit.Injectable; -import mockit.Tested; -import mockit.Verifications; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.net.URL; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; import static org.hamcrest.MatcherAssert.assertThat; -import static mockit.Deencapsulation.getField; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.sameInstance; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; public class SentryTest extends BaseTest { - @Tested private SentryClient sentryClient = null; - @Injectable private Connection mockConnection = null; - @Injectable private ContextManager contextManager = new SingletonContextManager(); - @Injectable private EventBuilderHelper mockEventBuilderHelper = null; - @BeforeMethod + @Before public void setup() { contextManager.clear(); + mockConnection = mock(Connection.class); + mockEventBuilderHelper = mock(EventBuilderHelper.class); + sentryClient = new SentryClient(mockConnection, contextManager); } @Test @@ -47,12 +45,11 @@ public void testSendEventStatically() throws Exception { Sentry.setStoredClient(sentryClient); Sentry.capture(event); - new Verifications() {{ - Event event; - mockConnection.send(event = withCapture()); - assertThat(event.getLevel(), equalTo(Event.Level.ERROR)); - assertThat(event.getMessage(), equalTo(message)); - }}; + ArgumentCaptor eventArgumentCaptor = ArgumentCaptor.forClass(Event.class); + verify(mockConnection).send(eventArgumentCaptor.capture()); + event = eventArgumentCaptor.getValue(); + assertThat(event.getLevel(), equalTo(Event.Level.ERROR)); + assertThat(event.getMessage(), equalTo(message)); } @Test @@ -64,13 +61,11 @@ public void testSendEventBuilderStatically() throws Exception { Sentry.setStoredClient(sentryClient); Sentry.capture(eventBuilder); - new Verifications() {{ - Event event; - mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); - mockConnection.send(event = withCapture()); - assertThat(event.getLevel(), equalTo(Event.Level.ERROR)); - assertThat(event.getMessage(), equalTo(message)); - }}; + ArgumentCaptor eventArgumentCaptor = ArgumentCaptor.forClass(Event.class); + verify(mockConnection).send(eventArgumentCaptor.capture()); + Event event = eventArgumentCaptor.getValue(); + assertThat(event.getLevel(), equalTo(Event.Level.ERROR)); + assertThat(event.getMessage(), equalTo(message)); } @Test @@ -81,13 +76,12 @@ public void testSendMessageStatically() throws Exception { Sentry.setStoredClient(sentryClient); Sentry.capture(message); - new Verifications() {{ - Event event; - mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); - mockConnection.send(event = withCapture()); - assertThat(event.getLevel(), equalTo(Event.Level.INFO)); - assertThat(event.getMessage(), equalTo(message)); - }}; + ArgumentCaptor eventArgumentCaptor = ArgumentCaptor.forClass(Event.class); + verify(mockEventBuilderHelper).helpBuildingEvent(any(EventBuilder.class)); + verify(mockConnection).send(eventArgumentCaptor.capture()); + Event event = eventArgumentCaptor.getValue(); + assertThat(event.getLevel(), equalTo(Event.Level.INFO)); + assertThat(event.getMessage(), equalTo(message)); } @Test @@ -99,68 +93,46 @@ public void testSendExceptionStatically() throws Exception { Sentry.setStoredClient(sentryClient); Sentry.capture(exception); - new Verifications() {{ - Event event; - mockEventBuilderHelper.helpBuildingEvent((EventBuilder) any); - mockConnection.send(event = withCapture()); - assertThat(event.getLevel(), equalTo(Event.Level.ERROR)); - assertThat(event.getMessage(), equalTo(message)); - assertThat(event.getSentryInterfaces(), hasKey(ExceptionInterface.EXCEPTION_INTERFACE)); - }}; + ArgumentCaptor eventArgumentCaptor = ArgumentCaptor.forClass(Event.class); + verify(mockEventBuilderHelper).helpBuildingEvent(any(EventBuilder.class)); + verify(mockConnection).send(eventArgumentCaptor.capture()); + Event event = eventArgumentCaptor.getValue(); + assertThat(event.getLevel(), equalTo(Event.Level.ERROR)); + assertThat(event.getMessage(), equalTo(message)); + assertThat(event.getSentryInterfaces(), hasKey(ExceptionInterface.EXCEPTION_INTERFACE)); } @Test public void testInitNoDsn() throws Exception { SentryClient sentryClient = Sentry.init(); - Object connection = getField(sentryClient, "connection"); - assertThat(connection, instanceOf(NoopConnection.class)); + assertThat(sentryClient.getConnection(), instanceOf(NoopConnection.class)); } @Test public void testInitNullDsn() throws Exception { SentryClient sentryClient = Sentry.init((String) null); - NoopConnection connection = getField(sentryClient, "connection"); - assertThat(connection, instanceOf(NoopConnection.class)); + assertThat(sentryClient.getConnection(), instanceOf(NoopConnection.class)); } @Test public void testInitNullFactory() throws Exception { SentryClient sentryClient = Sentry.init((SentryClientFactory) null); - NoopConnection connection = getField(sentryClient, "connection"); - assertThat(connection, instanceOf(NoopConnection.class)); + assertThat(sentryClient.getConnection(), instanceOf(NoopConnection.class)); } @Test public void testInitStringDsn() throws Exception { SentryClient sentryClient = Sentry.init("http://public:private@localhost:4567/1?async=false"); - HttpConnection connection = getField(sentryClient, "connection"); + Connection connection = sentryClient.getConnection(); assertThat(connection, instanceOf(HttpConnection.class)); - - URL sentryUrl = getField(connection, "sentryUrl"); - assertThat(sentryUrl.getHost(), equalTo("localhost")); - assertThat(sentryUrl.getProtocol(), equalTo("http")); - assertThat(sentryUrl.getPort(), equalTo(4567)); - assertThat(sentryUrl.getPath(), equalTo("/api/1/store/")); - - String authHeader = getField(connection, "authHeader"); - assertThat(authHeader, equalTo("Sentry sentry_version=6,sentry_client=sentry-java/" + Version.SDK_VERSION + ",sentry_key=public,sentry_secret=private")); } @Test public void testInitStringDsnAndFactory() throws Exception { SentryClient sentryClient = Sentry.init("http://public:private@localhost:4567/1?async=false", new DefaultSentryClientFactory()); - HttpConnection connection = getField(sentryClient, "connection"); + Connection connection = sentryClient.getConnection(); assertThat(connection, instanceOf(HttpConnection.class)); - - URL sentryUrl = getField(connection, "sentryUrl"); - assertThat(sentryUrl.getHost(), equalTo("localhost")); - assertThat(sentryUrl.getProtocol(), equalTo("http")); - assertThat(sentryUrl.getPort(), equalTo(4567)); - assertThat(sentryUrl.getPath(), equalTo("/api/1/store/")); - - String authHeader = getField(connection, "authHeader"); - assertThat(authHeader, equalTo("Sentry sentry_version=6,sentry_client=sentry-java/" + Version.SDK_VERSION + ",sentry_key=public,sentry_secret=private")); } @Test @@ -176,5 +148,4 @@ public SentryClient createSentryClient(Dsn dsn) { SentryClient sentryClient = Sentry.init(specificInstanceFactory); assertThat(sentryClient, sameInstance(specificInstance)); } - } diff --git a/sentry/src/test/java/io/sentry/SentryUncaughtExceptionHandlerTest.java b/sentry/src/test/java/io/sentry/SentryUncaughtExceptionHandlerTest.java index a21c48a71ee..87c7a644f3c 100644 --- a/sentry/src/test/java/io/sentry/SentryUncaughtExceptionHandlerTest.java +++ b/sentry/src/test/java/io/sentry/SentryUncaughtExceptionHandlerTest.java @@ -1,21 +1,21 @@ package io.sentry; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.Test; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + public class SentryUncaughtExceptionHandlerTest { private Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler; - @BeforeMethod + @Before public void setup() { defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); } - @AfterMethod + @After public void teardown() { Thread.setDefaultUncaughtExceptionHandler(defaultUncaughtExceptionHandler); } diff --git a/sentry/src/test/java/io/sentry/buffer/DiskBufferTest.java b/sentry/src/test/java/io/sentry/buffer/DiskBufferTest.java index 33ebd3e7369..5770700d690 100644 --- a/sentry/src/test/java/io/sentry/buffer/DiskBufferTest.java +++ b/sentry/src/test/java/io/sentry/buffer/DiskBufferTest.java @@ -5,13 +5,13 @@ import io.sentry.event.BreadcrumbBuilder; import io.sentry.event.Event; import io.sentry.event.EventBuilder; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; -import org.testng.collections.Lists; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -25,12 +25,12 @@ public class DiskBufferTest extends BaseTest { private DiskBuffer buffer; - @BeforeMethod + @Before public void setup() { buffer = new DiskBuffer(BUFFER_DIR, maxEvents); } - @AfterMethod + @After public void teardown() { delete(BUFFER_DIR); } @@ -38,7 +38,7 @@ public void teardown() { @Test public void testAddAndDiscard() throws IOException { Breadcrumb breadcrumb = new BreadcrumbBuilder().setMessage("MESSAGE").build(); - List breadcrumbs = Lists.newArrayList(); + List breadcrumbs = new ArrayList<>(); breadcrumbs.add(breadcrumb); Event event1 = new EventBuilder().withBreadcrumbs(breadcrumbs).build(); diff --git a/sentry/src/test/java/io/sentry/config/provider/EnvironmentConfigurationProviderTest.java b/sentry/src/test/java/io/sentry/config/provider/EnvironmentConfigurationProviderTest.java new file mode 100644 index 00000000000..aab10490a4b --- /dev/null +++ b/sentry/src/test/java/io/sentry/config/provider/EnvironmentConfigurationProviderTest.java @@ -0,0 +1,37 @@ +package io.sentry.config.provider; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class EnvironmentConfigurationProviderTest { + // the environment variables used in this test class have to be defined in the surefire configuration in pom.xml + + @Test + public void testCanReadEnvironment() throws Exception { + // given + EnvironmentConfigurationProvider provider = new EnvironmentConfigurationProvider(); + + // when + String val = provider.getProperty("test.property"); + + // then + assertNotNull(val); + assertThat(val, is(System.getenv("SENTRY_TEST_PROPERTY"))); + } + + @Test + public void testHonorsCustomEnvVarPrefix() throws Exception { + // given + EnvironmentConfigurationProvider provider = new EnvironmentConfigurationProvider("CUSTOM_PREFIX_"); + + // when + String val = provider.getProperty("test.property"); + + // then + assertNotNull(val); + assertThat(val, is(System.getenv("CUSTOM_PREFIX_TEST_PROPERTY"))); + } +} diff --git a/sentry/src/test/java/io/sentry/config/provider/JndiConfigurationProviderTest.java b/sentry/src/test/java/io/sentry/config/provider/JndiConfigurationProviderTest.java index 48f1fa68d90..0390c07b441 100644 --- a/sentry/src/test/java/io/sentry/config/provider/JndiConfigurationProviderTest.java +++ b/sentry/src/test/java/io/sentry/config/provider/JndiConfigurationProviderTest.java @@ -2,13 +2,18 @@ import static io.sentry.config.provider.JndiConfigurationProvider.DEFAULT_JNDI_PREFIX; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.NoInitialContextException; import io.sentry.config.provider.JndiConfigurationProvider.JndiContextProvider; import org.junit.Test; +import org.mockito.Mockito; public class JndiConfigurationProviderTest { @@ -45,4 +50,35 @@ public void testUsesCustomJNDIRootPath() throws Exception { // then assertEquals("val", val); } + + @Test + public void testReturnsNullOnNamingException() throws Exception { + testReturnsNullOnException(new NamingException()); + } + + @Test + public void testReturnsNullOnNoInitialContextException() throws Exception { + testReturnsNullOnException(new NoInitialContextException()); + } + + @Test + public void testReturnsNullOnRuntimeException() throws Exception { + testReturnsNullOnException(new IllegalStateException()); + } + + private void testReturnsNullOnException(Exception e) throws Exception { + // given + JndiContextProvider jndiProvider = mock(JndiContextProvider.class); + Context jndiContext = mock(Context.class); + when(jndiProvider.getContext()).thenReturn(jndiContext); + when(jndiContext.lookup(anyString())).thenThrow(e); + + JndiConfigurationProvider provider = new JndiConfigurationProvider("something", jndiProvider); + + // when + String val = provider.getProperty("property"); + + // then + assertNull(val); + } } diff --git a/sentry/src/test/java/io/sentry/config/provider/SystemPropertiesConfigurationProviderTest.java b/sentry/src/test/java/io/sentry/config/provider/SystemPropertiesConfigurationProviderTest.java new file mode 100644 index 00000000000..b26d91b10b6 --- /dev/null +++ b/sentry/src/test/java/io/sentry/config/provider/SystemPropertiesConfigurationProviderTest.java @@ -0,0 +1,48 @@ +package io.sentry.config.provider; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +import java.util.Random; + +import org.junit.Test; + +public class SystemPropertiesConfigurationProviderTest { + + private Random rnd = new Random(); + + @Test + public void testCanReadFromSystemProperties() throws Exception { + // given + SystemPropertiesConfigurationProvider provider = new SystemPropertiesConfigurationProvider(); + String propertyName = initRandomSystemProperty(SystemPropertiesConfigurationProvider.DEFAULT_SYSTEM_PROPERTY_PREFIX); + + // when + String val = provider.getProperty(propertyName); + + // then + assertNotNull(val); + assertThat(val, is("testValue")); + } + + @Test + public void testHonorsCustomPrefix() throws Exception { + // given + SystemPropertiesConfigurationProvider provider = new SystemPropertiesConfigurationProvider("custom.prefix"); + String propertyName = initRandomSystemProperty("custom.prefix"); + + // when + String val = provider.getProperty(propertyName); + + // then + assertNotNull(val); + assertThat(val, is("testValue")); + } + + private String initRandomSystemProperty(String prefix) { + String randomName = "system.properties.configuration.provider.test-" + rnd.nextLong(); + System.setProperty(prefix + randomName, "testValue"); + return randomName; + } +} diff --git a/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java b/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java index ff0e15a1a06..9a29ad1ef27 100644 --- a/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/AbstractConnectionTest.java @@ -3,37 +3,45 @@ import io.sentry.BaseTest; import io.sentry.environment.Version; import io.sentry.time.FixedClock; -import mockit.*; import io.sentry.event.Event; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; import java.util.Date; -import java.util.HashSet; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import static mockit.Deencapsulation.getField; -import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.testng.AssertJUnit.fail; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.withSettings; public class AbstractConnectionTest extends BaseTest { private static final Date FIXED_DATE = new Date(1483228800L); - @Injectable private final String publicKey = "9bcf4a8c-f353-4f25-9dda-76a873fff905"; - @Injectable private final String secretKey = "56a9d05e-9032-4fdd-8f67-867d526422f9"; - @Tested + private AbstractConnection abstractConnection = null; - private FixedClock fixedClock = new FixedClock(FIXED_DATE); - private LockdownManager lockdownManager = new LockdownManager(fixedClock); + private FixedClock fixedClock; + private LockdownManager lockdownManager; - @BeforeMethod + @Before public void setup() { fixedClock = new FixedClock(FIXED_DATE); - lockdownManager = new LockdownManager(fixedClock); + + lockdownManager = mock(LockdownManager.class, withSettings() + .useConstructor(fixedClock) + .defaultAnswer(CALLS_REAL_METHODS)); + + abstractConnection = mock(AbstractConnection.class, withSettings() + .useConstructor(publicKey, secretKey, lockdownManager) + .defaultAnswer(CALLS_REAL_METHODS)); } @Test @@ -47,31 +55,28 @@ public void testAuthHeader() throws Exception { } @Test - public void testSuccessfulSendCallsDoSend(@Injectable final Event mockEvent) throws Exception { + public void testSuccessfulSendCallsDoSend() throws Exception { + final Event mockEvent = mock(Event.class); + abstractConnection.send(mockEvent); - new Verifications() {{ - abstractConnection.doSend(mockEvent); - }}; + verify(abstractConnection).doSend(mockEvent); } @Test - public void testExceptionOnSendStartLockDown(@Injectable final Event mockEvent) throws Exception { - setField(abstractConnection, "lockdownManager", lockdownManager); + public void testExceptionOnSendStartLockDown() throws Exception { + final Event mockEvent = mock(Event.class); - new NonStrictExpectations() {{ - abstractConnection.doSend((Event) any); - result = new ConnectionException(); - }}; + doThrow(new ConnectionException()).when(abstractConnection).doSend(any(Event.class)); try { abstractConnection.send(mockEvent); - } catch (Exception e) { + } catch (ConnectionException e) { // ignore } - Date lockdownStartTime = getField(lockdownManager, "lockdownStartTime"); - assertThat(lockdownStartTime, is(FIXED_DATE)); + verify(lockdownManager).lockdown(any(ConnectionException.class)); + assertTrue(lockdownManager.isLockedDown()); // Send while in lockdown throws LockedDownException try { @@ -83,22 +88,19 @@ public void testExceptionOnSendStartLockDown(@Injectable final Event mockEvent) } @Test - public void testLockDownDoublesTheTime(@Injectable final Event mockEvent) throws Exception { - setField(abstractConnection, "lockdownManager", lockdownManager); + public void testLockDownDoublesTheTime() throws Exception { + Event mockEvent = mock(Event.class); - new NonStrictExpectations() {{ - abstractConnection.doSend((Event) any); - result = new ConnectionException(); - }}; + doThrow(new ConnectionException()).when(abstractConnection).doSend(any(Event.class)); try { abstractConnection.send(mockEvent); - } catch (Exception e) { + } catch (ConnectionException e) { // ignore } // Check for default lockdown time - long lockdownTimeAfter = getField(lockdownManager, "lockdownTime"); + long lockdownTimeAfter = lockdownManager.getLockdownTime(); assertThat(lockdownTimeAfter, is(LockdownManager.DEFAULT_BASE_LOCKDOWN_TIME)); // Roll forward by the base lockdown time, allowing the lockdown to retried @@ -107,38 +109,46 @@ public void testLockDownDoublesTheTime(@Injectable final Event mockEvent) throws // Send a second event, doubling the lockdown try { abstractConnection.send(mockEvent); - } catch (Exception e) { + } catch (ConnectionException e) { // ignore } // Check for doubled lockdown time - long lockdownTimeAfter2 = getField(lockdownManager, "lockdownTime"); + long lockdownTimeAfter2 = lockdownManager.getLockdownTime(); assertThat(lockdownTimeAfter2, is(LockdownManager.DEFAULT_BASE_LOCKDOWN_TIME * 2)); } @Test - public void testLockDownDoesntDoubleItAtMax(@Injectable final Event mockEvent) throws Exception { - setField(abstractConnection, "lockdownManager", lockdownManager); - setField(lockdownManager, "lockdownTime", LockdownManager.DEFAULT_MAX_LOCKDOWN_TIME); - setField(lockdownManager, "lockdownStartTime", fixedClock.date()); + public void testLockDownDoesntDoubleItAtMax() throws Exception { + Event mockEvent = mock(Event.class); + + lockdownManager.setBaseLockdownTime(LockdownManager.DEFAULT_MAX_LOCKDOWN_TIME); + + doThrow(new ConnectionException()).when(abstractConnection).doSend(any(Event.class)); + + try { + abstractConnection.send(mockEvent); + } catch (ConnectionException e) { + // ignore + } - new NonStrictExpectations() {{ - abstractConnection.doSend((Event) any); - result = new ConnectionException(); - }}; + long lockdownTimeAfter = lockdownManager.getLockdownTime(); + assertThat(lockdownTimeAfter, is(LockdownManager.DEFAULT_MAX_LOCKDOWN_TIME)); try { abstractConnection.send(mockEvent); - } catch (Exception e) { + } catch (LockedDownException e) { // ignore } - long lockdownTimeAfter = getField(lockdownManager, "lockdownTime"); + lockdownTimeAfter = lockdownManager.getLockdownTime(); assertThat(lockdownTimeAfter, is(LockdownManager.DEFAULT_MAX_LOCKDOWN_TIME)); } @Test - public void testEventSendCallbackSuccess(@Injectable final Event mockEvent) throws Exception { + public void testEventSendCallbackSuccess() throws Exception { + Event mockEvent = mock(Event.class); + final AtomicBoolean callbackCalled = new AtomicBoolean(false); EventSendCallback callback = new EventSendCallback() { @@ -153,17 +163,16 @@ public void onSuccess(Event event) { } }; - HashSet callbacks = new HashSet<>(); - callbacks.add(callback); - setField(abstractConnection, "eventSendCallbacks", callbacks); + abstractConnection.addEventSendCallback(callback); + abstractConnection.send(mockEvent); assertThat(callbackCalled.get(), is(true)); } @Test - public void testEventSendCallbackFailure(@Injectable final Event mockEvent) throws Exception { + public void testEventSendCallbackFailure() throws Exception { final AtomicBoolean callbackCalled = new AtomicBoolean(false); EventSendCallback callback = new EventSendCallback() { @@ -178,18 +187,16 @@ public void onSuccess(Event event) { } }; - HashSet callbacks = new HashSet<>(); - callbacks.add(callback); - setField(abstractConnection, "eventSendCallbacks", callbacks); - new NonStrictExpectations() {{ - abstractConnection.doSend((Event) any); - result = new ConnectionException(); - }}; + abstractConnection.addEventSendCallback(callback); + + doThrow(new ConnectionException()).when(abstractConnection).doSend(any(Event.class)); + + Event mockEvent = mock(Event.class); try { abstractConnection.send(mockEvent); - } catch (Exception e) { + } catch (ConnectionException e) { // ignore } @@ -197,22 +204,22 @@ public void onSuccess(Event event) { } @Test - public void testRecommendedLockdownRespected(@Injectable final Event mockEvent) throws Exception { - setField(abstractConnection, "lockdownManager", lockdownManager); + public void testRecommendedLockdownRespected() throws Exception { + Event mockEvent = mock(Event.class); final long recommendedLockdownWaitTime = 12345L; - new NonStrictExpectations() {{ - abstractConnection.doSend((Event) any); - result = new ConnectionException("Message", null, recommendedLockdownWaitTime, HttpConnection.HTTP_TOO_MANY_REQUESTS); - }}; + + doThrow(new ConnectionException("Message", null, recommendedLockdownWaitTime, + HttpConnection.HTTP_TOO_MANY_REQUESTS)) + .when(abstractConnection).doSend(any(Event.class)); try { abstractConnection.send(mockEvent); - } catch (Exception e) { + } catch (ConnectionException e) { // ignore } - long lockdownTimeAfter = getField(lockdownManager, "lockdownTime"); + long lockdownTimeAfter = lockdownManager.getLockdownTime(); assertThat(lockdownTimeAfter, is(recommendedLockdownWaitTime)); } } diff --git a/sentry/src/test/java/io/sentry/connection/AsyncConnectionTest.java b/sentry/src/test/java/io/sentry/connection/AsyncConnectionTest.java index b00939b6c54..d623a5dd665 100644 --- a/sentry/src/test/java/io/sentry/connection/AsyncConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/AsyncConnectionTest.java @@ -1,157 +1,151 @@ package io.sentry.connection; -import io.sentry.BaseTest; -import io.sentry.SentryClient; -import mockit.*; -import io.sentry.environment.SentryEnvironment; -import io.sentry.event.Event; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; +import io.sentry.BaseTest; +import io.sentry.environment.SentryEnvironment; +import io.sentry.event.Event; +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + public class AsyncConnectionTest extends BaseTest { - @Tested private AsyncConnection asyncConnection = null; - @Injectable private Connection mockConnection = null; - @Injectable private ExecutorService mockExecutorService = null; - @Injectable("false") - private boolean mockGracefulShutdown = false; - @Injectable private long mockTimeout = 10000L; - @SuppressWarnings("unused") - @Mocked("addShutdownHook") - private Runtime mockRuntime = null; - @BeforeMethod + @Before public void setUp() throws Exception { - new NonStrictExpectations() {{ - mockExecutorService.awaitTermination(anyLong, (TimeUnit) any); - result = true; - }}; + mockExecutorService = mock(ExecutorService.class); + when(mockExecutorService.awaitTermination(anyLong(), any(TimeUnit.class))).thenReturn(true); + + mockConnection = mock(Connection.class); + + asyncConnection = mock(AsyncConnection.class, withSettings() + .useConstructor(mockConnection, mockExecutorService, false, mockTimeout) + .defaultAnswer(CALLS_REAL_METHODS)); } @Test public void verifyShutdownHookIsAddedWhenGraceful() throws Exception { - // Ensure that the shutdown hooks for the unused @Tested instance are removed - asyncConnection.close(); + AsyncConnection conn = new AsyncConnection(mockConnection, mockExecutorService, true, mockTimeout); - new AsyncConnection(mockConnection, mockExecutorService, true, mockTimeout); + boolean registered = Runtime.getRuntime().removeShutdownHook(conn.shutDownHook); - new Verifications() {{ - mockRuntime.addShutdownHook((Thread) any); - }}; + assertTrue(registered); } @Test public void verifyShutdownHookNotAddedWhenNotGraceful() throws Exception { - // Ensure that the shutdown hooks for the unused @Tested instance are removed - asyncConnection.close(); + AsyncConnection conn = new AsyncConnection(mockConnection, mockExecutorService, false, mockTimeout); - new AsyncConnection(mockConnection, mockExecutorService, false, mockTimeout); + boolean registered = Runtime.getRuntime().removeShutdownHook(conn.shutDownHook); - new Verifications() {{ - mockRuntime.addShutdownHook((Thread) any); - times = 0; - }}; + assertFalse(registered); } @Test - public void verifyShutdownHookSetManagedBySentryAndCloseConnection( - @SuppressWarnings("unused") @Mocked({"startManagingThread", "stopManagingThread"}) SentryClient mockSentryClient) + public void verifyShutdownHookSetManagedBySentryAndCloseConnection() throws Exception { - // Ensure that the shutdown hooks for the unused @Tested instance are removed - asyncConnection.close(); - - new NonStrictExpectations() {{ - mockRuntime.addShutdownHook((Thread) any); - result = new Delegate() { - @SuppressWarnings("unused") - public void addShutdownHook(Thread hook) { - hook.run(); - } - }; - }}; - - new AsyncConnection(mockConnection, mockExecutorService, true, mockTimeout); - - new VerificationsInOrder() {{ - SentryEnvironment.startManagingThread(); - mockConnection.close(); - SentryEnvironment.stopManagingThread(); - }}; + //instantiate a new async connection installing the shutdown hook + AsyncConnection conn = new AsyncConnection(mockConnection, mockExecutorService, true, mockTimeout); + + assertFalse(SentryEnvironment.isManagingThread()); + + // the shutdown hook should start managing the thread and call close on the connection + // we take advantage of that and we check below that during the call to mockConnection.close() + // the thread is indeed managed. + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + assertTrue(SentryEnvironment.isManagingThread()); + return null; + } + }).when(mockConnection).close(); + + // this simulates the running of the shutdown hook + //noinspection CallToThreadRun + ((Thread) conn.shutDownHook).run();; + + verify(mockConnection).close(); + + // the thread should no longer be managed after the shutdown hook ran + assertFalse(SentryEnvironment.isManagingThread()); } @Test - public void ensureFailingShutdownHookStopsBeingManaged( - @SuppressWarnings("unused") @Mocked({"startManagingThread", "stopManagingThread"}) SentryClient mockSentryClient) + public void ensureFailingShutdownHookStopsBeingManaged() throws Exception { - // Ensure that the shutdown hooks for the unused @Tested instance are removed - asyncConnection.close(); - - new NonStrictExpectations() {{ - mockRuntime.addShutdownHook((Thread) any); - result = new Delegate() { - @SuppressWarnings("unused") - public void addShutdownHook(Thread hook) { - hook.run(); - } - }; - mockConnection.close(); - result = new RuntimeException("Close operation failed"); - }}; - - new AsyncConnection(mockConnection, mockExecutorService, true, mockTimeout); - - new Verifications() {{ - SentryEnvironment.stopManagingThread(); - }}; + //instantiate a new async connection installing the shutdown hook + AsyncConnection conn = new AsyncConnection(mockConnection, mockExecutorService, true, mockTimeout); + + assertFalse(SentryEnvironment.isManagingThread()); + + // the shutdown hook should start managing the thread and call close on the connection + // we take advantage of that and we check below that during the call to mockConnection.close() + // the thread is indeed managed. The close() operation then fails. + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + assertTrue(SentryEnvironment.isManagingThread()); + throw new RuntimeException("Close operation failed."); + } + }).when(mockConnection).close(); + + // this simulates the running of the shutdown hook + //noinspection CallToThreadRun + ((Thread) conn.shutDownHook).run();; + + verify(mockConnection).close(); + + // the thread should no longer be managed after the shutdown hook ran even if the close() failed. + assertFalse(SentryEnvironment.isManagingThread()); } @Test public void testCloseOperation() throws Exception { asyncConnection.close(); - new Verifications() {{ - mockConnection.close(); - mockExecutorService.awaitTermination(anyLong, (TimeUnit) any); - }}; + verify(mockConnection).close(); + verify(mockExecutorService).awaitTermination(anyLong(), any(TimeUnit.class)); } @Test - public void testSendEventQueued(@Injectable final Event mockEvent) throws Exception { - asyncConnection.send(mockEvent); - - new Verifications() {{ - mockExecutorService.execute((Runnable) any); - }}; + public void testSendEventQueued() throws Exception { + asyncConnection.send(mock(Event.class)); - // Ensure that the shutdown hooks for the used @Tested instance are removed - asyncConnection.close(); + verify(mockExecutorService).execute(any(Runnable.class)); } @Test - public void testQueuedEventExecuted(@Injectable final Event mockEvent) throws Exception { - new NonStrictExpectations() {{ - mockExecutorService.execute((Runnable) any); - result = new Delegate() { - @SuppressWarnings("unused") - public void execute(Runnable command) { - command.run(); - } - }; - }}; - - asyncConnection.send(mockEvent); - - new Verifications() {{ - mockConnection.send(mockEvent); - }}; - - // Ensure that the shutdown hooks for the used @Tested instance are removed - asyncConnection.close(); + public void testQueuedEventExecuted() throws Exception { + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + ((Runnable) invocation.getArgument(0)).run(); + return null; + } + }).when(mockExecutorService).execute(any(Runnable.class)); + + Event ev = mock(Event.class); + + asyncConnection.send(ev); + + verify(mockConnection).send(eq(ev)); } } diff --git a/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java b/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java index e0cc4e27f0f..2de22065ac4 100644 --- a/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java @@ -5,11 +5,12 @@ import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.time.FixedClock; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; -import org.testng.collections.Lists; -import org.testng.collections.Sets; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import java.io.IOException; import java.io.NotSerializableException; @@ -18,10 +19,16 @@ import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; -import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.core.Is.is; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.withSettings; public class BufferedConnectionTest extends BaseTest { private static final Date FIXED_DATE = new Date(1483228800L); @@ -30,55 +37,43 @@ public class BufferedConnectionTest extends BaseTest { private Set bufferedEvents; private List sentEvents; - private Buffer mockBuffer; - private Connection mockConnection; + private AbstractConnection mockConnection; private Connection bufferedConnection; - private ConnectionException connectionException; - - @BeforeMethod - public void setup() { - bufferedEvents = Sets.newHashSet(); - sentEvents = Lists.newArrayList(); - connectionException = null; - - mockConnection = new AbstractConnection("public", "private") { - @Override - protected void doSend(Event event) throws ConnectionException { - if (connectionException != null) { - throw connectionException; - } - - sentEvents.add(event); - } - - @Override - public void addEventSendCallback(EventSendCallback eventSendCallback) { - - } - - @Override - public void close() throws IOException { - - } - }; + @Before + public void setup() throws Exception { + bufferedEvents = new HashSet<>(); + sentEvents = new ArrayList<>(); fixedClock = new FixedClock(FIXED_DATE); lockdownManager = new LockdownManager(fixedClock); - mockBuffer = new Buffer() { + mockConnection = mock(AbstractConnection.class, withSettings() + .useConstructor("public", "private", lockdownManager) + .defaultAnswer(Mockito.CALLS_REAL_METHODS)); + + collectSentEvents(); + doNothing().when(mockConnection).addEventSendCallback(any(EventSendCallback.class)); + + Buffer mockBuffer = new Buffer() { @Override public void add(Event event) { - bufferedEvents.add(event); + synchronized (bufferedEvents) { + bufferedEvents.add(event); + } } @Override public void discard(Event event) { - bufferedEvents.remove(event); + synchronized (bufferedEvents) { + bufferedEvents.remove(event); + } } @Override public Iterator getEvents() { - return Lists.newArrayList(bufferedEvents).iterator(); + synchronized (bufferedEvents) { + return new ArrayList<>(bufferedEvents).iterator(); + } } }; @@ -88,22 +83,34 @@ public Iterator getEvents() { this.bufferedConnection = innerBufferedConnection.wrapConnectionWithBufferWriter(innerBufferedConnection); } - @AfterMethod + private void collectSentEvents() { + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + sentEvents.add(invocation.getArgument(0, Event.class)); + return null; + } + }).when(mockConnection).doSend(any(Event.class)); + } + + @After public void teardown() throws IOException { bufferedConnection.close(); } @Test public void test() throws Exception { - setField(mockConnection, "lockdownManager", lockdownManager); - Event event = new EventBuilder().build(); - connectionException = new ConnectionException(); + + doThrow(new ConnectionException()).when(mockConnection).doSend(any(Event.class)); + try { bufferedConnection.send(event); - } catch (Exception e) { + fail(); + } catch (ConnectionException e) { } + assertThat(bufferedEvents.size(), equalTo(1)); assertThat(bufferedEvents.iterator().next(), equalTo(event)); @@ -111,7 +118,8 @@ public void test() throws Exception { Event event2 = new EventBuilder().build(); try { bufferedConnection.send(event2); - } catch (Exception e) { + fail(); + } catch (LockedDownException e) { } assertThat(bufferedEvents.size(), equalTo(2)); @@ -119,13 +127,17 @@ public void test() throws Exception { // End the lockdown fixedClock.tick(LockdownManager.DEFAULT_MAX_LOCKDOWN_TIME, TimeUnit.MILLISECONDS); - connectionException = null; + collectSentEvents(); + waitUntilTrue(1000, new Callable() { @Override public Boolean call() throws Exception { - return bufferedEvents.size() == 0; + synchronized (bufferedEvents) { + return bufferedEvents.size() == 0; + } } }); + assertThat(bufferedEvents.size(), equalTo(0)); assertThat(sentEvents.contains(event), is(true)); assertThat(sentEvents.contains(event2), is(true)); @@ -134,10 +146,12 @@ public Boolean call() throws Exception { @Test public void testNotSerializableNotBuffered() throws Exception { Event event = new EventBuilder().build(); - connectionException = new ConnectionException("NonSerializable", new NotSerializableException()); + doThrow(new ConnectionException("NonSerializable", new NotSerializableException())) + .when(mockConnection).send(any(Event.class)); + try { bufferedConnection.send(event); - } catch (Exception e) { + } catch (ConnectionException e) { } assertThat(bufferedEvents.size(), equalTo(0)); @@ -146,10 +160,11 @@ public void testNotSerializableNotBuffered() throws Exception { @Test public void test500NotBuffered() throws Exception { Event event = new EventBuilder().build(); - connectionException = new ConnectionException("500", new IOException(), null, HttpURLConnection.HTTP_INTERNAL_ERROR); + doThrow(new ConnectionException("500", new IOException(), null, HttpURLConnection.HTTP_INTERNAL_ERROR)) + .when(mockConnection).send(any(Event.class)); try { bufferedConnection.send(event); - } catch (Exception e) { + } catch (ConnectionException e) { } assertThat(bufferedEvents.size(), equalTo(0)); @@ -158,7 +173,8 @@ public void test500NotBuffered() throws Exception { @Test public void test429IsNotBuffered() throws Exception { Event event = new EventBuilder().build(); - connectionException = new ConnectionException("429", new IOException(), null, HttpConnection.HTTP_TOO_MANY_REQUESTS); + doThrow(new ConnectionException("429", new IOException(), null, HttpConnection.HTTP_TOO_MANY_REQUESTS)) + .when(mockConnection).send(any(Event.class)); try { bufferedConnection.send(event); } catch (Exception e) { @@ -170,7 +186,8 @@ public void test429IsNotBuffered() throws Exception { @Test public void testNoResponseCodeIsBuffered() throws Exception { Event event = new EventBuilder().build(); - connectionException = new ConnectionException("NoResponseCode", new IOException(), null, null); + doThrow(new ConnectionException("NoResponseCode", new IOException(), null, null)) + .when(mockConnection).send(any(Event.class)); try { bufferedConnection.send(event); } catch (Exception e) { @@ -178,5 +195,4 @@ public void testNoResponseCodeIsBuffered() throws Exception { } assertThat(bufferedEvents.size(), equalTo(1)); } - } diff --git a/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java b/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java index 8aa4eda7771..0dfa3eadb63 100644 --- a/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/HttpConnectionTest.java @@ -1,12 +1,12 @@ package io.sentry.connection; import io.sentry.BaseTest; -import mockit.*; import io.sentry.environment.SentryEnvironment; import io.sentry.event.Event; import io.sentry.marshaller.Marshaller; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; @@ -18,169 +18,146 @@ import java.net.Proxy; import java.net.URI; import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; -import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.isEmptyOrNullString; import static org.hamcrest.Matchers.not; -import static org.testng.Assert.fail; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class HttpConnectionTest extends BaseTest { - @Injectable - private final String publicKey = "6cc48e8f-380c-44cc-986b-f566247a2af5"; - @Injectable - private final String secretKey = "e30cca23-3f97-470b-a8c2-e29b33dd25e0"; - @Injectable - private Proxy proxy = null; - @Injectable - private EventSampler eventSampler = null; - @Tested private HttpConnection httpConnection = null; - @Injectable private HttpsURLConnection mockUrlConnection = null; - @Injectable private Marshaller mockMarshaller = null; - @Injectable - private URL mockUrl = null; - @Injectable - private OutputStream mockOutputStream = null; - @Injectable - private InputStream mockInputStream = null; - - @BeforeMethod + + @Before public void setUp() throws Exception { - new NonStrictExpectations() {{ - eventSampler.shouldSendEvent((Event) any); - result = true; - mockUrl.openConnection((Proxy) any); - result = mockUrlConnection; - mockUrlConnection.getOutputStream(); - result = mockOutputStream; - mockUrlConnection.getInputStream(); - result = mockInputStream; - }}; + EventSampler eventSampler = mock(EventSampler.class); + when(eventSampler.shouldSendEvent(any(Event.class))).thenReturn(true); + + mockMarshaller = mock(Marshaller.class); + + OutputStream mockOutputStream = mock(OutputStream.class); + InputStream mockInputStream = mock(InputStream.class); + + mockUrlConnection = mock(HttpsURLConnection.class); + when(mockUrlConnection.getOutputStream()).thenReturn(mockOutputStream); + when(mockUrlConnection.getInputStream()).thenReturn(mockInputStream); + + URL mockUrl = new URL("sentry", "host", 80, "/some/file", new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL u) throws IOException { + return mockUrlConnection; + } + + @Override + protected URLConnection openConnection(URL u, Proxy p) throws IOException { + return mockUrlConnection; + } + }); + Proxy proxy = Proxy.NO_PROXY; + + String secretKey = "e30cca23-3f97-470b-a8c2-e29b33dd25e0"; + String publicKey = "6cc48e8f-380c-44cc-986b-f566247a2af5"; + httpConnection = new HttpConnection(mockUrl, publicKey, secretKey, proxy, eventSampler); + httpConnection.setMarshaller(mockMarshaller); } @Test - public void testConnectionTimeout(@Injectable final Event mockEvent) throws Exception { + public void testConnectionTimeout() throws Exception { final int timeout = 12; httpConnection.setConnectionTimeout(timeout); - httpConnection.send(mockEvent); + httpConnection.send(mock(Event.class)); - new Verifications() {{ - mockUrlConnection.setConnectTimeout(timeout); - }}; + verify(mockUrlConnection).setConnectTimeout(eq(timeout)); } @Test - public void testReadTimeout(@Injectable final Event mockEvent) throws Exception { + public void testReadTimeout() throws Exception { final int timeout = 42; httpConnection.setReadTimeout(timeout); - httpConnection.send(mockEvent); + httpConnection.send(mock(Event.class)); - new Verifications() {{ - mockUrlConnection.setReadTimeout(timeout); - }}; + verify(mockUrlConnection).setReadTimeout(eq(timeout)); } @Test - public void testByPassSecurityDefaultsToFalse(@Injectable final Event mockEvent) throws Exception { - httpConnection.send(mockEvent); - - new Verifications() {{ - mockUrlConnection.setHostnameVerifier((HostnameVerifier) any); - times = 0; - }}; + public void testByPassSecurityDefaultsToFalse() throws Exception { + httpConnection.send(mock(Event.class)); + verify(mockUrlConnection, never()).setHostnameVerifier(any(HostnameVerifier.class)); } @Test - public void testByPassSecurity(@Injectable final Event mockEvent, - @Injectable("fakehostna.me") final String mockHostname, - @Injectable final SSLSession mockSslSession) throws Exception { + public void testByPassSecurity() throws Exception { httpConnection.setBypassSecurity(true); - httpConnection.send(mockEvent); + httpConnection.send(mock(Event.class)); - new Verifications() {{ - HostnameVerifier hostnameVerifier; - mockUrlConnection.setHostnameVerifier(hostnameVerifier = withCapture()); - assertThat(hostnameVerifier.verify(mockHostname, mockSslSession), is(true)); - }}; + ArgumentCaptor captor = ArgumentCaptor.forClass(HostnameVerifier.class); + verify(mockUrlConnection).setHostnameVerifier(captor.capture()); + assertThat(captor.getValue().verify("fakehostna.me", mock(SSLSession.class)), is(true)); } @Test - public void testDontByPassSecurity(@Injectable final Event mockEvent) throws Exception { + public void testDontByPassSecurity() throws Exception { httpConnection.setBypassSecurity(false); - httpConnection.send(mockEvent); + httpConnection.send(mock(Event.class)); - new Verifications() {{ - mockUrlConnection.setHostnameVerifier((HostnameVerifier) any); - times = 0; - }}; + verify(mockUrlConnection, never()).setHostnameVerifier(any(HostnameVerifier.class)); } @Test - public void testContentMarshalled(@Injectable final Event mockEvent) throws Exception { + public void testContentMarshalled() throws Exception { + Event mockEvent = mock(Event.class); httpConnection.send(mockEvent); - - new Verifications() {{ - mockMarshaller.marshall(mockEvent, (OutputStream) any); - }}; + verify(mockMarshaller).marshall(eq(mockEvent), any(OutputStream.class)); } @Test - public void testAuthHeaderSent(@Injectable final Event mockEvent) throws Exception { - httpConnection.send(mockEvent); - - new Verifications() {{ - mockUrlConnection.setRequestProperty("User-Agent", SentryEnvironment.getSentryName()); - mockUrlConnection.setRequestProperty("X-Sentry-Auth", httpConnection.getAuthHeader()); - }}; + public void testAuthHeaderSent() throws Exception { + httpConnection.send(mock(Event.class)); + verify(mockUrlConnection).setRequestProperty(eq("User-Agent"), eq(SentryEnvironment.getSentryName())); + verify(mockUrlConnection).setRequestProperty(eq("X-Sentry-Auth"), eq(httpConnection.getAuthHeader())); } - @Test(expectedExceptions = {ConnectionException.class}) - public void testHttpErrorThrowsAnException(@Injectable final Event mockEvent) throws Exception { + @Test(expected = ConnectionException.class) + public void testHttpErrorThrowsAnException() throws Exception { final String httpErrorMessage = "93e3ddb1-c4f3-46c3-9900-529de83678b7"; - new NonStrictExpectations() {{ - mockUrlConnection.getOutputStream(); - result = new IOException(); - mockUrlConnection.getErrorStream(); - result = new ByteArrayInputStream(httpErrorMessage.getBytes()); - }}; - - httpConnection.doSend(mockEvent); + when(mockUrlConnection.getOutputStream()).thenThrow(new IOException()); + when(mockUrlConnection.getErrorStream()).thenReturn(new ByteArrayInputStream(httpErrorMessage.getBytes())); + + httpConnection.doSend(mock(Event.class)); } @Test - public void testHttp403DoesntThrow(@Injectable final Event mockEvent) throws Exception { - new NonStrictExpectations() {{ - mockUrlConnection.getOutputStream(); - result = new IOException(); - mockUrlConnection.getResponseCode(); - result = 403; - }}; - - httpConnection.doSend(mockEvent); + public void testHttp403DoesntThrow() throws Exception { + when(mockUrlConnection.getOutputStream()).thenThrow(new IOException()); + when(mockUrlConnection.getResponseCode()).thenReturn(403); + + httpConnection.doSend(mock(Event.class)); } @Test - public void testRetryAfterHeader(@Injectable final Event mockEvent) throws Exception { + public void testRetryAfterHeader() throws Exception { final String httpErrorMessage = "93e3ddb1-c4f3-46c3-9900-529de83678b7"; - new NonStrictExpectations() {{ - mockUrlConnection.getOutputStream(); - result = new IOException(); - mockUrlConnection.getErrorStream(); - result = new ByteArrayInputStream(httpErrorMessage.getBytes()); - mockUrlConnection.getHeaderField("Retry-After"); - result = "12345.25"; - }}; + when(mockUrlConnection.getOutputStream()).thenThrow(new IOException()); + when(mockUrlConnection.getErrorStream()).thenReturn(new ByteArrayInputStream(httpErrorMessage.getBytes())); + when(mockUrlConnection.getHeaderField(eq("Retry-After"))).thenReturn("12345.25"); try { - httpConnection.doSend(mockEvent); + httpConnection.doSend(mock(Event.class)); fail(); } catch (ConnectionException e) { assertThat(e.getRecommendedLockdownTime(), is(12345250L)); @@ -188,13 +165,10 @@ public void testRetryAfterHeader(@Injectable final Event mockEvent) throws Excep } @Test - public void testApiUrlCreation(@Injectable final URI sentryUri) throws Exception { - final String uri = "http://host/sentry/"; + public void testApiUrlCreation() throws Exception { + String uri = "http://host/sentry/"; + URI sentryUri = URI.create(uri); final String projectId = "293b4958-71f8-40a9-b588-96f004f64463"; - new Expectations() {{ - sentryUri.toString(); - result = uri; - }}; URL sentryApiUrl = HttpConnection.getSentryApiUrl(sentryUri, projectId); @@ -202,16 +176,12 @@ public void testApiUrlCreation(@Injectable final URI sentryUri) throws Exception } @Test - public void testEmptyStringDoesNotSIOOBE(@Injectable final Event mockEvent) throws Exception { - new NonStrictExpectations() {{ - mockUrlConnection.getOutputStream(); - result = new IOException(); - mockUrlConnection.getErrorStream(); - result = new ByteArrayInputStream(new byte[0]); - }}; + public void testEmptyStringDoesNotSIOOBE() throws Exception { + when(mockUrlConnection.getOutputStream()).thenThrow(new IOException()); + when(mockUrlConnection.getErrorStream()).thenReturn(new ByteArrayInputStream(new byte[0])); try { - httpConnection.doSend(mockEvent); - assertThat("Should not exit normally with IOE", false); + httpConnection.doSend(mock(Event.class)); + fail("Should not exit normally with IOE"); } catch (ConnectionException ce) { assertThat(ce.getMessage(), not(isEmptyOrNullString())); } diff --git a/sentry/src/test/java/io/sentry/connection/OutputStreamConnectionTest.java b/sentry/src/test/java/io/sentry/connection/OutputStreamConnectionTest.java index a657a5d1605..5b849f91f96 100644 --- a/sentry/src/test/java/io/sentry/connection/OutputStreamConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/OutputStreamConnectionTest.java @@ -1,49 +1,41 @@ package io.sentry.connection; import io.sentry.BaseTest; -import mockit.*; -import io.sentry.environment.SentryEnvironment; import io.sentry.event.Event; import io.sentry.marshaller.Marshaller; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSession; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; +import org.junit.Before; +import org.junit.Test; + import java.io.OutputStream; -import java.net.URI; -import java.net.URL; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; public class OutputStreamConnectionTest extends BaseTest { - @Tested private OutputStreamConnection outputStreamConnection = null; - @Injectable private Marshaller mockMarshaller = null; - @Injectable private OutputStream mockOutputStream = null; + @Before + public void setup() { + mockOutputStream = mock(OutputStream.class); + mockMarshaller = mock(Marshaller.class); + + outputStreamConnection = new OutputStreamConnection(mockOutputStream); + outputStreamConnection.setMarshaller(mockMarshaller); + } + @Test - public void testContentMarshalled(@Injectable final Event mockEvent) throws Exception { + public void testContentMarshalled() throws Exception { + Event mockEvent = mock(Event.class); outputStreamConnection.send(mockEvent); - - new Verifications() {{ - mockMarshaller.marshall(mockEvent, mockOutputStream); - }}; + verify(mockMarshaller).marshall(eq(mockEvent), eq(mockOutputStream)); } @Test public void testClose() throws Exception { outputStreamConnection.close(); - - new Verifications() {{ - mockOutputStream.close(); - }}; + verify(mockOutputStream).close(); } } diff --git a/sentry/src/test/java/io/sentry/connection/RandomEventSamplerTest.java b/sentry/src/test/java/io/sentry/connection/RandomEventSamplerTest.java index d5187b57678..d7140809622 100644 --- a/sentry/src/test/java/io/sentry/connection/RandomEventSamplerTest.java +++ b/sentry/src/test/java/io/sentry/connection/RandomEventSamplerTest.java @@ -3,8 +3,7 @@ import io.sentry.BaseTest; import io.sentry.event.Event; import io.sentry.event.EventBuilder; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.Test; import java.util.Random; diff --git a/sentry/src/test/java/io/sentry/dsn/DsnTest.java b/sentry/src/test/java/io/sentry/dsn/DsnTest.java index 954e969543f..6395e67f756 100644 --- a/sentry/src/test/java/io/sentry/dsn/DsnTest.java +++ b/sentry/src/test/java/io/sentry/dsn/DsnTest.java @@ -1,36 +1,29 @@ package io.sentry.dsn; -import io.sentry.BaseTest; -import io.sentry.config.JndiLookup; -import io.sentry.config.Lookup; -import mockit.Expectations; -import mockit.Mocked; -import mockit.NonStrictExpectations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isEmptyOrNullString; -import javax.naming.Context; import java.net.URI; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import io.sentry.BaseTest; +import io.sentry.EmptyConfigurationProvider; +import io.sentry.config.Lookup; +import io.sentry.config.provider.ConfigurationProvider; +import io.sentry.util.Nullable; +import org.junit.Test; public class DsnTest extends BaseTest { - @Mocked - private Context mockContext = null; - - @BeforeMethod - public void setUp() throws Exception { - InitialContextFactory.context = mockContext; - } - - @Test(expectedExceptions = InvalidDsnException.class) + @Test(expected = InvalidDsnException.class) public void testEmptyDsnInvalid() throws Exception { new Dsn(""); } - @Test(expectedExceptions = InvalidDsnException.class) + @Test(expected = InvalidDsnException.class) public void testDsnFromInvalidUri() throws Exception { new Dsn(URI.create("")); } @@ -73,85 +66,36 @@ public void testSimpleDsnNoSecretValid() throws Exception { @Test public void testDsnLookupWithNothingSet() throws Exception { - assertThat(Dsn.dsnLookup(), is(Dsn.DEFAULT_DSN)); - } - - @Test - public void testJndiLookupFailsWithException( - @SuppressWarnings("unused") @Mocked("jndiLookup") JndiLookup mockJndiLookup) throws Exception { - new NonStrictExpectations() {{ - JndiLookup.jndiLookup("dsn"); - result = new ClassNotFoundException("Couldn't find the JNDI classes"); - }}; - - assertThat(Dsn.dsnLookup(), is(Dsn.DEFAULT_DSN)); + assertThat(Dsn.dsnFrom(new Lookup(new EmptyConfigurationProvider(), new EmptyConfigurationProvider())), + is(Dsn.DEFAULT_DSN)); } @Test - public void testJndiLookupFailsWithError( - @SuppressWarnings("unused") @Mocked("jndiLookup") JndiLookup mockJndiLookup) throws Exception { - new NonStrictExpectations() {{ - JndiLookup.jndiLookup("dsn"); - result = new NoClassDefFoundError("Couldn't find the JNDI classes"); - }}; - - assertThat(Dsn.dsnLookup(), is(Dsn.DEFAULT_DSN)); - } + public void testEmptyStringAsDsnYieldsDefaultDsn() throws Exception { + // given + Lookup lookup = new Lookup(new ConfigurationProvider() { + @Nullable + @Override + public String getProperty(String key) { + return "dsn".equals(key) ? "" : null; + } + }, new EmptyConfigurationProvider()); - @Test - public void testDsnLookupWithJndi() throws Exception { - final String dsn = "6621980c-e27b-4dc9-9130-7fc5e9ea9750"; - new Expectations() {{ - mockContext.lookup("java:comp/env/sentry/dsn"); - result = dsn; - }}; - - assertThat(Dsn.dsnLookup(), is(dsn)); - } - - @Test - public void testDsnLookupWithSystemProperty() throws Exception { - String dsn = "aa9171a4-7e9b-4e3c-b3cc-fe537dc03527"; - System.setProperty("sentry.dsn", dsn); - - assertThat(Dsn.dsnLookup(), is(dsn)); - - System.clearProperty("sentry.dsn"); - } - - @Test - public void testDsnLookupWithEnvironmentVariable(@Mocked("getenv") final System system) throws Exception { - final String dsn = "759ed060-dd4f-4478-8a1a-3f23e044787c"; - new NonStrictExpectations() {{ - System.getenv("SENTRY_DSN"); - result = dsn; - }}; - - assertThat(Dsn.dsnLookup(), is(dsn)); - } - - @Test - public void testDsnLookupWithEmptyEnvironmentVariable(@Mocked("getenv") final System system) throws Exception { - final String dsn = ""; - new NonStrictExpectations() {{ - System.getenv("SENTRY_DSN"); - result = dsn; - }}; - - assertThat(Dsn.dsnLookup(), is(Dsn.DEFAULT_DSN)); + // then + assertThat(Dsn.dsnFrom(lookup), is(Dsn.DEFAULT_DSN)); } - @Test(expectedExceptions = InvalidDsnException.class) + @Test(expected = InvalidDsnException.class) public void testMissingHostInvalid() throws Exception { new Dsn("http://publicKey:secretKey@/9"); } - @Test(expectedExceptions = InvalidDsnException.class) + @Test(expected = InvalidDsnException.class) public void testMissingPathInvalid() throws Exception { new Dsn("http://publicKey:secretKey@host"); } - @Test(expectedExceptions = InvalidDsnException.class) + @Test(expected = InvalidDsnException.class) public void testMissingProjectIdInvalid() throws Exception { new Dsn("http://publicKey:secretKey@host/"); } @@ -174,14 +118,14 @@ public void testAdvancedDsnValid() throws Exception { assertThat(dsn.getOptions().get("option2"), is("valueOption2")); } - @Test(expectedExceptions = UnsupportedOperationException.class) + @Test(expected = UnsupportedOperationException.class) public void testOptionsImmutable() throws Exception { Dsn dsn = new Dsn("http://publicKey:secretKey@host/9"); dsn.getOptions().put("test", "test"); } - @Test(expectedExceptions = UnsupportedOperationException.class) + @Test(expected = UnsupportedOperationException.class) public void testProtocolSettingsImmutable() throws Exception { Dsn dsn = new Dsn("http://publicKey:secretKey@host/9"); diff --git a/sentry/src/test/java/io/sentry/dsn/InitialContextFactory.java b/sentry/src/test/java/io/sentry/dsn/InitialContextFactory.java deleted file mode 100644 index e26468aeb91..00000000000 --- a/sentry/src/test/java/io/sentry/dsn/InitialContextFactory.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.sentry.dsn; - -import javax.naming.Context; -import javax.naming.NamingException; -import java.util.Hashtable; - -/** - * JNDI initial context factory allowing to create custom mocked contexts. - * - * @see DsnTest#setUp() - */ -public class InitialContextFactory implements javax.naming.spi.InitialContextFactory { - public static Context context; - - @Override - public Context getInitialContext(Hashtable environment) throws NamingException { - return context; - } -} diff --git a/sentry/src/test/java/io/sentry/environment/SentryEnvironmentTest.java b/sentry/src/test/java/io/sentry/environment/SentryEnvironmentTest.java index 63ab7a36822..953239ad272 100644 --- a/sentry/src/test/java/io/sentry/environment/SentryEnvironmentTest.java +++ b/sentry/src/test/java/io/sentry/environment/SentryEnvironmentTest.java @@ -1,14 +1,14 @@ package io.sentry.environment; import io.sentry.BaseTest; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.Test; +import org.junit.After; +import org.junit.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; public class SentryEnvironmentTest extends BaseTest { - @AfterMethod + @After public void tearDown() throws Exception { SentryEnvironment.SENTRY_THREAD.remove(); } diff --git a/sentry/src/test/java/io/sentry/event/BreadcrumbTest.java b/sentry/src/test/java/io/sentry/event/BreadcrumbTest.java index 95ee298f8e6..3e4c7c086e9 100644 --- a/sentry/src/test/java/io/sentry/event/BreadcrumbTest.java +++ b/sentry/src/test/java/io/sentry/event/BreadcrumbTest.java @@ -6,29 +6,27 @@ import io.sentry.context.ContextManager; import io.sentry.context.SingletonContextManager; import io.sentry.event.helper.ContextBuilderHelper; -import mockit.Injectable; -import mockit.Tested; -import mockit.Verifications; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; import java.util.ArrayList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsEqual.equalTo; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; public class BreadcrumbTest extends BaseTest { - @Tested private SentryClient sentryClient = null; - - @Injectable private Connection mockConnection = null; - @Injectable private ContextManager contextManager = new SingletonContextManager(); - @BeforeMethod + @Before public void setup() { contextManager.clear(); + mockConnection = mock(Connection.class); + sentryClient = new SentryClient(mockConnection, contextManager); } @Test @@ -47,11 +45,9 @@ public void testBreadcrumbsViaContextRecording() { .withMessage("Some random message") .withLevel(Event.Level.INFO)); - new Verifications() {{ - Event event; - mockConnection.send(event = withCapture()); - assertThat(event.getBreadcrumbs().size(), equalTo(1)); - }}; + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + verify(mockConnection).send(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getBreadcrumbs().size(), equalTo(1)); } @Test @@ -72,11 +68,9 @@ public void testBreadcrumbsViaEventBuilder() { .withMessage("Some random message") .withLevel(Event.Level.INFO)); - new Verifications() {{ - Event event; - mockConnection.send(event = withCapture()); - assertThat(event.getBreadcrumbs().size(), equalTo(1)); - }}; + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + verify(mockConnection).send(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getBreadcrumbs().size(), equalTo(1)); } @Test @@ -98,10 +92,8 @@ public void testBreadcrumbsViaEvent() { .withLevel(Event.Level.INFO) .build()); - new Verifications() {{ - Event event; - mockConnection.send(event = withCapture()); - assertThat(event.getBreadcrumbs().size(), equalTo(1)); - }}; + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + verify(mockConnection).send(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getBreadcrumbs().size(), equalTo(1)); } } diff --git a/sentry/src/test/java/io/sentry/event/EventBuilderHostnameCacheTest.java b/sentry/src/test/java/io/sentry/event/EventBuilderHostnameCacheTest.java index 15b19505cae..8b47f726aeb 100644 --- a/sentry/src/test/java/io/sentry/event/EventBuilderHostnameCacheTest.java +++ b/sentry/src/test/java/io/sentry/event/EventBuilderHostnameCacheTest.java @@ -1,99 +1,79 @@ package io.sentry.event; import io.sentry.BaseTest; -import mockit.*; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import io.sentry.time.FixedClock; +import org.junit.Before; +import org.junit.Test; import java.net.InetAddress; +import java.util.Date; +import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; -import static mockit.Deencapsulation.getField; -import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; -@Test(singleThreaded = true) public class EventBuilderHostnameCacheTest extends BaseTest { - @Injectable private InetAddress mockLocalHost = null; - @Injectable("serverName") - private String mockLocalHostName = null; - @Injectable private InetAddress mockTimingOutLocalHost = null; + private FixedClock fixedClock = new FixedClock(new Date()); - private static void resetHostnameCache() { - setField(getHostnameCache(), "expirationTimestamp", 0L); - setField(getHostnameCache(), "hostname", EventBuilder.DEFAULT_HOSTNAME); + @Before + public void setUp() throws Exception { + mockLocalHost = mock(InetAddress.class); + mockTimingOutLocalHost = mock(InetAddress.class); + when(mockLocalHost.getCanonicalHostName()).thenReturn("mockLocalhost"); + when(mockTimingOutLocalHost.getCanonicalHostName()) + .thenThrow(new RuntimeException("For all intents and purposes, an exception is the same as a timeout")); } - private static Object getHostnameCache() { - return getField(EventBuilder.class, "HOSTNAME_CACHE"); + private EventBuilder.HostnameCache createCacheUsingLocalhost(final InetAddress mockAddress) { + return new EventBuilder.HostnameCache(EventBuilder.HOSTNAME_CACHE_DURATION, fixedClock, new Callable() { + @Override + public InetAddress call() throws Exception { + return mockAddress; + } + }); } - @BeforeMethod - public void setUp() throws Exception { - new NonStrictExpectations() {{ - mockLocalHost.getCanonicalHostName(); - result = mockLocalHostName; - - mockTimingOutLocalHost.getCanonicalHostName(); - result = new RuntimeException("For all intents and purposes, an exception is the same as a timeout"); - }}; - // Clean Hostname Cache - resetHostnameCache(); + @Test + public void successfulHostnameRetrievalIsCachedForFiveHours() throws Exception { + EventBuilder.HostnameCache cache = createCacheUsingLocalhost(mockLocalHost); + cache.getHostname(); + assertThat(cache.expirationTimestamp, is(EventBuilder.HOSTNAME_CACHE_DURATION + fixedClock.millis())); } @Test - public void successfulHostnameRetrievalIsCachedForFiveHours( - @SuppressWarnings("unused") @Mocked("currentTimeMillis") final System system) - throws Exception { - new NonStrictExpectations(InetAddress.class) {{ - System.currentTimeMillis(); - result = 1L; - InetAddress.getLocalHost(); - result = mockLocalHost; - }}; - - new EventBuilder().build(); - final long expirationTime = Deencapsulation.getField(getHostnameCache(), "expirationTimestamp"); - - assertThat(expirationTime, is(TimeUnit.HOURS.toMillis(5) + System.currentTimeMillis())); + public void unsuccessfulHostnameRetrievalIsCachedForOneSecond() throws Exception { + EventBuilder.HostnameCache cache = createCacheUsingLocalhost(mockTimingOutLocalHost); + cache.getHostname(); + assertThat(cache.expirationTimestamp, is(TimeUnit.SECONDS.toMillis(1) + fixedClock.millis())); } @Test - public void unsuccessfulHostnameRetrievalIsCachedForOneSecond( - @SuppressWarnings("unused") @Mocked("currentTimeMillis") final System system) - throws Exception { - new NonStrictExpectations(InetAddress.class) {{ - System.currentTimeMillis(); - result = 1L; - InetAddress.getLocalHost(); - result = mockTimingOutLocalHost; - }}; - - new EventBuilder().build(); - final long expirationTime = Deencapsulation.getField(getHostnameCache(), "expirationTimestamp"); - - assertThat(expirationTime, is(TimeUnit.SECONDS.toMillis(1) + System.currentTimeMillis())); + public void unsuccessfulHostnameRetrievalUsesDefaultHostnameAsResult() throws Exception { + EventBuilder.HostnameCache cache = createCacheUsingLocalhost(mockTimingOutLocalHost); + assertThat(cache.getHostname(), is(EventBuilder.DEFAULT_HOSTNAME)); } @Test public void unsuccessfulHostnameRetrievalUsesLastKnownCachedValue() throws Exception { - new NonStrictExpectations(InetAddress.class) {{ - InetAddress.getLocalHost(); - result = mockLocalHost; - result = mockTimingOutLocalHost; - }}; + InetAddress localhost = mock(InetAddress.class); + when(localhost.getCanonicalHostName()).thenReturn("mockLocalhost").thenThrow(new RuntimeException()); + + EventBuilder.HostnameCache cache = createCacheUsingLocalhost(localhost); + + // get the "last known cached value" + String resolved = cache.getHostname(); + assertThat(resolved, is("mockLocalhost")); - new EventBuilder().build(); - setField(getHostnameCache(), "expirationTimestamp", 0l); - Event event = new EventBuilder().build(); + // expire the cache + fixedClock.tick(cache.cacheDuration + 1, TimeUnit.MILLISECONDS); - assertThat(event.getServerName(), is(mockLocalHostName)); - new Verifications() {{ - mockLocalHost.getCanonicalHostName(); - mockTimingOutLocalHost.getCanonicalHostName(); - }}; + // check that the second failing invocation returns the previously cached value + resolved = cache.getHostname(); + assertThat(resolved, is("mockLocalhost")); } } diff --git a/sentry/src/test/java/io/sentry/event/EventBuilderTest.java b/sentry/src/test/java/io/sentry/event/EventBuilderTest.java index 047b4a269b8..589bd890e5f 100644 --- a/sentry/src/test/java/io/sentry/event/EventBuilderTest.java +++ b/sentry/src/test/java/io/sentry/event/EventBuilderTest.java @@ -2,70 +2,50 @@ import io.sentry.BaseTest; import io.sentry.event.interfaces.DebugMetaInterface; -import mockit.Injectable; -import mockit.NonStrictExpectations; +import junitparams.JUnitParamsRunner; +import junitparams.NamedParameters; +import junitparams.Parameters; import io.sentry.event.interfaces.SentryInterface; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; -import java.net.InetAddress; import java.util.Date; +import java.util.Random; import java.util.UUID; -import static mockit.Deencapsulation.getField; -import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +@RunWith(JUnitParamsRunner.class) public class EventBuilderTest extends BaseTest { - @Injectable - private InetAddress mockLocalHost = null; - - private static void resetHostnameCache() { - setField(getHostnameCache(), "expirationTimestamp", 0l); - setField(getHostnameCache(), "hostname", EventBuilder.DEFAULT_HOSTNAME); - } - - private static Object getHostnameCache() { - return getField(EventBuilder.class, "HOSTNAME_CACHE"); - } - - @BeforeMethod + @Before public void setUp() throws Exception { - new NonStrictExpectations(InetAddress.class) {{ - InetAddress.getLocalHost(); - result = mockLocalHost; - mockLocalHost.getCanonicalHostName(); - result = "local"; - }}; + EventBuilder.HOSTNAME_CACHE.reset(System.currentTimeMillis()); } @Test - public void builtEventHasRandomlyGeneratedUuid(@Injectable final UUID mockUuid) - throws Exception { - new NonStrictExpectations(UUID.class) {{ - UUID.randomUUID(); - result = mockUuid; - }}; - final EventBuilder eventBuilder = new EventBuilder(); + public void builtEventHasRandomlyGeneratedUuid() throws Exception { - final Event event = eventBuilder.build(); + Event ev1 = new EventBuilder().build(); + Event ev2 = new EventBuilder().build(); - assertThat(event.getId(), is(sameInstance(mockUuid))); + assertThat(ev1.getId(), not(is(ev2.getId()))); } @Test - public void builtEventWithCustomUuidHasProperUuid(@Injectable final UUID mockUuid) - throws Exception { - final EventBuilder eventBuilder = new EventBuilder(mockUuid); + public void builtEventWithCustomUuidHasProperUuid() throws Exception { + UUID id = UUID.randomUUID(); + final EventBuilder eventBuilder = new EventBuilder(id); final Event event = eventBuilder.build(); - assertThat(event.getId(), is(sameInstance(mockUuid))); + assertThat(event.getId(), is(sameInstance(id))); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected= IllegalArgumentException.class) public void builtEventWithCustomNullUuidFails() throws Exception { new EventBuilder(null); } @@ -80,9 +60,8 @@ public void builtEventWithoutMessageHasNullMessage() throws Exception { } @Test - public void builtEventWithMessageHasProperMessage( - @Injectable("message") final String mockMessage) - throws Exception { + public void builtEventWithMessageHasProperMessage() throws Exception { + String mockMessage = "teh massage"; final EventBuilder eventBuilder = new EventBuilder(); eventBuilder.withMessage(mockMessage); @@ -92,34 +71,31 @@ public void builtEventWithMessageHasProperMessage( } @Test - public void builtEventWithoutTimestampHasDefaultTimestamp(@Injectable final Date mockTimestamp) - throws Exception { - new NonStrictExpectations(Date.class) {{ - new Date(); - result = mockTimestamp; - mockTimestamp.clone(); - result = mockTimestamp; - }}; + public void builtEventWithoutTimestampHasDefaultTimestamp() throws Exception { + long before = System.currentTimeMillis(); + final EventBuilder eventBuilder = new EventBuilder(); final Event event = eventBuilder.build(); - assertThat(event.getTimestamp(), is(sameInstance(mockTimestamp))); + long after = System.currentTimeMillis(); + + long eventTimestamp = event.getTimestamp().getTime(); + + assertThat(eventTimestamp, is(greaterThanOrEqualTo(before))); + assertThat(eventTimestamp, is(lessThanOrEqualTo(after))); } @Test - public void builtEventWithTimestampHasProperTimestamp(@Injectable final Date mockTimestamp) - throws Exception { - new NonStrictExpectations() {{ - mockTimestamp.clone(); - result = mockTimestamp; - }}; + public void builtEventWithTimestampHasProperTimestamp() throws Exception { + Date mockTimestamp = new Date(new Random().nextLong()); final EventBuilder eventBuilder = new EventBuilder(); eventBuilder.withTimestamp(mockTimestamp); final Event event = eventBuilder.build(); - assertThat(event.getTimestamp(), is(sameInstance(mockTimestamp))); + assertThat(event.getTimestamp(), is(not(sameInstance(mockTimestamp)))); + assertThat(event.getTimestamp(), is(mockTimestamp)); } @Test @@ -132,8 +108,8 @@ public void builtEventWithoutLevelHasNullLevel() throws Exception { } @Test - public void builtEventWithLevelHasProperLevel(@Injectable final Event.Level mockLevel) - throws Exception { + public void builtEventWithLevelHasProperLevel() throws Exception { + Event.Level mockLevel = Event.Level.INFO; final EventBuilder eventBuilder = new EventBuilder(); eventBuilder.withLevel(mockLevel); @@ -152,8 +128,8 @@ public void builtEventWithoutLoggerHasNullLogger() throws Exception { } @Test - public void builtEventWithLoggerHasProperLogger(@Injectable("logger") final String mockLogger) - throws Exception { + public void builtEventWithLoggerHasProperLogger() throws Exception { + String mockLogger = "mockLogger"; final EventBuilder eventBuilder = new EventBuilder(); eventBuilder.withLogger(mockLogger); @@ -172,8 +148,8 @@ public void builtEventWithoutPlatformHasDefaultPlatform() throws Exception { } @Test - public void builtEventWithPlatformHasProperPlatform(@Injectable("platform") final String mockPlatform) - throws Exception { + public void builtEventWithPlatformHasProperPlatform() throws Exception { + String mockPlatform = "mockPlatform"; final EventBuilder eventBuilder = new EventBuilder(); eventBuilder.withPlatform(mockPlatform); @@ -192,8 +168,8 @@ public void builtEventWithoutCulpritHasNullCulprit() throws Exception { } @Test - public void builtEventWithCulpritHasProperCulprit(@Injectable("culprit") final String mockCulprit) - throws Exception { + public void builtEventWithCulpritHasProperCulprit() throws Exception { + String mockCulprit = "mockCulprit"; final EventBuilder eventBuilder = new EventBuilder(); eventBuilder.withCulprit(mockCulprit); @@ -202,7 +178,7 @@ public void builtEventWithCulpritHasProperCulprit(@Injectable("culprit") final S assertThat(event.getCulprit(), is(sameInstance(mockCulprit))); } - @DataProvider + @NamedParameters("stackFrames") public Object[][] stackFrameProvider() { return new Object[][]{ {new StackTraceElement("class", "method", "file", 12), "class.method(file:12)"}, @@ -212,7 +188,8 @@ public Object[][] stackFrameProvider() { }; } - @Test(dataProvider = "stackFrameProvider") + @Test + @Parameters(named = "stackFrames") public void builtEventWithStackFrameAsCulpritHasProperCulprit(StackTraceElement mockStackFrame, String expectedCulprit) throws Exception { @@ -224,7 +201,7 @@ public void builtEventWithStackFrameAsCulpritHasProperCulprit(StackTraceElement assertThat(event.getCulprit(), is(expectedCulprit)); } - @Test(expectedExceptions = UnsupportedOperationException.class) + @Test(expected = UnsupportedOperationException.class) public void builtEventHasImmutableTags() throws Exception { final EventBuilder eventBuilder = new EventBuilder(); final Event event = eventBuilder.build(); @@ -242,9 +219,9 @@ public void builtEventWithoutTagsHasEmptyTags() throws Exception { } @Test - public void builtEventWithTagsHasProperTags(@Injectable("tagKey") final String mockTagKey, - @Injectable("tagValue") final String mockTagValue) - throws Exception { + public void builtEventWithTagsHasProperTags() throws Exception { + String mockTagKey = "tagKey"; + String mockTagValue = "tagValue"; final EventBuilder eventBuilder = new EventBuilder(); eventBuilder.withTag(mockTagKey, mockTagValue); @@ -254,47 +231,25 @@ public void builtEventWithTagsHasProperTags(@Injectable("tagKey") final String m assertThat(event.getTags().entrySet(), hasSize(1)); } + @Test - public void builtEventWithNoServerNameUsesDefaultIfSearchTimesOut() - throws Exception { - resetHostnameCache(); - new NonStrictExpectations(InetAddress.class) {{ - InetAddress.getLocalHost(); - result = mockLocalHost; - mockLocalHost.getCanonicalHostName(); - result = new RuntimeException("For all intents and purposes, an exception is the same as a timeout"); - }}; + public void builtEventWithNoServerNameUsesLocalHost() throws Exception { + EventBuilder.HOSTNAME_CACHE.reset(System.currentTimeMillis() + 1_000_000_000); final EventBuilder eventBuilder = new EventBuilder(); final Event event = eventBuilder.build(); - synchronized (this) { - this.notify(); - } assertThat(event.getServerName(), is(EventBuilder.DEFAULT_HOSTNAME)); } @Test - public void builtEventWithNoServerNameUsesLocalHost(@Injectable("serverName") final String mockServerName) - throws Exception { - resetHostnameCache(); - new NonStrictExpectations(InetAddress.class) {{ - InetAddress.getLocalHost(); - result = mockLocalHost; - mockLocalHost.getCanonicalHostName(); - result = mockServerName; - }}; - final EventBuilder eventBuilder = new EventBuilder(); - - final Event event = eventBuilder.build(); + public void builtEventWithServerNameUsesProvidedServerName() throws Exception { + String mockServerName = "mockServer.name"; - assertThat(event.getServerName(), is(mockServerName)); - } + // this is to make the hostname cache think it has to resolve the hostname. But because we provide the + // server name on our own, this should actually never happen. + EventBuilder.HOSTNAME_CACHE.reset(System.currentTimeMillis() - 100); - @Test - public void builtEventWithServerNameUsesProvidedServerName(@Injectable("serverName") final String mockServerName) - throws Exception { - resetHostnameCache(); final EventBuilder eventBuilder = new EventBuilder(); eventBuilder.withServerName(mockServerName); @@ -303,7 +258,7 @@ public void builtEventWithServerNameUsesProvidedServerName(@Injectable("serverNa assertThat(event.getServerName(), is(mockServerName)); } - @Test(expectedExceptions = UnsupportedOperationException.class) + @Test(expected = UnsupportedOperationException.class) public void builtEventHasImmutableExtras() throws Exception { final EventBuilder eventBuilder = new EventBuilder(); final Event event = eventBuilder.build(); @@ -321,9 +276,9 @@ public void builtEventWithoutExtrasHasEmptyExtras() throws Exception { } @Test - public void builtEventWithExtrasHasProperExtras(@Injectable("extraKey") final String mockExtraKey, - @Injectable("extraValue") final String mockExtraValue) - throws Exception { + public void builtEventWithExtrasHasProperExtras() throws Exception { + String mockExtraKey = "key"; + String mockExtraValue = "value"; final EventBuilder eventBuilder = new EventBuilder(); eventBuilder.withExtra(mockExtraKey, mockExtraValue); @@ -343,9 +298,8 @@ public void builtEventWithoutCheckHasNullChecksum() throws Exception { } @Test - public void builtEventWithChecksumHasProperChecksum( - @Injectable("checksum") final String mockChecksum) - throws Exception { + public void builtEventWithChecksumHasProperChecksum() throws Exception { + String mockChecksum = "checksum"; final EventBuilder eventBuilder = new EventBuilder(); eventBuilder.withChecksum(mockChecksum); @@ -354,7 +308,7 @@ public void builtEventWithChecksumHasProperChecksum( assertThat(event.getChecksum(), is(sameInstance(mockChecksum))); } - @DataProvider + @NamedParameters("checksums") public Object[][] checksumProvider() { return new Object[][]{ {"", "0"}, @@ -363,7 +317,8 @@ public Object[][] checksumProvider() { }; } - @Test(dataProvider = "checksumProvider") + @Test + @Parameters(named = "checksums") public void builtEventWithGeneratedChecksumHasCRC32Checksum(String string, String expectedChecksum) throws Exception { final EventBuilder eventBuilder = new EventBuilder(); @@ -374,13 +329,12 @@ public void builtEventWithGeneratedChecksumHasCRC32Checksum(String string, Strin assertThat(event.getChecksum(), is(expectedChecksum)); } - @Test(expectedExceptions = UnsupportedOperationException.class) - public void builtEventHasImmutableSentryInterfaces(@Injectable final SentryInterface mockSentryInterface) - throws Exception { + @Test(expected = UnsupportedOperationException.class) + public void builtEventHasImmutableSentryInterfaces() throws Exception { final EventBuilder eventBuilder = new EventBuilder(); final Event event = eventBuilder.build(); - event.getSentryInterfaces().put("interfaceName", mockSentryInterface); + event.getSentryInterfaces().put("interfaceName", mock(SentryInterface.class)); } @Test @@ -393,35 +347,29 @@ public void builtEventWithoutSentryInterfacesHasEmptySentryInterfaces() throws E } @Test - public void builtEventWithSentryInterfacesHasProperSentryInterfaces( - @Injectable("sentryInterfaceName") final String mockSentryInterfaceName, - @Injectable final SentryInterface mockSentryInterface) - throws Exception { - new NonStrictExpectations() {{ - mockSentryInterface.getInterfaceName(); - result = mockSentryInterfaceName; - }}; + public void builtEventWithSentryInterfacesHasProperSentryInterfaces() throws Exception { + + SentryInterface mockSentryInterface = mock(SentryInterface.class); + when(mockSentryInterface.getInterfaceName()).thenReturn("sentryInterfaceName"); + final EventBuilder eventBuilder = new EventBuilder(); eventBuilder.withSentryInterface(mockSentryInterface); final Event event = eventBuilder.build(); - assertThat(event.getSentryInterfaces(), hasEntry(mockSentryInterfaceName, mockSentryInterface)); + assertThat(event.getSentryInterfaces(), hasEntry("sentryInterfaceName", mockSentryInterface)); assertThat(event.getSentryInterfaces().entrySet(), hasSize(1)); } @Test - public void builtEventReplacesSentryInterfacesWithSameNameByDefault( - @Injectable("sentryInterfaceName") final String mockSentryInterfaceName, - @Injectable final SentryInterface mockSentryInterface, - @Injectable final SentryInterface mockSentryInterface2) - throws Exception { - new NonStrictExpectations() {{ - mockSentryInterface.getInterfaceName(); - result = mockSentryInterfaceName; - mockSentryInterface2.getInterfaceName(); - result = mockSentryInterfaceName; - }}; + public void builtEventReplacesSentryInterfacesWithSameNameByDefault() throws Exception { + String mockSentryInterfaceName = "sentryInterfaceName"; + SentryInterface mockSentryInterface = mock(SentryInterface.class); + SentryInterface mockSentryInterface2 = mock(SentryInterface.class); + + when(mockSentryInterface.getInterfaceName()).thenReturn(mockSentryInterfaceName); + when(mockSentryInterface2.getInterfaceName()).thenReturn(mockSentryInterfaceName); + final EventBuilder eventBuilder = new EventBuilder(); eventBuilder.withSentryInterface(mockSentryInterface); eventBuilder.withSentryInterface(mockSentryInterface2); @@ -432,19 +380,15 @@ public void builtEventReplacesSentryInterfacesWithSameNameByDefault( assertThat(event.getSentryInterfaces().entrySet(), hasSize(1)); } + @Test + public void builtEventReplacesSentryInterfacesWithSameNameIfReplacementEnabled() throws Exception { + String mockSentryInterfaceName = "sentryInterfaceName"; + SentryInterface mockSentryInterface = mock(SentryInterface.class); + SentryInterface mockSentryInterface2 = mock(SentryInterface.class); + + when(mockSentryInterface.getInterfaceName()).thenReturn(mockSentryInterfaceName); + when(mockSentryInterface2.getInterfaceName()).thenReturn(mockSentryInterfaceName); - @Test - public void builtEventReplacesSentryInterfacesWithSameNameIfReplacementEnabled( - @Injectable("sentryInterfaceName") final String mockSentryInterfaceName, - @Injectable final SentryInterface mockSentryInterface, - @Injectable final SentryInterface mockSentryInterface2) - throws Exception { - new NonStrictExpectations() {{ - mockSentryInterface.getInterfaceName(); - result = mockSentryInterfaceName; - mockSentryInterface2.getInterfaceName(); - result = mockSentryInterfaceName; - }}; final EventBuilder eventBuilder = new EventBuilder(); eventBuilder.withSentryInterface(mockSentryInterface); eventBuilder.withSentryInterface(mockSentryInterface2, true); @@ -456,17 +400,14 @@ public void builtEventReplacesSentryInterfacesWithSameNameIfReplacementEnabled( } @Test - public void builtEventKeepsSentryInterfacesWithSameNameIfReplacementDisabled( - @Injectable("sentryInterfaceName") final String mockSentryInterfaceName, - @Injectable final SentryInterface mockSentryInterface, - @Injectable final SentryInterface mockSentryInterface2) - throws Exception { - new NonStrictExpectations() {{ - mockSentryInterface.getInterfaceName(); - result = mockSentryInterfaceName; - mockSentryInterface2.getInterfaceName(); - result = mockSentryInterfaceName; - }}; + public void builtEventKeepsSentryInterfacesWithSameNameIfReplacementDisabled() throws Exception { + String mockSentryInterfaceName = "sentryInterfaceName"; + SentryInterface mockSentryInterface = mock(SentryInterface.class); + SentryInterface mockSentryInterface2 = mock(SentryInterface.class); + + when(mockSentryInterface.getInterfaceName()).thenReturn(mockSentryInterfaceName); + when(mockSentryInterface2.getInterfaceName()).thenReturn(mockSentryInterfaceName); + final EventBuilder eventBuilder = new EventBuilder(); eventBuilder.withSentryInterface(mockSentryInterface); eventBuilder.withSentryInterface(mockSentryInterface2, false); @@ -477,7 +418,7 @@ public void builtEventKeepsSentryInterfacesWithSameNameIfReplacementDisabled( assertThat(event.getSentryInterfaces().entrySet(), hasSize(1)); } - @Test(expectedExceptions = IllegalStateException.class) + @Test(expected = IllegalStateException.class) public void buildingTheEventTwiceFails() throws Exception { final EventBuilder eventBuilder = new EventBuilder(); eventBuilder.build(); diff --git a/sentry/src/test/java/io/sentry/event/EventTest.java b/sentry/src/test/java/io/sentry/event/EventTest.java index fb83f9de3c4..32339898c09 100644 --- a/sentry/src/test/java/io/sentry/event/EventTest.java +++ b/sentry/src/test/java/io/sentry/event/EventTest.java @@ -1,10 +1,8 @@ package io.sentry.event; import io.sentry.BaseTest; -import mockit.Injectable; -import mockit.NonStrictExpectations; import org.hamcrest.Matchers; -import org.testng.annotations.Test; +import org.junit.Test; import java.io.*; import java.util.Date; @@ -13,38 +11,35 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.sameInstance; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class EventTest extends BaseTest { - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void ensureEventIdCantBeNull() throws Exception { new Event(null); } @Test - public void returnsCloneOfTimestamp(@Injectable final Date mockTimestamp, - @Injectable final Date mockCloneTimestamp, - @Injectable final UUID mockUuid) - throws Exception { - new NonStrictExpectations() {{ - mockTimestamp.clone(); - result = mockCloneTimestamp; - }}; - final Event event = new Event(mockUuid); + public void returnsCloneOfTimestamp() throws Exception { + final Event event = new Event(UUID.randomUUID()); - event.setTimestamp(mockTimestamp); + Date timeStamp = new Date(); + event.setTimestamp(timeStamp); - assertThat(event.getTimestamp(), is(sameInstance(mockCloneTimestamp))); + assertThat(event.getTimestamp(), is(equalTo(timeStamp))); + assertThat(event.getTimestamp(), is(not(sameInstance(timeStamp)))); } @Test - public void serializedEventContainsSerializableExtras(@Injectable final Object nonSerializableObject) - throws Exception { + public void serializedEventContainsSerializableExtras() throws Exception { final Event event = new Event(UUID.fromString("fb3fe928-69af-41a5-b76b-1db4c324caf6")); - new NonStrictExpectations() {{ - nonSerializableObject.toString(); - result = "3c644639-9721-4e32-8cc8-a2b5b77f4424"; - }}; + + Object nonSerializableObject = mock(Object.class); + when(nonSerializableObject.toString()).thenReturn("3c644639-9721-4e32-8cc8-a2b5b77f4424"); + event.getExtra().put("SerializableEntry", 38295L); event.getExtra().put("NonSerializableEntry", nonSerializableObject); event.getExtra().put("NullEntry", null); diff --git a/sentry/src/test/java/io/sentry/event/UserTest.java b/sentry/src/test/java/io/sentry/event/UserTest.java index 7d1253817c0..210b0f1014c 100644 --- a/sentry/src/test/java/io/sentry/event/UserTest.java +++ b/sentry/src/test/java/io/sentry/event/UserTest.java @@ -1,5 +1,13 @@ package io.sentry.event; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.util.HashMap; +import java.util.Map; + import io.sentry.BaseTest; import io.sentry.SentryClient; import io.sentry.connection.Connection; @@ -7,29 +15,20 @@ import io.sentry.context.SingletonContextManager; import io.sentry.event.helper.ContextBuilderHelper; import io.sentry.event.interfaces.UserInterface; -import mockit.Injectable; -import mockit.Tested; -import mockit.Verifications; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.util.HashMap; -import java.util.Map; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.IsEqual.equalTo; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; public class UserTest extends BaseTest { - @Tested private SentryClient sentryClient = null; - @Injectable private Connection mockConnection = null; - @Injectable private ContextManager contextManager = new SingletonContextManager(); - @BeforeMethod + @Before public void setup() { contextManager.clear(); + mockConnection = mock(Connection.class); + sentryClient = new SentryClient(mockConnection, contextManager); } @Test @@ -54,16 +53,16 @@ public void testUserPropagation() { map.put("foo", "bar"); map.put("baz", 2); - new Verifications() {{ - Event event; - mockConnection.send(event = withCapture()); - UserInterface userInterface = (UserInterface) event.getSentryInterfaces().get(UserInterface.USER_INTERFACE); - assertThat(userInterface.getId(), equalTo(user.getId())); - assertThat(userInterface.getEmail(), equalTo(user.getEmail())); - assertThat(userInterface.getIpAddress(), equalTo(user.getIpAddress())); - assertThat(userInterface.getUsername(), equalTo(user.getUsername())); - assertThat(userInterface.getData(), equalTo(map)); - }}; + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + verify(mockConnection).send(eventCaptor.capture()); + Event event = eventCaptor.getValue(); + UserInterface userInterface = (UserInterface) event.getSentryInterfaces().get(UserInterface.USER_INTERFACE); + assertThat(userInterface.getId(), equalTo(user.getId())); + assertThat(userInterface.getEmail(), equalTo(user.getEmail())); + assertThat(userInterface.getIpAddress(), equalTo(user.getIpAddress())); + assertThat(userInterface.getUsername(), equalTo(user.getUsername())); + assertThat(userInterface.getData(), equalTo(map)); } } diff --git a/sentry/src/test/java/io/sentry/event/helper/ContextBuilderHelperTest.java b/sentry/src/test/java/io/sentry/event/helper/ContextBuilderHelperTest.java index 04c438494b7..5f8e9960949 100644 --- a/sentry/src/test/java/io/sentry/event/helper/ContextBuilderHelperTest.java +++ b/sentry/src/test/java/io/sentry/event/helper/ContextBuilderHelperTest.java @@ -1,30 +1,32 @@ package io.sentry.event.helper; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.everyItem; +import static org.hamcrest.Matchers.isIn; +import static org.mockito.Mockito.mock; + +import java.util.HashMap; +import java.util.Map; + import io.sentry.SentryClient; import io.sentry.connection.Connection; import io.sentry.context.Context; import io.sentry.context.ContextManager; import io.sentry.context.SingletonContextManager; -import io.sentry.event.*; +import io.sentry.event.Breadcrumb; +import io.sentry.event.BreadcrumbBuilder; +import io.sentry.event.Event; +import io.sentry.event.EventBuilder; +import io.sentry.event.User; +import io.sentry.event.UserBuilder; import io.sentry.event.interfaces.UserInterface; -import mockit.Injectable; -import mockit.Tested; -import mockit.Verifications; -import org.testng.annotations.Test; - -import java.util.HashMap; -import java.util.Map; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import org.junit.Test; public class ContextBuilderHelperTest { - @Tested - private SentryClient client = null; - @Injectable - private Connection mockConnection = null; - @Injectable private ContextManager contextManager = new SingletonContextManager(); + private SentryClient client = new SentryClient(mock(Connection.class), contextManager); @Test public void testHelper() { @@ -61,11 +63,9 @@ public void testHelper() { tags.put("tag1", "value1"); tags.put("tag2", "value2"); - new Verifications() {{ - assertThat(event.getBreadcrumbs(), contains(breadcrumb1, breadcrumb2)); - assertThat(userInterface.getEmail(), equalTo(user.getEmail())); - assertThat(event.getExtra().entrySet(), everyItem(isIn(extra.entrySet()))); - assertThat(event.getTags().entrySet(), everyItem(isIn(tags.entrySet()))); - }}; + assertThat(event.getBreadcrumbs(), contains(breadcrumb1, breadcrumb2)); + assertThat(userInterface.getEmail(), equalTo(user.getEmail())); + assertThat(event.getExtra().entrySet(), everyItem(isIn(extra.entrySet()))); + assertThat(event.getTags().entrySet(), everyItem(isIn(tags.entrySet()))); } } diff --git a/sentry/src/test/java/io/sentry/event/helper/HttpEventBuilderHelperTest.java b/sentry/src/test/java/io/sentry/event/helper/HttpEventBuilderHelperTest.java index ddd0c591575..02485d4a40e 100644 --- a/sentry/src/test/java/io/sentry/event/helper/HttpEventBuilderHelperTest.java +++ b/sentry/src/test/java/io/sentry/event/helper/HttpEventBuilderHelperTest.java @@ -1,98 +1,123 @@ package io.sentry.event.helper; +import static java.util.Collections.emptyList; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +import java.security.Principal; +import java.util.Collections; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestEvent; +import javax.servlet.http.HttpServletRequest; + import io.sentry.BaseTest; -import mockit.*; import io.sentry.event.EventBuilder; import io.sentry.event.interfaces.HttpInterface; import io.sentry.event.interfaces.SentryInterface; import io.sentry.event.interfaces.UserInterface; import io.sentry.servlet.SentryServletRequestListener; -import org.testng.annotations.Test; - -import javax.servlet.http.HttpServletRequest; -import java.security.Principal; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; public class HttpEventBuilderHelperTest extends BaseTest { - @Tested - private HttpEventBuilderHelper httpEventBuilderHelper = null; - @Injectable + private HttpEventBuilderHelper httpEventBuilderHelper; private EventBuilder mockEventBuilder = null; - @SuppressWarnings("unused") - @Mocked - private HttpInterface mockHttpInterface = null; + private ServletRequestEvent mockServletRequestEvent; + private HttpServletRequest mockServletRequest; + + @Before + public void setup() { + httpEventBuilderHelper = new HttpEventBuilderHelper(); + mockEventBuilder = mock(EventBuilder.class); + mockServletRequestEvent = mock(ServletRequestEvent.class); + mockServletRequest = mock(HttpServletRequest.class); + + when(mockServletRequest.getRequestURL()).thenReturn(new StringBuffer("request")); + when(mockServletRequest.getHeaderNames()).thenReturn(Collections.emptyEnumeration()); + + when(mockServletRequestEvent.getServletRequest()).thenReturn(mockServletRequest); + } @Test public void testNoRequest() throws Exception { - new NonStrictExpectations(SentryServletRequestListener.class) {{ - SentryServletRequestListener.getServletRequest(); - result = null; - }}; + new SentryServletRequestListener().requestDestroyed(mockServletRequestEvent); httpEventBuilderHelper.helpBuildingEvent(mockEventBuilder); - new Verifications() {{ - new HttpInterface(withInstanceOf(HttpServletRequest.class)); - times = 0; - mockEventBuilder.withSentryInterface(withInstanceOf(SentryInterface.class), anyBoolean); - times = 0; - }}; + verify(mockEventBuilder, never()).withSentryInterface(any(SentryInterface.class), anyBoolean()); } @Test - public void testWithRequest(@Injectable final HttpServletRequest mockHttpServletRequest) throws Exception { + public void testWithRequest() throws Exception { + try { + new SentryServletRequestListener().requestInitialized(mockServletRequestEvent); - new NonStrictExpectations(SentryServletRequestListener.class) {{ - SentryServletRequestListener.getServletRequest(); - result = mockHttpServletRequest; - }}; + httpEventBuilderHelper.helpBuildingEvent(mockEventBuilder); - httpEventBuilderHelper.helpBuildingEvent(mockEventBuilder); - - new Verifications() {{ - new HttpInterface(mockHttpServletRequest, httpEventBuilderHelper.getRemoteAddressResolver()); - new UserInterface(null, null, null, null); - mockEventBuilder.withSentryInterface(this.withNotNull(), false); - mockEventBuilder.withSentryInterface(this.withNotNull(), false); - }}; + verify(mockEventBuilder).withSentryInterface(isA(HttpInterface.class), eq(false)); + verify(mockEventBuilder).withSentryInterface(isA(UserInterface.class), eq(false)); + } finally { + new SentryServletRequestListener().requestDestroyed(null); + } } @Test - public void testWithUserPrincipal(@Injectable final HttpServletRequest mockHttpServletRequest, - @Injectable final Principal mockUserPrincipal, - @Injectable("93ad24e4-cad1-4214-af8e-2e48e76b02de") final String mockUsername) - throws Exception { - new NonStrictExpectations(SentryServletRequestListener.class) {{ - SentryServletRequestListener.getServletRequest(); - result = mockHttpServletRequest; - mockHttpServletRequest.getUserPrincipal(); - result = mockUserPrincipal; - mockUserPrincipal.getName(); - result = mockUsername; - }}; + public void testWithUserPrincipal() throws Exception { + try { + new SentryServletRequestListener().requestInitialized(mockServletRequestEvent); - httpEventBuilderHelper.helpBuildingEvent(mockEventBuilder); + Principal mockUserPrincipal = mock(Principal.class); + when(mockUserPrincipal.getName()).thenReturn("93ad24e4-cad1-4214-af8e-2e48e76b02de"); + when(mockServletRequest.getUserPrincipal()).thenReturn(mockUserPrincipal); + + httpEventBuilderHelper.helpBuildingEvent(mockEventBuilder); + + ArgumentCaptor ifaceCaptor = ArgumentCaptor.forClass(SentryInterface.class); + + verify(mockEventBuilder, times(2)).withSentryInterface(ifaceCaptor.capture(), eq(false)); + SentryInterface sentryInterface = ifaceCaptor.getAllValues().get(1); + Assert.assertTrue(sentryInterface instanceof UserInterface); - new Verifications() {{ - new UserInterface(null, mockUsername, null, null); - mockEventBuilder.withSentryInterface(this.withNotNull(), false); - }}; + UserInterface userIface = (UserInterface) sentryInterface; + + Assert.assertEquals("93ad24e4-cad1-4214-af8e-2e48e76b02de", userIface.getUsername()); + } finally { + new SentryServletRequestListener().requestDestroyed(mockServletRequestEvent); + } } @Test - public void testWithIpAddress(@Injectable final HttpServletRequest mockHttpServletRequest, - @Injectable("d90e92cc-1929-4f9e-a44c-3062a8b00c70") final String mockIpAddress) - throws Exception { - new NonStrictExpectations(SentryServletRequestListener.class) {{ - SentryServletRequestListener.getServletRequest(); - result = mockHttpServletRequest; - mockHttpServletRequest.getRemoteAddr(); - result = mockIpAddress; - }}; + public void testWithIpAddress() throws Exception { + try { + new SentryServletRequestListener().requestInitialized(mockServletRequestEvent); - httpEventBuilderHelper.helpBuildingEvent(mockEventBuilder); + when(mockServletRequest.getRemoteAddr()).thenReturn("d90e92cc-1929-4f9e-a44c-3062a8b00c70"); + + httpEventBuilderHelper.helpBuildingEvent(mockEventBuilder); + + ArgumentCaptor ifaceCaptor = ArgumentCaptor.forClass(SentryInterface.class); + + verify(mockEventBuilder, times(2)).withSentryInterface(ifaceCaptor.capture(), eq(false)); + SentryInterface sentryInterface = ifaceCaptor.getAllValues().get(1); + Assert.assertTrue(sentryInterface instanceof UserInterface); + + UserInterface userIface = (UserInterface) sentryInterface; - new Verifications() {{ - new UserInterface(null, null, mockIpAddress, null); - mockEventBuilder.withSentryInterface(this.withNotNull(), false); - }}; + Assert.assertEquals("d90e92cc-1929-4f9e-a44c-3062a8b00c70", userIface.getIpAddress()); + } finally { + new SentryServletRequestListener().requestDestroyed(mockServletRequestEvent); + } } } diff --git a/sentry/src/test/java/io/sentry/event/interfaces/DebugMetaInterfaceTest.java b/sentry/src/test/java/io/sentry/event/interfaces/DebugMetaInterfaceTest.java index c6e58c91210..9d6578697d9 100644 --- a/sentry/src/test/java/io/sentry/event/interfaces/DebugMetaInterfaceTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/DebugMetaInterfaceTest.java @@ -1,7 +1,7 @@ package io.sentry.event.interfaces; import io.sentry.BaseTest; -import org.testng.annotations.Test; +import org.junit.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; diff --git a/sentry/src/test/java/io/sentry/event/interfaces/HttpInterfaceTest.java b/sentry/src/test/java/io/sentry/event/interfaces/HttpInterfaceTest.java index 395fc6d8b68..5695c380c02 100644 --- a/sentry/src/test/java/io/sentry/event/interfaces/HttpInterfaceTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/HttpInterfaceTest.java @@ -1,65 +1,54 @@ package io.sentry.event.interfaces; -import io.sentry.BaseTest; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import static java.util.Collections.enumeration; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonMap; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Collections; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; -import java.util.Arrays; -import java.util.Collections; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import io.sentry.BaseTest; +import org.junit.Before; +import org.junit.Test; public class HttpInterfaceTest extends BaseTest { - @Injectable private HttpServletRequest mockHttpServletRequest = null; - @Injectable private Cookie mockCookie = null; - @BeforeMethod + @Before public void setUp() throws Exception { - new NonStrictExpectations() {{ - mockHttpServletRequest.getRequestURL(); - result = new StringBuffer(); - mockHttpServletRequest.getMethod(); - result = "method"; - mockHttpServletRequest.getParameterMap(); - result = Collections.emptyMap(); - mockHttpServletRequest.getQueryString(); - result = "queryString"; - mockHttpServletRequest.getCookies();//Could be null - result = null; - mockHttpServletRequest.getRemoteAddr(); - result = "remoteAddr"; - mockHttpServletRequest.getServerName(); - result = "serverName"; - mockHttpServletRequest.getServerPort(); - result = 24; - mockHttpServletRequest.getLocalAddr(); - result = "localAddr"; - mockHttpServletRequest.getLocalName(); - result = "localName"; - mockHttpServletRequest.getLocalPort(); - result = 42; - mockHttpServletRequest.getProtocol(); - result = "protocol"; - mockHttpServletRequest.isSecure(); - result = false; - mockHttpServletRequest.isAsyncStarted(); - result = false; - mockHttpServletRequest.getAuthType(); - result = "authType"; - mockHttpServletRequest.getRemoteUser(); - result = "remoteUser"; - mockHttpServletRequest.getHeaderNames(); - result = Collections.emptyEnumeration(); - mockHttpServletRequest.getHeaders(anyString); - result = Collections.emptyEnumeration(); - }}; + mockHttpServletRequest = mock(HttpServletRequest.class); + when(mockHttpServletRequest.getRequestURL()).thenReturn(new StringBuffer()); + when(mockHttpServletRequest.getMethod()).thenReturn("method"); + when(mockHttpServletRequest.getParameterMap()).thenReturn(Collections.emptyMap()); + when(mockHttpServletRequest.getQueryString()).thenReturn("queryString"); + when(mockHttpServletRequest.getCookies()).thenReturn(null); + when(mockHttpServletRequest.getRemoteAddr()).thenReturn("remoteAddr"); + when(mockHttpServletRequest.getServerName()).thenReturn("serverName"); + when(mockHttpServletRequest.getServerPort()).thenReturn(24); + when(mockHttpServletRequest.getLocalAddr()).thenReturn("localAddr"); + when(mockHttpServletRequest.getLocalName()).thenReturn("localName"); + when(mockHttpServletRequest.getLocalPort()).thenReturn(42); + when(mockHttpServletRequest.getProtocol()).thenReturn("protocol"); + when(mockHttpServletRequest.isSecure()).thenReturn(false); + when(mockHttpServletRequest.isAsyncStarted()).thenReturn(false); + when(mockHttpServletRequest.getAuthType()).thenReturn("authType"); + when(mockHttpServletRequest.getRemoteUser()).thenReturn("remoteUser"); + when(mockHttpServletRequest.getHeaderNames()).thenReturn(Collections.emptyEnumeration()); + when(mockHttpServletRequest.getHeaders(anyString())).thenReturn(Collections.emptyEnumeration()); + + mockCookie = mock(Cookie.class); } @Test @@ -85,48 +74,30 @@ public void testHttpServletCopied() throws Exception { final String headerKey = "2c4a28c6-cef6-4847-92be-bf161ec4edc6"; final String headerValue = "2327b4fe-c35f-4bbb-842a-a89c718f5f01"; - new NonStrictExpectations() {{ - mockHttpServletRequest.getRequestURL(); - result = new StringBuffer(requestUrl); - mockHttpServletRequest.getMethod(); - result = method; - mockHttpServletRequest.getParameterMap(); - result = Collections.singletonMap(parameterName, new String[]{parameterValue}); - mockHttpServletRequest.getQueryString(); - result = queryString; - mockCookie.getName(); - result = cookieName; - mockCookie.getValue(); - result = cookieValue; - mockHttpServletRequest.getCookies(); - result = new Cookie[]{mockCookie}; - mockHttpServletRequest.getRemoteAddr(); - result = remoteAddr; - mockHttpServletRequest.getServerName(); - result = serverName; - mockHttpServletRequest.getServerPort(); - result = serverPort; - mockHttpServletRequest.getLocalAddr(); - result = localAddr; - mockHttpServletRequest.getLocalName(); - result = localName; - mockHttpServletRequest.getLocalPort(); - result = localPort; - mockHttpServletRequest.getProtocol(); - result = protocol; - mockHttpServletRequest.isSecure(); - result = secure; - mockHttpServletRequest.isAsyncStarted(); - result = asyncStarted; - mockHttpServletRequest.getAuthType(); - result = authType; - mockHttpServletRequest.getRemoteUser(); - result = remoteUser; - mockHttpServletRequest.getHeaderNames(); - result = Collections.enumeration(Arrays.asList(headerKey)); - mockHttpServletRequest.getHeaders(headerKey); - result = Collections.enumeration(Arrays.asList(headerValue)); - }}; + when(mockHttpServletRequest.getRequestURL()).thenReturn(new StringBuffer(requestUrl)); + when(mockHttpServletRequest.getMethod()).thenReturn(method); + when(mockHttpServletRequest.getParameterMap()) + .thenReturn(singletonMap(parameterName, new String[]{parameterValue})); + + when(mockHttpServletRequest.getQueryString()).thenReturn(queryString); + + when(mockCookie.getName()).thenReturn(cookieName); + when(mockCookie.getValue()).thenReturn(cookieValue); + when(mockHttpServletRequest.getCookies()).thenReturn(new Cookie[]{mockCookie}); + + when(mockHttpServletRequest.getRemoteAddr()).thenReturn(remoteAddr); + when(mockHttpServletRequest.getServerName()).thenReturn(serverName); + when(mockHttpServletRequest.getServerPort()).thenReturn(serverPort); + when(mockHttpServletRequest.getLocalAddr()).thenReturn(localAddr); + when(mockHttpServletRequest.getLocalName()).thenReturn(localName); + when(mockHttpServletRequest.getLocalPort()).thenReturn(localPort); + when(mockHttpServletRequest.getProtocol()).thenReturn(protocol); + when(mockHttpServletRequest.isSecure()).thenReturn(secure); + when(mockHttpServletRequest.isAsyncStarted()).thenReturn(asyncStarted); + when(mockHttpServletRequest.getAuthType()).thenReturn(authType); + when(mockHttpServletRequest.getRemoteUser()).thenReturn(remoteUser); + when(mockHttpServletRequest.getHeaderNames()).thenReturn(enumeration(singleton(headerKey))); + when(mockHttpServletRequest.getHeaders(eq(headerKey))).thenReturn(enumeration(singleton(headerValue))); HttpInterface httpInterface = new HttpInterface(mockHttpServletRequest); @@ -150,11 +121,6 @@ public void testHttpServletCopied() throws Exception { @Test public void testNullCookies() throws Exception { - new NonStrictExpectations() {{ - mockHttpServletRequest.getCookies(); - result = null; - }}; - HttpInterface httpInterface = new HttpInterface(mockHttpServletRequest); assertThat(httpInterface.getCookies().size(), is(0)); diff --git a/sentry/src/test/java/io/sentry/event/interfaces/MessageInterfaceTest.java b/sentry/src/test/java/io/sentry/event/interfaces/MessageInterfaceTest.java index 008b26ce45f..d512236d383 100644 --- a/sentry/src/test/java/io/sentry/event/interfaces/MessageInterfaceTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/MessageInterfaceTest.java @@ -1,7 +1,7 @@ package io.sentry.event.interfaces; import io.sentry.BaseTest; -import org.testng.annotations.Test; +import org.junit.Test; import java.util.Collections; import java.util.List; diff --git a/sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java b/sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java index bbb5148a50a..0f117d1dffe 100644 --- a/sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/SentryExceptionTest.java @@ -1,72 +1,41 @@ package io.sentry.event.interfaces; -import io.sentry.BaseTest; -import java.util.Deque; -import mockit.Delegate; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import org.testng.annotations.Test; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; - import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class SentryExceptionTest extends BaseTest { - - @Injectable - private Throwable mockThrowable = null; +import java.util.Deque; - @Injectable - private InnerClassThrowable mockInnerClassThrowable = null; +import io.sentry.BaseTest; +import org.junit.Test; +public class SentryExceptionTest extends BaseTest { @Test - public void ensureConversionToQueueKeepsOrder(@Injectable final Throwable mockCause) throws Exception { + public void ensureConversionToQueueKeepsOrder() throws Exception { final String exceptionMessage = "208ea34a-9c99-42d6-a399-59a4c85900dc"; final String causeMessage = "46a1b2ee-629b-49eb-a2be-f5250c995ea4"; - new NonStrictExpectations() {{ - mockThrowable.getCause(); - result = new Delegate() { - @SuppressWarnings("unused") - public Throwable getCause() { - return mockCause; - } - }; - mockThrowable.getMessage(); - result = exceptionMessage; - mockCause.getMessage(); - result = causeMessage; - }}; - - Deque exceptions = SentryException.extractExceptionQueue(mockThrowable); + Throwable exception = new Exception(exceptionMessage, new Exception(causeMessage)); + + Deque exceptions = SentryException.extractExceptionQueue(exception); assertThat(exceptions.getFirst().getExceptionMessage(), is(exceptionMessage)); assertThat(exceptions.getLast().getExceptionMessage(), is(causeMessage)); } @Test - public void ensureInnerClassesAreRepresentedCorrectly(@Injectable final InnerClassThrowable mockCause) throws Exception { - new NonStrictExpectations() {{ - mockInnerClassThrowable.getCause(); - result = new Delegate() { - @SuppressWarnings("unused") - public Throwable getCause() { - return mockCause; - } - }; - }}; - - Deque exceptions = SentryException.extractExceptionQueue(mockInnerClassThrowable); + public void ensureInnerClassesAreRepresentedCorrectly() throws Exception { + InnerClassThrowable exception = new InnerClassThrowable(new InnerClassThrowable()); + + Deque exceptions = SentryException.extractExceptionQueue(exception); assertThat(exceptions.getFirst().getExceptionClassName(), is("SentryExceptionTest$InnerClassThrowable")); } @Test public void exceptionMechanismThrowerIsUnwrappedViaExtractExceptionQueue() throws Exception { - Throwable throwableMock = mock(Throwable.class); String expectedMessage = "message"; when(throwableMock.getMessage()).thenReturn(expectedMessage); @@ -122,6 +91,11 @@ public void exceptionMechanismThrowerIsUnwrappedInInnerException() throws Except } private static final class InnerClassThrowable extends Throwable { + InnerClassThrowable() { + } + InnerClassThrowable(Throwable cause) { + super(cause); + } } } diff --git a/sentry/src/test/java/io/sentry/event/interfaces/SentryStackTraceElementTest.java b/sentry/src/test/java/io/sentry/event/interfaces/SentryStackTraceElementTest.java index c89cbaa5244..21d4ffb2387 100644 --- a/sentry/src/test/java/io/sentry/event/interfaces/SentryStackTraceElementTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/SentryStackTraceElementTest.java @@ -2,8 +2,8 @@ import io.sentry.BaseTest; import io.sentry.jvmti.Frame; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; import java.lang.reflect.Method; @@ -16,7 +16,7 @@ public class SentryStackTraceElementTest extends BaseTest { private Method method2; private Method method3; - @BeforeMethod + @Before public void setup() { // setup valid Method instances (since they can't be easily constructed) method1(); diff --git a/sentry/src/test/java/io/sentry/event/interfaces/StackTraceInterfaceTest.java b/sentry/src/test/java/io/sentry/event/interfaces/StackTraceInterfaceTest.java index cb3e82ed606..91c86158157 100644 --- a/sentry/src/test/java/io/sentry/event/interfaces/StackTraceInterfaceTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/StackTraceInterfaceTest.java @@ -2,7 +2,7 @@ import io.sentry.BaseTest; -import org.testng.annotations.Test; +import org.junit.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; diff --git a/sentry/src/test/java/io/sentry/event/interfaces/UserInterfaceTest.java b/sentry/src/test/java/io/sentry/event/interfaces/UserInterfaceTest.java index 9a294145732..d774a7c8640 100644 --- a/sentry/src/test/java/io/sentry/event/interfaces/UserInterfaceTest.java +++ b/sentry/src/test/java/io/sentry/event/interfaces/UserInterfaceTest.java @@ -1,7 +1,7 @@ package io.sentry.event.interfaces; import io.sentry.BaseTest; -import org.testng.annotations.Test; +import org.junit.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; diff --git a/sentry/src/test/java/io/sentry/helper/RemoteAddressResolverTest.java b/sentry/src/test/java/io/sentry/helper/RemoteAddressResolverTest.java index 657b3bf5b10..78d360fc2ec 100644 --- a/sentry/src/test/java/io/sentry/helper/RemoteAddressResolverTest.java +++ b/sentry/src/test/java/io/sentry/helper/RemoteAddressResolverTest.java @@ -1,31 +1,33 @@ package io.sentry.helper; import io.sentry.BaseTest; -import io.sentry.dsn.Dsn; import io.sentry.event.helper.BasicRemoteAddressResolver; import io.sentry.event.helper.ForwardedAddressResolver; -import mockit.Expectations; -import mockit.Mocked; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; import javax.servlet.http.HttpServletRequest; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class RemoteAddressResolverTest extends BaseTest { - @Mocked private HttpServletRequest request; + @Before + public void setup() { + request = mock(HttpServletRequest.class); + } + @Test public void testBasicRemoteAddressResolver() { BasicRemoteAddressResolver resolver = new BasicRemoteAddressResolver(); - new Expectations() {{ - request.getRemoteAddr(); - result = "1.2.3.4"; - }}; + when(request.getRemoteAddr()).thenReturn("1.2.3.4"); String remoteAddress = resolver.getRemoteAddress(request); assertThat(remoteAddress, is("1.2.3.4")); @@ -34,13 +36,8 @@ public void testBasicRemoteAddressResolver() { @Test public void testBasicRemoteAddressResolverWithXForwardedFor() { BasicRemoteAddressResolver resolver = new BasicRemoteAddressResolver(); - - new Expectations() {{ - request.getRemoteAddr(); - result = "1.2.3.4"; - request.getHeader("X-FORWARDED-FOR"); - result = "9.9.9.9"; - }}; + when(request.getRemoteAddr()).thenReturn("1.2.3.4"); + when(request.getHeader(eq("X-FORWARDED-FOR"))).thenReturn("9.9.9.9"); String remoteAddress = resolver.getRemoteAddress(request); assertThat(remoteAddress, is("1.2.3.4")); @@ -53,10 +50,7 @@ public void testBasicRemoteAddressResolverWithXForwardedFor() { public void testForwardedAddressResolver() { ForwardedAddressResolver resolver = new ForwardedAddressResolver(); - new Expectations() {{ - request.getHeader("X-FORWARDED-FOR"); - result = "9.9.9.9"; - }}; + when(request.getHeader(eq("X-FORWARDED-FOR"))).thenReturn("9.9.9.9"); String remoteAddress = resolver.getRemoteAddress(request); assertThat(remoteAddress, is("9.9.9.9")); @@ -66,13 +60,9 @@ public void testForwardedAddressResolver() { public void testForwardedAddressResolverFallthrough() { ForwardedAddressResolver resolver = new ForwardedAddressResolver(); - new Expectations() {{ - request.getRemoteAddr(); - result = "1.2.3.4"; - }}; + when(request.getRemoteAddr()).thenReturn("1.2.3.4"); String remoteAddress = resolver.getRemoteAddress(request); assertThat(remoteAddress, is("1.2.3.4")); } - } diff --git a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java index 15983e42630..6f12d9c6f7b 100644 --- a/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java +++ b/sentry/src/test/java/io/sentry/jul/SentryHandlerEventBuildingTest.java @@ -3,17 +3,17 @@ import io.sentry.BaseTest; import io.sentry.Sentry; import io.sentry.SentryClient; -import io.sentry.environment.SentryEnvironment; import io.sentry.event.interfaces.*; -import mockit.Injectable; -import mockit.Tested; -import mockit.Verifications; +import junitparams.JUnitParamsRunner; +import junitparams.NamedParameters; +import junitparams.Parameters; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import org.hamcrest.Matchers; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.junit.Test; import java.util.Date; import java.util.Map; @@ -22,26 +22,33 @@ import java.util.logging.LogRecord; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; - +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +@RunWith(JUnitParamsRunner.class) public class SentryHandlerEventBuildingTest extends BaseTest { - @Tested private SentryHandler sentryHandler = null; - @Injectable private ErrorManager errorManager = null; - @Injectable private SentryClient mockSentryClient = null; - @BeforeMethod + @Before public void setup() { + mockSentryClient = mock(SentryClient.class); + errorManager = mock(ErrorManager.class); Sentry.setStoredClient(mockSentryClient); + sentryHandler = new SentryHandler(); + sentryHandler.setErrorManager(errorManager); } private void assertNoErrorsInErrorManager() throws Exception { - new Verifications() {{ - errorManager.error(anyString, (Exception) any, anyInt); - times = 0; - }}; + verify(errorManager, never()).error(anyString(), any(Exception.class), anyInt()); } @Test @@ -55,28 +62,24 @@ public void testSimpleMessageLogging() throws Exception { sentryHandler.setLevel(Level.INFO); sentryHandler.publish(newLogRecord(loggerName, Level.INFO, message, arguments, null, null, threadId, date.getTime())); + ArgumentCaptor eventBuilderCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderCaptor.capture()); + Event event = eventBuilderCaptor.getValue().build(); + assertThat(event.getMessage(), is(message)); + Map sentryInterfaces = event.getSentryInterfaces(); + assertThat(sentryInterfaces, hasKey(MessageInterface.MESSAGE_INTERFACE)); + MessageInterface messageInterface = (MessageInterface) sentryInterfaces.get(MessageInterface.MESSAGE_INTERFACE); + assertThat(messageInterface.getParameters(), contains(arguments)); + assertThat(event.getLogger(), is(loggerName)); + assertThat(event.getExtra(), Matchers.hasEntry(SentryHandler.THREAD_ID, (int) threadId)); + assertThat(event.getTimestamp(), is(date)); + assertThat(event.getSdk().getIntegrations(), contains("java.util.logging")); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getMessage(), is(message)); - Map sentryInterfaces = event.getSentryInterfaces(); - assertThat(sentryInterfaces, hasKey(MessageInterface.MESSAGE_INTERFACE)); - MessageInterface messageInterface = (MessageInterface) sentryInterfaces.get(MessageInterface.MESSAGE_INTERFACE); - assertThat(messageInterface.getParameters(), contains(arguments)); - assertThat(event.getLogger(), is(loggerName)); - assertThat(event.getExtra(), Matchers.hasEntry(SentryHandler.THREAD_ID, (int) threadId)); - assertThat(event.getTimestamp(), is(date)); - event.getSdk(); - event.getSdk().getIntegrations(); - assertThat(event.getSdk().getIntegrations(), contains("java.util.logging")); - }}; assertNoErrorsInErrorManager(); } - @DataProvider(name = "levels") + @NamedParameters("levelConversions") private Object[][] levelConversions() { return new Object[][]{ {Event.Level.DEBUG, Level.FINEST}, @@ -88,17 +91,16 @@ private Object[][] levelConversions() { {Event.Level.ERROR, Level.SEVERE}}; } - @Test(dataProvider = "levels") + @Test + @Parameters(named = "levelConversions") public void testLevelConversion(final Event.Level expectedLevel, Level level) throws Exception { sentryHandler.setLevel(Level.ALL); sentryHandler.publish(newLogRecord(null, level, null, null, null)); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getLevel(), is(expectedLevel)); - }}; + ArgumentCaptor eventBuilderCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderCaptor.capture()); + Event event = eventBuilderCaptor.getValue().build(); + assertThat(event.getLevel(), is(expectedLevel)); assertNoErrorsInErrorManager(); } @@ -108,17 +110,15 @@ public void testExceptionLogging() throws Exception { sentryHandler.publish(newLogRecord(null, Level.SEVERE, null, null, exception)); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() - .get(ExceptionInterface.EXCEPTION_INTERFACE); - final SentryException sentryException = exceptionInterface.getExceptions().getFirst(); - assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); - assertThat(sentryException.getStackTraceInterface().getStackTrace(), - is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace(), null))); - }}; + ArgumentCaptor eventBuilderCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderCaptor.capture()); + Event event = eventBuilderCaptor.getValue().build(); + ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() + .get(ExceptionInterface.EXCEPTION_INTERFACE); + SentryException sentryException = exceptionInterface.getExceptions().getFirst(); + assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); + assertThat(sentryException.getStackTraceInterface().getStackTrace(), + is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace(), null))); assertNoErrorsInErrorManager(); } diff --git a/sentry/src/test/java/io/sentry/jvmti/FrameCacheTest.java b/sentry/src/test/java/io/sentry/jvmti/FrameCacheTest.java index fcaa66d464b..9a05b879006 100644 --- a/sentry/src/test/java/io/sentry/jvmti/FrameCacheTest.java +++ b/sentry/src/test/java/io/sentry/jvmti/FrameCacheTest.java @@ -1,33 +1,19 @@ package io.sentry.jvmti; import io.sentry.BaseTest; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.util.Set; -import java.util.WeakHashMap; +import org.junit.Before; +import org.junit.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static mockit.Deencapsulation.getField; -import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; public class FrameCacheTest extends BaseTest { - ThreadLocal> cache; - - @BeforeMethod - @AfterMethod + @Before public void setup() { - cache = getField(FrameCache.class, "cache"); - cache.get().clear(); - - Set appPackages = getField(FrameCache.class, "appPackages"); - appPackages.clear(); + FrameCache.reset(); } - @Test public void test() throws Exception { FrameCache.addAppPackage("io.sentry.jvmti"); diff --git a/sentry/src/test/java/io/sentry/marshaller/UncloseableOutputStreamTest.java b/sentry/src/test/java/io/sentry/marshaller/UncloseableOutputStreamTest.java index a4f0ba622d0..7ddc67fe22f 100644 --- a/sentry/src/test/java/io/sentry/marshaller/UncloseableOutputStreamTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/UncloseableOutputStreamTest.java @@ -1,28 +1,33 @@ package io.sentry.marshaller; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + import io.sentry.BaseTest; -import mockit.Injectable; -import mockit.Tested; -import mockit.Verifications; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; import java.io.OutputStream; public class UncloseableOutputStreamTest extends BaseTest { - @Tested private Marshaller.UncloseableOutputStream uncloseableOutputStream = null; - @Injectable private OutputStream mockOutputStream = null; + @Before + public void setup() { + mockOutputStream = mock(OutputStream.class); + uncloseableOutputStream = new Marshaller.UncloseableOutputStream(mockOutputStream); + } + @Test public void testWriteSingleByte() throws Exception { final int i = 12; uncloseableOutputStream.write(i); - new Verifications() {{ - mockOutputStream.write(i); - }}; + verify(mockOutputStream).write(eq(i)); } @Test @@ -31,9 +36,7 @@ public void testWriteByteArray() throws Exception { uncloseableOutputStream.write(array); - new Verifications() {{ - mockOutputStream.write(array); - }}; + verify(mockOutputStream).write(eq(array)); } @Test @@ -44,28 +47,20 @@ public void testWritePartOfByteArray() throws Exception { uncloseableOutputStream.write(array, off, len); - new Verifications() {{ - mockOutputStream.write(array, off, len); - }}; + verify(mockOutputStream).write(eq(array), eq(off), eq(len)); } @Test public void testFlush() throws Exception { uncloseableOutputStream.flush(); - new Verifications() {{ - mockOutputStream.flush(); - }}; - + verify(mockOutputStream).flush(); } @Test public void testClose() throws Exception { uncloseableOutputStream.close(); - new Verifications() {{ - mockOutputStream.close(); - times = 0; - }}; + verify(mockOutputStream, never()).close(); } } diff --git a/sentry/src/test/java/io/sentry/marshaller/json/DebugMetaInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/DebugMetaInterfaceBindingTest.java index 3a123add6c7..f5b06c614a9 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/DebugMetaInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/DebugMetaInterfaceBindingTest.java @@ -1,12 +1,8 @@ package io.sentry.marshaller.json; - import io.sentry.BaseTest; import io.sentry.event.interfaces.DebugMetaInterface; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Tested; -import org.testng.annotations.Test; +import org.junit.Test; import java.util.ArrayList; @@ -14,13 +10,10 @@ import static io.sentry.marshaller.json.JsonComparisonUtil.newJsonGenerator; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class DebugMetaInterfaceBindingTest extends BaseTest { - @Tested - private DebugMetaInterfaceBinding debugMetaInterfaceBinding = null; - @Injectable - private DebugMetaInterface mockDebugMetaInterface = null; - @Test public void testSimpleDebugImage() throws Exception { final JsonComparisonUtil.JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); @@ -28,10 +21,11 @@ public void testSimpleDebugImage() throws Exception { final ArrayList images = new ArrayList<>(); images.add(new DebugMetaInterface.DebugImage("abcd")); - new NonStrictExpectations() {{ - mockDebugMetaInterface.getDebugImages(); - result = images; - }}; + DebugMetaInterface mockDebugMetaInterface = mock(DebugMetaInterface.class); + when(mockDebugMetaInterface.getDebugImages()).thenReturn(images); + + DebugMetaInterfaceBinding debugMetaInterfaceBinding = new DebugMetaInterfaceBinding(); + debugMetaInterfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockDebugMetaInterface); assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/Proguard.json"))); diff --git a/sentry/src/test/java/io/sentry/marshaller/json/ExceptionInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/ExceptionInterfaceBindingTest.java index c8b0f30313f..70361b27cde 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/ExceptionInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/ExceptionInterfaceBindingTest.java @@ -1,49 +1,53 @@ package io.sentry.marshaller.json; +import static io.sentry.marshaller.json.JsonComparisonUtil.JsonGeneratorParser; +import static io.sentry.marshaller.json.JsonComparisonUtil.jsonResource; +import static io.sentry.marshaller.json.JsonComparisonUtil.newJsonGenerator; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Deque; + import com.fasterxml.jackson.core.JsonGenerator; import io.sentry.BaseTest; -import mockit.Delegate; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Tested; import io.sentry.event.EventBuilder; import io.sentry.event.interfaces.ExceptionInterface; -import io.sentry.event.interfaces.SentryException; -import io.sentry.event.interfaces.StackTraceInterface; import io.sentry.event.interfaces.ExceptionMechanism; import io.sentry.event.interfaces.ExceptionMechanismThrowable; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.io.IOException; -import java.util.Deque; - -import static mockit.Deencapsulation.setField; -import static io.sentry.marshaller.json.JsonComparisonUtil.*; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; +import io.sentry.event.interfaces.SentryException; +import io.sentry.event.interfaces.StackTraceInterface; +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; public class ExceptionInterfaceBindingTest extends BaseTest { - @Tested private ExceptionInterfaceBinding interfaceBinding = null; - @Injectable private ExceptionInterface mockExceptionInterface = null; - @Injectable private InterfaceBinding mockStackTraceInterfaceBinding = null; - @BeforeMethod + @Before public void setUp() throws Exception { - new NonStrictExpectations() {{ - mockStackTraceInterfaceBinding.writeInterface(withInstanceOf(JsonGenerator.class), (StackTraceInterface) any); - result = new Delegate() { - @SuppressWarnings("unused") - public void writeInterface(JsonGenerator generator, StackTraceInterface sentryInterface) - throws IOException { - generator.writeStartObject(); - generator.writeEndObject(); - } - }; - }}; + mockExceptionInterface = mock(ExceptionInterface.class); + //noinspection unchecked + mockStackTraceInterfaceBinding = mock(InterfaceBinding.class); + + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + JsonGenerator generator = invocation.getArgument(0); + generator.writeStartObject(); + generator.writeEndObject(); + return null; + } + }).when(mockStackTraceInterfaceBinding).writeInterface(any(JsonGenerator.class), any(StackTraceInterface.class)); + + interfaceBinding = new ExceptionInterfaceBinding(mockStackTraceInterfaceBinding); + } @Test @@ -51,15 +55,13 @@ public void testSimpleException() throws Exception { final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final String message = "6e65f60d-9f22-495a-9556-7a61eeea2a14"; final Throwable throwable = new IllegalStateException(message); - new NonStrictExpectations() {{ - mockExceptionInterface.getExceptions(); - result = new Delegate>() { - @SuppressWarnings("unused") - public Deque getExceptions() { - return SentryException.extractExceptionQueue(throwable); - } - }; - }}; + + when(mockExceptionInterface.getExceptions()).thenAnswer(new Answer>() { + @Override + public Deque answer(InvocationOnMock invocation) throws Throwable { + return SentryException.extractExceptionQueue(throwable); + } + }); interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockExceptionInterface); @@ -68,18 +70,15 @@ public Deque getExceptions() { @Test public void testClassInDefaultPackage() throws Exception { - setField((Object) DefaultPackageException.class, "name", DefaultPackageException.class.getSimpleName()); final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); - final Throwable throwable = new DefaultPackageException(); - new NonStrictExpectations() {{ - mockExceptionInterface.getExceptions(); - result = new Delegate>() { - @SuppressWarnings("unused") - public Deque getExceptions() { - return SentryException.extractExceptionQueue(throwable); - } - }; - }}; + final Throwable throwable = (Throwable) Class.forName("DefaultPackageException").newInstance(); + + when(mockExceptionInterface.getExceptions()).thenAnswer(new Answer>() { + @Override + public Deque answer(InvocationOnMock invocation) throws Throwable { + return SentryException.extractExceptionQueue(throwable); + } + }); interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockExceptionInterface); @@ -93,15 +92,13 @@ public void testChainedException() throws Exception { final Throwable throwable1 = new IllegalStateException(message1); final String message2 = "f1296959-5b86-45f7-853a-cdc25196710b"; final Throwable throwable2 = new IllegalStateException(message2, throwable1); - new NonStrictExpectations() {{ - mockExceptionInterface.getExceptions(); - result = new Delegate>() { - @SuppressWarnings("unused") - public Deque getExceptions() { - return SentryException.extractExceptionQueue(throwable2); - } - }; - }}; + + when(mockExceptionInterface.getExceptions()).thenAnswer(new Answer>() { + @Override + public Deque answer(InvocationOnMock invocation) throws Throwable { + return SentryException.extractExceptionQueue(throwable2); + } + }); interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockExceptionInterface); @@ -119,15 +116,12 @@ public void testExceptionWithMechanism() throws Exception { final Throwable throwable = new ExceptionMechanismThrowable(mechanism, originalThrowable); builder.withSentryInterface(new ExceptionInterface(throwable)); - new NonStrictExpectations() {{ - mockExceptionInterface.getExceptions(); - result = new Delegate>() { - @SuppressWarnings("unused") - public Deque getExceptions() { - return SentryException.extractExceptionQueue(throwable); - } - }; - }}; + when(mockExceptionInterface.getExceptions()).thenAnswer(new Answer>() { + @Override + public Deque answer(InvocationOnMock invocation) throws Throwable { + return SentryException.extractExceptionQueue(throwable); + } + }); interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockExceptionInterface); @@ -135,13 +129,3 @@ public Deque getExceptions() { } } - -/** - * Exception used to test exceptions defined in the default package. - *

    - * Obviously we can't use an Exception which is really defined in the default package within those tests - * (can't import it), so instead set the name of the class to remove the package name.
    - * {@code Deencapsulation.setField(Object) DefaultPackageException.class, "name", "DefaultPackageClass")} - */ -class DefaultPackageException extends Exception { -} diff --git a/sentry/src/test/java/io/sentry/marshaller/json/HttpInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/HttpInterfaceBindingTest.java index 285ad9b025a..a791874285a 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/HttpInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/HttpInterfaceBindingTest.java @@ -1,13 +1,13 @@ package io.sentry.marshaller.json; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; + import com.fasterxml.jackson.databind.JsonNode; import io.sentry.BaseTest; import io.sentry.event.interfaces.HttpInterface; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Tested; -import org.testng.annotations.Test; -import org.testng.collections.Lists; +import org.junit.Before; +import org.junit.Test; import java.util.Collection; import java.util.HashMap; @@ -16,97 +16,55 @@ import static io.sentry.marshaller.json.JsonComparisonUtil.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class HttpInterfaceBindingTest extends BaseTest { - @Tested private HttpInterfaceBinding interfaceBinding = null; - @Injectable private HttpInterface mockMessageInterface = null; + private JsonGeneratorParser jsonGeneratorParser; + + @Before + public void setup() throws Exception { + mockMessageInterface = mock(HttpInterface.class); + interfaceBinding = new HttpInterfaceBinding(); + jsonGeneratorParser = newJsonGenerator(); - @Test - public void testHeaders() throws Exception { - final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final Map> headers = new HashMap<>(); - headers.put("Header1", Lists.newArrayList("Value1")); - headers.put("Header2", Lists.newArrayList("Value1", "Value2")); + headers.put("Header1", singletonList("Value1")); + headers.put("Header2", asList("Value1", "Value2")); final HashMap cookies = new HashMap<>(); cookies.put("Cookie1", "Value1"); - new NonStrictExpectations() {{ - mockMessageInterface.getHeaders(); - result = headers; - mockMessageInterface.getRequestUrl(); - result = "http://host/url"; - mockMessageInterface.getMethod(); - result = "GET"; - mockMessageInterface.getQueryString(); - result = "query"; - mockMessageInterface.getCookies(); - result = cookies; - mockMessageInterface.getRemoteAddr(); - result = "1.2.3.4"; - mockMessageInterface.getServerName(); - result = "server-name"; - mockMessageInterface.getServerPort(); - result = 1234; - mockMessageInterface.getLocalPort(); - result = 5678; - mockMessageInterface.getProtocol(); - result = "HTTP"; - mockMessageInterface.getLocalAddr(); - result = "5.6.7.8"; - mockMessageInterface.getLocalName(); - result = "local-name"; - mockMessageInterface.getBody(); - result = "body"; - }}; - interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockMessageInterface); + when(mockMessageInterface.getHeaders()).thenReturn(headers); + when(mockMessageInterface.getRequestUrl()).thenReturn("http://host/url"); + when(mockMessageInterface.getMethod()).thenReturn("GET"); + when(mockMessageInterface.getQueryString()).thenReturn("query"); + when(mockMessageInterface.getCookies()).thenReturn(cookies); + when(mockMessageInterface.getRemoteAddr()).thenReturn("1.2.3.4"); + when(mockMessageInterface.getServerName()).thenReturn("server-name"); + when(mockMessageInterface.getServerPort()).thenReturn(1234); + when(mockMessageInterface.getLocalPort()).thenReturn(5678); + when(mockMessageInterface.getProtocol()).thenReturn("HTTP"); + when(mockMessageInterface.getLocalAddr()).thenReturn("5.6.7.8"); + when(mockMessageInterface.getLocalName()).thenReturn("local-name"); + when(mockMessageInterface.getBody()).thenReturn("body"); + } + @Test + public void testHeaders() throws Exception { + interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockMessageInterface); JsonNode value = jsonGeneratorParser.value(); assertThat(value, is(jsonResource("/io/sentry/marshaller/json/Http1.json"))); } @Test public void testBodyWithParameters() throws Exception { - final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); - final Map> headers = new HashMap<>(); - headers.put("Header1", Lists.newArrayList("Value1")); - headers.put("Header2", Lists.newArrayList("Value1", "Value2")); - final HashMap cookies = new HashMap<>(); - cookies.put("Cookie1", "Value1"); final HashMap> parameters = new HashMap<>(); - parameters.put("Parameter1", Lists.newArrayList("Value1")); - parameters.put("Parameter2", Lists.newArrayList("Value2", "Value3")); - new NonStrictExpectations() {{ - mockMessageInterface.getHeaders(); - result = headers; - mockMessageInterface.getRequestUrl(); - result = "http://host/url"; - mockMessageInterface.getMethod(); - result = "GET"; - mockMessageInterface.getQueryString(); - result = "query"; - mockMessageInterface.getCookies(); - result = cookies; - mockMessageInterface.getRemoteAddr(); - result = "1.2.3.4"; - mockMessageInterface.getServerName(); - result = "server-name"; - mockMessageInterface.getServerPort(); - result = 1234; - mockMessageInterface.getLocalPort(); - result = 5678; - mockMessageInterface.getProtocol(); - result = "HTTP"; - mockMessageInterface.getLocalAddr(); - result = "5.6.7.8"; - mockMessageInterface.getLocalName(); - result = "local-name"; - mockMessageInterface.getBody(); - result = "body"; - mockMessageInterface.getParameters(); - result = parameters; - }}; + parameters.put("Parameter1", singletonList("Value1")); + parameters.put("Parameter2", asList("Value2", "Value3")); + + when(mockMessageInterface.getParameters()).thenReturn(parameters); interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockMessageInterface); diff --git a/sentry/src/test/java/io/sentry/marshaller/json/JsonComparisonUtil.java b/sentry/src/test/java/io/sentry/marshaller/json/JsonComparisonUtil.java index 473d9a45dd2..ac774d83d03 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/JsonComparisonUtil.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/JsonComparisonUtil.java @@ -3,12 +3,11 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import io.sentry.BaseTest; import java.io.ByteArrayOutputStream; import java.io.OutputStream; -public final class JsonComparisonUtil extends BaseTest { +public final class JsonComparisonUtil { private static final ObjectMapper objectMapper = new ObjectMapper(); private JsonComparisonUtil() { diff --git a/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java b/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java index fd9689834fb..480461a6090 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java @@ -1,5 +1,7 @@ package io.sentry.marshaller.json; +import static java.util.Arrays.asList; + import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -7,57 +9,65 @@ import io.sentry.event.Breadcrumb; import io.sentry.event.BreadcrumbBuilder; import io.sentry.event.Sdk; -import mockit.*; +import junitparams.JUnitParamsRunner; +import junitparams.NamedParameters; +import junitparams.Parameters; import io.sentry.event.Event; import io.sentry.event.interfaces.SentryInterface; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.*; -import java.util.zip.DataFormatException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.UUID; import java.util.zip.GZIPInputStream; -import java.util.zip.Inflater; -import java.util.zip.InflaterInputStream; import static io.sentry.marshaller.json.JsonComparisonUtil.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; - +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(JUnitParamsRunner.class) public class JsonMarshallerTest extends BaseTest { - @Tested private JsonMarshaller jsonMarshaller = null; - @Injectable private Event mockEvent = null; - @BeforeMethod + @Before public void setUp() throws Exception { jsonMarshaller = new JsonMarshaller(); // Do not compress by default during the tests jsonMarshaller.setCompression(false); - new NonStrictExpectations() {{ - mockEvent.getId(); - result = UUID.fromString("00000000-0000-0000-0000-000000000000"); - mockEvent.getTimestamp(); - result = new Date(0); - mockEvent.getLevel(); - result = null; - }}; + mockEvent = mock(Event.class); + when(mockEvent.getId()).thenReturn(UUID.fromString("00000000-0000-0000-0000-000000000000")); + when(mockEvent.getTimestamp()).thenReturn(new Date(0)); + when(mockEvent.getLevel()).thenReturn(null); + when(mockEvent.getSdk()).thenReturn(new Sdk(null, null, Collections.emptySet())); } @Test - public void testEventIdWrittenProperly(@Injectable final UUID mockUuid) throws Exception { + public void testEventIdWrittenProperly() throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getId(); - result = mockUuid; - mockUuid.toString(); - result = "3b71fba5-413e-4022-ae98-5f0b80a155a5"; - }}; + + UUID id = UUID.fromString("3b71fba5-413e-4022-ae98-5f0b80a155a5"); + when(mockEvent.getId()).thenReturn(id); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); @@ -65,12 +75,10 @@ public void testEventIdWrittenProperly(@Injectable final UUID mockUuid) throws E } @Test - public void testEventMessageWrittenProperly(@Injectable("message") final String mockMessage) throws Exception { + public void testEventMessageWrittenProperly() throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getMessage(); - result = mockMessage; - }}; + + when(mockEvent.getMessage()).thenReturn("message"); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); @@ -78,22 +86,19 @@ public void testEventMessageWrittenProperly(@Injectable("message") final String } @Test - public void testEventTimestampWrittenProperly(@Injectable final Date mockTimestamp) throws Exception { + public void testEventTimestampWrittenProperly() throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getTimestamp(); - result = mockTimestamp; - mockTimestamp.getTime(); - // 2013-11-24T04:11:35.338 (UTC) - result = 1385266295338L; - }}; + + // 2013-11-24T04:11:35.338 (UTC) + Date mockTimestamp = new Date(1385266295338L); + when(mockEvent.getTimestamp()).thenReturn(mockTimestamp); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testTimestamp.json"))); } - @DataProvider(name = "levelProvider") + @NamedParameters("levelProvider") public Object[][] levelProvider() { return new Object[][]{ {Event.Level.DEBUG, "/io/sentry/marshaller/json/jsonmarshallertest/testLevelDebug.json"}, @@ -104,13 +109,11 @@ public Object[][] levelProvider() { }; } - @Test(dataProvider = "levelProvider") + @Test + @Parameters(named = "levelProvider") public void testEventLevelWrittenProperly(final Event.Level eventLevel, String levelFile) throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getLevel(); - result = eventLevel; - }}; + when(mockEvent.getLevel()).thenReturn(eventLevel); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); @@ -118,12 +121,9 @@ public void testEventLevelWrittenProperly(final Event.Level eventLevel, String l } @Test - public void testEventLoggerWrittenProperly(@Injectable("logger") final String mockLogger) throws Exception { + public void testEventLoggerWrittenProperly() throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getLogger(); - result = mockLogger; - }}; + when(mockEvent.getLogger()).thenReturn("logger"); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); @@ -131,12 +131,9 @@ public void testEventLoggerWrittenProperly(@Injectable("logger") final String mo } @Test - public void testEventPlaftormWrittenProperly(@Injectable("platform") final String mockPlatform) throws Exception { + public void testEventPlaftormWrittenProperly() throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getPlatform(); - result = mockPlatform; - }}; + when(mockEvent.getPlatform()).thenReturn("platform"); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); @@ -150,10 +147,8 @@ public void testEventSdkWrittenProperly() throws Exception { integrations.add("integration2"); final Sdk sdk = new Sdk("sdkName", "sdkVersion", integrations); final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getSdk(); - result = sdk; - }}; + + when(mockEvent.getSdk()).thenReturn(sdk); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); @@ -161,12 +156,9 @@ public void testEventSdkWrittenProperly() throws Exception { } @Test - public void testEventCulpritWrittenProperly(@Injectable("culprit") final String mockCulprit) throws Exception { + public void testEventCulpritWrittenProperly() throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getCulprit(); - result = mockCulprit; - }}; + when(mockEvent.getCulprit()).thenReturn("culprit"); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); @@ -174,12 +166,9 @@ public void testEventCulpritWrittenProperly(@Injectable("culprit") final String } @Test - public void testEventTransactionWrittenProperly(@Injectable("transaction") final String mockTransaction) throws Exception { + public void testEventTransactionWrittenProperly() throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getTransaction(); - result = mockTransaction; - }}; + when(mockEvent.getTransaction()).thenReturn("transaction"); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); @@ -187,13 +176,9 @@ public void testEventTransactionWrittenProperly(@Injectable("transaction") final } @Test - public void testEventTagsWrittenProperly(@Injectable("tagName") final String mockTagName, - @Injectable("tagValue") final String mockTagValue) throws Exception { + public void testEventTagsWrittenProperly() throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getTags(); - result = Collections.singletonMap(mockTagName, mockTagValue); - }}; + when(mockEvent.getTags()).thenReturn(Collections.singletonMap("tagName", "tagValue")); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); @@ -201,27 +186,20 @@ public void testEventTagsWrittenProperly(@Injectable("tagName") final String moc } @Test - public void testFingerPrintWrittenProperly(@Injectable("fingerprint1") final String mockFingerprint1, - @Injectable("fingerprint2") final String mockFingerprint2) throws Exception { + public void testFingerPrintWrittenProperly() throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getFingerprint(); - result = Arrays.asList(mockFingerprint1, mockFingerprint2); - }}; + when(mockEvent.getFingerprint()).thenReturn(asList("fingerprint1", "fingerprint2")); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testFingerprint.json"))); } - @Test - public void testEventServerNameWrittenProperly(@Injectable("serverName") final String mockServerName) throws Exception { + public void testEventServerNameWrittenProperly() throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getServerName(); - result = mockServerName; - }}; + + when(mockEvent.getServerName()).thenReturn("serverName"); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); @@ -229,12 +207,10 @@ public void testEventServerNameWrittenProperly(@Injectable("serverName") final S } @Test - public void testEventReleaseWrittenProperly(@Injectable("release") final String mockRelease) throws Exception { + public void testEventReleaseWrittenProperly() throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getRelease(); - result = mockRelease; - }}; + + when(mockEvent.getRelease()).thenReturn("release"); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); @@ -242,15 +218,11 @@ public void testEventReleaseWrittenProperly(@Injectable("release") final String } @Test - public void testEventDistWrittenProperly(@Injectable("release") final String mockRelease, - @Injectable("dist") final String mockDist) throws Exception { + public void testEventDistWrittenProperly() throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getRelease(); - result = mockRelease; - mockEvent.getDist(); - result = mockDist; - }}; + + when(mockEvent.getRelease()).thenReturn("release"); + when(mockEvent.getDist()).thenReturn("dist"); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); @@ -258,12 +230,10 @@ public void testEventDistWrittenProperly(@Injectable("release") final String moc } @Test - public void testEventEnvironmentWrittenProperly(@Injectable("environment") final String mockEnvironment) throws Exception { + public void testEventEnvironmentWrittenProperly() throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getEnvironment(); - result = mockEnvironment; - }}; + + when(mockEvent.getEnvironment()).thenReturn("environment"); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); @@ -291,10 +261,7 @@ public void testEventBreadcrumbsWrittenProperly() throws Exception { breadcrumbs.add(breadcrumb1); breadcrumbs.add(breadcrumb2); - new NonStrictExpectations() {{ - mockEvent.getBreadcrumbs(); - result = breadcrumbs; - }}; + when(mockEvent.getBreadcrumbs()).thenReturn(breadcrumbs); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); @@ -321,10 +288,7 @@ public void testEventContextsWrittenProperly() throws Exception { contexts.put("context1", context1); contexts.put("context2", context2); - new NonStrictExpectations() {{ - mockEvent.getContexts(); - result = contexts; - }}; + when(mockEvent.getContexts()).thenReturn(contexts); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); @@ -332,19 +296,16 @@ public void testEventContextsWrittenProperly() throws Exception { } @Test - public void testEventChecksumWrittenProperly(@Injectable("1234567890abcdef") final String mockChecksum) throws Exception { + public void testEventChecksumWrittenProperly() throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getChecksum(); - result = mockChecksum; - }}; + when(mockEvent.getChecksum()).thenReturn("1234567890abcdef"); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testChecksum.json"))); } - @DataProvider(name = "extraProvider") + @NamedParameters("extraProvider") public Object[][] extraProvider() { return new Object[][]{ {"key", "string", "/io/sentry/marshaller/json/jsonmarshallertest/testExtraString.json"}, @@ -361,13 +322,11 @@ public Object[][] extraProvider() { }; } - @Test(dataProvider = "extraProvider") + @Test + @Parameters(named = "extraProvider") public void testEventExtraWrittenProperly(final String extraKey, final Object extraValue, String extraFile) throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getExtra(); - result = Collections.singletonMap(extraKey, extraValue); - }}; + when(mockEvent.getExtra()).thenReturn(Collections.singletonMap(extraKey, extraValue)); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); @@ -375,15 +334,15 @@ public void testEventExtraWrittenProperly(final String extraKey, final Object ex } @Test - public void testEventExtraWrittenProperly(@Injectable("key") final String mockExtraKey, - @Injectable final Object mockExtraValue) throws Exception { + public void testEventExtraWrittenProperly() throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getExtra(); - result = Collections.singletonMap(mockExtraKey, mockExtraValue); - mockExtraValue.toString(); - result = "test"; - }}; + + when(mockEvent.getExtra()).thenReturn(Collections.singletonMap("key", new Object() { + @Override + public String toString() { + return "test"; + } + })); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); @@ -391,28 +350,29 @@ public void testEventExtraWrittenProperly(@Injectable("key") final String mockEx } @Test - public void testInterfaceBindingIsProperlyUsed( - @Injectable final SentryInterface mockSentryInterface, - @Injectable final InterfaceBinding mockInterfaceBinding) throws Exception { + public void testInterfaceBindingIsProperlyUsed() throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); - new NonStrictExpectations() {{ - mockEvent.getSentryInterfaces(); - result = Collections.singletonMap("interfaceKey", mockSentryInterface); - mockInterfaceBinding.writeInterface((JsonGenerator) any, mockSentryInterface); - result = new Delegate() { - @SuppressWarnings("unused") - public void writeInterface(JsonGenerator generator, SentryInterface sentryInterface) throws IOException { - generator.writeNull(); - } - }; - }}; + + SentryInterface mockSentryInterface = mock(SentryInterface.class); + when(mockEvent.getSentryInterfaces()).thenReturn(Collections.singletonMap("interfaceKey", mockSentryInterface)); + + @SuppressWarnings("unchecked") + InterfaceBinding mockInterfaceBinding = mock(InterfaceBinding.class); + + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + JsonGenerator generator = invocation.getArgument(0); + generator.writeNull(); + return null; + } + }).when(mockInterfaceBinding).writeInterface(any(JsonGenerator.class), eq(mockSentryInterface)); jsonMarshaller.addInterfaceBinding(mockSentryInterface.getClass(), mockInterfaceBinding); jsonMarshaller.marshall(mockEvent, jsonOutputStreamParser.outputStream()); - new Verifications() {{ - mockInterfaceBinding.writeInterface((JsonGenerator) any, mockSentryInterface); - }}; + verify(mockInterfaceBinding).writeInterface(any(JsonGenerator.class), eq(mockSentryInterface)); + assertThat(jsonOutputStreamParser.value(), is(jsonResource("/io/sentry/marshaller/json/jsonmarshallertest/testInterfaceBinding.json"))); } diff --git a/sentry/src/test/java/io/sentry/marshaller/json/MessageInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/MessageInterfaceBindingTest.java index d00a95d6579..5ed135710e7 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/MessageInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/MessageInterfaceBindingTest.java @@ -1,11 +1,8 @@ package io.sentry.marshaller.json; import io.sentry.BaseTest; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Tested; import io.sentry.event.interfaces.MessageInterface; -import org.testng.annotations.Test; +import org.junit.Test; import java.util.Arrays; import java.util.List; @@ -13,25 +10,22 @@ import static io.sentry.marshaller.json.JsonComparisonUtil.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class MessageInterfaceBindingTest extends BaseTest { - @Tested - private MessageInterfaceBinding interfaceBinding = null; - @Injectable - private MessageInterface mockMessageInterface = null; - @Test public void testSimpleMessage() throws Exception { final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final String message = "550ee459-cbb5-438e-91d2-b0bbdefab670"; final List parameters = Arrays.asList("33ed929b-d803-46b6-a57b-9c0feab1f468", "5fc10379-6392-470d-9de5-e4cb805ab78c"); - new NonStrictExpectations() {{ - mockMessageInterface.getMessage(); - result = message; - mockMessageInterface.getParameters(); - result = parameters; - }}; + + MessageInterface mockMessageInterface = mock(MessageInterface.class); + when(mockMessageInterface.getMessage()).thenReturn(message); + when(mockMessageInterface.getParameters()).thenReturn(parameters); + + MessageInterfaceBinding interfaceBinding = new MessageInterfaceBinding(); interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockMessageInterface); diff --git a/sentry/src/test/java/io/sentry/marshaller/json/SentryJsonGeneratorTest.java b/sentry/src/test/java/io/sentry/marshaller/json/SentryJsonGeneratorTest.java index 14ce7b82052..6619645cbeb 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/SentryJsonGeneratorTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/SentryJsonGeneratorTest.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.core.JsonFactory; import io.sentry.BaseTest; -import org.testng.annotations.Test; +import org.junit.Test; import java.io.OutputStream; import java.nio.file.Path; diff --git a/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java index d4f961342ac..efbe04b6c0a 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java @@ -6,54 +6,37 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Tested; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; import static io.sentry.marshaller.json.JsonComparisonUtil.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class StackTraceInterfaceBindingTest extends BaseTest { - - @Tested private StackTraceInterfaceBinding interfaceBinding = null; - - @Injectable private StackTraceInterface mockStackTraceInterface = null; + @Before + public void setup() { + mockStackTraceInterface = mock(StackTraceInterface.class); + interfaceBinding = new StackTraceInterfaceBinding(); + } + @Test public void testSingleSentryStackFrame() throws Exception { final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final SentryStackTraceElement sentryStackTraceElement = new SentryStackTraceElement( "", "throwError", "index.js", 100, 10, "http://localhost", "javascript", null); - new NonStrictExpectations() {{ - mockStackTraceInterface.getStackTrace(); - result = new SentryStackTraceElement[]{sentryStackTraceElement}; - }}; - interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockStackTraceInterface); - - assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/SentryStackTrace.json"))); - } - @Test - public void testSingleStackFrame() throws Exception { - final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); - final String methodName = "0cce55c9-478f-4386-8ede-4b6f000da3e6"; - final String className = "31b26f01-9b97-442b-9f36-8a317f94ad76"; - final int lineNumber = 1; - final SentryStackTraceElement stackTraceElement = new SentryStackTraceElement(className, methodName, - "File.java", lineNumber, null, null, null, null); - new NonStrictExpectations() {{ - mockStackTraceInterface.getStackTrace(); - result = new SentryStackTraceElement[]{stackTraceElement}; - }}; + when(mockStackTraceInterface.getStackTrace()).thenReturn(new SentryStackTraceElement[]{sentryStackTraceElement}); interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockStackTraceInterface); - assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/StackTrace1.json"))); + assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/SentryStackTrace.json"))); } @Test @@ -61,14 +44,12 @@ public void testFramesCommonWithEnclosing() throws Exception { final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final SentryStackTraceElement stackTraceElement = new SentryStackTraceElement("", "", "File.java", 0, null, null, null, null); - new NonStrictExpectations() {{ - mockStackTraceInterface.getStackTrace(); - result = new SentryStackTraceElement[]{stackTraceElement, stackTraceElement}; - mockStackTraceInterface.getFramesCommonWithEnclosing(); - result = 1; - }}; - interfaceBinding.setRemoveCommonFramesWithEnclosing(true); + when(mockStackTraceInterface.getStackTrace()) + .thenReturn(new SentryStackTraceElement[]{stackTraceElement, stackTraceElement}); + when(mockStackTraceInterface.getFramesCommonWithEnclosing()).thenReturn(1); + + interfaceBinding.setRemoveCommonFramesWithEnclosing(true); interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockStackTraceInterface); assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/StackTrace2.json"))); @@ -79,12 +60,11 @@ public void testFramesCommonWithEnclosingDisabled() throws Exception { final JsonGeneratorParser jsonGeneratorParser = newJsonGenerator(); final SentryStackTraceElement stackTraceElement = new SentryStackTraceElement("", "", "File.java", 0, null, null, null, null); - new NonStrictExpectations() {{ - mockStackTraceInterface.getStackTrace(); - result = new SentryStackTraceElement[]{stackTraceElement, stackTraceElement}; - mockStackTraceInterface.getFramesCommonWithEnclosing(); - result = 1; - }}; + + when(mockStackTraceInterface.getStackTrace()) + .thenReturn(new SentryStackTraceElement[]{stackTraceElement, stackTraceElement}); + when(mockStackTraceInterface.getFramesCommonWithEnclosing()).thenReturn(1); + interfaceBinding.setRemoveCommonFramesWithEnclosing(false); interfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockStackTraceInterface); @@ -114,10 +94,8 @@ public void testInAppFrames() throws Exception { "File.java", 5, null, null, null, null) ); - new NonStrictExpectations() {{ - mockStackTraceInterface.getStackTrace(); - result = sentryStackTraceElements.toArray(new SentryStackTraceElement[4]); - }}; + when(mockStackTraceInterface.getStackTrace()) + .thenReturn(sentryStackTraceElements.toArray(new SentryStackTraceElement[4])); List inAppModules = new ArrayList<>(); inAppModules.add("inAppModule"); diff --git a/sentry/src/test/java/io/sentry/marshaller/json/UserInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/UserInterfaceBindingTest.java index 098b2bcda18..41694d9f521 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/UserInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/UserInterfaceBindingTest.java @@ -1,12 +1,9 @@ package io.sentry.marshaller.json; import io.sentry.BaseTest; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Tested; import io.sentry.event.interfaces.UserInterface; import io.sentry.marshaller.json.JsonComparisonUtil.JsonGeneratorParser; -import org.testng.annotations.Test; +import org.junit.Test; import java.util.HashMap; import java.util.Map; @@ -17,10 +14,6 @@ import static org.hamcrest.Matchers.is; public class UserInterfaceBindingTest extends BaseTest { - @Tested - private UserInterfaceBinding userInterfaceBinding = null; - @Injectable - private UserInterface mockUserInterface = null; @Test public void testSimpleMessage() throws Exception { @@ -34,20 +27,11 @@ public void testSimpleMessage() throws Exception { data.put("baz", 2); data.put("qux", null); - new NonStrictExpectations() {{ - mockUserInterface.getId(); - result = id; - mockUserInterface.getUsername(); - result = username; - mockUserInterface.getEmail(); - result = email; - mockUserInterface.getIpAddress(); - result = ipAddress; - mockUserInterface.getData(); - result = data; - }}; - - userInterfaceBinding.writeInterface(jsonGeneratorParser.generator(), mockUserInterface); + UserInterface userInterface = new UserInterface(id, username, ipAddress, email, data); + + UserInterfaceBinding userInterfaceBinding = new UserInterfaceBinding(); + + userInterfaceBinding.writeInterface(jsonGeneratorParser.generator(), userInterface); assertThat(jsonGeneratorParser.value(), is(jsonResource("/io/sentry/marshaller/json/User1.json"))); } diff --git a/sentry/src/test/java/io/sentry/servlet/SentryServletContainerInitializerTest.java b/sentry/src/test/java/io/sentry/servlet/SentryServletContainerInitializerTest.java index 1e41ded6aca..bc2ba615449 100644 --- a/sentry/src/test/java/io/sentry/servlet/SentryServletContainerInitializerTest.java +++ b/sentry/src/test/java/io/sentry/servlet/SentryServletContainerInitializerTest.java @@ -1,10 +1,7 @@ package io.sentry.servlet; import io.sentry.BaseTest; -import mockit.Injectable; -import mockit.Tested; -import mockit.Verifications; -import org.testng.annotations.Test; +import org.junit.Test; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; @@ -13,10 +10,11 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.instanceOf; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; public class SentryServletContainerInitializerTest extends BaseTest { - @Tested - private SentryServletContainerInitializer sentryServletContainerInitializer = null; @Test public void testInitializerInjectedViaServiceLoader() throws Exception { @@ -25,11 +23,10 @@ public void testInitializerInjectedViaServiceLoader() throws Exception { } @Test - public void testFilterAddedToServletContext(@Injectable final ServletContext mockServletContext) throws Exception { - sentryServletContainerInitializer.onStartup(null, mockServletContext); + public void testFilterAddedToServletContext() throws Exception { + ServletContext mockServletContext = mock(ServletContext.class); + new SentryServletContainerInitializer().onStartup(null, mockServletContext); - new Verifications() {{ - mockServletContext.addListener(SentryServletRequestListener.class); - }}; + verify(mockServletContext).addListener(eq(SentryServletRequestListener.class)); } } diff --git a/sentry/src/test/java/io/sentry/servlet/SentryServletRequestListenerTest.java b/sentry/src/test/java/io/sentry/servlet/SentryServletRequestListenerTest.java index 9a0331e2bec..5cb8560328a 100644 --- a/sentry/src/test/java/io/sentry/servlet/SentryServletRequestListenerTest.java +++ b/sentry/src/test/java/io/sentry/servlet/SentryServletRequestListenerTest.java @@ -1,31 +1,28 @@ package io.sentry.servlet; import io.sentry.BaseTest; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Tested; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; import javax.servlet.ServletRequest; import javax.servlet.ServletRequestEvent; import javax.servlet.http.HttpServletRequest; -import static mockit.Deencapsulation.getField; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class SentryServletRequestListenerTest extends BaseTest { - @Tested private SentryServletRequestListener sentryServletRequestListener = null; - @Injectable private ServletRequestEvent mockServletRequestEvent = null; - @AfterMethod - public void tearDown() throws Exception { - // Reset the threadLocal value - ((ThreadLocal) getField(SentryServletRequestListener.class, "THREAD_REQUEST")).remove(); + @Before + public void setup() { + mockServletRequestEvent = mock(ServletRequestEvent.class); + sentryServletRequestListener = new SentryServletRequestListener(); + SentryServletRequestListener.reset(); } @Test @@ -34,12 +31,9 @@ public void requestListenerEmptyByDefault() throws Exception { } @Test - public void requestListenerContainsTheCurrentRequest(@Injectable final HttpServletRequest mockHttpServletRequest) - throws Exception { - new NonStrictExpectations() {{ - mockServletRequestEvent.getServletRequest(); - result = mockHttpServletRequest; - }}; + public void requestListenerContainsTheCurrentRequest() throws Exception { + HttpServletRequest mockHttpServletRequest = mock(HttpServletRequest.class); + when(mockServletRequestEvent.getServletRequest()).thenReturn(mockHttpServletRequest); sentryServletRequestListener.requestInitialized(mockServletRequestEvent); @@ -47,12 +41,9 @@ public void requestListenerContainsTheCurrentRequest(@Injectable final HttpServl } @Test - public void requestListenerDoesntWorkWithNonHttpRequests(@Injectable final ServletRequest mockServletRequest) - throws Exception { - new NonStrictExpectations() {{ - mockServletRequestEvent.getServletRequest(); - result = mockServletRequest; - }}; + public void requestListenerDoesntWorkWithNonHttpRequests() throws Exception { + ServletRequest mockServletRequest = mock(ServletRequest.class); + when(mockServletRequestEvent.getServletRequest()).thenReturn(mockServletRequest); sentryServletRequestListener.requestInitialized(mockServletRequestEvent); @@ -60,12 +51,10 @@ public void requestListenerDoesntWorkWithNonHttpRequests(@Injectable final Servl } @Test - public void requestListenerDestroyRemovesTheCurrentRequest(@Injectable final HttpServletRequest mockHttpServletRequest) - throws Exception { - new NonStrictExpectations() {{ - mockServletRequestEvent.getServletRequest(); - result = mockHttpServletRequest; - }}; + public void requestListenerDestroyRemovesTheCurrentRequest() throws Exception { + HttpServletRequest mockHttpServletRequest = mock(HttpServletRequest.class); + when(mockServletRequestEvent.getServletRequest()).thenReturn(mockHttpServletRequest); + sentryServletRequestListener.requestInitialized(mockServletRequestEvent); sentryServletRequestListener.requestDestroyed(mockServletRequestEvent); @@ -74,12 +63,9 @@ public void requestListenerDestroyRemovesTheCurrentRequest(@Injectable final Htt } @Test - public void requestListenerSpecificToLocalThread(@Injectable final HttpServletRequest mockHttpServletRequest) - throws Exception { - new NonStrictExpectations() {{ - mockServletRequestEvent.getServletRequest(); - result = mockHttpServletRequest; - }}; + public void requestListenerSpecificToLocalThread() throws Exception { + HttpServletRequest mockHttpServletRequest = mock(HttpServletRequest.class); + when(mockServletRequestEvent.getServletRequest()).thenReturn(mockHttpServletRequest); new Thread() { @Override diff --git a/sentry/src/test/java/io/sentry/time/ClockTest.java b/sentry/src/test/java/io/sentry/time/ClockTest.java index 30eb5cf8ba2..b31569b9de7 100644 --- a/sentry/src/test/java/io/sentry/time/ClockTest.java +++ b/sentry/src/test/java/io/sentry/time/ClockTest.java @@ -1,7 +1,7 @@ package io.sentry.time; import io.sentry.BaseTest; -import org.testng.annotations.Test; +import org.junit.Test; import java.util.Date; import java.util.concurrent.TimeUnit; diff --git a/sentry/src/test/resources/jndi.properties b/sentry/src/test/resources/jndi.properties deleted file mode 100644 index 5edb4d7d547..00000000000 --- a/sentry/src/test/resources/jndi.properties +++ /dev/null @@ -1 +0,0 @@ -java.naming.factory.initial=io.sentry.dsn.InitialContextFactory From 268f5dcb6e6555d9e679307e9259278047d8c8f4 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Thu, 12 Sep 2019 15:11:07 -0400 Subject: [PATCH 2101/2152] chore: Discord link --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 1a50dc09a13..1abf9498877 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,4 @@ based application. * [Examples](https://github.com/getsentry/examples) * [Bug Tracker](http://github.com/getsentry/sentry-java/issues) * [Code](http://github.com/getsentry/sentry-java) -* [Mailing List](https://groups.google.com/group/getsentry) -* [IRC](irc://irc.freenode.net/sentry) (irc.freenode.net, #sentry) -* [Travis CI](http://travis-ci.org/getsentry/sentry-java) - +* [![Discord](https://img.shields.io/discord/621778831602221064)](https://discord.gg/Ww9hbqr) From 37da069ec9b408f7b2754bf5150f6c44c23baa1c Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Tue, 17 Sep 2019 22:14:02 +0200 Subject: [PATCH 2102/2152] Sentry appengine test rewrite (#763) --- sentry-appengine/pom.xml | 28 +++- .../connection/AppEngineAsyncConnection.java | 26 ++- .../AppEngineSentryClientFactoryTest.java | 123 +++++++------- .../appengine/TestQueueFactoryProvider.java | 36 ++++ .../AppEngineAsyncConnectionTest.java | 157 ++++++++++-------- .../appengine/connection/ConnectionsInfo.java | 16 ++ .../AppEngineEventBuilderHelperTest.java | 115 ++++++------- .../com.google.appengine.spi.FactoryProvider | 1 + 8 files changed, 307 insertions(+), 195 deletions(-) create mode 100644 sentry-appengine/src/test/java/io/sentry/appengine/TestQueueFactoryProvider.java create mode 100644 sentry-appengine/src/test/java/io/sentry/appengine/connection/ConnectionsInfo.java create mode 100644 sentry-appengine/src/test/resources/META-INF/services/com.google.appengine.spi.FactoryProvider diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 0fab85ab536..f5f26452b9b 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -32,11 +32,6 @@ slf4j-api - - org.jmockit - jmockit - test - org.hamcrest hamcrest-core @@ -48,9 +43,28 @@ test - org.testng - testng + junit + junit + test + + + org.mockito + mockito-core test + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + true + + + + + diff --git a/sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java b/sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java index 5708f93b941..b8b1b5654b9 100644 --- a/sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java +++ b/sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java @@ -33,7 +33,10 @@ */ public class AppEngineAsyncConnection implements Connection { private static final Logger logger = LoggerFactory.getLogger(AppEngineAsyncConnection.class); - private static final Map APP_ENGINE_ASYNC_CONNECTIONS = new HashMap<>(); + + // visible for testing + static final Map APP_ENGINE_ASYNC_CONNECTIONS = new HashMap<>(); + /** * Identifier of the async connection. */ @@ -104,10 +107,19 @@ public void setQueue(String queueName) { this.queue = QueueFactory.getQueue(queueName); } + /** + * Gets the queue that the events are sent to. + * @return the App Engine Queue used by this connection + */ + public String getQueue() { + return queue == null ? null : queue.getQueueName(); + } + /** * Simple DeferredTask using the {@link #send(Event)} method of the {@link #actualConnection}. */ - private static final class EventSubmitter implements DeferredTask { + // visible for testing + static final class EventSubmitter implements DeferredTask { private final String connectionId; private final Event event; @@ -116,6 +128,16 @@ private EventSubmitter(String connectionId, Event event) { this.event = event; } + // visible for testing + String getConnectionId() { + return connectionId; + } + + // visible for testing + Event getEvent() { + return event; + } + @Override public void run() { setDoNotRetry(true); diff --git a/sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryClientFactoryTest.java b/sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryClientFactoryTest.java index 8f2095ae8c5..e2024cc7e7e 100644 --- a/sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryClientFactoryTest.java +++ b/sentry-appengine/src/test/java/io/sentry/appengine/AppEngineSentryClientFactoryTest.java @@ -1,90 +1,101 @@ package io.sentry.appengine; +import com.google.appengine.api.taskqueue.Queue; +import com.google.appengine.api.utils.SystemProperty; +import io.sentry.appengine.connection.ConnectionsInfo; import io.sentry.config.Lookup; -import mockit.*; +import io.sentry.config.provider.ConfigurationProvider; import io.sentry.appengine.connection.AppEngineAsyncConnection; import io.sentry.connection.Connection; import io.sentry.dsn.Dsn; -import org.testng.annotations.Test; - -import java.util.Collections; +import org.junit.Before; +import org.junit.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class AppEngineSentryClientFactoryTest { - @Tested private AppEngineSentryClientFactory appEngineSentryClientFactory; - @Injectable private Connection mockConnection; - @Injectable - private Dsn mockDsn; - @Injectable - private Lookup mockLookup; + private ConfigurationProvider mockLookupConfig; + + @Before + public void setup() { + TestQueueFactoryProvider.reset(); + mockLookupConfig = mock(ConfigurationProvider.class); + Lookup mockLookup = new Lookup(mockLookupConfig, mock(ConfigurationProvider.class)); + mockConnection = mock(Connection.class); + appEngineSentryClientFactory = new AppEngineSentryClientFactory(mockLookup); + } @Test public void asyncConnectionCreatedByAppEngineSentryClientFactoryIsForAppEngine() throws Exception { - Connection connection = appEngineSentryClientFactory.createAsyncConnection(mockDsn, mockConnection); + Connection connection = appEngineSentryClientFactory.createAsyncConnection(mock(Dsn.class), mockConnection); assertThat(connection, is(instanceOf(AppEngineAsyncConnection.class))); } @Test public void asyncConnectionWithoutConnectionIdGeneratesDefaultId() throws Exception { - final String dnsString = "a1fe25d3-bc41-4040-8aa2-484e5aae87c5"; - new NonStrictExpectations() {{ - mockDsn.toString(); - result = dnsString; - }}; - - appEngineSentryClientFactory.createAsyncConnection(mockDsn, mockConnection); - - new Verifications() {{ - String connectionId = AppEngineSentryClientFactory.class.getCanonicalName() + dnsString; - new AppEngineAsyncConnection(connectionId, mockConnection); - }}; + Dsn mockDsn = new Dsn("noop://localhost?key=lsdfjop"); + + AppEngineAsyncConnection conn = (AppEngineAsyncConnection) appEngineSentryClientFactory.createAsyncConnection(mockDsn, mockConnection); + + String expectedId = AppEngineSentryClientFactory.class.getCanonicalName() + mockDsn.toString() + + SystemProperty.version.get(); + + AppEngineAsyncConnection foundConnection = ConnectionsInfo.getExistingConnectionById(expectedId); + + assertSame(conn, foundConnection); } @Test - public void asyncConnectionWithConnectionIdUsesId( - @Injectable("543afd41-379d-41cb-8c99-8ce73e83a0cc") final String connectionId) throws Exception { - new NonStrictExpectations() {{ - mockDsn.getOptions(); - result = Collections.singletonMap(AppEngineSentryClientFactory.CONNECTION_IDENTIFIER, connectionId); - }}; - - appEngineSentryClientFactory.createAsyncConnection(mockDsn, mockConnection); - - new Verifications() {{ - new AppEngineAsyncConnection(connectionId, mockConnection); - }}; + public void asyncConnectionWithConnectionIdUsesId() throws Exception { + String connectionId = "543afd41-379d-41cb-8c99-8ce73e83a0cc"; + Dsn dsn = new Dsn(Dsn.DEFAULT_DSN + "?" + AppEngineSentryClientFactory.CONNECTION_IDENTIFIER + "=" + + connectionId); + + String expectedId = AppEngineSentryClientFactory.class.getCanonicalName() + + dsn + SystemProperty.version.get(); + + Connection conn = appEngineSentryClientFactory.createAsyncConnection(dsn, mockConnection); + + AppEngineAsyncConnection foundConnection = ConnectionsInfo.getExistingConnectionById(expectedId); + + assertSame(conn, foundConnection); } @Test - public void asyncConnectionWithoutQueueNameKeepsDefaultQueue( - @Mocked final AppEngineAsyncConnection mockAppEngineAsyncConnection) throws Exception { - appEngineSentryClientFactory.createAsyncConnection(mockDsn, mockConnection); - - new Verifications() {{ - mockAppEngineAsyncConnection.setQueue(anyString); - times = 0; - }}; + public void asyncConnectionWithoutQueueNameKeepsDefaultQueue() throws Exception { + Dsn dsn = new Dsn(Dsn.DEFAULT_DSN); + AppEngineAsyncConnection conn = (AppEngineAsyncConnection) appEngineSentryClientFactory + .createAsyncConnection(dsn, mockConnection); + + assertNull(conn.getQueue()); } @Test - public void asyncConnectionWithQueueNameSetsQueue( - @Mocked final AppEngineAsyncConnection mockAppEngineAsyncConnection, - @Injectable("queueName") final String mockQueueName) throws Exception { - new NonStrictExpectations() {{ - mockLookup.get(AppEngineSentryClientFactory.QUEUE_NAME, (Dsn) any); - result = mockQueueName; - }}; - - appEngineSentryClientFactory.createAsyncConnection(mockDsn, mockConnection); - - new Verifications() {{ - mockAppEngineAsyncConnection.setQueue(mockQueueName); - }}; + public void asyncConnectionWithQueueNameSetsQueue() throws Exception { + String mockQueueName = "queueName"; + + Queue mockQueue = mock(Queue.class); + when(mockQueue.getQueueName()).thenReturn(mockQueueName); + + TestQueueFactoryProvider.registerQueue(mockQueueName, mockQueue); + + when(mockLookupConfig.getProperty(eq(AppEngineSentryClientFactory.QUEUE_NAME))) + .thenReturn(mockQueueName); + + AppEngineAsyncConnection conn = (AppEngineAsyncConnection) appEngineSentryClientFactory + .createAsyncConnection(mock(Dsn.class), mockConnection); + + assertEquals(mockQueueName, conn.getQueue()); } } diff --git a/sentry-appengine/src/test/java/io/sentry/appengine/TestQueueFactoryProvider.java b/sentry-appengine/src/test/java/io/sentry/appengine/TestQueueFactoryProvider.java new file mode 100644 index 00000000000..69ee5934df7 --- /dev/null +++ b/sentry-appengine/src/test/java/io/sentry/appengine/TestQueueFactoryProvider.java @@ -0,0 +1,36 @@ +package io.sentry.appengine; + +import java.util.HashMap; +import java.util.Map; + +import com.google.appengine.api.taskqueue.IQueueFactory; +import com.google.appengine.api.taskqueue.Queue; +import com.google.appengine.spi.FactoryProvider; + +public class TestQueueFactoryProvider extends FactoryProvider { + private static Map QUEUES = new HashMap<>(); + + private final IQueueFactory factory = new IQueueFactory() { + @Override + public Queue getQueue(String s) { + return QUEUES.get(s); + } + }; + + public TestQueueFactoryProvider() { + super(IQueueFactory.class); + } + + public static void registerQueue(String name, Queue queue) { + QUEUES.put(name, queue); + } + + public static void reset() { + QUEUES.clear(); + } + + @Override + protected IQueueFactory getFactoryInstance() { + return factory; + } +} diff --git a/sentry-appengine/src/test/java/io/sentry/appengine/connection/AppEngineAsyncConnectionTest.java b/sentry-appengine/src/test/java/io/sentry/appengine/connection/AppEngineAsyncConnectionTest.java index f37f1571414..dbb4e212f46 100644 --- a/sentry-appengine/src/test/java/io/sentry/appengine/connection/AppEngineAsyncConnectionTest.java +++ b/sentry-appengine/src/test/java/io/sentry/appengine/connection/AppEngineAsyncConnectionTest.java @@ -1,37 +1,39 @@ package io.sentry.appengine.connection; import com.google.appengine.api.taskqueue.*; -import mockit.*; +import com.google.apphosting.api.ApiProxy; +import io.sentry.appengine.TestQueueFactoryProvider; import io.sentry.connection.Connection; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import org.hamcrest.Matchers; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; import java.util.Map; -import java.util.UUID; -import static mockit.Deencapsulation.getField; -import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class AppEngineAsyncConnectionTest { - @Tested private AppEngineAsyncConnection asyncConnection = null; - @Injectable private Connection mockConnection = null; - @Injectable private Queue mockQueue = null; - @SuppressWarnings("unused") - @Mocked("getDefaultQueue") - private QueueFactory queueFactory = null; - @Injectable("7b55a129-6975-4434-8edc-29ceefd38c95") private String mockConnectionId = null; private static DeferredTask extractDeferredTask(TaskOptions taskOptions) throws Exception { @@ -40,37 +42,51 @@ private static DeferredTask extractDeferredTask(TaskOptions taskOptions) throws } private static AppEngineAsyncConnection getTaskConnection(DeferredTask deferredTask) throws Exception { - Map appEngineAsyncConnectionRegister - = getField(AppEngineAsyncConnection.class, "APP_ENGINE_ASYNC_CONNECTIONS"); - return appEngineAsyncConnectionRegister.get(Deencapsulation.getField(deferredTask, "connectionId")); + AppEngineAsyncConnection.EventSubmitter submitter = (AppEngineAsyncConnection.EventSubmitter) deferredTask; + String connectionId = submitter.getConnectionId(); + return AppEngineAsyncConnection.APP_ENGINE_ASYNC_CONNECTIONS.get(connectionId); } - @BeforeMethod + @Before public void setUp() throws Exception { + TestQueueFactoryProvider.reset(); + mockQueue = mock(Queue.class); + TestQueueFactoryProvider.registerQueue(getClass().getSimpleName(), mockQueue); + + mockConnection = mock(Connection.class); + mockConnectionId = "7b55a129-6975-4434-8edc-29ceefd38c95"; asyncConnection = new AppEngineAsyncConnection(mockConnectionId, mockConnection); - new NonStrictExpectations() {{ - QueueFactory.getDefaultQueue(); - result = mockQueue; - }}; + asyncConnection.setQueue(getClass().getSimpleName()); + + Map attrs = new HashMap<>(); + ApiProxy.Environment env = mock(ApiProxy.Environment.class); + when(env.getAttributes()).thenReturn(attrs); + + ApiProxy.setEnvironmentForCurrentThread(env); + } + + @After + public void tearDown() { + ApiProxy.setEnvironmentForCurrentThread(null); } @Test - public void testRegisterNewInstance( - @Injectable("1bac02f7-c9ed-41b8-9126-e2da257a06ef") final String mockConnectionId) throws Exception { + public void testRegisterNewInstance() throws Exception { + String mockConnectionId = "1bac02f7-c9ed-41b8-9126-e2da257a06ef"; AppEngineAsyncConnection asyncConnection2 = new AppEngineAsyncConnection(mockConnectionId, mockConnection); - Map appEngineAsyncConnectionRegister - = getField(AppEngineAsyncConnection.class, "APP_ENGINE_ASYNC_CONNECTIONS"); + Map appEngineAsyncConnectionRegister = + AppEngineAsyncConnection.APP_ENGINE_ASYNC_CONNECTIONS; assertThat(appEngineAsyncConnectionRegister, hasEntry(mockConnectionId, asyncConnection2)); } @Test - public void testUnregisterInstance( - @Injectable("648f76e2-39ed-40e0-91a2-b1887a03b782") final String mockConnectionId) throws Exception { + public void testUnregisterInstance() throws Exception { + String mockConnectionId = "648f76e2-39ed-40e0-91a2-b1887a03b782"; new AppEngineAsyncConnection(mockConnectionId, mockConnection).close(); - Map appEngineAsyncConnectionRegister - = getField(AppEngineAsyncConnection.class, "APP_ENGINE_ASYNC_CONNECTIONS"); + Map appEngineAsyncConnectionRegister = + AppEngineAsyncConnection.APP_ENGINE_ASYNC_CONNECTIONS; assertThat(appEngineAsyncConnectionRegister, not(hasKey(mockConnectionId))); } @@ -80,64 +96,59 @@ public void testSendEventQueued() throws Exception { asyncConnection.send(event); - new Verifications() {{ - TaskOptions taskOptions; - DeferredTask deferredTask; - mockQueue.add(taskOptions = withCapture()); - - deferredTask = extractDeferredTask(taskOptions); - assertThat(getField(deferredTask, "event"), Matchers.equalTo(event)); - }}; + ArgumentCaptor taskOptionsArgumentCaptor = ArgumentCaptor.forClass(TaskOptions.class); + verify(mockQueue).add(taskOptionsArgumentCaptor.capture()); + TaskOptions taskOptions = taskOptionsArgumentCaptor.getValue(); + DeferredTask deferredTask = extractDeferredTask(taskOptions); + AppEngineAsyncConnection.EventSubmitter eventSubmitter = (AppEngineAsyncConnection.EventSubmitter) deferredTask; + assertThat(eventSubmitter.getEvent(), Matchers.equalTo(event)); } @Test - public void testQueuedEventSubmitted(@SuppressWarnings("unused") - @Mocked("setDoNotRetry") DeferredTaskContext deferredTaskContext) - throws Exception { + public void testQueuedEventSubmitted() throws Exception { final Event event = new EventBuilder().build(); - new NonStrictExpectations() {{ - mockQueue.add((TaskOptions) any); - result = new Delegate() { - @SuppressWarnings("unused") - TaskHandle add(TaskOptions taskOptions) { - try { - extractDeferredTask(taskOptions).run(); - } catch (Exception e) { - throw new RuntimeException("Couldn't extract the task", e); - } - return null; + when(mockQueue.add(any(TaskOptions.class))).thenAnswer(new Answer() { + @Override + public TaskHandle answer(InvocationOnMock invocation) throws Throwable { + TaskOptions taskOptions = invocation.getArgument(0); + try { + extractDeferredTask(taskOptions).run(); + } catch (Exception e) { + throw new RuntimeException("Couldn't extract the task", e); } - }; - }}; + return null; + } + }); asyncConnection.send(event); - new Verifications() {{ - DeferredTaskContext.setDoNotRetry(true); - mockConnection.send((Event) any); - }}; + verify(mockConnection).send(any(Event.class)); + + // the below verifies that we called DeferredTask.setDoNotRetry(true) + ApiProxy.Environment env = ApiProxy.getCurrentEnvironment(); + Map attrs = env.getAttributes(); + String key = String.valueOf(DeferredTaskContext.class.getName()).concat(".doNotRetry"); + assertThat(attrs.get(key), is((Object) true)); } @Test - public void testEventLinkedToCorrectConnection( - @Injectable("eb37bfe4-7316-47e8-94e4-073aefd0fbf8") final String mockConnectionId) throws Exception { - final AppEngineAsyncConnection asyncConnection2 = new AppEngineAsyncConnection(mockConnectionId, mockConnection); + public void testEventLinkedToCorrectConnection() throws Exception { + final AppEngineAsyncConnection asyncConnection2 = + new AppEngineAsyncConnection("eb37bfe4-7316-47e8-94e4-073aefd0fbf8", mockConnection); + asyncConnection2.setQueue(getClass().getSimpleName()); + final Event event = new EventBuilder().build(); asyncConnection.send(event); asyncConnection2.send(event); - new Verifications() {{ - List taskOptionsList = new ArrayList<>(); - DeferredTask deferredTask; - - mockQueue.add(withCapture(taskOptionsList)); + ArgumentCaptor taskOptionsCaptor = ArgumentCaptor.forClass(TaskOptions.class); + verify(mockQueue, times(2)).add(taskOptionsCaptor.capture()); - deferredTask = extractDeferredTask(taskOptionsList.get(0)); - assertThat(getTaskConnection(deferredTask), is(asyncConnection)); + DeferredTask deferredTask = extractDeferredTask(taskOptionsCaptor.getAllValues().get(0)); + assertThat(getTaskConnection(deferredTask), is(asyncConnection)); - deferredTask = extractDeferredTask(taskOptionsList.get(1)); - assertThat(getTaskConnection(deferredTask), is(asyncConnection2)); - }}; + deferredTask = extractDeferredTask(taskOptionsCaptor.getAllValues().get(1)); + assertThat(getTaskConnection(deferredTask), is(asyncConnection2)); } } diff --git a/sentry-appengine/src/test/java/io/sentry/appengine/connection/ConnectionsInfo.java b/sentry-appengine/src/test/java/io/sentry/appengine/connection/ConnectionsInfo.java new file mode 100644 index 00000000000..9e2825d1821 --- /dev/null +++ b/sentry-appengine/src/test/java/io/sentry/appengine/connection/ConnectionsInfo.java @@ -0,0 +1,16 @@ +package io.sentry.appengine.connection; + +import io.sentry.util.Nullable; + +/** + * This is a helper class to extract information about the {@link AppEngineAsyncConnection} for the test purposes. + * + * This is used by the tests living outside of this package to be able to access package-private information. + */ +public class ConnectionsInfo { + + @Nullable + public static AppEngineAsyncConnection getExistingConnectionById(String id) { + return AppEngineAsyncConnection.APP_ENGINE_ASYNC_CONNECTIONS.get(id); + } +} diff --git a/sentry-appengine/src/test/java/io/sentry/appengine/event/helper/AppEngineEventBuilderHelperTest.java b/sentry-appengine/src/test/java/io/sentry/appengine/event/helper/AppEngineEventBuilderHelperTest.java index 04fdeec778e..b4d415ee7f7 100644 --- a/sentry-appengine/src/test/java/io/sentry/appengine/event/helper/AppEngineEventBuilderHelperTest.java +++ b/sentry-appengine/src/test/java/io/sentry/appengine/event/helper/AppEngineEventBuilderHelperTest.java @@ -1,92 +1,93 @@ package io.sentry.appengine.event.helper; +import static java.util.Collections.singletonMap; + import com.google.appengine.api.utils.SystemProperty; import com.google.apphosting.api.ApiProxy; -import mockit.*; import io.sentry.event.EventBuilder; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.util.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; -import static mockit.Deencapsulation.setField; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; public class AppEngineEventBuilderHelperTest { - @Tested private AppEngineEventBuilderHelper eventBuilderHelper = null; - @Injectable private EventBuilder mockEventBuilder = null; - @SuppressWarnings("unused") - @Mocked("getCurrentEnvironment") - private ApiProxy mockApiProxy; - @Injectable private ApiProxy.Environment mockEnvironment = null; - @Injectable - private SystemProperty mockApplicationId = null; - @Injectable - private SystemProperty mockApplicationVersion = null; + private String originalApplicationId; + private String originalApplicationVersion; - @BeforeMethod + @Before public void setUp() throws Exception { - setField(SystemProperty.class, "applicationId", mockApplicationId); - setField(SystemProperty.class, "applicationVersion", mockApplicationVersion); - - new NonStrictExpectations() {{ - ApiProxy.getCurrentEnvironment(); - result = mockEnvironment; - mockEnvironment.getAttributes(); - result = Collections.emptyMap(); - }}; + originalApplicationId = SystemProperty.applicationId.get(); + originalApplicationVersion = SystemProperty.applicationVersion.get(); + + mockEventBuilder = mock(EventBuilder.class); + + mockEnvironment = mock(ApiProxy.Environment.class); + when(mockEnvironment.getAttributes()).thenReturn(Collections.emptyMap()); + + ApiProxy.setEnvironmentForCurrentThread(mockEnvironment); + + eventBuilderHelper = new AppEngineEventBuilderHelper(); + } + + @After + public void tearDown() { + ApiProxy.setEnvironmentForCurrentThread(null); + resetSystemProperty(SystemProperty.applicationId, originalApplicationId); + resetSystemProperty(SystemProperty.applicationVersion, originalApplicationVersion); + } + + private static void resetSystemProperty(SystemProperty property, String value) { + if (value == null) { + System.clearProperty(property.key()); + } else { + property.set(value); + } } @Test - public void ensureHostnameDefineByApiProxyEnvironment( - @Injectable("d7b8f251-ebe1-446f-8549-2b37982bd548") final String mockHostname) - throws Exception { - new NonStrictExpectations() {{ - mockEnvironment.getAttributes(); - result = Collections.singletonMap("com.google.appengine.runtime.default_version_hostname", mockHostname); - }}; + public void ensureHostnameDefineByApiProxyEnvironment() throws Exception { + Object mockHostName = "d7b8f251-ebe1-446f-8549-2b37982bd548"; + when(mockEnvironment.getAttributes()) + .thenReturn(singletonMap("com.google.appengine.runtime.default_version_hostname", + mockHostName)); eventBuilderHelper.helpBuildingEvent(mockEventBuilder); - new Verifications() {{ - String hostname; - mockEventBuilder.withServerName(hostname = withCapture()); - assertThat(hostname, is(mockHostname)); - }}; + ArgumentCaptor hostnameCaptor = ArgumentCaptor.forClass(String.class); + verify(mockEventBuilder).withServerName(hostnameCaptor.capture()); + String hostname = hostnameCaptor.getValue(); + assertThat(hostname, is(mockHostName)); } @Test - public void ensureApplicationVersionIsAddedAsTag( - @Injectable("dc485fa3-fc23-4e6c-b374-0d05d248e5a5") final String version) throws Exception { - new NonStrictExpectations() {{ - mockApplicationVersion.get(); - result = version; - }}; + public void ensureApplicationVersionIsAddedAsTag() throws Exception { + String version = "dc485fa3-fc23-4e6c-b374-0d05d248e5a5"; + SystemProperty.applicationVersion.set(version); eventBuilderHelper.helpBuildingEvent(mockEventBuilder); - new Verifications() {{ - mockEventBuilder.withTag("GAE Application Version", version); - }}; + verify(mockEventBuilder).withTag(eq("GAE Application Version"), eq(version)); } @Test - public void ensureApplicationIdIsAddedAsTag( - @Injectable("50a180eb-1484-4a07-9e44-b60d394cad18") final String applicationId) throws Exception { - new NonStrictExpectations() {{ - mockApplicationId.get(); - result = applicationId; - }}; + public void ensureApplicationIdIsAddedAsTag() throws Exception { + String applicationId = "50a180eb-1484-4a07-9e44-b60d394cad18"; + SystemProperty.applicationId.set(applicationId); eventBuilderHelper.helpBuildingEvent(mockEventBuilder); - new Verifications() {{ - mockEventBuilder.withTag("GAE Application Id", applicationId); - }}; + verify(mockEventBuilder).withTag(eq("GAE Application Id"), eq(applicationId)); } } diff --git a/sentry-appengine/src/test/resources/META-INF/services/com.google.appengine.spi.FactoryProvider b/sentry-appengine/src/test/resources/META-INF/services/com.google.appengine.spi.FactoryProvider new file mode 100644 index 00000000000..a00deec333f --- /dev/null +++ b/sentry-appengine/src/test/resources/META-INF/services/com.google.appengine.spi.FactoryProvider @@ -0,0 +1 @@ +io.sentry.appengine.TestQueueFactoryProvider From 8e39866ee492631b0483498efc80de2b83c9b151 Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Wed, 18 Sep 2019 19:52:22 +0200 Subject: [PATCH 2103/2152] Sentry log4j test rewrite (#764) --- sentry-log4j/pom.xml | 14 +- .../java/io/sentry/log4j/ErrorCounter.java | 47 ++++ .../io/sentry/log4j/MockUpErrorHandler.java | 33 --- .../sentry/log4j/SentryAppenderCloseTest.java | 47 ++-- .../SentryAppenderEventBuildingTest.java | 240 ++++++++---------- .../log4j/SentryAppenderFailuresTest.java | 54 ++-- 6 files changed, 200 insertions(+), 235 deletions(-) create mode 100644 sentry-log4j/src/test/java/io/sentry/log4j/ErrorCounter.java delete mode 100644 sentry-log4j/src/test/java/io/sentry/log4j/MockUpErrorHandler.java diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index b55ec1f08ab..1e5e5ceb42e 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -31,13 +31,8 @@ - org.jmockit - jmockit - test - - - org.testng - testng + org.mockito + mockito-core test @@ -55,6 +50,11 @@ junit test + + pl.pragmatists + JUnitParams + test + com.github.tomakehurst wiremock-standalone diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/ErrorCounter.java b/sentry-log4j/src/test/java/io/sentry/log4j/ErrorCounter.java new file mode 100644 index 00000000000..0977eaddf01 --- /dev/null +++ b/sentry-log4j/src/test/java/io/sentry/log4j/ErrorCounter.java @@ -0,0 +1,47 @@ +package io.sentry.log4j; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.log4j.Appender; +import org.apache.log4j.spi.ErrorHandler; +import org.apache.log4j.spi.LoggingEvent; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ErrorCounter { + private static final Logger logger = LoggerFactory.getLogger("CountingErrorHandler"); + private final ErrorHandler handler; + private int errorCount; + + public ErrorCounter() { + handler = mock(ErrorHandler.class); + + Answer count = new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + errorCount++; + logger.error((String) invocation.getArgument(0)); + return null; + } + }; + + doAnswer(count).when(handler).error(anyString()); + doAnswer(count).when(handler).error(anyString(), any(Exception.class), anyInt()); + doAnswer(count).when(handler).error(anyString(), any(Exception.class), anyInt(), any(LoggingEvent.class)); + } + + public ErrorHandler getErrorHandler() { + return handler; + } + + public int getErrorCount() { + return errorCount; + } +} diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/MockUpErrorHandler.java b/sentry-log4j/src/test/java/io/sentry/log4j/MockUpErrorHandler.java deleted file mode 100644 index 657979926df..00000000000 --- a/sentry-log4j/src/test/java/io/sentry/log4j/MockUpErrorHandler.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.sentry.log4j; - -import mockit.Mock; -import mockit.MockUp; -import org.apache.log4j.spi.ErrorHandler; -import org.apache.log4j.spi.LoggingEvent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MockUpErrorHandler extends MockUp { - private static final Logger logger = LoggerFactory.getLogger("ErrorHandler"); - private int errorCount = 0; - - @Mock - public void error(String message) { - errorCount++; - logger.error(message); - } - - @Mock - public void error(String message, Exception e, int errorCode) { - error(message); - } - - @Mock - public void error(String message, Exception e, int errorCode, LoggingEvent event) { - error(message); - } - - public int getErrorCount() { - return errorCount; - } -} diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java index feaa35357d6..a9b9b7abd3f 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderCloseTest.java @@ -2,49 +2,39 @@ import io.sentry.BaseTest; import io.sentry.Sentry; -import mockit.*; import io.sentry.SentryClient; -import io.sentry.SentryClientFactory; -import io.sentry.dsn.Dsn; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; public class SentryAppenderCloseTest extends BaseTest { - private MockUpErrorHandler mockUpErrorHandler; - @Injectable + private ErrorCounter errorCounter; private SentryClient mockSentryClient = null; - @SuppressWarnings("unused") - @Mocked("sentryClient") - private SentryClientFactory mockSentryClientFactory = null; - @SuppressWarnings("unused") - @Mocked("dsnLookup") - private Dsn mockDsn = null; - - @BeforeMethod + + @Before public void setUp() throws Exception { - resetClient(); - mockUpErrorHandler = new MockUpErrorHandler(); + errorCounter = new ErrorCounter(); + mockSentryClient = mock(SentryClient.class); } private void assertNoErrorsInErrorHandler() throws Exception { - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + assertThat(errorCounter.getErrorCount(), is(0)); } @Test public void testConnectionClosedWhenAppenderClosed() throws Exception { Sentry.setStoredClient(mockSentryClient); final SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.setErrorHandler(errorCounter.getErrorHandler()); sentryAppender.activateOptions(); sentryAppender.close(); - new Verifications() {{ - mockSentryClient.closeConnection(); - }}; + verify(mockSentryClient).closeConnection(); assertNoErrorsInErrorHandler(); } @@ -53,18 +43,18 @@ public void testCloseDoNotFailIfSentryNull() throws Exception { // This checks that even if sentry wasn't setup correctly its appender can still be closed. Sentry.setStoredClient(null); final SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.setErrorHandler(errorCounter.getErrorHandler()); sentryAppender.close(); - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + assertNoErrorsInErrorHandler(); } @Test public void testCloseDoNotFailIfNoInit() throws Exception { final SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.setErrorHandler(errorCounter.getErrorHandler()); sentryAppender.close(); @@ -75,16 +65,13 @@ public void testCloseDoNotFailIfNoInit() public void testCloseDoNotFailWhenMultipleCalls() throws Exception { Sentry.setStoredClient(mockSentryClient); final SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.setErrorHandler(errorCounter.getErrorHandler()); sentryAppender.activateOptions(); sentryAppender.close(); sentryAppender.close(); - new Verifications() {{ - mockSentryClient.closeConnection(); - times = 1; - }}; + verify(mockSentryClient).closeConnection(); assertNoErrorsInErrorHandler(); } } diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java index 03c46cce583..b9177d6111b 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderEventBuildingTest.java @@ -1,54 +1,77 @@ package io.sentry.log4j; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + import io.sentry.BaseTest; import io.sentry.Sentry; import io.sentry.SentryClient; -import io.sentry.event.interfaces.SentryStackTraceElement; -import mockit.*; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.interfaces.ExceptionInterface; import io.sentry.event.interfaces.SentryException; +import io.sentry.event.interfaces.SentryStackTraceElement; import io.sentry.event.interfaces.StackTraceInterface; +import junitparams.JUnitParamsRunner; +import junitparams.NamedParameters; +import junitparams.Parameters; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; import org.hamcrest.Matchers; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import java.util.*; - -import static mockit.Deencapsulation.setField; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +@RunWith(JUnitParamsRunner.class) public class SentryAppenderEventBuildingTest extends BaseTest { - @Tested - private SentryAppender sentryAppender = null; - private MockUpErrorHandler mockUpErrorHandler; - @Injectable - private SentryClient mockSentryClient = null; - @Injectable - private Logger mockLogger = null; + private SentryAppender sentryAppender; + private ErrorCounter errorCounter; + private SentryClient mockSentryClient; private String mockExtraTag = "a8e0ad33-3c11-4899-b8c7-c99926c6d7b8"; private Set extraTags; + private Logger fakeLogger; - @BeforeMethod + @Before public void setUp() throws Exception { + mockSentryClient = mock(SentryClient.class); Sentry.setStoredClient(mockSentryClient); sentryAppender = new SentryAppender(); - mockUpErrorHandler = new MockUpErrorHandler(); - sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); + errorCounter = new ErrorCounter(); + sentryAppender.setErrorHandler(errorCounter.getErrorHandler()); extraTags = new HashSet<>(); extraTags.add(mockExtraTag); sentryAppender.activateOptions(); + + fakeLogger = new Logger(null) {}; } private void assertNoErrorsInErrorHandler() throws Exception { - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + assertThat(errorCounter.getErrorCount(), is(0)); + } + + private Event verifySendEventCalled() { + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + return eventBuilderArgumentCaptor.getValue().build(); } @Test @@ -57,29 +80,23 @@ public void testSimpleMessageLogging() throws Exception { final String message = "fae94de0-0df3-4f96-92d5-a3fb66e714f3"; final String threadName = "78ecbf4d-aa61-4dd7-8ef4-b1c49232e8f4"; final Date date = new Date(1373883196416L); - new Expectations() {{ - mockLogger.getName(); - result = loggerName; - }}; - sentryAppender.append(new LoggingEvent(null, mockLogger, date.getTime(), Level.INFO, message, threadName, + fakeLogger = new Logger(loggerName) {}; + + sentryAppender.append(new LoggingEvent(null, fakeLogger, date.getTime(), Level.INFO, message, threadName, null, null, null, null)); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getMessage(), is(message)); - assertThat(event.getLogger(), is(loggerName)); - assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); - assertThat(event.getTimestamp(), is(date)); - assertThat(event.getSdk().getIntegrations(), contains("log4j")); - }}; + Event event = verifySendEventCalled(); + assertThat(event.getMessage(), is(message)); + assertThat(event.getLogger(), is(loggerName)); + assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); + assertThat(event.getTimestamp(), is(date)); + assertThat(event.getSdk().getIntegrations(), contains("log4j")); assertNoErrorsInErrorHandler(); } - @DataProvider(name = "levels") - private Object[][] levelConversions() { + @NamedParameters("levels") + public Object[][] levelConversions() { return new Object[][]{ {Event.Level.DEBUG, Level.TRACE}, {Event.Level.DEBUG, Level.DEBUG}, @@ -89,16 +106,13 @@ private Object[][] levelConversions() { {Event.Level.FATAL, Level.FATAL}}; } - @Test(dataProvider = "levels") - public void testLevelConversion(final Event.Level expectedLevel, Level level) throws Exception { - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, level, null, null)); + @Test + @Parameters(named = "levels") + public void testLevelConversion(Event.Level expectedLevel, Level level) throws Exception { + sentryAppender.append(new LoggingEvent(null, fakeLogger, 0, level, null, null)); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getLevel(), is(expectedLevel)); - }}; + Event event = verifySendEventCalled(); + assertThat(event.getLevel(), is(expectedLevel)); assertNoErrorsInErrorHandler(); } @@ -106,19 +120,15 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th public void testExceptionLogging() throws Exception { final Exception exception = new Exception("027a0db3-fb98-4377-bafb-fe5a49f067e8"); - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, exception)); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() - .get(ExceptionInterface.EXCEPTION_INTERFACE); - SentryException sentryException = exceptionInterface.getExceptions().getFirst(); - assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); - assertThat(sentryException.getStackTraceInterface().getStackTrace(), - is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace(), null))); - }}; + sentryAppender.append(new LoggingEvent(null, fakeLogger, 0, Level.ERROR, null, exception)); + + Event event = verifySendEventCalled(); + ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() + .get(ExceptionInterface.EXCEPTION_INTERFACE); + SentryException sentryException = exceptionInterface.getExceptions().getFirst(); + assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); + assertThat(sentryException.getStackTraceInterface().getStackTrace(), + is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace(), null))); assertNoErrorsInErrorHandler(); } @@ -127,15 +137,11 @@ public void testMdcAddedToExtra() throws Exception { final String extraKey = "1aeb7253-6e0d-4902-86d6-7e4b36571cfd"; final String extraValue = "b2e19866-08a2-4611-b72c-150fa6aa3394"; - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, + sentryAppender.append(new LoggingEvent(null, fakeLogger, 0, Level.ERROR, null, null, null, null, null, Collections.singletonMap(extraKey, extraValue))); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); - }}; + Event event = verifySendEventCalled(); + assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); assertNoErrorsInErrorHandler(); } @@ -143,49 +149,31 @@ public void testMdcAddedToExtra() throws Exception { public void testNdcAddedToExtra() throws Exception { final String ndcEntries = "930580ba-f92f-4893-855b-ac24efa1a6c2 fa32ad74-a015-492a-991f-c6a0e04accaf be9dd914-3690-4781-97b2-fe14aedb4cbd"; - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, + sentryAppender.append(new LoggingEvent(null, fakeLogger, 0, Level.ERROR, null, null, null, ndcEntries, null, null)); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.LOG4J_NDC, ndcEntries)); - }}; + Event event = verifySendEventCalled(); + assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.LOG4J_NDC, ndcEntries)); assertNoErrorsInErrorHandler(); } @Test - public void testSourceUsedAsStacktrace(@Injectable final LocationInfo locationInfo) throws Exception { + public void testSourceUsedAsStacktrace() throws Exception { final String className = "8004ac1e-8bd3-4762-abe0-2d0d79ae4e40"; final String methodName = "ce7cd195-9e6d-4315-b883-12951be3da6e"; final String fileName = "1ab50f43-f11c-4439-a05c-d089281411fa"; final int line = 42; - new NonStrictExpectations() {{ - locationInfo.getClassName(); - result = className; - locationInfo.getMethodName(); - result = methodName; - locationInfo.getFileName(); - result = fileName; - locationInfo.getLineNumber(); - result = Integer.toString(line); - setField(locationInfo, "fullInfo", ""); - }}; - - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, + LocationInfo locationInfo = new LocationInfo(fileName, className, methodName, Integer.toString(line)); + + sentryAppender.append(new LoggingEvent(null, fakeLogger, 0, Level.ERROR, null, null, null, null, locationInfo, null)); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() - .get(StackTraceInterface.STACKTRACE_INTERFACE); - assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); - assertThat(stackTraceInterface.getStackTrace()[0], - is(new SentryStackTraceElement(className, methodName, fileName, line, null, null, null, null))); - }}; + Event event = verifySendEventCalled(); + StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() + .get(StackTraceInterface.STACKTRACE_INTERFACE); + assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); + assertThat(stackTraceInterface.getStackTrace()[0], + is(new SentryStackTraceElement(className, methodName, fileName, line, null, null, null, null))); assertNoErrorsInErrorHandler(); } @@ -195,45 +183,29 @@ public void testExtraTagObtainedFromMdc() throws Exception { properties.put(mockExtraTag, "ac84f38a-3889-41ed-9519-201402688abb"); properties.put("other_property", "10ebc4f6-a915-46d0-bb60-75bc9bd71371"); - new NonStrictExpectations() {{ - mockSentryClient.getMdcTags(); - result = extraTags; - }}; - - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, null, null, null, properties)); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getTags().entrySet(), hasSize(1)); - assertThat(event.getTags(), hasEntry(mockExtraTag, "ac84f38a-3889-41ed-9519-201402688abb")); - assertThat(event.getExtra(), not(hasKey(mockExtraTag))); - assertThat(event.getExtra(), Matchers.hasEntry("other_property", "10ebc4f6-a915-46d0-bb60-75bc9bd71371")); - }}; + when(mockSentryClient.getMdcTags()).thenReturn(extraTags); + + sentryAppender.append(new LoggingEvent(null, fakeLogger, 0, Level.ERROR, null, null, null, null, null, properties)); + + Event event = verifySendEventCalled(); + assertThat(event.getTags().entrySet(), hasSize(1)); + assertThat(event.getTags(), hasEntry(mockExtraTag, "ac84f38a-3889-41ed-9519-201402688abb")); + assertThat(event.getExtra(), not(hasKey(mockExtraTag))); + assertThat(event.getExtra(), Matchers.hasEntry("other_property", "10ebc4f6-a915-46d0-bb60-75bc9bd71371")); assertNoErrorsInErrorHandler(); } @Test - public void testExtraTagObtainedFromMdcConvertedToString(@Injectable final Object extraTagValue) throws Exception { - Map properties = Collections.singletonMap(mockExtraTag, extraTagValue); - new NonStrictExpectations() {{ - extraTagValue.toString(); - result = "3c8981b4-01ad-47ec-8a3a-77a0bbcb42e2"; - mockSentryClient.getMdcTags(); - result = extraTags; - }}; - - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.ERROR, null, null, null, null, null, properties)); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getTags().entrySet(), hasSize(1)); - assertThat(event.getTags(), hasEntry(mockExtraTag, "3c8981b4-01ad-47ec-8a3a-77a0bbcb42e2")); - assertThat(event.getExtra(), not(hasKey(mockExtraTag))); - }}; + public void testExtraTagObtainedFromMdcConvertedToString() throws Exception { + Map properties = Collections.singletonMap(mockExtraTag, (Object) "3c8981b4-01ad-47ec-8a3a-77a0bbcb42e2"); + when(mockSentryClient.getMdcTags()).thenReturn(extraTags); + + sentryAppender.append(new LoggingEvent(null, fakeLogger, 0, Level.ERROR, null, null, null, null, null, properties)); + + Event event = verifySendEventCalled(); + assertThat(event.getTags().entrySet(), hasSize(1)); + assertThat(event.getTags(), hasEntry(mockExtraTag, "3c8981b4-01ad-47ec-8a3a-77a0bbcb42e2")); + assertThat(event.getExtra(), not(hasKey(mockExtraTag))); assertNoErrorsInErrorHandler(); } } diff --git a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java index 9d206d35ca3..a4f1928c13f 100644 --- a/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java +++ b/sentry-log4j/src/test/java/io/sentry/log4j/SentryAppenderFailuresTest.java @@ -3,65 +3,57 @@ import io.sentry.BaseTest; import io.sentry.Sentry; import io.sentry.SentryClient; +import io.sentry.event.Event; import io.sentry.event.EventBuilder; -import mockit.*; -import io.sentry.SentryClientFactory; -import io.sentry.dsn.Dsn; import io.sentry.environment.SentryEnvironment; -import io.sentry.event.Event; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.spi.LoggingEvent; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; public class SentryAppenderFailuresTest extends BaseTest { - @Tested - private SentryAppender sentryAppender = null; - private MockUpErrorHandler mockUpErrorHandler; - @Injectable - private SentryClient mockSentryClient = null; - @Injectable - private Logger mockLogger = null; - @SuppressWarnings("unused") - @Mocked("sentryClient") - private SentryClientFactory mockSentryClientFactory = null; + private SentryAppender sentryAppender; + private ErrorCounter errorCounter; + private SentryClient mockSentryClient; + private Logger fakeLogger = null; - @BeforeMethod + @Before public void setUp() throws Exception { + mockSentryClient = mock(SentryClient.class); Sentry.setStoredClient(mockSentryClient); sentryAppender = new SentryAppender(); - mockUpErrorHandler = new MockUpErrorHandler(); - sentryAppender.setErrorHandler(mockUpErrorHandler.getMockInstance()); + errorCounter = new ErrorCounter(); + sentryAppender.setErrorHandler(errorCounter.getErrorHandler()); sentryAppender.activateOptions(); + fakeLogger = new Logger(null) {}; } @Test public void testSentryFailureDoesNotPropagate() throws Exception { - new NonStrictExpectations() {{ - mockSentryClient.sendEvent((EventBuilder) any); - result = new UnsupportedOperationException(); - }}; + doThrow(new UnsupportedOperationException()).when(mockSentryClient).sendEvent(any(EventBuilder.class)); - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); + sentryAppender.append(new LoggingEvent(null, fakeLogger, 0, Level.INFO, null, null)); - assertThat(mockUpErrorHandler.getErrorCount(), is(1)); + assertThat(errorCounter.getErrorCount(), is(1)); } @Test public void testAppendFailIfCurrentThreadSpawnedBySentry() throws Exception { SentryEnvironment.startManagingThread(); try { - sentryAppender.append(new LoggingEvent(null, mockLogger, 0, Level.INFO, null, null)); + sentryAppender.append(new LoggingEvent(null, fakeLogger, 0, Level.INFO, null, null)); - new Verifications() {{ - mockSentryClient.sendEvent((EventBuilder) any); - times = 0; - }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + verify(mockSentryClient, never()).sendEvent(any(Event.class)); + assertThat(errorCounter.getErrorCount(), is(0)); } finally { SentryEnvironment.stopManagingThread(); } From 07a7d63e76005951ddb111ec33982f5c06901c9e Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Sun, 22 Sep 2019 15:40:44 +0200 Subject: [PATCH 2104/2152] Sentry log4j2 test rewrite (#766) --- sentry-log4j2/pom.xml | 20 +- .../java/io/sentry/log4j2/ErrorCounter.java | 44 +++++ .../io/sentry/log4j2/MockUpErrorHandler.java | 33 ---- .../log4j2/SentryAppenderCloseTest.java | 47 ++--- .../SentryAppenderEventBuildingTest.java | 171 ++++++++---------- .../log4j2/SentryAppenderFailuresTest.java | 51 ++---- 6 files changed, 168 insertions(+), 198 deletions(-) create mode 100644 sentry-log4j2/src/test/java/io/sentry/log4j2/ErrorCounter.java delete mode 100644 sentry-log4j2/src/test/java/io/sentry/log4j2/MockUpErrorHandler.java diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index f47d10e3d80..e826b904d29 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -35,16 +35,6 @@ - - org.jmockit - jmockit - test - - - org.testng - testng - test - org.hamcrest hamcrest-core @@ -65,6 +55,16 @@ wiremock-standalone test + + org.mockito + mockito-core + test + + + pl.pragmatists + JUnitParams + test + diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/ErrorCounter.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/ErrorCounter.java new file mode 100644 index 00000000000..1cdfae097a8 --- /dev/null +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/ErrorCounter.java @@ -0,0 +1,44 @@ +package io.sentry.log4j2; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; + +import org.apache.logging.log4j.core.ErrorHandler; +import org.apache.logging.log4j.core.LogEvent; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ErrorCounter { + private static final Logger logger = LoggerFactory.getLogger("CountingErrorHandler"); + private final ErrorHandler handler; + private int errorCount; + + public ErrorCounter() { + handler = mock(ErrorHandler.class); + + Answer count = new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + errorCount++; + logger.error((String) invocation.getArgument(0)); + return null; + } + }; + + doAnswer(count).when(handler).error(anyString()); + doAnswer(count).when(handler).error(anyString(), any(Exception.class)); + doAnswer(count).when(handler).error(anyString(), any(LogEvent.class), any(Exception.class)); + } + + public ErrorHandler getErrorHandler() { + return handler; + } + + public int getErrorCount() { + return errorCount; + } +} diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/MockUpErrorHandler.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/MockUpErrorHandler.java deleted file mode 100644 index 4a7c4ccd5d6..00000000000 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/MockUpErrorHandler.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.sentry.log4j2; - -import mockit.Mock; -import mockit.MockUp; -import org.apache.logging.log4j.core.ErrorHandler; -import org.apache.logging.log4j.core.LogEvent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MockUpErrorHandler extends MockUp { - private static final Logger logger = LoggerFactory.getLogger("ErrorHandler"); - private int errorCount = 0; - - @Mock - public void error(String msg) { - errorCount++; - logger.error(msg); - } - - @Mock - public void error(String msg, Throwable t) { - error(msg); - } - - @Mock - public void error(String msg, LogEvent event, Throwable t) { - error(msg); - } - - public int getErrorCount() { - return errorCount; - } -} diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java index 962aa02a013..db17cc9de22 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderCloseTest.java @@ -3,47 +3,38 @@ import io.sentry.BaseTest; import io.sentry.Sentry; import io.sentry.SentryClient; -import mockit.*; -import io.sentry.SentryClientFactory; -import io.sentry.dsn.Dsn; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; public class SentryAppenderCloseTest extends BaseTest { - private MockUpErrorHandler mockUpErrorHandler; - @Injectable + private ErrorCounter errorCounter; private SentryClient mockSentryClient = null; - @SuppressWarnings("unused") - @Mocked("sentryClient") - private SentryClientFactory mockSentryClientFactory = null; - @SuppressWarnings("unused") - @Mocked("dsnLookup") - private Dsn mockDsn = null; - - @BeforeMethod + + @Before public void setUp() throws Exception { - mockUpErrorHandler = new MockUpErrorHandler(); + errorCounter = new ErrorCounter(); + mockSentryClient = mock(SentryClient.class); } private void assertNoErrorsInErrorHandler() throws Exception { - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + assertThat(errorCounter.getErrorCount(), is(0)); } @Test public void testConnectionClosedWhenAppenderStopped() throws Exception { Sentry.setStoredClient(mockSentryClient); final SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.setHandler(errorCounter.getErrorHandler()); sentryAppender.start(); sentryAppender.stop(); - new Verifications() {{ - mockSentryClient.closeConnection(); - }}; + verify(mockSentryClient).closeConnection(); assertNoErrorsInErrorHandler(); } @@ -52,20 +43,19 @@ public void testStopDoNotFailIfSentryNull() throws Exception { // This checks that even if sentry wasn't setup correctly its appender can still be closed. Sentry.setStoredClient(null); final SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.setHandler(errorCounter.getErrorHandler()); sentryAppender.start(); sentryAppender.stop(); - //Two errors, one because of the exception, one because of the null event. - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + assertNoErrorsInErrorHandler(); } @Test public void testStopDoNotFailIfNoInit() throws Exception { final SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.setHandler(errorCounter.getErrorHandler()); sentryAppender.stop(); @@ -76,16 +66,13 @@ public void testStopDoNotFailIfNoInit() public void testStopDoNotFailWhenMultipleCalls() throws Exception { Sentry.setStoredClient(mockSentryClient); final SentryAppender sentryAppender = new SentryAppender(); - sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + sentryAppender.setHandler(errorCounter.getErrorHandler()); sentryAppender.start(); sentryAppender.stop(); sentryAppender.stop(); - new Verifications() {{ - mockSentryClient.closeConnection(); - times = 1; - }}; + verify(mockSentryClient).closeConnection(); assertNoErrorsInErrorHandler(); } } diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java index d2b42f07f1f..7907f4c8002 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderEventBuildingTest.java @@ -6,10 +6,9 @@ import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.interfaces.*; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Tested; -import mockit.Verifications; +import junitparams.JUnitParamsRunner; +import junitparams.NamedParameters; +import junitparams.Parameters; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.ThreadContext; @@ -20,36 +19,40 @@ import org.apache.logging.log4j.spi.DefaultThreadContextStack; import org.apache.logging.log4j.util.StringMap; import org.hamcrest.Matchers; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import java.util.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +@RunWith(JUnitParamsRunner.class) public class SentryAppenderEventBuildingTest extends BaseTest { - @Tested private SentryAppender sentryAppender = null; - private MockUpErrorHandler mockUpErrorHandler; - @Injectable + private ErrorCounter errorCounter; private SentryClient mockSentryClient = null; private String mockExtraTag = "d421627f-7a25-4d43-8210-140dfe73ff10"; private Set extraTags; - @BeforeMethod + @Before public void setUp() { + mockSentryClient = mock(SentryClient.class); Sentry.setStoredClient(mockSentryClient); sentryAppender = new SentryAppender(); - mockUpErrorHandler = new MockUpErrorHandler(); - sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + errorCounter = new ErrorCounter(); + sentryAppender.setHandler(errorCounter.getErrorHandler()); extraTags = new HashSet<>(); extraTags.add(mockExtraTag); } private void assertNoErrorsInErrorHandler() { - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + assertThat(errorCounter.getErrorCount(), is(0)); } @Test @@ -69,20 +72,18 @@ public void testSimpleMessageLogging() { sentryAppender.append(event); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getMessage(), is(message)); - assertThat(event.getLogger(), is(loggerName)); - assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); - assertThat(event.getTimestamp(), is(date)); - assertThat(event.getSdk().getIntegrations(), contains("log4j2")); - }}; + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event sentryEvent = eventBuilderArgumentCaptor.getValue().build(); + assertThat(sentryEvent.getMessage(), is(message)); + assertThat(sentryEvent.getLogger(), is(loggerName)); + assertThat(sentryEvent.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); + assertThat(sentryEvent.getTimestamp(), is(date)); + assertThat(sentryEvent.getSdk().getIntegrations(), contains("log4j2")); assertNoErrorsInErrorHandler(); } - @DataProvider(name = "levels") + @NamedParameters("levels") private Object[][] levelConversions() { return new Object[][]{ {Event.Level.DEBUG, Level.TRACE}, @@ -93,7 +94,8 @@ private Object[][] levelConversions() { {Event.Level.FATAL, Level.FATAL}}; } - @Test(dataProvider = "levels") + @Test + @Parameters(named = "levels") public void testLevelConversion(final Event.Level expectedLevel, Level level) throws Exception { Log4jLogEvent event = Log4jLogEvent.newBuilder() @@ -103,12 +105,10 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th sentryAppender.append(event); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getLevel(), is(expectedLevel)); - }}; + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event sentryEvent = eventBuilderArgumentCaptor.getValue().build(); + assertThat(sentryEvent.getLevel(), is(expectedLevel)); assertNoErrorsInErrorHandler(); } @@ -118,17 +118,15 @@ public void testExceptionLogging() { sentryAppender.append(new Log4jLogEvent(null, null, null, Level.ERROR, new SimpleMessage(""), exception)); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() - .get(ExceptionInterface.EXCEPTION_INTERFACE); - SentryException sentryException = exceptionInterface.getExceptions().getFirst(); - assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); - assertThat(sentryException.getStackTraceInterface().getStackTrace(), - is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace(), null))); - }}; + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event sentryEvent = eventBuilderArgumentCaptor.getValue().build(); + ExceptionInterface exceptionInterface = (ExceptionInterface) sentryEvent.getSentryInterfaces() + .get(ExceptionInterface.EXCEPTION_INTERFACE); + SentryException sentryException = exceptionInterface.getExceptions().getFirst(); + assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); + assertThat(sentryException.getStackTraceInterface().getStackTrace(), + is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace(), null))); assertNoErrorsInErrorHandler(); } @@ -144,17 +142,15 @@ public void testLogParametrisedMessage() { sentryAppender.append(event); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - MessageInterface messageInterface = (MessageInterface) event.getSentryInterfaces() - .get(MessageInterface.MESSAGE_INTERFACE); - assertThat(event.getMessage(), is("Formatted message first parameter [] null")); - assertThat(messageInterface.getMessage(), is(messagePattern)); - assertThat(messageInterface.getParameters(), - is(Arrays.asList(parameters[0].toString(), parameters[1].toString(), null))); - }}; + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event sentryEvent = eventBuilderArgumentCaptor.getValue().build(); + MessageInterface messageInterface = (MessageInterface) sentryEvent.getSentryInterfaces() + .get(MessageInterface.MESSAGE_INTERFACE); + assertThat(sentryEvent.getMessage(), is("Formatted message first parameter [] null")); + assertThat(messageInterface.getMessage(), is(messagePattern)); + assertThat(messageInterface.getParameters(), + is(Arrays.asList(parameters[0].toString(), parameters[1].toString(), null))); assertNoErrorsInErrorHandler(); } @@ -170,12 +166,10 @@ public void testMarkerAddedToTag() { sentryAppender.append(event); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getTags(), Matchers.hasEntry(SentryAppender.LOG4J_MARKER, markerName)); - }}; + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event sentryEvent = eventBuilderArgumentCaptor.getValue().build(); + assertThat(sentryEvent.getTags(), Matchers.hasEntry(SentryAppender.LOG4J_MARKER, markerName)); assertNoErrorsInErrorHandler(); } @@ -196,12 +190,10 @@ public void testMdcAddedToExtra() { sentryAppender.append(event); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); - }}; + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event sentryEvent = eventBuilderArgumentCaptor.getValue().build(); + assertThat(sentryEvent.getExtra(), Matchers.hasEntry(extraKey, extraValue)); assertNoErrorsInErrorHandler(); } @@ -222,12 +214,10 @@ public void testNdcAddedToExtra() { sentryAppender.append(event); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat((List) event.getExtra().get(SentryAppender.LOG4J_NDC), equalTo(contextStack.asList())); - }}; + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event sentryEvent = eventBuilderArgumentCaptor.getValue().build(); + assertThat((List) sentryEvent.getExtra().get(SentryAppender.LOG4J_NDC), equalTo(contextStack.asList())); assertNoErrorsInErrorHandler(); } @@ -245,15 +235,13 @@ public void testSourceUsedAsStacktrace() { sentryAppender.append(event); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() - .get(StackTraceInterface.STACKTRACE_INTERFACE); - assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); - assertThat(stackTraceInterface.getStackTrace()[0], is(SentryStackTraceElement.fromStackTraceElement(location))); - }}; + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event sentryEvent = eventBuilderArgumentCaptor.getValue().build(); + StackTraceInterface stackTraceInterface = (StackTraceInterface) sentryEvent.getSentryInterfaces() + .get(StackTraceInterface.STACKTRACE_INTERFACE); + assertThat(stackTraceInterface.getStackTrace(), arrayWithSize(1)); + assertThat(stackTraceInterface.getStackTrace()[0], is(SentryStackTraceElement.fromStackTraceElement(location))); assertNoErrorsInErrorHandler(); } @@ -263,10 +251,7 @@ public void testExtraTagObtainedFromMdc() { mdc.putValue(mockExtraTag, "565940d2-f4a4-42f6-9496-42e3c7c85c43"); mdc.putValue("other_property", "395856e8-fa1d-474f-8fa9-c062b4886527"); - new NonStrictExpectations() {{ - mockSentryClient.getMdcTags(); - result = extraTags; - }}; + when(mockSentryClient.getMdcTags()).thenReturn(extraTags); Log4jLogEvent event = Log4jLogEvent.newBuilder() .setContextData(mdc) @@ -277,15 +262,13 @@ public void testExtraTagObtainedFromMdc() { sentryAppender.append(event); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getTags().entrySet(), hasSize(1)); - assertThat(event.getTags(), hasEntry(mockExtraTag, "565940d2-f4a4-42f6-9496-42e3c7c85c43")); - assertThat(event.getExtra(), not(hasKey(mockExtraTag))); - assertThat(event.getExtra(), Matchers.hasEntry("other_property", "395856e8-fa1d-474f-8fa9-c062b4886527")); - }}; + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event sentryEvent = eventBuilderArgumentCaptor.getValue().build(); + assertThat(sentryEvent.getTags().entrySet(), hasSize(1)); + assertThat(sentryEvent.getTags(), hasEntry(mockExtraTag, "565940d2-f4a4-42f6-9496-42e3c7c85c43")); + assertThat(sentryEvent.getExtra(), not(hasKey(mockExtraTag))); + assertThat(sentryEvent.getExtra(), Matchers.hasEntry("other_property", "395856e8-fa1d-474f-8fa9-c062b4886527")); assertNoErrorsInErrorHandler(); } } diff --git a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java index 1192d8031f2..644878f46cf 100644 --- a/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java +++ b/sentry-log4j2/src/test/java/io/sentry/log4j2/SentryAppenderFailuresTest.java @@ -1,54 +1,46 @@ package io.sentry.log4j2; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + import io.sentry.BaseTest; import io.sentry.Sentry; -import io.sentry.event.EventBuilder; -import mockit.Injectable; -import mockit.Mocked; -import mockit.NonStrictExpectations; -import mockit.Verifications; import io.sentry.SentryClient; -import io.sentry.SentryClientFactory; -import io.sentry.dsn.Dsn; import io.sentry.environment.SentryEnvironment; -import io.sentry.event.Event; +import io.sentry.event.EventBuilder; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.SimpleMessage; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; +import org.junit.Before; +import org.junit.Test; public class SentryAppenderFailuresTest extends BaseTest { private SentryAppender sentryAppender; - private MockUpErrorHandler mockUpErrorHandler; - @Injectable + private ErrorCounter errorCounter; private SentryClient mockSentryClient = null; - @SuppressWarnings("unused") - @Mocked("sentryClient") - private SentryClientFactory mockSentryClientFactory = null; - @BeforeMethod + @Before public void setUp() throws Exception { + mockSentryClient = mock(SentryClient.class); Sentry.setStoredClient(mockSentryClient); sentryAppender = new SentryAppender(); - mockUpErrorHandler = new MockUpErrorHandler(); - sentryAppender.setHandler(mockUpErrorHandler.getMockInstance()); + errorCounter = new ErrorCounter(); + sentryAppender.setHandler(errorCounter.getErrorHandler()); } @Test public void testSentryFailureDoesNotPropagate() throws Exception { - new NonStrictExpectations() {{ - mockSentryClient.sendEvent((EventBuilder) any); - result = new UnsupportedOperationException(); - }}; + doThrow(new UnsupportedOperationException()).when(mockSentryClient).sendEvent(any(EventBuilder.class)); sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); - assertThat(mockUpErrorHandler.getErrorCount(), is(1)); + assertThat(errorCounter.getErrorCount(), is(1)); } @Test @@ -57,11 +49,8 @@ public void testAppendFailIfCurrentThreadSpawnedBySentry() throws Exception { try { sentryAppender.append(new Log4jLogEvent(null, null, null, Level.INFO, new SimpleMessage(""), null)); - new Verifications() {{ - mockSentryClient.sendEvent((EventBuilder) any); - times = 0; - }}; - assertThat(mockUpErrorHandler.getErrorCount(), is(0)); + verify(mockSentryClient, never()).sendEvent(any(EventBuilder.class)); + assertThat(errorCounter.getErrorCount(), is(0)); } finally { SentryEnvironment.stopManagingThread(); } From 131d4cb10a1741f11599ad42b262b68271590b41 Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Sun, 22 Sep 2019 21:05:18 +0200 Subject: [PATCH 2105/2152] Sentry logback test rewrite (#767) --- sentry-logback/pom.xml | 20 +- .../sentry/logback/MockUpStatusPrinter.java | 30 --- .../logback/SentryAppenderCloseTest.java | 53 ++-- .../SentryAppenderEventBuildingTest.java | 240 ++++++++---------- .../SentryAppenderEventLevelFilterTest.java | 66 +++-- .../logback/SentryAppenderFailuresTest.java | 58 ++--- ...oggingEvent.java => TestLoggingEvent.java} | 83 +++--- 7 files changed, 237 insertions(+), 313 deletions(-) delete mode 100644 sentry-logback/src/test/java/io/sentry/logback/MockUpStatusPrinter.java rename sentry-logback/src/test/java/io/sentry/logback/{MockUpLoggingEvent.java => TestLoggingEvent.java} (72%) diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 88b897605d4..d56eeb4a9e7 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -39,16 +39,6 @@ - - org.jmockit - jmockit - test - - - org.testng - testng - test - org.hamcrest hamcrest-core @@ -69,6 +59,16 @@ wiremock-standalone test + + org.mockito + mockito-core + test + + + pl.pragmatists + JUnitParams + test + diff --git a/sentry-logback/src/test/java/io/sentry/logback/MockUpStatusPrinter.java b/sentry-logback/src/test/java/io/sentry/logback/MockUpStatusPrinter.java deleted file mode 100644 index e210ffd6ef1..00000000000 --- a/sentry-logback/src/test/java/io/sentry/logback/MockUpStatusPrinter.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.sentry.logback; - -import ch.qos.logback.core.status.Status; -import ch.qos.logback.core.util.StatusPrinter; -import mockit.Mock; -import mockit.MockUp; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MockUpStatusPrinter extends MockUp { - private static final Logger logger = LoggerFactory.getLogger("ErrorHandler"); - - @Mock - public static void buildStr(StringBuilder sb, String indentation, Status s) { - switch (s.getEffectiveLevel()) { - case Status.INFO: - logger.info("{} - {}", s.getOrigin(), s.getMessage()); - return; - case Status.WARN: - logger.warn("{} - {}", s.getOrigin(), s.getMessage()); - return; - case Status.ERROR: - logger.error("{} - {}", s.getOrigin(), s.getMessage()); - return; - default: - logger.debug("{} - {}", s.getOrigin(), s.getMessage()); - return; - } - } -} diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java index 0ad83566e9b..0c0914a1e69 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderCloseTest.java @@ -5,40 +5,31 @@ import ch.qos.logback.core.status.OnConsoleStatusListener; import io.sentry.BaseTest; import io.sentry.Sentry; -import mockit.*; import io.sentry.SentryClient; -import io.sentry.SentryClientFactory; -import io.sentry.dsn.Dsn; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class SentryAppenderCloseTest extends BaseTest { - @Injectable private SentryClient mockSentryClient = null; - @Injectable private Context mockContext = null; - @SuppressWarnings("unused") - @Mocked("sentryClient") - private SentryClientFactory mockSentryClientFactory = null; - @SuppressWarnings("unused") - @Mocked("dsnLookup") - private Dsn mockDsn = null; - - @BeforeMethod + + @Before public void setUp() throws Exception { - new MockUpStatusPrinter(); - new NonStrictExpectations() {{ - final BasicStatusManager statusManager = new BasicStatusManager(); - final OnConsoleStatusListener listener = new OnConsoleStatusListener(); - listener.start(); - statusManager.add(listener); - - mockContext.getStatusManager(); - result = statusManager; - }}; + mockSentryClient = mock(SentryClient.class); + + final BasicStatusManager statusManager = new BasicStatusManager(); + final OnConsoleStatusListener listener = new OnConsoleStatusListener(); + listener.start(); + statusManager.add(listener); + + mockContext = mock(Context.class); + when(mockContext.getStatusManager()).thenReturn(statusManager); } private void assertNoErrorsInStatusManager() throws Exception { @@ -54,9 +45,7 @@ public void testConnectionClosedWhenAppenderStopped() throws Exception { sentryAppender.stop(); - new Verifications() {{ - mockSentryClient.closeConnection(); - }}; + verify(mockSentryClient).closeConnection(); assertNoErrorsInStatusManager(); } @@ -75,8 +64,7 @@ public void testStopDoNotFailIfSentryNull() throws Exception { } @Test - public void testStopDoNotFailIfNoInit() - throws Exception { + public void testStopDoNotFailIfNoInit() throws Exception { final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setContext(mockContext); @@ -95,10 +83,7 @@ public void testStopDoNotFailWhenMultipleCalls() throws Exception { sentryAppender.stop(); sentryAppender.stop(); - new Verifications() {{ - mockSentryClient.closeConnection(); - times = 1; - }}; + verify(mockSentryClient).closeConnection(); assertNoErrorsInStatusManager(); } } diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java index 5c72d68ad8c..02610cc7ec5 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java @@ -9,37 +9,40 @@ import io.sentry.Sentry; import io.sentry.SentryClient; import io.sentry.event.interfaces.*; -import mockit.Injectable; -import mockit.NonStrictExpectations; -import mockit.Tested; -import mockit.Verifications; +import junitparams.JUnitParamsRunner; +import junitparams.NamedParameters; +import junitparams.Parameters; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.slf4j.MarkerFactory; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import java.util.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +@RunWith(JUnitParamsRunner.class) public class SentryAppenderEventBuildingTest extends BaseTest { - @Tested private SentryAppender sentryAppender = null; - @Injectable private SentryClient mockSentryClient = null; - @Injectable private Context mockContext = null; private String mockExtraTag = "60f42409-c029-447d-816a-fb2722913c93"; private String mockMinLevel = "ALL"; private Set extraTags; - @BeforeMethod + @Before public void setUp() throws Exception { - new MockUpStatusPrinter(); + mockSentryClient = mock(SentryClient.class); + mockContext = mock(Context.class); + Sentry.setStoredClient(mockSentryClient); sentryAppender = new SentryAppender(); sentryAppender.setContext(mockContext); @@ -47,15 +50,12 @@ public void setUp() throws Exception { extraTags = new HashSet<>(); extraTags.add(mockExtraTag); - new NonStrictExpectations() {{ - final BasicStatusManager statusManager = new BasicStatusManager(); - final OnConsoleStatusListener listener = new OnConsoleStatusListener(); - listener.start(); - statusManager.add(listener); + final BasicStatusManager statusManager = new BasicStatusManager(); + final OnConsoleStatusListener listener = new OnConsoleStatusListener(); + listener.start(); + statusManager.add(listener); - mockContext.getStatusManager(); - result = statusManager; - }}; + when(mockContext.getStatusManager()).thenReturn(statusManager); } private void assertNoErrorsInStatusManager() throws Exception { @@ -69,24 +69,22 @@ public void testSimpleMessageLogging() throws Exception { final String threadName = "a70e658d-f5fa-4707-b7ac-0d503429f1dd"; final Date date = new Date(1373883196416L); - ILoggingEvent loggingEvent = new MockUpLoggingEvent(loggerName, null, Level.INFO, message, null, null, null, - threadName, null, date.getTime()).getMockInstance(); + ILoggingEvent loggingEvent = new TestLoggingEvent(loggerName, null, Level.INFO, message, null, null, null, + threadName, null, date.getTime()); sentryAppender.append(loggingEvent); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getMessage(), is(message)); - assertThat(event.getLogger(), is(loggerName)); - assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); - assertThat(event.getTimestamp(), is(date)); - assertThat(event.getSdk().getIntegrations(), contains("logback")); - }}; + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event event = eventBuilderArgumentCaptor.getValue().build(); + assertThat(event.getMessage(), is(message)); + assertThat(event.getLogger(), is(loggerName)); + assertThat(event.getExtra(), Matchers.hasEntry(SentryAppender.THREAD_NAME, threadName)); + assertThat(event.getTimestamp(), is(date)); + assertThat(event.getSdk().getIntegrations(), contains("logback")); assertNoErrorsInStatusManager(); } - @DataProvider(name = "levels") + @NamedParameters("levels") private Object[][] levelConversions() { return new Object[][]{ {Event.Level.DEBUG, Level.TRACE}, @@ -96,16 +94,15 @@ private Object[][] levelConversions() { {Event.Level.ERROR, Level.ERROR}}; } - @Test(dataProvider = "levels") + @Test + @Parameters(named = "levels") public void testLevelConversion(final Event.Level expectedLevel, Level level) throws Exception { - sentryAppender.append(new MockUpLoggingEvent(null, null, level, null, null, null).getMockInstance()); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getLevel(), is(expectedLevel)); - }}; + sentryAppender.append(new TestLoggingEvent(null, null, level, null, null, null)); + + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event event = eventBuilderArgumentCaptor.getValue().build(); + assertThat(event.getLevel(), is(expectedLevel)); assertNoErrorsInStatusManager(); } @@ -113,19 +110,17 @@ public void testLevelConversion(final Event.Level expectedLevel, Level level) th public void testExceptionLogging() throws Exception { final Exception exception = new Exception(); - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.ERROR, null, null, exception).getMockInstance()); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() - .get(ExceptionInterface.EXCEPTION_INTERFACE); - SentryException sentryException = exceptionInterface.getExceptions().getFirst(); - assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); - assertThat(sentryException.getStackTraceInterface().getStackTrace(), - is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace(), null))); - }}; + sentryAppender.append(new TestLoggingEvent(null, null, Level.ERROR, null, null, exception)); + + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event event = eventBuilderArgumentCaptor.getValue().build(); + ExceptionInterface exceptionInterface = (ExceptionInterface) event.getSentryInterfaces() + .get(ExceptionInterface.EXCEPTION_INTERFACE); + SentryException sentryException = exceptionInterface.getExceptions().getFirst(); + assertThat(sentryException.getExceptionMessage(), is(exception.getMessage())); + assertThat(sentryException.getStackTraceInterface().getStackTrace(), + is(SentryStackTraceElement.fromStackTraceElements(exception.getStackTrace(), null))); assertNoErrorsInStatusManager(); } @@ -134,21 +129,18 @@ public void testLogParametrisedMessage() throws Exception { final String messagePattern = "Formatted message {} {} {}"; final Object[] parameters = {"first parameter", new Object[0], null}; - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, messagePattern, parameters, null) - .getMockInstance()); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - MessageInterface messageInterface = (MessageInterface) event.getSentryInterfaces() - .get(MessageInterface.MESSAGE_INTERFACE); - - assertThat(event.getMessage(), is("Formatted message first parameter [] null")); - assertThat(messageInterface.getMessage(), is(messagePattern)); - assertThat(messageInterface.getParameters(), - is(Arrays.asList(parameters[0].toString(), parameters[1].toString(), null))); - }}; + sentryAppender.append(new TestLoggingEvent(null, null, Level.INFO, messagePattern, parameters, null)); + + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event event = eventBuilderArgumentCaptor.getValue().build(); + MessageInterface messageInterface = (MessageInterface) event.getSentryInterfaces() + .get(MessageInterface.MESSAGE_INTERFACE); + + assertThat(event.getMessage(), is("Formatted message first parameter [] null")); + assertThat(messageInterface.getMessage(), is(messagePattern)); + assertThat(messageInterface.getParameters(), + is(Arrays.asList(parameters[0].toString(), parameters[1].toString(), null))); assertNoErrorsInStatusManager(); } @@ -157,15 +149,12 @@ public void testMarkerAddedToTag() throws Exception { final String markerName = "d33e3927-ea6c-4a5a-b66c-8dcb2052e812"; sentryAppender.append( - new MockUpLoggingEvent(null, MarkerFactory.getMarker(markerName), Level.INFO, null, null, null) - .getMockInstance()); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getTags(), Matchers.hasEntry(SentryAppender.LOGBACK_MARKER, markerName)); - }}; + new TestLoggingEvent(null, MarkerFactory.getMarker(markerName), Level.INFO, null, null, null)); + + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event event = eventBuilderArgumentCaptor.getValue().build(); + assertThat(event.getTags(), Matchers.hasEntry(SentryAppender.LOGBACK_MARKER, markerName)); assertNoErrorsInStatusManager(); } @@ -174,15 +163,13 @@ public void testMdcAddedToExtra() throws Exception { final String extraKey = "10e09b11-546f-4c57-99b2-cf3c627c8737"; final String extraValue = "5f7a53b1-4354-4120-a368-78a615705540"; - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null, - Collections.singletonMap(extraKey, extraValue), null, null, 0).getMockInstance()); + sentryAppender.append(new TestLoggingEvent(null, null, Level.INFO, null, null, null, + Collections.singletonMap(extraKey, extraValue), null, null, 0)); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); - }}; + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event event = eventBuilderArgumentCaptor.getValue().build(); + assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); assertNoErrorsInStatusManager(); } @@ -191,15 +178,13 @@ public void testContextPropertiesAddedToExtra() throws Exception { final String extraKey = "0489bc59-b4ba-4890-9a60-58e65624fe8c"; final String extraValue = "986adaa7-c0e4-4c09-9c5e-49edaf2e6d53"; - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null, - null, null, null, 0, Collections.singletonMap(extraKey, extraValue)).getMockInstance()); + sentryAppender.append(new TestLoggingEvent(null, null, Level.INFO, null, null, null, + null, null, null, 0, Collections.singletonMap(extraKey, extraValue))); - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); - }}; + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event event = eventBuilderArgumentCaptor.getValue().build(); + assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); assertNoErrorsInStatusManager(); } @@ -210,16 +195,14 @@ public void testMdcTakesPrecedenceOverContextProperties() throws Exception { final String contextKey = mdcKey; final String contextValue = "66d123eb-7786-4f3d-86f1-a906039401d9"; - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null, + sentryAppender.append(new TestLoggingEvent(null, null, Level.INFO, null, null, null, Collections.singletonMap(mdcKey, mdcValue), null, null, 0, - Collections.singletonMap(contextKey, contextValue)).getMockInstance()); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getExtra(), Matchers.hasEntry(mdcKey, mdcValue)); - }}; + Collections.singletonMap(contextKey, contextValue))); + + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event event = eventBuilderArgumentCaptor.getValue().build(); + assertThat(event.getExtra(), Matchers.hasEntry(mdcKey, mdcValue)); assertNoErrorsInStatusManager(); } @@ -229,17 +212,15 @@ public void testSourceUsedAsStacktrace() throws Exception { "49974348-1704-47cc-be5a-4e72f2e0db33", "bf48ef03-657c-4924-844a-317743c4599b", 42)}; - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null, null, null, location, 0) - .getMockInstance()); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() - .get(StackTraceInterface.STACKTRACE_INTERFACE); - assertThat(stackTraceInterface.getStackTrace(), is(SentryStackTraceElement.fromStackTraceElements(location, null))); - }}; + sentryAppender.append(new TestLoggingEvent(null, null, Level.INFO, null, null, null, null, null, location, 0) + ); + + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event event = eventBuilderArgumentCaptor.getValue().build(); + StackTraceInterface stackTraceInterface = (StackTraceInterface) event.getSentryInterfaces() + .get(StackTraceInterface.STACKTRACE_INTERFACE); + assertThat(stackTraceInterface.getStackTrace(), is(SentryStackTraceElement.fromStackTraceElements(location, null))); assertNoErrorsInStatusManager(); } @@ -249,23 +230,18 @@ public void testExtraTagObtainedFromMdc() throws Exception { mdcPropertyMap.put(mockExtraTag, "47008f35-50c8-4e40-94ca-c8c1a3ddb729"); mdcPropertyMap.put("other_property", "cb9c92a1-0182-4e9c-866f-b06b271cd196"); - new NonStrictExpectations() {{ - mockSentryClient.getMdcTags(); - result = extraTags; - }}; - - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null, mdcPropertyMap, null, - null, 0).getMockInstance()); - - new Verifications() {{ - EventBuilder eventBuilder; - mockSentryClient.sendEvent(eventBuilder = withCapture()); - Event event = eventBuilder.build(); - assertThat(event.getTags().entrySet(), hasSize(1)); - assertThat(event.getTags(), hasEntry(mockExtraTag, "47008f35-50c8-4e40-94ca-c8c1a3ddb729")); - assertThat(event.getExtra(), not(hasKey(mockExtraTag))); - assertThat(event.getExtra(), Matchers.hasEntry("other_property", "cb9c92a1-0182-4e9c-866f-b06b271cd196")); - }}; + when(mockSentryClient.getMdcTags()).thenReturn(extraTags); + + sentryAppender.append(new TestLoggingEvent(null, null, Level.INFO, null, null, null, mdcPropertyMap, null, + null, 0)); + + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event event = eventBuilderArgumentCaptor.getValue().build(); + assertThat(event.getTags().entrySet(), hasSize(1)); + assertThat(event.getTags(), hasEntry(mockExtraTag, "47008f35-50c8-4e40-94ca-c8c1a3ddb729")); + assertThat(event.getExtra(), not(hasKey(mockExtraTag))); + assertThat(event.getExtra(), Matchers.hasEntry("other_property", "cb9c92a1-0182-4e9c-866f-b06b271cd196")); assertNoErrorsInStatusManager(); } } diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java index 56d543a242c..6f89c2fc771 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventLevelFilterTest.java @@ -1,39 +1,43 @@ package io.sentry.logback; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + import ch.qos.logback.classic.Level; import ch.qos.logback.core.Context; import io.sentry.BaseTest; import io.sentry.Sentry; import io.sentry.event.EventBuilder; -import mockit.Injectable; -import mockit.Tested; -import mockit.Verifications; +import junitparams.JUnitParamsRunner; +import junitparams.NamedParameters; +import junitparams.Parameters; import io.sentry.SentryClient; -import io.sentry.event.Event; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; /** * @author Felipe G Almeida */ +@RunWith(JUnitParamsRunner.class) public class SentryAppenderEventLevelFilterTest extends BaseTest { - @Tested private SentryAppender sentryAppender = null; - @Injectable private SentryClient mockSentryClient = null; - @Injectable private Context mockContext = null; - @BeforeMethod + @Before public void setUp() throws Exception { - new MockUpStatusPrinter(); + mockSentryClient = mock(SentryClient.class); + mockContext = mock(Context.class); + Sentry.setStoredClient(mockSentryClient); sentryAppender = new SentryAppender(); sentryAppender.setContext(mockContext); } - @DataProvider(name = "levels") + @NamedParameters("levels") private Object[][] levelConversions() { return new Object[][]{ {"ALL", 5}, @@ -47,35 +51,27 @@ private Object[][] levelConversions() { {null, 5}}; } - @Test(dataProvider = "levels") + @Test + @Parameters(named = "levels") public void testLevelFilter(final String minLevel, final Integer expectedEvents) throws Exception { sentryAppender.setMinLevel(minLevel); - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.TRACE, null, null, null).getMockInstance()); - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.DEBUG, null, null, null).getMockInstance()); - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null).getMockInstance()); - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.WARN, null, null, null).getMockInstance()); - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.ERROR, null, null, null).getMockInstance()); + sentryAppender.append(new TestLoggingEvent(null, null, Level.TRACE, null, null, null)); + sentryAppender.append(new TestLoggingEvent(null, null, Level.DEBUG, null, null, null)); + sentryAppender.append(new TestLoggingEvent(null, null, Level.INFO, null, null, null)); + sentryAppender.append(new TestLoggingEvent(null, null, Level.WARN, null, null, null)); + sentryAppender.append(new TestLoggingEvent(null, null, Level.ERROR, null, null, null)); - new Verifications() {{ - mockSentryClient.sendEvent((EventBuilder) any); - minTimes = expectedEvents; - maxTimes = expectedEvents; - }}; + verify(mockSentryClient, times(expectedEvents)).sendEvent(any(EventBuilder.class)); } @Test public void testDefaultLevelFilter() throws Exception { - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.TRACE, null, null, null).getMockInstance()); - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.DEBUG, null, null, null).getMockInstance()); - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null).getMockInstance()); - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.WARN, null, null, null).getMockInstance()); - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.ERROR, null, null, null).getMockInstance()); + sentryAppender.append(new TestLoggingEvent(null, null, Level.TRACE, null, null, null)); + sentryAppender.append(new TestLoggingEvent(null, null, Level.DEBUG, null, null, null)); + sentryAppender.append(new TestLoggingEvent(null, null, Level.INFO, null, null, null)); + sentryAppender.append(new TestLoggingEvent(null, null, Level.WARN, null, null, null)); + sentryAppender.append(new TestLoggingEvent(null, null, Level.ERROR, null, null, null)); - new Verifications() {{ - mockSentryClient.sendEvent((EventBuilder) any); - minTimes = 5; - maxTimes = 5; - }}; + verify(mockSentryClient, times(5)).sendEvent(any(EventBuilder.class)); } - } diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java index d4ee0e39933..d90fbd6efa2 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderFailuresTest.java @@ -8,38 +8,34 @@ import io.sentry.Sentry; import io.sentry.SentryClient; import io.sentry.event.EventBuilder; -import mockit.*; -import io.sentry.SentryClientFactory; -import io.sentry.dsn.Dsn; import io.sentry.environment.SentryEnvironment; -import io.sentry.event.Event; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.Before; +import org.junit.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class SentryAppenderFailuresTest extends BaseTest { - @Injectable private SentryClient mockSentryClient = null; - @Injectable private Context mockContext = null; - @SuppressWarnings("unused") - @Mocked("sentryClient") - private SentryClientFactory mockSentryClientFactory; - @BeforeMethod + @Before public void setUp() throws Exception { - new MockUpStatusPrinter(); - new NonStrictExpectations() {{ - final BasicStatusManager statusManager = new BasicStatusManager(); - final OnConsoleStatusListener listener = new OnConsoleStatusListener(); - listener.start(); - statusManager.add(listener); + mockSentryClient = mock(SentryClient.class); + mockContext = mock(Context.class); - mockContext.getStatusManager(); - result = statusManager; - }}; + final BasicStatusManager statusManager = new BasicStatusManager(); + final OnConsoleStatusListener listener = new OnConsoleStatusListener(); + listener.start(); + statusManager.add(listener); + + when(mockContext.getStatusManager()).thenReturn(statusManager); } @Test @@ -48,17 +44,14 @@ public void testSentryFailureDoesNotPropagate() throws Exception { final SentryAppender sentryAppender = new SentryAppender(); sentryAppender.setContext(mockContext); sentryAppender.setMinLevel("ALL"); - new NonStrictExpectations() {{ - mockSentryClient.sendEvent((EventBuilder) any); - result = new UnsupportedOperationException(); - }}; + + doThrow(new UnsupportedOperationException()).when(mockSentryClient).sendEvent(any(EventBuilder.class)); + sentryAppender.start(); - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null).getMockInstance()); + sentryAppender.append(new TestLoggingEvent(null, null, Level.INFO, null, null, null)); - new Verifications() {{ - mockSentryClient.sendEvent((EventBuilder) any); - }}; + verify(mockSentryClient).sendEvent(any(EventBuilder.class)); assertThat(mockContext.getStatusManager().getCount(), is(1)); } @@ -71,12 +64,9 @@ public void testAppendFailIfCurrentThreadSpawnedBySentry() throws Exception { sentryAppender.setContext(mockContext); sentryAppender.start(); - sentryAppender.append(new MockUpLoggingEvent(null, null, Level.INFO, null, null, null).getMockInstance()); + sentryAppender.append(new TestLoggingEvent(null, null, Level.INFO, null, null, null)); - new Verifications() {{ - mockSentryClient.sendEvent((EventBuilder) any); - times = 0; - }}; + verify(mockSentryClient, never()).sendEvent(any(EventBuilder.class)); assertThat(mockContext.getStatusManager().getCount(), is(0)); } finally { SentryEnvironment.stopManagingThread(); diff --git a/sentry-logback/src/test/java/io/sentry/logback/MockUpLoggingEvent.java b/sentry-logback/src/test/java/io/sentry/logback/TestLoggingEvent.java similarity index 72% rename from sentry-logback/src/test/java/io/sentry/logback/MockUpLoggingEvent.java rename to sentry-logback/src/test/java/io/sentry/logback/TestLoggingEvent.java index 8cc6b2b1d9b..6c3649cae2b 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/MockUpLoggingEvent.java +++ b/sentry-logback/src/test/java/io/sentry/logback/TestLoggingEvent.java @@ -1,39 +1,36 @@ package io.sentry.logback; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.IThrowableProxy; import ch.qos.logback.classic.spi.LoggerContextVO; import ch.qos.logback.classic.spi.ThrowableProxy; -import mockit.Mock; -import mockit.MockUp; import org.slf4j.Marker; import org.slf4j.helpers.MessageFormatter; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -public class MockUpLoggingEvent extends MockUp { - private String loggerName; - private Marker marker; - private Level level; - private String message; - private Object[] argumentArray; - private Throwable throwable; - private Map mdcPropertyMap; - private String threadName; - private StackTraceElement[] callerData; - private long timestamp; - private LoggerContextVO loggerContextVO; - - - public MockUpLoggingEvent(String loggerName, Marker marker, Level level, String message, +public class TestLoggingEvent implements ILoggingEvent { + private final String loggerName; + private final Marker marker; + private final Level level; + private final String message; + private final Object[] argumentArray; + private final Throwable throwable; + private final Map mdcPropertyMap; + private final String threadName; + private final StackTraceElement[] callerData; + private final long timestamp; + private final LoggerContextVO loggerContextVO; + + public TestLoggingEvent(String loggerName, Marker marker, Level level, String message, Object[] argumentArray, Throwable t) { this(loggerName, marker, level, message, argumentArray, t, null, null, null, System.currentTimeMillis()); } - public MockUpLoggingEvent(String loggerName, Marker marker, Level level, String message, Object[] argumentArray, + public TestLoggingEvent(String loggerName, Marker marker, Level level, String message, Object[] argumentArray, Throwable throwable, Map mdcPropertyMap, String threadName, StackTraceElement[] callerData, long timestamp) { this(loggerName, @@ -49,7 +46,7 @@ public MockUpLoggingEvent(String loggerName, Marker marker, Level level, String new HashMap()); } - public MockUpLoggingEvent(String loggerName, Marker marker, Level level, String message, Object[] argumentArray, + public TestLoggingEvent(String loggerName, Marker marker, Level level, String message, Object[] argumentArray, Throwable throwable, Map mdcPropertyMap, String threadName, StackTraceElement[] callerData, long timestamp, Map contextProperties) { this.loggerName = loggerName; @@ -65,68 +62,78 @@ public MockUpLoggingEvent(String loggerName, Marker marker, Level level, String this.loggerContextVO = new LoggerContextVO("loggerContextOf" + loggerName, contextProperties, System.currentTimeMillis()); } - @Mock + @Override public String getThreadName() { return threadName; } - @Mock + @Override public Level getLevel() { return level; } - @Mock + @Override public String getMessage() { return message; } - @Mock + @Override public Object[] getArgumentArray() { return argumentArray; } - @Mock + @Override public String getFormattedMessage() { return argumentArray != null ? MessageFormatter.arrayFormat(message, argumentArray).getMessage() : message; } - @Mock + @Override public String getLoggerName() { return loggerName; } - @Mock + @Override + public LoggerContextVO getLoggerContextVO() { + return loggerContextVO; + } + + @Override public IThrowableProxy getThrowableProxy() { return throwable != null ? new ThrowableProxy(throwable) : null; } - @Mock + @Override public StackTraceElement[] getCallerData() { return callerData != null ? callerData : new StackTraceElement[0]; } - @Mock + @Override public boolean hasCallerData() { return callerData != null && callerData.length > 0; } - @Mock + @Override public Marker getMarker() { return marker; } - @Mock + @Override public Map getMDCPropertyMap() { return mdcPropertyMap != null ? mdcPropertyMap : Collections.emptyMap(); } - @Mock + @Override + public Map getMdc() { + return getMDCPropertyMap(); + } + + @Override public long getTimeStamp() { return timestamp; } - @Mock - public LoggerContextVO getLoggerContextVO() { - return loggerContextVO; + @Override + public void prepareForDeferredProcessing() { + } } From 4dbeeb8dce08d1a70591a20612e6bafea8a1b415 Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Sun, 22 Sep 2019 21:29:49 +0200 Subject: [PATCH 2106/2152] Remove traces of JMockit and TestNG from the POMs. (#768) --- pom.xml | 12 ------------ sentry-spring/pom.xml | 10 ---------- 2 files changed, 22 deletions(-) diff --git a/pom.xml b/pom.xml index 84cab5dbcf8..d3d90aa2027 100644 --- a/pom.xml +++ b/pom.xml @@ -120,8 +120,6 @@ 1.7.24 2.9.9 - 1.14 - 6.9.10 1.3 4.12 2.5.1 @@ -163,16 +161,6 @@ jackson-databind ${jackson.version} - - org.jmockit - jmockit - ${jmockit.version} - - - org.testng - testng - ${testng.version} - org.hamcrest hamcrest-core diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 476a06c2a5e..e12c736a06f 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -44,16 +44,6 @@ - - org.jmockit - jmockit - test - - - org.testng - testng - test - org.hamcrest hamcrest-core From 96d040476253de629385374ce8cf95536c2df155 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2019 13:36:18 +0200 Subject: [PATCH 2107/2152] Bump jackson.version from 2.9.9 to 2.10.0 (#771) Bumps `jackson.version` from 2.9.9 to 2.10.0. Updates `jackson-core` from 2.9.9 to 2.10.0 - [Release notes](https://github.com/FasterXML/jackson-core/releases) - [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.9.9...jackson-core-2.10.0) Updates `jackson-annotations` from 2.9.9 to 2.10.0 - [Release notes](https://github.com/FasterXML/jackson/releases) - [Commits](https://github.com/FasterXML/jackson/commits) Updates `jackson-databind` from 2.9.9 to 2.10.0 - [Release notes](https://github.com/FasterXML/jackson/releases) - [Commits](https://github.com/FasterXML/jackson/commits) Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d3d90aa2027..e11e9873b19 100644 --- a/pom.xml +++ b/pom.xml @@ -119,7 +119,7 @@ 1.7.24 - 2.9.9 + 2.10.0 1.3 4.12 2.5.1 From 41ce264d0f309d5624f94aa8f4cc661632a1a9f0 Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Wed, 2 Oct 2019 13:37:03 +0200 Subject: [PATCH 2108/2152] Fixes #663. Don't attempt to send events for null payloads. (#769) --- .../src/main/java/io/sentry/SentryClient.java | 14 ++++++++ .../test/java/io/sentry/SentryClientTest.java | 36 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 6e9a30b9e01..b5667151ffb 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -124,6 +124,10 @@ public void runBuilderHelpers(EventBuilder eventBuilder) { * @param event event to send to Sentry. */ public void sendEvent(Event event) { + if (event == null) { + return; + } + for (ShouldSendEventCallback shouldSendEventCallback : shouldSendEventCallbacks) { if (!shouldSendEventCallback.shouldSend(event)) { logger.trace("Not sending Event because of ShouldSendEventCallback: {}", shouldSendEventCallback); @@ -148,6 +152,9 @@ public void sendEvent(Event event) { * @param eventBuilder {@link EventBuilder} to send to Sentry. */ public void sendEvent(EventBuilder eventBuilder) { + if (eventBuilder == null) { + return; + } Event event = buildEvent(eventBuilder); sendEvent(event); } @@ -205,6 +212,9 @@ Event buildEvent(EventBuilder eventBuilder) { * @param message message to send to Sentry. */ public void sendMessage(String message) { + if (message == null) { + return; + } EventBuilder eventBuilder = new EventBuilder().withMessage(message) .withLevel(Event.Level.INFO); sendEvent(eventBuilder); @@ -218,6 +228,10 @@ public void sendMessage(String message) { * @param throwable exception to send to Sentry. */ public void sendException(Throwable throwable) { + if (throwable == null) { + return; + } + EventBuilder eventBuilder = new EventBuilder().withMessage(throwable.getMessage()) .withLevel(Event.Level.ERROR) .withSentryInterface(new ExceptionInterface(throwable)); diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.java b/sentry/src/test/java/io/sentry/SentryClientTest.java index 395bbaf4d5f..95892dab90c 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.java +++ b/sentry/src/test/java/io/sentry/SentryClientTest.java @@ -53,6 +53,15 @@ public void testSendEvent() throws Exception { verify(mockConnection).send(eq(mockEvent)); } + @Test + public void testSendNullEvent() throws Exception { + // when + sentryClient.sendEvent((Event) null); + + // then + verify(mockConnection, never()).send(any(Event.class)); + } + @Test public void testSendEventBuilder() throws Exception { final String message = "e960981e-656d-4404-9b1d-43b483d3f32c"; @@ -71,6 +80,15 @@ public void testSendEventBuilder() throws Exception { assertThat(event.getMessage(), equalTo(message)); } + @Test + public void testSendNullEventBuilder() throws Exception { + // when + sentryClient.sendEvent((EventBuilder) null); + + // then + verify(mockConnection, never()).send(any(Event.class)); + } + @Test public void testSendEventFailingIsCaught() throws Exception { doThrow(new RuntimeException()).when(mockConnection).send(eq(mockEvent)); @@ -95,6 +113,15 @@ public void testSendMessage() throws Exception { assertThat(event.getMessage(), equalTo(message)); } + @Test + public void testSendNullMessage() throws Exception { + // when + sentryClient.sendMessage(null); + + // then + verify(mockConnection, never()).send(any(Event.class)); + } + @Test public void testSendException() throws Exception { final String message = "7b61ddb1-eb32-428d-bad9-a7d842605ba7"; @@ -112,6 +139,15 @@ public void testSendException() throws Exception { assertThat(event.getSentryInterfaces(), hasKey(ExceptionInterface.EXCEPTION_INTERFACE)); } + @Test + public void testSendNullException() throws Exception { + // when + sentryClient.sendException(null); + + // then + verify(mockConnection, never()).send(any(Event.class)); + } + @Test public void testAddRemoveBuilderHelpers() throws Exception { EventBuilderHelper mockBuilderHelper = mock(EventBuilderHelper.class); From b5c38dae46f7dcf54c7477ec4910e7c2e854fb1a Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 7 Oct 2019 05:18:49 -0400 Subject: [PATCH 2109/2152] fix: Huawei `getPackageInfo` throws (#774) * Resolves #773 --- .../sentry/android/event/helper/AndroidEventBuilderHelper.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java index 8339813244b..4759d1089a0 100644 --- a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java +++ b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java @@ -6,7 +6,6 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; @@ -186,7 +185,7 @@ protected static String[] getProGuardUuids(Context ctx) { protected static PackageInfo getPackageInfo(Context ctx) { try { return ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0); - } catch (PackageManager.NameNotFoundException e) { + } catch (Exception e) { Log.e(TAG, "Error getting package info.", e); return null; } From edd722711001a3fb034db64005a757748f165343 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 7 Oct 2019 11:38:23 -0400 Subject: [PATCH 2110/2152] fix: Make sure files list returned (#775) * fix: Make sure files list returned * ref: Use Collections.emptyIterator(); --- sentry/src/main/java/io/sentry/buffer/DiskBuffer.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java b/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java index 33a1c9c8902..8f643c515df 100644 --- a/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java +++ b/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java @@ -6,6 +6,7 @@ import java.io.*; import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; /** @@ -173,7 +174,11 @@ private Event getNextEvent(Iterator files) { */ @Override public Iterator getEvents() { - final Iterator files = Arrays.asList(bufferDir.listFiles()).iterator(); + final File[] fileArray = bufferDir.listFiles(); + if (fileArray == null) { + return Collections.emptyIterator(); + } + final Iterator files = Arrays.asList(fileArray).iterator(); return new Iterator() { private Event next = getNextEvent(files); From 996157967003a3c1cda3a77eaa76cb5a25d497c4 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 7 Oct 2019 13:20:01 -0400 Subject: [PATCH 2111/2152] feat: Milliseconds to breadcrumbs (#777) * resolves #776 --- .../main/java/io/sentry/marshaller/json/JsonMarshaller.java | 6 ++++-- .../java/io/sentry/marshaller/json/JsonMarshallerTest.java | 4 ++-- .../marshaller/json/jsonmarshallertest/testBreadcrumbs.json | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java b/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java index 146499dacac..37bef30e9e7 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/JsonMarshaller.java @@ -295,8 +295,10 @@ private void writeBreadcumbs(JsonGenerator generator, List breadcrum generator.writeArrayFieldStart("values"); for (Breadcrumb breadcrumb : breadcrumbs) { generator.writeStartObject(); - // getTime() returns ts in millis, but breadcrumbs expect seconds - generator.writeNumberField("timestamp", breadcrumb.getTimestamp().getTime() / 1000); + TimeZone tz = TimeZone.getTimeZone("UTC"); + DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + df.setTimeZone(tz); + generator.writeStringField("timestamp", df.format(breadcrumb.getTimestamp())); if (breadcrumb.getType() != null) { generator.writeStringField("type", breadcrumb.getType().getValue()); diff --git a/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java b/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java index 480461a6090..55698b63732 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/JsonMarshallerTest.java @@ -245,13 +245,13 @@ public void testEventBreadcrumbsWrittenProperly() throws Exception { final JsonOutputStreamParser jsonOutputStreamParser = newJsonOutputStream(); Breadcrumb breadcrumb1 = new BreadcrumbBuilder() - .setTimestamp(new Date(1463169342000L)) + .setTimestamp(new Date(1463169342123L)) .setLevel(Breadcrumb.Level.INFO) .setCategory("foo") .setMessage("test1") .build(); Breadcrumb breadcrumb2 = new BreadcrumbBuilder() - .setTimestamp(new Date(1463169343000L)) + .setTimestamp(new Date(1463169343111L)) .setLevel(Breadcrumb.Level.INFO) .setCategory("foo") .setMessage("test2") diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json index 237e982b23e..e772e3db559 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/jsonmarshallertest/testBreadcrumbs.json @@ -17,13 +17,13 @@ "breadcrumbs": { "values": [ { - "timestamp": 1463169342, + "timestamp": "2016-05-13T19:55:42.123Z", "level": "info", "message": "test1", "category": "foo" }, { - "timestamp": 1463169343, + "timestamp": "2016-05-13T19:55:43.111Z", "level": "info", "message": "test2", "category": "foo" From a5b4fd12b65f46a593f0f8317dbfad4aac95097b Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 8 Oct 2019 05:50:18 -0400 Subject: [PATCH 2112/2152] fix: Use empty iterator android 19 (#778) --- sentry/src/main/java/io/sentry/buffer/DiskBuffer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java b/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java index 8f643c515df..6baab9fc34d 100644 --- a/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java +++ b/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java @@ -176,7 +176,7 @@ private Event getNextEvent(Iterator files) { public Iterator getEvents() { final File[] fileArray = bufferDir.listFiles(); if (fileArray == null) { - return Collections.emptyIterator(); + return Collections.emptyList().iterator(); } final Iterator files = Arrays.asList(fileArray).iterator(); From ee571cd618c7b3623fbad2f9e07e9b76f7cab868 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 14 Oct 2019 18:18:08 -0400 Subject: [PATCH 2113/2152] chore: Changes --- CHANGES | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 5aea395af22..a007a15f7af 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,12 @@ Version 1.7.28 -------------- -- +- Allow an implementation of Lookup to be provided #755 +- Don't attemp to send null events #769 +- Fix error calling getPackageInfo on Huawei devices #774 +- Fix bug where null return instead of file list #775 +- Add millisecond precision to breadcrumb #777 +- Jackson version bumped to 2.10.0 #771 Version 1.7.27 -------------- From c4f3dfa298b3e9153b87fa99af0bb6ae17445d31 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 14 Oct 2019 18:20:08 -0400 Subject: [PATCH 2114/2152] [maven-release-plugin] prepare release v1.7.28 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index e11e9873b19..a3f1754e663 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.28-SNAPSHOT + 1.7.28 pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.28 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 012746d9760..46a97db3c62 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.28-SNAPSHOT + 1.7.28 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index f5f26452b9b..91aae42ce1b 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.28-SNAPSHOT + 1.7.28 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 1e5e5ceb42e..656fd16785d 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.28-SNAPSHOT + 1.7.28 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index e826b904d29..a684fd03fe1 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.28-SNAPSHOT + 1.7.28 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index d56eeb4a9e7..41d7dd429fb 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.28-SNAPSHOT + 1.7.28 sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index a023cb3031e..13bb1e7cada 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.28-SNAPSHOT + 1.7.28 sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.28-SNAPSHOT + 1.7.28 diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index e12c736a06f..1f9e9ccc419 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.28-SNAPSHOT + 1.7.28 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index f1bb71d690a..da800bd2048 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.28-SNAPSHOT + 1.7.28 sentry From edc2e5543eb46675668775b91ba1c7b60fa8195d Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 14 Oct 2019 18:20:08 -0400 Subject: [PATCH 2115/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index a3f1754e663..796528d663c 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.28 + 1.7.29-SNAPSHOT pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.28 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 46a97db3c62..194928878fa 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.28 + 1.7.29-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 91aae42ce1b..47da1eac13d 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.28 + 1.7.29-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 656fd16785d..a80b7256627 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.28 + 1.7.29-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index a684fd03fe1..1fbde706b09 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.28 + 1.7.29-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 41d7dd429fb..85ecaf839b0 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.28 + 1.7.29-SNAPSHOT sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 13bb1e7cada..38dd8a021dd 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.28 + 1.7.29-SNAPSHOT sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.28 + 1.7.29-SNAPSHOT diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 1f9e9ccc419..a87668dc67c 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.28 + 1.7.29-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index da800bd2048..ce4087fc0d2 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.28 + 1.7.29-SNAPSHOT sentry From 27563f8d04801c2eaa4c9c93ff82a3ce2fa99187 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 14 Oct 2019 18:20:10 -0400 Subject: [PATCH 2116/2152] Bump CHANGES to 1.7.29 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index a007a15f7af..7ed79aa4978 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +-e Version 1.7.29 +-------------- + +- + Version 1.7.28 -------------- From fa62925c72f4d3fea5831e9a03bfef2d8bc0a868 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 14 Oct 2019 18:48:28 -0400 Subject: [PATCH 2117/2152] chore; bump sentry-cli --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 42e60c80f30..e485ea93944 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.47.1 +VERSION=1.48.0 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* From d6b5363dc0497e0e8bbfe6db17ef8ab2294367bf Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 14 Oct 2019 18:56:41 -0400 Subject: [PATCH 2118/2152] chore: release 1.28 --- CHANGES | 2 +- sentry-android-gradle-plugin/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 7ed79aa4978..ebd1034e568 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,4 @@ --e Version 1.7.29 +Version 1.7.29 -------------- - diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index ddba3f74f6a..d005aa11bb7 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.28-SNAPSHOT +version = 1.7.29-SNAPSHOT From a8eef94ba510c43d4f0941a3eb096f814e371190 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 22 Oct 2019 16:25:54 -0400 Subject: [PATCH 2119/2152] Add codeowners to get assigned automatically --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000000..2350cdd0e53 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @bruno-garcia From 3bf2fae9e92e9f9e144ca8be508faf477435634e Mon Sep 17 00:00:00 2001 From: JGA Date: Wed, 30 Oct 2019 18:58:55 +0100 Subject: [PATCH 2120/2152] Add method to check if Sentry is already initializated #783 (#784) * Add method to check if Sentry is already initializated #783 * Improve comment detail for initialize method --- sentry/src/main/java/io/sentry/Sentry.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 2865c40a5d7..10244cff50a 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -143,6 +143,18 @@ public static SentryClient init(SentryOptions sentryOptions) { return client; } + /** + * Returns {@code true} if the Sentry object has been already initialized. + *

    + * Note that this method will return true even when {@link SentryClient} was disabled passing an empty value + * as {@link Dsn} + * + * @return {@code true} if stored {@link SentryClient} is not null + */ + public static boolean isInitialized() { + return storedClient != null; + } + /** * Returns the last statically stored {@link SentryClient} instance. If no instance * is already stored, an attempt will be made to create a {@link SentryClient} from the configuration @@ -152,7 +164,7 @@ public static SentryClient init(SentryOptions sentryOptions) { */ public static SentryClient getStoredClient() { synchronized (STORED_CLIENT_ACCESS) { - if (storedClient != null) { + if (isInitialized()) { return storedClient; } @@ -197,7 +209,7 @@ public static void clearContext() { */ public static void setStoredClient(SentryClient client) { synchronized (STORED_CLIENT_ACCESS) { - if (storedClient != null) { + if (isInitialized()) { logger.warn("Overwriting statically stored SentryClient instance {} with {}.", storedClient, client); } @@ -274,7 +286,7 @@ public static void setUser(User user) { */ public static void close() { synchronized (STORED_CLIENT_ACCESS) { - if (storedClient == null) { + if (!isInitialized()) { return; } From 961cc3fbaac7cd8e4da130df1888870d0f4f8b83 Mon Sep 17 00:00:00 2001 From: Alexey Zhokhov Date: Mon, 11 Nov 2019 00:25:05 +0800 Subject: [PATCH 2121/2152] Added ability to init SentryClient use Spring Boot auto-configuration (#779) --- sentry-spring-boot-starter/pom.xml | 5 + .../SentryAutoConfiguration.java | 103 +++- .../autoconfigure/SentryProperties.java | 466 ++++++++++++++++++ .../SpringBootConfigurationProvider.java | 74 +++ ...itional-spring-configuration-metadata.json | 6 + .../SentryAutoConfigurationTest.java | 139 ++++-- .../main/java/io/sentry/config/Lookup.java | 62 ++- .../ResourceLoaderConfigurationProvider.java | 19 +- 8 files changed, 814 insertions(+), 60 deletions(-) create mode 100644 sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryProperties.java create mode 100644 sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SpringBootConfigurationProvider.java diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 38dd8a021dd..9ed1721a4a5 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -32,6 +32,11 @@ spring-boot-autoconfigure-processor true + + org.springframework.boot + spring-boot-configuration-processor + true + org.springframework.boot diff --git a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryAutoConfiguration.java b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryAutoConfiguration.java index 336f54163a4..4f7de43f622 100644 --- a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryAutoConfiguration.java +++ b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryAutoConfiguration.java @@ -1,29 +1,45 @@ package io.sentry.spring.autoconfigure; +import io.sentry.Sentry; +import io.sentry.SentryClient; +import io.sentry.SentryOptions; +import io.sentry.config.Lookup; +import io.sentry.config.provider.ConfigurationProvider; +import io.sentry.connection.EventSendCallback; +import io.sentry.event.helper.EventBuilderHelper; +import io.sentry.event.helper.ShouldSendEventCallback; import io.sentry.spring.SentryExceptionResolver; import io.sentry.spring.SentryServletContextInitializer; - +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; import org.springframework.web.servlet.HandlerExceptionResolver; +import java.util.Collections; +import java.util.List; +import java.util.Map; + /** * Spring Auto Configuration for Sentry. */ @Configuration -@ConditionalOnClass({ HandlerExceptionResolver.class, SentryExceptionResolver.class }) +@ConditionalOnClass({HandlerExceptionResolver.class, SentryExceptionResolver.class}) +@EnableConfigurationProperties(SentryProperties.class) @ConditionalOnWebApplication @ConditionalOnProperty(name = "sentry.enabled", havingValue = "true", matchIfMissing = true) public class SentryAutoConfiguration { /** * Resolves a {@link HandlerExceptionResolver}. - * @return a new instance of {@link SentryAutoConfiguration}. + * + * @return a new instance of {@link HandlerExceptionResolver}. */ @Bean @ConditionalOnMissingBean(SentryExceptionResolver.class) @@ -33,6 +49,7 @@ public HandlerExceptionResolver sentryExceptionResolver() { /** * Initializes a {@link ServletContextInitializer}. + * * @return a new instance of {@link SentryServletContextInitializer}. */ @Bean @@ -41,4 +58,84 @@ public ServletContextInitializer sentryServletContextInitializer() { return new SentryServletContextInitializer(); } + /** + * Initializes a {@link SentryClient}. + * + * @return a new instance of {@link SentryClient}. + */ + @Bean + @ConditionalOnMissingBean(SentryClient.class) + @ConditionalOnProperty(name = "sentry.init-default-client", havingValue = "true", matchIfMissing = true) + public SentryClient sentryClient(SentryProperties properties, + @Autowired(required = false) List eventBuilderHelpers, + @Autowired(required = false) List eventSendCallbacks, + @Autowired(required = false) List shouldSendEventCallbacks) { + String dsn = properties.getDsn() != null ? properties.getDsn().toString() : null; + + SentryOptions sentryOptions = SentryOptions.from(createLookup(properties), dsn, null); + + SentryClient sentryClient = Sentry.init(sentryOptions); + + if (!StringUtils.isEmpty(properties.getRelease())) { + sentryClient.setRelease(properties.getRelease()); + } + + if (!StringUtils.isEmpty(properties.getDist())) { + sentryClient.setDist(properties.getDist()); + } + + if (!StringUtils.isEmpty(properties.getEnvironment())) { + sentryClient.setEnvironment(properties.getEnvironment()); + } + + if (!StringUtils.isEmpty(properties.getServerName())) { + sentryClient.setServerName(properties.getServerName()); + } + + if (properties.getTags() != null && !properties.getTags().isEmpty()) { + for (Map.Entry tag : properties.getTags().entrySet()) { + sentryClient.addTag(tag.getKey(), tag.getValue()); + } + } + + if (properties.getMdcTags() != null && !properties.getMdcTags().isEmpty()) { + for (String mdcTag : properties.getMdcTags()) { + sentryClient.addMdcTag(mdcTag); + } + } + + if (properties.getExtra() != null && !properties.getExtra().isEmpty()) { + for (Map.Entry extra : properties.getExtra().entrySet()) { + sentryClient.addExtra(extra.getKey(), extra.getValue()); + } + } + + if (eventBuilderHelpers != null && !eventBuilderHelpers.isEmpty()) { + for (EventBuilderHelper eventBuilderHelper : eventBuilderHelpers) { + sentryClient.addBuilderHelper(eventBuilderHelper); + } + } + + if (eventSendCallbacks != null && !eventSendCallbacks.isEmpty()) { + for (EventSendCallback eventSendCallback : eventSendCallbacks) { + sentryClient.addEventSendCallback(eventSendCallback); + } + } + + if (shouldSendEventCallbacks != null && !shouldSendEventCallbacks.isEmpty()) { + for (ShouldSendEventCallback shouldSendEventCallback : shouldSendEventCallbacks) { + sentryClient.addShouldSendEventCallback(shouldSendEventCallback); + } + } + + return sentryClient; + } + + private Lookup createLookup(SentryProperties properties) { + return Lookup.getDefaultWithAdditionalProviders( + Collections.singletonList(new SpringBootConfigurationProvider(properties)), + Collections.emptyList() + ); + } + } diff --git a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryProperties.java b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryProperties.java new file mode 100644 index 00000000000..56961352e53 --- /dev/null +++ b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryProperties.java @@ -0,0 +1,466 @@ +package io.sentry.spring.autoconfigure; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.net.URL; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +/** + * Configuration for Sentry, example: + * + *

    + * sentry:
    + *     enabled: true
    + *     init-default-client: true
    + *     dsn: https://00059966e6224d03a77ea5eca10fbe18@sentry.mycompany.com/14
    + *     release: "1.0.1"
    + *     dist: x86
    + *     environment: staging
    + *     server-name: megaServer
    + *     tags:
    + *         firstTag: Hello
    + *         secondTag: Awesome
    + *     mdc-tags: [mdcTagA, mdcTagB]
    + *     extra:
    + *         extraTag: extra
    + * 
    + */ +@ConfigurationProperties("sentry") +public class SentryProperties { + + /** + * Whether to enable sentry. + */ + private boolean enabled = true; + + /** + * Whether to initialize Sentry Client as a bean. + */ + private boolean initDefaultClient = true; + + /** + * Data source name + * All of the options can be configured by setting querystring parameters on the DSN itself. + * https://docs.sentry.io/clients/java/config/#configuration-via-the-dsn + * More information about configuration via DSN https://docs.sentry.io/clients/java/config/#configuration-via-the-dsn + */ + private URL dsn; + + /** + * The application version that will be sent with each event. + */ + private String release; + + /** + * The application distribution that will be sent with each event. + * Note that the distribution is only useful (and used) if the release is also set. + */ + private String dist; + + /** + * The application environment that will be sent with each event. + */ + private String environment; + + /** + * The server name that will be sent with each event. + */ + private String serverName; + + /** + * Tags that will be sent with each event. + */ + private Map tags = new LinkedHashMap<>(); + + /** + * Set tag names that are extracted from the SLF4J MDC system. + */ + private Set mdcTags = new HashSet<>(); + + /** + * Set extra data that will be sent with each event (but not as tags). + */ + private Map extra = new LinkedHashMap<>(); + + /** + * By default the content sent to Sentry is compressed before being sent. However, compressing and encoding the + * data adds a small CPU and memory hit which might not be useful if the connection to Sentry is fast and reliable. + * Depending on the limitations of the project (e.g. a mobile application with a limited connection, Sentry hosted + * on an external network), it can be useful to compress the data beforehand or not. + */ + private Boolean compression; + + /** + * By default only the first 1000 characters of a message will be sent to the server. + */ + private Integer maxMessageLength; + + /** + * A timeout is set to avoid blocking Sentry threads because establishing a connection is taking too long. + */ + private Integer timeout; + + /** + * Sentry can be configured to sample events. + * This option takes a number from 0.0 to 1.0, representing the percent of events to allow through to server + * (from 0% to 100%). By default all events will be sent to the Sentry server. + */ + private Double sampleRate; + + /** + * By default, an UncaughtExceptionHandler is configured that will attempt to send exceptions to Sentry. + * Exceptions are sent asynchronously by default, and there is no guarantee they will be sent before the JVM exits. + * This option is best used in conjunction with the disk buffering system. + */ + private Boolean uncaughtHandlerEnabled; + + private final Stacktrace stacktrace = new Stacktrace(); + private final Buffer buffer = new Buffer(); + private final Async async = new Async(); + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isInitDefaultClient() { + return initDefaultClient; + } + + public void setInitDefaultClient(boolean initDefaultClient) { + this.initDefaultClient = initDefaultClient; + } + + public URL getDsn() { + return dsn; + } + + public void setDsn(URL dsn) { + this.dsn = dsn; + } + + public String getRelease() { + return release; + } + + public void setRelease(String release) { + this.release = release; + } + + public String getDist() { + return dist; + } + + public void setDist(String dist) { + this.dist = dist; + } + + public String getEnvironment() { + return environment; + } + + public void setEnvironment(String environment) { + this.environment = environment; + } + + public String getServerName() { + return serverName; + } + + public void setServerName(String serverName) { + this.serverName = serverName; + } + + public Map getTags() { + return tags; + } + + public void setTags(Map tags) { + this.tags = tags; + } + + public Set getMdcTags() { + return mdcTags; + } + + public void setMdcTags(Set mdcTags) { + this.mdcTags = mdcTags; + } + + public Map getExtra() { + return extra; + } + + public void setExtra(Map extra) { + this.extra = extra; + } + + public Boolean getCompression() { + return compression; + } + + public void setCompression(Boolean compression) { + this.compression = compression; + } + + public Integer getMaxMessageLength() { + return maxMessageLength; + } + + public void setMaxMessageLength(Integer maxMessageLength) { + this.maxMessageLength = maxMessageLength; + } + + public Integer getTimeout() { + return timeout; + } + + public void setTimeout(Integer timeout) { + this.timeout = timeout; + } + + public Double getSampleRate() { + return sampleRate; + } + + public void setSampleRate(Double sampleRate) { + this.sampleRate = sampleRate; + } + + public Boolean getUncaughtHandlerEnabled() { + return uncaughtHandlerEnabled; + } + + public void setUncaughtHandlerEnabled(Boolean uncaughtHandlerEnabled) { + this.uncaughtHandlerEnabled = uncaughtHandlerEnabled; + } + + public Stacktrace getStacktrace() { + return stacktrace; + } + + public Buffer getBuffer() { + return buffer; + } + + public Async getAsync() { + return async; + } + + public static class Stacktrace { + + /** + * Some frames are replaced by the ... N more line as they are the same frames as in the enclosing exception. + *

    + * Similar behaviour is enabled by default in Sentry. + */ + private Boolean hideCommon; + + /** + * Configure which package prefixes your application uses. + */ + private Set appPackages; + + public Boolean getHideCommon() { + return hideCommon; + } + + public void setHideCommon(Boolean hideCommon) { + this.hideCommon = hideCommon; + } + + public Set getAppPackages() { + return appPackages; + } + + public void setAppPackages(Set appPackages) { + this.appPackages = appPackages; + } + + } + + public static class Buffer { + + /** + * Sentry can be configured to write events to a specified directory on disk anytime communication with the Sentry + * server fails. If the directory doesn’t exist, Sentry will attempt to create it on startup and may therefore + * need write permission on the parent directory. Sentry always requires write permission on the buffer + * directory itself. + */ + private String dir; + + /** + * The maximum number of events that will be stored on disk defaults to 10. + */ + private Integer size; + + /** + * If a buffer directory is provided, a background thread will periodically attempt to re-send the events that + * are found on disk. By default it will attempt to send events every 60 seconds. + */ + private Integer flushTime; + + /** + * In order to shutdown the buffer flushing thread gracefully, a ShutdownHook is created. By default, the buffer + * flushing thread is given 1 second to shutdown gracefully. + * The special value -1 can be used to disable the timeout and wait indefinitely for the executor to terminate. + */ + private Integer shutdownTimeout; + + /** + * The ShutdownHook could lead to memory leaks in an environment where the life cycle of Sentry doesn’t match + * the life cycle of the JVM. + * An example would be in a JEE environment where the application using Sentry could be deployed and undeployed + * regularly. + * To avoid this behaviour, it is possible to disable the graceful shutdown. + */ + private Boolean gracefulShutdown; + + public String getDir() { + return dir; + } + + public void setDir(String dir) { + this.dir = dir; + } + + public Integer getSize() { + return size; + } + + public void setSize(Integer size) { + this.size = size; + } + + public Integer getFlushTime() { + return flushTime; + } + + public void setFlushTime(Integer flushTime) { + this.flushTime = flushTime; + } + + public Integer getShutdownTimeout() { + return shutdownTimeout; + } + + public void setShutdownTimeout(Integer shutdownTimeout) { + this.shutdownTimeout = shutdownTimeout; + } + + public Boolean getGracefulShutdown() { + return gracefulShutdown; + } + + public void setGracefulShutdown(Boolean gracefulShutdown) { + this.gracefulShutdown = gracefulShutdown; + } + + } + + public static class Async { + + /** + * In order to avoid performance issues due to a large amount of logs being generated or a slow connection to + * the Sentry server, an asynchronous connection is set up, using a low priority thread pool to submit events + * to Sentry. + */ + private Boolean enabled; + + /** + * In order to shutdown the asynchronous connection gracefully, a ShutdownHook is created. By default, the + * asynchronous connection is given 1 second to shutdown gracefully. + * The special value -1 can be used to disable the timeout and wait indefinitely for the executor to terminate. + */ + private Integer shutdownTimeout; + + /** + * The ShutdownHook could lead to memory leaks in an environment where the life cycle of Sentry doesn’t match + * the life cycle of the JVM. + * An example would be in a JEE environment where the application using Sentry could be deployed and undeployed + * regularly. + * To avoid this behaviour, it is possible to disable the graceful shutdown. This might lead to some log entries + * being lost if the log application doesn’t shut down the SentryClient instance nicely. + */ + private Boolean gracefulShutdown; + + /** + * The default queue used to store unprocessed events is limited to 50 items. Additional items added once the + * queue is full are dropped and never sent to the Sentry server. Depending on the environment (if the memory + * is sparse) it is important to be able to control the size of that queue to avoid memory issues. + * This means that if the connection to the Sentry server is down, only the 100 most recent events will be + * stored and processed as soon as the server is back up. + * The special value -1 can be used to enable an unlimited queue. Beware that network connectivity or Sentry + * server issues could mean your process will run out of memory. + */ + private Integer queueSize; + + /** + * By default the thread pool used by the async connection contains one thread per processor available to the JVM. + * It’s possible to manually set the number of threads. + */ + private Integer threads; + + /** + * In most cases sending logs to Sentry isn’t as important as an application running smoothly, so the threads + * have a minimal priority: https://docs.oracle.com/javase/6/docs/api/java/lang/Thread.html#MIN_PRIORITY. + */ + private Integer priority; + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public Integer getShutdownTimeout() { + return shutdownTimeout; + } + + public void setShutdownTimeout(Integer shutdownTimeout) { + this.shutdownTimeout = shutdownTimeout; + } + + public Boolean getGracefulShutdown() { + return gracefulShutdown; + } + + public void setGracefulShutdown(Boolean gracefulShutdown) { + this.gracefulShutdown = gracefulShutdown; + } + + public Integer getQueueSize() { + return queueSize; + } + + public void setQueueSize(Integer queueSize) { + this.queueSize = queueSize; + } + + public Integer getThreads() { + return threads; + } + + public void setThreads(Integer threads) { + this.threads = threads; + } + + public Integer getPriority() { + return priority; + } + + public void setPriority(Integer priority) { + this.priority = priority; + } + + } + +} diff --git a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SpringBootConfigurationProvider.java b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SpringBootConfigurationProvider.java new file mode 100644 index 00000000000..dd4316173dd --- /dev/null +++ b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SpringBootConfigurationProvider.java @@ -0,0 +1,74 @@ +package io.sentry.spring.autoconfigure; + +import io.sentry.DefaultSentryClientFactory; +import io.sentry.config.provider.ConfigurationProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SpringBootConfigurationProvider implements ConfigurationProvider { + + private static final Logger logger = LoggerFactory.getLogger(SpringBootConfigurationProvider.class); + + private final SentryProperties sentryProperties; + + public SpringBootConfigurationProvider(SentryProperties sentryProperties) { + this.sentryProperties = sentryProperties; + } + + @Override + public String getProperty(String key) { + switch (key) { + case DefaultSentryClientFactory.COMPRESSION_OPTION: + return logAndReturnIfSet(key, sentryProperties.getCompression()); + case DefaultSentryClientFactory.MAX_MESSAGE_LENGTH_OPTION: + return logAndReturnIfSet(key, sentryProperties.getMaxMessageLength()); + case DefaultSentryClientFactory.CONNECTION_TIMEOUT_OPTION: + return logAndReturnIfSet(key, sentryProperties.getTimeout()); + case DefaultSentryClientFactory.SAMPLE_RATE_OPTION: + return logAndReturnIfSet(key, sentryProperties.getSampleRate()); + case DefaultSentryClientFactory.UNCAUGHT_HANDLER_ENABLED_OPTION: + return logAndReturnIfSet(key, sentryProperties.getUncaughtHandlerEnabled()); + case DefaultSentryClientFactory.HIDE_COMMON_FRAMES_OPTION: + return logAndReturnIfSet(key, sentryProperties.getStacktrace().getHideCommon()); + case DefaultSentryClientFactory.IN_APP_FRAMES_OPTION: + return logAndReturnIfSet(key, sentryProperties.getStacktrace().getAppPackages()); + case DefaultSentryClientFactory.BUFFER_DIR_OPTION: + return logAndReturnIfSet(key, sentryProperties.getBuffer().getDir()); + case DefaultSentryClientFactory.BUFFER_SIZE_OPTION: + return logAndReturnIfSet(key, sentryProperties.getBuffer().getSize()); + case DefaultSentryClientFactory.BUFFER_FLUSHTIME_OPTION: + return logAndReturnIfSet(key, sentryProperties.getBuffer().getFlushTime()); + case DefaultSentryClientFactory.BUFFER_SHUTDOWN_TIMEOUT_OPTION: + return logAndReturnIfSet(key, sentryProperties.getBuffer().getShutdownTimeout()); + case DefaultSentryClientFactory.BUFFER_GRACEFUL_SHUTDOWN_OPTION: + return logAndReturnIfSet(key, sentryProperties.getBuffer().getGracefulShutdown()); + case DefaultSentryClientFactory.ASYNC_OPTION: + return logAndReturnIfSet(key, sentryProperties.getAsync().getEnabled()); + case DefaultSentryClientFactory.ASYNC_SHUTDOWN_TIMEOUT_OPTION: + return logAndReturnIfSet(key, sentryProperties.getAsync().getShutdownTimeout()); + case DefaultSentryClientFactory.ASYNC_GRACEFUL_SHUTDOWN_OPTION: + return logAndReturnIfSet(key, sentryProperties.getAsync().getGracefulShutdown()); + case DefaultSentryClientFactory.ASYNC_QUEUE_SIZE_OPTION: + return logAndReturnIfSet(key, sentryProperties.getAsync().getQueueSize()); + case DefaultSentryClientFactory.ASYNC_THREADS_OPTION: + return logAndReturnIfSet(key, sentryProperties.getAsync().getThreads()); + case DefaultSentryClientFactory.ASYNC_PRIORITY_OPTION: + return logAndReturnIfSet(key, sentryProperties.getAsync().getPriority()); + default: + logger.debug("Unsupported option: {}", key); + return null; + } + } + + private String logAndReturnIfSet(String key, Object value) { + if (value == null) + return null; + + String ret = value.toString(); + + logger.debug("Found {}={} in Spring Boot config.", key, ret); + + return ret; + } + +} diff --git a/sentry-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/sentry-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 6a21f452e32..930d1e35896 100644 --- a/sentry-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/sentry-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -5,6 +5,12 @@ "type": "java.lang.Boolean", "description": "Whether to enable sentry.", "defaultValue": "true" + }, + { + "name": "sentry.init-default-client", + "type": "java.lang.Boolean", + "description": "Whether to initialize Sentry Client as a bean.", + "defaultValue": "true" } ] } diff --git a/sentry-spring-boot-starter/src/test/java/io/sentry/spring/autoconfigure/SentryAutoConfigurationTest.java b/sentry-spring-boot-starter/src/test/java/io/sentry/spring/autoconfigure/SentryAutoConfigurationTest.java index 758ba580c1e..7a63ac74540 100644 --- a/sentry-spring-boot-starter/src/test/java/io/sentry/spring/autoconfigure/SentryAutoConfigurationTest.java +++ b/sentry-spring-boot-starter/src/test/java/io/sentry/spring/autoconfigure/SentryAutoConfigurationTest.java @@ -1,11 +1,11 @@ package io.sentry.spring.autoconfigure; +import io.sentry.SentryClient; import io.sentry.spring.SentryExceptionResolver; import io.sentry.spring.SentryServletContextInitializer; import org.junit.After; import org.junit.Before; import org.junit.Test; - import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; @@ -16,50 +16,97 @@ public class SentryAutoConfigurationTest { - private AnnotationConfigEmbeddedWebApplicationContext context; - - @Before - public void setup() { - if (this.context == null) { - this.context = new AnnotationConfigEmbeddedWebApplicationContext(); - } - } - - @After - public void teardown() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void testSentryExceptionResolverIsAvailable() { - load(); - this.context.refresh(); - String[] exceptionResolverBeans = this.context - .getBeanNamesForType(SentryExceptionResolver.class); - String[] servletContextInitialerBeans = this.context - .getBeanNamesForType(SentryServletContextInitializer.class); - - assertThat(exceptionResolverBeans).contains("sentryExceptionResolver"); - assertThat(servletContextInitialerBeans) - .contains("sentryServletContextInitializer"); - } - - @Test - public void testSentryAutoConfigurationIsEnabled() { - load(); - EnvironmentTestUtils.addEnvironment(this.context, "sentry.enabled:false"); - this.context.refresh(); - String[] beans = this.context.getBeanNamesForType(SentryExceptionResolver.class); - assertThat(beans).isEmpty(); - } - - private void load() { - this.context.register(EmbeddedServletContainerAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, - SentryAutoConfiguration.class); - } + private AnnotationConfigEmbeddedWebApplicationContext context; + + @Before + public void setup() { + if (this.context == null) { + this.context = new AnnotationConfigEmbeddedWebApplicationContext(); + } + } + + @After + public void teardown() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void testSentryDefaultAutoConfiguration() { + load(); + this.context.refresh(); + String[] exceptionResolverBeans = this.context + .getBeanNamesForType(SentryExceptionResolver.class); + String[] servletContextInitialerBeans = this.context + .getBeanNamesForType(SentryServletContextInitializer.class); + String[] sentryClientBeans = this.context + .getBeanNamesForType(SentryClient.class); + + assertThat(exceptionResolverBeans).contains("sentryExceptionResolver"); + assertThat(servletContextInitialerBeans).contains("sentryServletContextInitializer"); + assertThat(sentryClientBeans).contains("sentryClient"); + } + + @Test + public void testSentryAutoConfigurationIsEnabled() { + load(); + EnvironmentTestUtils.addEnvironment(this.context, "sentry.enabled:false"); + this.context.refresh(); + + String[] beans = this.context.getBeanNamesForType(SentryExceptionResolver.class); + assertThat(beans).isEmpty(); + + beans = this.context.getBeanNamesForType(SentryClient.class); + assertThat(beans).isEmpty(); + } + + @Test + public void testSentryClientIsDisabled() { + load(); + EnvironmentTestUtils.addEnvironment(this.context, "sentry.init-default-client:false"); + this.context.refresh(); + + String[] beans = this.context.getBeanNamesForType(SentryExceptionResolver.class); + assertThat(beans).isNotEmpty(); + + beans = this.context.getBeanNamesForType(SentryClient.class); + assertThat(beans).isEmpty(); + } + + @Test + public void testSentryClientConfig() { + load(); + EnvironmentTestUtils.addEnvironment(this.context, + "sentry.dsn:https://00059966e6224d03a77ea5eca10fbe18@sentry.mycompany.com/14", + "sentry.release:1.0.1", + "sentry.dist:x86", + "sentry.environment:staging", + "sentry.server-name:megaServer", + "sentry.tags.firstTag:Hello", + "sentry.mdc-tags:mdcTagA", + "sentry.extra.extraTag:extra"); + this.context.refresh(); + + String[] beans = this.context.getBeanNamesForType(SentryClient.class); + assertThat(beans).isNotEmpty(); + + SentryClient sentryClient = this.context.getBean(SentryClient.class); + + assertThat(sentryClient.getRelease()).isEqualTo("1.0.1"); + assertThat(sentryClient.getDist()).isEqualTo("x86"); + assertThat(sentryClient.getEnvironment()).isEqualTo("staging"); + assertThat(sentryClient.getServerName()).isEqualTo("megaServer"); + assertThat(sentryClient.getTags()).isNotEmpty().containsKey("firstTag"); + assertThat(sentryClient.getMdcTags()).isNotEmpty().contains("mdcTagA"); + assertThat(sentryClient.getExtra()).isNotEmpty().containsKey("extraTag"); + } + + private void load() { + this.context.register(EmbeddedServletContainerAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, + SentryAutoConfiguration.class); + } } diff --git a/sentry/src/main/java/io/sentry/config/Lookup.java b/sentry/src/main/java/io/sentry/config/Lookup.java index 42f942b1bd2..0c93348b10d 100644 --- a/sentry/src/main/java/io/sentry/config/Lookup.java +++ b/sentry/src/main/java/io/sentry/config/Lookup.java @@ -1,13 +1,13 @@ package io.sentry.config; import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; import io.sentry.Sentry; @@ -69,19 +69,56 @@ public Lookup(ConfigurationProvider highPriorityProvider, ConfigurationProvider * @return the default lookup instance */ public static Lookup getDefault() { - return new Lookup(new CompoundConfigurationProvider(getDefaultHighPriorityConfigurationProviders()), - new CompoundConfigurationProvider(getDefaultLowPriorityConfigurationProviders())); + return new Lookup( + new CompoundConfigurationProvider( + getDefaultHighPriorityConfigurationProviders(Collections.emptyList()) + ), + new CompoundConfigurationProvider( + getDefaultLowPriorityConfigurationProviders(Collections.emptyList()) + ) + ); + } + + /** + * In the default lookup returned from this method, the configuration properties are looked up in the sources in the + * following order. + * + *

      + *
    1. Additional high priority providers
    2. + *
    3. JNDI, if available + *
    4. Java System Properties + *
    5. System Environment Variables + *
    6. Additional low priority providers
    7. + *
    8. DSN options, if a non-null DSN is provided + *
    9. Sentry properties file found in resources + *
    + * + * @param highPriorityProviders the list of providers with high priority + * @param lowPriorityProviders the list of providers with low priority + * @return the default lookup instance + */ + public static Lookup getDefaultWithAdditionalProviders(Collection highPriorityProviders, + Collection lowPriorityProviders) { + return new Lookup( + new CompoundConfigurationProvider(getDefaultHighPriorityConfigurationProviders(highPriorityProviders)), + new CompoundConfigurationProvider(getDefaultLowPriorityConfigurationProviders(lowPriorityProviders)) + ); } private static List getDefaultResourceLocators() { return asList(new SystemPropertiesBasedLocator(), new EnvironmentBasedLocator(), new StaticFileLocator()); } - private static List getDefaultHighPriorityConfigurationProviders() { + private static List getDefaultHighPriorityConfigurationProviders( + Collection additionalProviders + ) { boolean jndiPresent = JndiSupport.isAvailable(); @SuppressWarnings("checkstyle:MagicNumber") - List providers = new ArrayList<>(jndiPresent ? 3 : 2); + int providersCount = jndiPresent ? 3 + additionalProviders.size() : 2 + additionalProviders.size(); + + List providers = new ArrayList<>(providersCount); + providers.addAll(additionalProviders); if (jndiPresent) { providers.add(new JndiConfigurationProvider()); @@ -101,15 +138,20 @@ private static List getDefaultResourceLoaders() { : Arrays.asList(new FileResourceLoader(), sentryLoader, new ContextClassLoaderResourceLoader()); } - private static List getDefaultLowPriorityConfigurationProviders() { + private static List getDefaultLowPriorityConfigurationProviders( + Collection additionalProviders + ) { + + List providers = new ArrayList<>(additionalProviders.size()); + providers.addAll(additionalProviders); + try { - return singletonList((ConfigurationProvider) - new LocatorBasedConfigurationProvider(new CompoundResourceLoader(getDefaultResourceLoaders()), + providers.add(new LocatorBasedConfigurationProvider(new CompoundResourceLoader(getDefaultResourceLoaders()), new CompoundResourceLocator(getDefaultResourceLocators()), Charset.defaultCharset())); } catch (IOException e) { logger.debug("Failed to instantiate resource locator-based configuration provider.", e); - return emptyList(); } + return providers; } diff --git a/sentry/src/main/java/io/sentry/config/provider/ResourceLoaderConfigurationProvider.java b/sentry/src/main/java/io/sentry/config/provider/ResourceLoaderConfigurationProvider.java index 1b74996a07a..7d7e0e4b6b9 100644 --- a/sentry/src/main/java/io/sentry/config/provider/ResourceLoaderConfigurationProvider.java +++ b/sentry/src/main/java/io/sentry/config/provider/ResourceLoaderConfigurationProvider.java @@ -9,10 +9,16 @@ import io.sentry.config.ResourceLoader; import io.sentry.util.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * A configuration provider that loads the properties from a {@link ResourceLoader}. */ public class ResourceLoaderConfigurationProvider implements ConfigurationProvider { + + private static final Logger logger = LoggerFactory.getLogger(ResourceLoaderConfigurationProvider.class); + @Nullable private final Properties properties; @@ -50,6 +56,17 @@ private static Properties loadProperties(ResourceLoader rl, @Nullable String fil @Override public String getProperty(String key) { - return properties == null ? null : properties.getProperty(key); + if (properties == null) { + return null; + } + + String ret = properties.getProperty(key); + + if (ret != null) { + logger.debug("Found {}={} in properties file.", key, ret); + } + + return ret; } + } From 4a7e5b092dbfc90af78962fd4e3c33e34c76c7ea Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Sun, 10 Nov 2019 13:13:52 -0500 Subject: [PATCH 2122/2152] fix(proguard): Keep exception type names (#790) --- .../io/sentry/android/gradle/SentryProguardConfigTask.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy index 0cd7a358df0..62835d7a399 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy @@ -23,6 +23,7 @@ class SentryProguardConfigTask extends DefaultTask { "-dontwarn com.facebook.fbui.**\n" + "-dontwarn org.slf4j.**\n" + "-dontwarn javax.**\n" + + "-keep class * extends java.lang.Exception\n" + "-keep class io.sentry.event.Event { *; }\n") f.close() applicationVariant.getBuildType().buildType.proguardFiles(file) From ce1017316d714d9f7fbcd4e3d4aafff7f652b088 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Fri, 15 Nov 2019 20:57:31 -0500 Subject: [PATCH 2123/2152] doc; mention new Android SDK (#793) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1abf9498877..143db951e81 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ [![Travis](https://travis-ci.org/getsentry/sentry-java.svg?branch=master)](https://travis-ci.org/getsentry/sentry-java) +> Looking for Android? This repo has the stable, long term support Android SDK. Please note that Sentry has been working on a new [Sentry unified SDK](https://blog.sentry.io/2018/09/19/new-sdks-unified-python-javascript-dotnet-rust) with things like NDK support and much more. The first [preview is out, in a separate GitHub repository](https://github.com/getsentry/sentry-android/releases), published to bintray, jcenter and we're looking for feedback. + This is the Java SDK for [Sentry](https://sentry.io/). It provides out-of-the-box support for many popular JVM based frameworks and libraries, including Android, Log4j, Logback, and more. From ba3df8508e2ef1440d3c52bc98d287d4f7adc6ce Mon Sep 17 00:00:00 2001 From: James Zheng Date: Fri, 29 Nov 2019 04:37:41 +0800 Subject: [PATCH 2124/2152] Blacklist other frameworks using cglib dynamic classes, like Guice (#796) * Blacklist other frameworks using cglib dynamic classes, like Guice * Fix the checkstyle issue --- .../json/StackTraceInterfaceBinding.java | 7 +++++-- .../json/StackTraceInterfaceBindingTest.java | 8 +++++++- .../marshaller/json/StackTraceBlacklist.json | 14 ++++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java b/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java index 1ed507d45a9..ad7ca561c0b 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/StackTraceInterfaceBinding.java @@ -27,7 +27,8 @@ public class StackTraceInterfaceBinding implements InterfaceBinding inAppFrames = Collections.emptyList(); @@ -88,7 +89,9 @@ private boolean isFrameInApp(SentryStackTraceElement stackTraceElement) { } private boolean classIsBlacklisted(String className) { - return (className.contains("CGLIB") || className.contains("Hibernate")) + return (className.contains("$$EnhancerBy") + || className.contains("$$FastClassBy") + || className.contains("$Hibernate")) && IN_APP_BLACKLIST.matcher(className).find(); } diff --git a/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java b/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java index efbe04b6c0a..911bdf058a1 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/StackTraceInterfaceBindingTest.java @@ -91,7 +91,13 @@ public void testInAppFrames() throws Exception { "File.java", 4, null, null, null, null), new SentryStackTraceElement( "inAppModule.Blacklisted$$EnhancerBySpringCGLIB$$", "blacklisted", - "File.java", 5, null, null, null, null) + "File.java", 5, null, null, null, null), + new SentryStackTraceElement( + "inAppModule.Blacklisted$$EnhancerByGuice$$3e9263f5$$FastClassByGuice$$19497ba4", "blacklisted", + "File.java", 6, null, null, null, null), + new SentryStackTraceElement( + "inAppModule.Blacklisted$$EnhancerByGuice$$3e9263f5.CGLIB$", "blacklisted", + "File.java", 7, null, null, null, null) ); when(mockStackTraceInterface.getStackTrace()) diff --git a/sentry/src/test/resources/io/sentry/marshaller/json/StackTraceBlacklist.json b/sentry/src/test/resources/io/sentry/marshaller/json/StackTraceBlacklist.json index da15e286c3d..e3649d07479 100644 --- a/sentry/src/test/resources/io/sentry/marshaller/json/StackTraceBlacklist.json +++ b/sentry/src/test/resources/io/sentry/marshaller/json/StackTraceBlacklist.json @@ -1,5 +1,19 @@ { "frames": [ + { + "filename":"File.java", + "module":"inAppModule.Blacklisted$$EnhancerByGuice$$3e9263f5.CGLIB$", + "in_app":false, + "function":"blacklisted", + "lineno":7 + }, + { + "filename":"File.java", + "module":"inAppModule.Blacklisted$$EnhancerByGuice$$3e9263f5$$FastClassByGuice$$19497ba4", + "in_app":false, + "function":"blacklisted", + "lineno":6 + }, { "filename": "File.java", "module": "inAppModule.Blacklisted$$EnhancerBySpringCGLIB$$", From aaad3678cf7d885aa0c1f8f1353a0a732fe2d748 Mon Sep 17 00:00:00 2001 From: jeacott1 Date: Fri, 29 Nov 2019 07:09:39 +1030 Subject: [PATCH 2125/2152] adds support for logback encoders (the message field is encoded). (#794) adds support for global tags and extras. tags and extras are distinguished by the existing mdcTags property. basic usage: encoders ``` WARN ... ... ``` basic usage: global entries as tags or extras ``` LoggerContext context = (LoggerContext)LoggerFactory.getILoggerFactory(); context.putProperty("global", "value"); ``` --- .../io/sentry/logback/SentryAppender.java | 40 +++++++++++- .../SentryAppenderEventBuildingTest.java | 63 ++++++++++++++++++- 2 files changed, 100 insertions(+), 3 deletions(-) diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index 7c9f6d6cd59..30d2423b39b 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -5,6 +5,7 @@ import ch.qos.logback.classic.spi.IThrowableProxy; import ch.qos.logback.classic.spi.StackTraceElementProxy; import ch.qos.logback.core.UnsynchronizedAppenderBase; +import ch.qos.logback.core.encoder.Encoder; import ch.qos.logback.core.filter.Filter; import ch.qos.logback.core.spi.FilterReply; import io.sentry.Sentry; @@ -16,6 +17,7 @@ import io.sentry.event.interfaces.SentryException; import io.sentry.event.interfaces.StackTraceInterface; +import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Date; @@ -38,6 +40,12 @@ public class SentryAppender extends UnsynchronizedAppenderBase { * Name of the {@link Event#extra} property containing the Thread name. */ public static final String THREAD_NAME = "Sentry-Threadname"; + + /** + * Appender encoder. + */ + protected Encoder encoder; + /** * If set, only events with level = minLevel and up will be recorded. * @@ -91,6 +99,7 @@ protected static Event.Level formatLevel(Level level) { @Override protected void append(ILoggingEvent iLoggingEvent) { + // Do not log the event if the current thread is managed by sentry if (isNotLoggable(iLoggingEvent) || SentryEnvironment.isManagingThread()) { return; @@ -122,10 +131,19 @@ private boolean isNotLoggable(ILoggingEvent iLoggingEvent) { * @return EventBuilder containing details provided by the logging system. */ protected EventBuilder createEventBuilder(ILoggingEvent iLoggingEvent) { + final String formattedMessage; + + if (this.encoder != null) { + byte[] byteArray = this.encoder.encode(iLoggingEvent); + formattedMessage = new String(byteArray, StandardCharsets.UTF_8); + } else { + formattedMessage = iLoggingEvent.getFormattedMessage(); + } + EventBuilder eventBuilder = new EventBuilder() .withSdkIntegration("logback") .withTimestamp(new Date(iLoggingEvent.getTimeStamp())) - .withMessage(iLoggingEvent.getFormattedMessage()) + .withMessage(formattedMessage) .withLogger(iLoggingEvent.getLoggerName()) .withLevel(formatLevel(iLoggingEvent.getLevel())) .withExtra(THREAD_NAME, iLoggingEvent.getThreadName()); @@ -144,7 +162,11 @@ protected EventBuilder createEventBuilder(ILoggingEvent iLoggingEvent) { } for (Map.Entry contextEntry : iLoggingEvent.getLoggerContextVO().getPropertyMap().entrySet()) { - eventBuilder.withExtra(contextEntry.getKey(), contextEntry.getValue()); + if (Sentry.getStoredClient().getMdcTags().contains(contextEntry.getKey())) { + eventBuilder.withTag(contextEntry.getKey(), contextEntry.getValue()); + } else { + eventBuilder.withExtra(contextEntry.getKey(), contextEntry.getValue()); + } } for (Map.Entry mdcEntry : iLoggingEvent.getMDCPropertyMap().entrySet()) { @@ -286,6 +308,19 @@ public void stop() { } } + public Encoder getEncoder() { + return encoder; + } + + /** + * Set logback encoder. + * + * @param encoder logback encoder. + */ + public void setEncoder(Encoder encoder) { + this.encoder = encoder; + } + private class DropSentryFilter extends Filter { @Override public FilterReply decide(ILoggingEvent event) { @@ -296,4 +331,5 @@ public FilterReply decide(ILoggingEvent event) { return FilterReply.NEUTRAL; } } + } diff --git a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java index 02610cc7ec5..60c79efcc80 100644 --- a/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java +++ b/sentry-logback/src/test/java/io/sentry/logback/SentryAppenderEventBuildingTest.java @@ -1,6 +1,8 @@ package io.sentry.logback; import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.BasicStatusManager; import ch.qos.logback.core.Context; @@ -41,7 +43,7 @@ public class SentryAppenderEventBuildingTest extends BaseTest { @Before public void setUp() throws Exception { mockSentryClient = mock(SentryClient.class); - mockContext = mock(Context.class); + mockContext = mock(LoggerContext.class); Sentry.setStoredClient(mockSentryClient); sentryAppender = new SentryAppender(); @@ -205,6 +207,22 @@ public void testMdcTakesPrecedenceOverContextProperties() throws Exception { assertThat(event.getExtra(), Matchers.hasEntry(mdcKey, mdcValue)); assertNoErrorsInStatusManager(); } + + @Test + public void testGLobalAddedToExtra() throws Exception { + final String extraKey = "10e09b11-546f-4c57-99b2-cf3c627c8737"; + final String extraValue = "5f7a53b1-4354-4120-a368-78a615705540"; + + + sentryAppender.append(new TestLoggingEvent(null, null, Level.INFO, null, null, null, + null, null, null, 0, Collections.singletonMap(extraKey, extraValue))); + + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event event = eventBuilderArgumentCaptor.getValue().build(); + assertThat(event.getExtra(), Matchers.hasEntry(extraKey, extraValue)); + assertNoErrorsInStatusManager(); + } @Test public void testSourceUsedAsStacktrace() throws Exception { @@ -244,4 +262,47 @@ public void testExtraTagObtainedFromMdc() throws Exception { assertThat(event.getExtra(), Matchers.hasEntry("other_property", "cb9c92a1-0182-4e9c-866f-b06b271cd196")); assertNoErrorsInStatusManager(); } + + @Test + public void testExtraTagObtainedFromGlobal() throws Exception { + Map propertyMap = new HashMap<>(); + propertyMap.put(mockExtraTag, "47008f35-50c8-4e40-94ca-c8c1a3ddb729"); + propertyMap.put("other_property", "cb9c92a1-0182-4e9c-866f-b06b271cd196"); + + when(mockSentryClient.getMdcTags()).thenReturn(extraTags); + + sentryAppender.append(new TestLoggingEvent(null, null, Level.INFO, null, null, null, null, null, + null, 0, propertyMap)); + + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event event = eventBuilderArgumentCaptor.getValue().build(); + assertThat(event.getTags().entrySet(), hasSize(1)); + assertThat(event.getTags(), hasEntry(mockExtraTag, "47008f35-50c8-4e40-94ca-c8c1a3ddb729")); + assertThat(event.getExtra(), not(hasKey(mockExtraTag))); + assertThat(event.getExtra(), Matchers.hasEntry("other_property", "cb9c92a1-0182-4e9c-866f-b06b271cd196")); + assertNoErrorsInStatusManager(); + } + + @Test + public void testSetEncoder() throws Exception { + when(mockSentryClient.getMdcTags()).thenReturn(extraTags); + + PatternLayoutEncoder encoder = new PatternLayoutEncoder(); + encoder.setPattern("%-5level : %message"); + encoder.setContext(mockContext); + encoder.start(); + sentryAppender.setEncoder(encoder); + sentryAppender.start(); + + String expectedMessage = "some message"; + sentryAppender.append(new TestLoggingEvent(null, null, Level.INFO, expectedMessage, null, null, null, null, + null, 0)); + + ArgumentCaptor eventBuilderArgumentCaptor = ArgumentCaptor.forClass(EventBuilder.class); + verify(mockSentryClient).sendEvent(eventBuilderArgumentCaptor.capture()); + Event event = eventBuilderArgumentCaptor.getValue().build(); + assertThat(event.getMessage(), is("INFO : " + expectedMessage)); + assertNoErrorsInStatusManager(); + } } From 722a59db524ab0f6f418f302a3ed0f364c9508fd Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com> Date: Wed, 4 Dec 2019 19:14:10 +0100 Subject: [PATCH 2126/2152] changes for making it work with AGP 3.0, 3.1 and 3.2 (#798) --- sentry-android-gradle-plugin/build.gradle | 2 +- .../download-sentry-cli.sh | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 54712 -> 55616 bytes .../gradle/wrapper/gradle-wrapper.properties | 3 +- sentry-android-gradle-plugin/gradlew | 18 ++- sentry-android-gradle-plugin/gradlew.bat | 18 ++- .../sentry/android/gradle/SentryPlugin.groovy | 122 +++++++++++++++--- 7 files changed, 141 insertions(+), 24 deletions(-) diff --git a/sentry-android-gradle-plugin/build.gradle b/sentry-android-gradle-plugin/build.gradle index 5ae88e84413..57c7e0bd40b 100644 --- a/sentry-android-gradle-plugin/build.gradle +++ b/sentry-android-gradle-plugin/build.gradle @@ -20,7 +20,7 @@ compileGroovy { dependencies { compileOnly gradleApi() compileOnly localGroovy() - compileOnly 'com.android.tools.build:gradle:3.4.0' + compileOnly 'com.android.tools.build:gradle:3.5.2' } publishing { diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index e485ea93944..16f4cafcd5d 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.48.0 +VERSION=1.49.0 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* diff --git a/sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.jar index 2aad956cf8c61442315ba247692d5a5e4b19add8..5c2d1cf016b3885f6930543d57b744ea8c220a1a 100644 GIT binary patch literal 55616 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

    49F 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 pugjmECO^C`Kg%uv?7#V7-b#Z3) { static String getSentryCli(Project project) { // if a path is provided explicitly use that first def propertiesFile = "${project.rootDir.toPath()}/sentry.properties" + project.logger.info("propertiesFile: ${propertiesFile}") + Properties sentryProps = new Properties() try { sentryProps.load(new FileInputStream(propertiesFile)) - } catch (FileNotFoundException e) { + } catch (FileNotFoundException ignored) { + project.logger.error(ignored.getMessage()) // it's okay, we can ignore it. } def rv = sentryProps.getProperty("cli.executable") if (rv != null) { return rv + } else { + project.logger.info("cli.executable is null") } // in case there is a version from npm right around the corner use that one. This // is the case for react-native-sentry for instance def possibleExePaths = [ - "${project.rootDir.toPath()}/../node_modules/@sentry/cli/bin/sentry-cli", - "${project.rootDir.toPath()}/../node_modules/sentry-cli-binary/bin/sentry-cli" + "${project.rootDir.toPath()}/../node_modules/@sentry/cli/bin/sentry-cli", + "${project.rootDir.toPath()}/../node_modules/sentry-cli-binary/bin/sentry-cli" ] possibleExePaths.each { if ((new File(it)).exists()) { + project.logger.info("possibleExePaths: ${it}") return it } if ((new File(it + ".exe")).exists()) { + project.logger.info("possibleExePaths: ${it}.exe") return it + ".exe" } + project.logger.info("possibleExePaths files dont exist") } // next up try a packaged version of sentry-cli def cliSuffix def osName = System.getProperty("os.name").toLowerCase() + project.logger.info("osName: ${osName}") + if (osName.indexOf("mac") >= 0) { cliSuffix = "Darwin-x86_64" } else if (osName.indexOf("linux") >= 0) { @@ -68,6 +78,8 @@ class SentryPlugin implements Plugin { cliSuffix = "Linux-" + arch } else if (osName.indexOf("win") >= 0) { cliSuffix = "Windows-i686.exe" + } else { + project.logger.info("cliSuffix not assigned") } if (cliSuffix != null) { @@ -76,12 +88,21 @@ class SentryPlugin implements Plugin { // if we are not in a jar, we can use the file directly if ((new File(fsPath)).exists()) { + project.logger.info("fsPath: ${fsPath}") return fsPath + } else { + project.logger.info("fsPath doesnt exist") } // otherwise we need to unpack into a file def resStream = SentryPlugin.class.getResourceAsStream(resPath) File tempFile = File.createTempFile(".sentry-cli", ".exe") + if (tempFile != null) { + project.logger.info("tempFile: ${tempFile.path}") + } else { + project.logger.info("tempFile is null") + } + tempFile.deleteOnExit() def out = new FileOutputStream(tempFile) try { @@ -122,9 +143,9 @@ class SentryPlugin implements Plugin { */ static Task getDexTask(Project project, ApplicationVariant variant) { def names = [ - "transformClassesWithDexFor${variant.name.capitalize()}", - "transformClassesWithDexBuilderFor${variant.name.capitalize()}", - "transformClassesAndDexWithShrinkResFor${variant.name.capitalize()}" + "transformClassesWithDexFor${variant.name.capitalize()}", + "transformClassesWithDexBuilderFor${variant.name.capitalize()}", + "transformClassesAndDexWithShrinkResFor${variant.name.capitalize()}" ] return names.findResult { project.tasks.findByName(it) } ?: project.tasks.findByName("dex${names[0]}") @@ -149,41 +170,81 @@ class SentryPlugin implements Plugin { * @return */ static String getDebugMetaPropPath(Project project, ApplicationVariant variant) { + try { + return variant.mergeAssetsProvider.get().outputDir.get().file("sentry-debug-meta.properties").getAsFile().path + } catch (Exception ignored) { + project.logger.error("getDebugMetaPropPath 1: ${ignored.getMessage()}") + } + try { return variant.mergeAssets.outputDir.get().file("sentry-debug-meta.properties").getAsFile().path - } catch (Throwable ignored) { - return "${variant.mergeAssets.outputDir}/sentry-debug-meta.properties" + } catch (Exception ignored) { + project.logger.error("getDebugMetaPropPath 2: ${ignored.getMessage()}") + } + + try { + return "${variant.mergeAssets.outputDir.get().asFile.path}/sentry-debug-meta.properties" + } catch (Exception ignored) { + project.logger.error("getDebugMetaPropPath 3: ${ignored.getMessage()}") } + try { + return "${variant.mergeAssets.outputDir}/sentry-debug-meta.properties" + } catch (Exception ignored) { + project.logger.error("getDebugMetaPropPath 4: ${ignored.getMessage()}") + } } void apply(Project project) { SentryPluginExtension extension = project.extensions.create("sentry", SentryPluginExtension) project.afterEvaluate { - if(!project.plugins.hasPlugin(AppPlugin) && !project.getPlugins().hasPlugin(LibraryPlugin)) { + if (!project.plugins.hasPlugin(AppPlugin) && !project.getPlugins().hasPlugin(LibraryPlugin)) { throw new IllegalStateException('Must apply \'com.android.application\' first!') } project.android.applicationVariants.all { ApplicationVariant variant -> variant.outputs.each { variantOutput -> def manifestPath = extension.manifestPath - if (manifestPath == null) { - def dir = findAndroidManifestFileDir(variantOutput) + def dir = findAndroidManifestFileDir(project, variantOutput) + if (dir != null) { + project.logger.info("manifestDir: ${dir.path}") + } else { + project.logger.info("manifestDir is null") + } + manifestPath = new File(new File(dir, variantOutput.dirName), "AndroidManifest.xml") + project.logger.info("manifestPath: ${manifestPath}") + } else { + project.logger.info("manifestPath: ${manifestPath}") } def mappingFile = variant.getMappingFile() def proguardTask = getProguardTask(project, variant) + def dexTask = getDexTask(project, variant) + if (dexTask != null) { + project.logger.info("dexTask ${dexTask.path}") + } else { + project.logger.info("dexTask is null") + } + def bundleTask = getBundleTask(project, variant) + if (bundleTask != null) { + project.logger.info("bundleTask ${bundleTask.path}") + } else { + project.logger.info("bundleTask is null") + } if (proguardTask == null) { + project.logger.info("proguardTask is null") return + } else { + project.logger.info("proguardTask ${proguardTask.path}") } - // create a task to configure proguard automatically unless the user disabled it. +// create a task to configure proguard automatically unless the user disabled it. if (extension.autoProguardConfig) { def addProguardSettingsTaskName = "addSentryProguardSettingsFor${variant.name.capitalize()}" if (!project.tasks.findByName(addProguardSettingsTaskName)) { @@ -243,15 +304,20 @@ class SentryPlugin implements Plugin { if (propsFile != null) { environment("SENTRY_PROPERTIES", propsFile) + } else { + project.logger.info("propsFile is null") } + def debugMetaPropPath = getDebugMetaPropPath(project, variant) + project.logger.info("debugMetaPropPath: ${debugMetaPropPath}") + def args = [ cli, "upload-proguard", "--android-manifest", manifestPath, "--write-properties", - getDebugMetaPropPath(project, variant), + debugMetaPropPath, mappingFile ] @@ -296,7 +362,7 @@ class SentryPlugin implements Plugin { } } - static File findAndroidManifestFileDir(BaseVariantOutput variantOutput) { + static File findAndroidManifestFileDir(Project project, BaseVariantOutput variantOutput) { // Gradle 4.7 introduced the lazy task API and AGP 3.3+ adopts that, // so we apparently have a Provider here instead // TODO: This will let us depend on the configuration of each flavor's @@ -307,14 +373,34 @@ class SentryPlugin implements Plugin { try { // Android Gradle Plugin >= 3.3.0 return variantOutput.processManifestProvider.get().manifestOutputDirectory.get().asFile - } catch (Exception ignored) {} + } catch (Exception ignored) { + project.logger.error("findAndroidManifestFileDir 1: ${ignored.getMessage()}") + } try { // Android Gradle Plugin >= 3.0.0 return variantOutput.processManifest.manifestOutputDirectory.get().asFile - } catch (Exception ignored) {} + } catch (Exception ignored) { + project.logger.error("findAndroidManifestFileDir 2: ${ignored.getMessage()}") + } - try { // Android Gradle Plugin < 3.0.0 + // Android Gradle Plugin < 3.0.0 + try { return new File(variantOutput.processManifest.manifestOutputFile).parentFile - } catch (Exception ignored) {} + } catch (Exception ignored) { + project.logger.error("findAndroidManifestFileDir 3: ${ignored.getMessage()}") + } + + // https://github.com/Tencent/tinker/pull/620/files + try { + return variantOutput.processResourcesProvider.get().manifestFile.parentFile + } catch (Exception ignored) { + project.logger.error("findAndroidManifestFileDir 4: ${ignored.getMessage()}") + } + + try { + return variantOutput.processResources.manifestFile.parentFile + } catch (Exception ignored) { + project.logger.error("findAndroidManifestFileDir 5: ${ignored.getMessage()}") + } } } From 714dbaa4761173493a6af938e0cd88d63776e98d Mon Sep 17 00:00:00 2001 From: zhurs Date: Fri, 6 Dec 2019 18:04:19 +0300 Subject: [PATCH 2127/2152] Fixed loosing of interruption status in EventBuilder. (#799) --- .../helper/AndroidEventBuilderHelper.java | 33 ++++++++++--------- .../connection/AppEngineAsyncConnection.java | 2 +- .../AppEngineAsyncConnectionTest.java | 2 +- .../java/io/sentry/log4j/SentryAppender.java | 4 +-- .../java/io/sentry/log4j2/SentryAppender.java | 4 +-- .../io/sentry/logback/SentryAppender.java | 4 +-- .../io/sentry/DefaultSentryClientFactory.java | 2 +- .../src/main/java/io/sentry/SentryClient.java | 2 +- .../main/java/io/sentry/SentryOptions.java | 2 +- .../SentryUncaughtExceptionHandler.java | 2 +- .../java/io/sentry/buffer/DiskBuffer.java | 8 ++--- .../sentry/connection/AbstractConnection.java | 4 +-- .../io/sentry/connection/AsyncConnection.java | 4 +-- .../sentry/connection/BufferedConnection.java | 8 ++--- .../io/sentry/connection/HttpConnection.java | 2 +- .../java/io/sentry/event/EventBuilder.java | 21 ++++++++---- .../java/io/sentry/jul/SentryHandler.java | 8 ++--- .../marshaller/json/SentryJsonGenerator.java | 2 +- .../servlet/SentryServletRequestListener.java | 2 +- sentry/src/main/java/io/sentry/util/Util.java | 3 +- .../connection/BufferedConnectionTest.java | 4 +-- .../java/io/sentry/jvmti/FrameCacheTest.java | 2 +- .../marshaller/json/JsonComparisonUtil.java | 8 +++-- .../io/sentry/unmarshaller/JsonDecoder.java | 2 +- 24 files changed, 74 insertions(+), 61 deletions(-) diff --git a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java index 4759d1089a0..6674401ee1d 100644 --- a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java +++ b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java @@ -6,6 +6,7 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; @@ -168,7 +169,7 @@ protected static String[] getProGuardUuids(Context ctx) { } } catch (FileNotFoundException e) { Log.d(TAG, "Proguard UUIDs file not found."); - } catch (Exception e) { + } catch (IOException | RuntimeException e) { Log.e(TAG, "Error getting Proguard UUIDs.", e); } @@ -185,7 +186,7 @@ protected static String[] getProGuardUuids(Context ctx) { protected static PackageInfo getPackageInfo(Context ctx) { try { return ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0); - } catch (Exception e) { + } catch (PackageManager.NameNotFoundException | RuntimeException e) { Log.e(TAG, "Error getting package info.", e); return null; } @@ -200,7 +201,7 @@ protected static PackageInfo getPackageInfo(Context ctx) { protected static String getFamily() { try { return Build.MODEL.split(" ")[0]; - } catch (Exception e) { + } catch (RuntimeException e) { Log.e(TAG, "Error getting device family.", e); return null; } @@ -221,7 +222,7 @@ protected static Boolean isEmulator() { || Build.MANUFACTURER.contains("Genymotion") || (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) || "google_sdk".equals(Build.PRODUCT); - } catch (Exception e) { + } catch (RuntimeException e) { Log.e(TAG, "Error checking whether application is running in an emulator.", e); return null; } @@ -239,7 +240,7 @@ protected static ActivityManager.MemoryInfo getMemInfo(Context ctx) { ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); actManager.getMemoryInfo(memInfo); return memInfo; - } catch (Exception e) { + } catch (RuntimeException e) { Log.e(TAG, "Error getting MemoryInfo.", e); return null; } @@ -266,7 +267,7 @@ protected static String getOrientation(Context ctx) { break; } return o; - } catch (Exception e) { + } catch (RuntimeException e) { Log.e(TAG, "Error getting device orientation.", e); return null; } @@ -297,7 +298,7 @@ protected static Float getBatteryLevel(Context ctx) { // CHECKSTYLE.ON: MagicNumber return ((float) level / (float) scale) * percentMultiplier; - } catch (Exception e) { + } catch (RuntimeException e) { Log.e(TAG, "Error getting device battery level.", e); return null; } @@ -318,7 +319,7 @@ protected static Boolean isCharging(Context ctx) { int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); return plugged == BatteryManager.BATTERY_PLUGGED_AC || plugged == BatteryManager.BATTERY_PLUGGED_USB; - } catch (Exception e) { + } catch (RuntimeException e) { Log.e(TAG, "Error getting device charging state.", e); return null; } @@ -343,7 +344,7 @@ protected static String getKernelVersion() { br = new BufferedReader(new FileReader(file)); return br.readLine(); - } catch (Exception e) { + } catch (IOException | RuntimeException e) { Log.e(TAG, errorMsg, e); } finally { if (br != null) { @@ -392,7 +393,7 @@ protected static Boolean isRooted() { if (new File(probableRootPath).exists()) { return true; } - } catch (Exception e) { + } catch (RuntimeException e) { Log.e(TAG, "Exception while attempting to detect whether the device is rooted", e); } } @@ -417,7 +418,7 @@ protected static Long getUnusedInternalStorage() { long blockSize = stat.getBlockSize(); long availableBlocks = stat.getAvailableBlocks(); return availableBlocks * blockSize; - } catch (Exception e) { + } catch (RuntimeException e) { Log.e(TAG, "Error getting unused internal storage amount.", e); return null; } @@ -435,7 +436,7 @@ protected static Long getTotalInternalStorage() { long blockSize = stat.getBlockSize(); long totalBlocks = stat.getBlockCount(); return totalBlocks * blockSize; - } catch (Exception e) { + } catch (RuntimeException e) { Log.e(TAG, "Error getting total internal storage amount.", e); return null; } @@ -457,7 +458,7 @@ protected static Long getUnusedExternalStorage() { long availableBlocks = stat.getAvailableBlocks(); return availableBlocks * blockSize; } - } catch (Exception e) { + } catch (RuntimeException e) { Log.e(TAG, "Error getting unused external storage amount.", e); } @@ -480,7 +481,7 @@ protected static Long getTotalExternalStorage() { long totalBlocks = stat.getBlockCount(); return totalBlocks * blockSize; } - } catch (Exception e) { + } catch (RuntimeException e) { Log.e(TAG, "Error getting total external storage amount.", e); } @@ -496,7 +497,7 @@ protected static Long getTotalExternalStorage() { protected static DisplayMetrics getDisplayMetrics(Context ctx) { try { return ctx.getResources().getDisplayMetrics(); - } catch (Exception e) { + } catch (RuntimeException e) { Log.e(TAG, "Error getting DisplayMetrics.", e); return null; } @@ -530,7 +531,7 @@ protected static String getApplicationName(Context ctx) { } else { return ctx.getString(stringId); } - } catch (Exception e) { + } catch (RuntimeException e) { Log.e(TAG, "Error getting application name.", e); } diff --git a/sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java b/sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java index b8b1b5654b9..7728626febc 100644 --- a/sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java +++ b/sentry-appengine/src/main/java/io/sentry/appengine/connection/AppEngineAsyncConnection.java @@ -151,7 +151,7 @@ public void run() { return; } connection.actualConnection.send(event); - } catch (Exception e) { + } catch (RuntimeException e) { logger.error("An exception occurred while sending the event to Sentry.", e); } finally { SentryEnvironment.stopManagingThread(); diff --git a/sentry-appengine/src/test/java/io/sentry/appengine/connection/AppEngineAsyncConnectionTest.java b/sentry-appengine/src/test/java/io/sentry/appengine/connection/AppEngineAsyncConnectionTest.java index dbb4e212f46..21ce4147fb0 100644 --- a/sentry-appengine/src/test/java/io/sentry/appengine/connection/AppEngineAsyncConnectionTest.java +++ b/sentry-appengine/src/test/java/io/sentry/appengine/connection/AppEngineAsyncConnectionTest.java @@ -113,7 +113,7 @@ public TaskHandle answer(InvocationOnMock invocation) throws Throwable { TaskOptions taskOptions = invocation.getArgument(0); try { extractDeferredTask(taskOptions).run(); - } catch (Exception e) { + } catch (RuntimeException e) { throw new RuntimeException("Couldn't extract the task", e); } return null; diff --git a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java index 088e3ef0f67..82d105caf49 100644 --- a/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java +++ b/sentry-log4j/src/main/java/io/sentry/log4j/SentryAppender.java @@ -83,7 +83,7 @@ protected void append(LoggingEvent loggingEvent) { try { EventBuilder eventBuilder = createEventBuilder(loggingEvent); Sentry.capture(eventBuilder); - } catch (Exception e) { + } catch (RuntimeException e) { getErrorHandler().error("An exception occurred while creating a new event in Sentry", e, ErrorCode.WRITE_FAILURE); } finally { @@ -151,7 +151,7 @@ public void close() { } this.closed = true; Sentry.close(); - } catch (Exception e) { + } catch (RuntimeException e) { getErrorHandler().error("An exception occurred while closing the Sentry connection", e, ErrorCode.CLOSE_FAILURE); } finally { diff --git a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java index cbf7daab4e5..4a912326c95 100644 --- a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -131,7 +131,7 @@ public void append(LogEvent logEvent) { try { EventBuilder eventBuilder = createEventBuilder(logEvent); Sentry.capture(eventBuilder); - } catch (Exception e) { + } catch (RuntimeException e) { error("An exception occurred while creating a new event in Sentry", logEvent, e); } finally { SentryEnvironment.stopManagingThread(); @@ -201,7 +201,7 @@ public void stop() { } super.stop(); Sentry.close(); - } catch (Exception e) { + } catch (RuntimeException e) { error("An exception occurred while closing the Sentry connection", e); } finally { SentryEnvironment.stopManagingThread(); diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index 30d2423b39b..3c9e05e28e3 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -113,7 +113,7 @@ protected void append(ILoggingEvent iLoggingEvent) { EventBuilder eventBuilder = createEventBuilder(iLoggingEvent); Sentry.capture(eventBuilder); - } catch (Exception e) { + } catch (RuntimeException e) { addError("An exception occurred while creating a new event in Sentry", e); } finally { SentryEnvironment.stopManagingThread(); @@ -301,7 +301,7 @@ public void stop() { } super.stop(); Sentry.close(); - } catch (Exception e) { + } catch (RuntimeException e) { addError("An exception occurred while closing the Sentry connection", e); } finally { SentryEnvironment.stopManagingThread(); diff --git a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java index 74ed125aae7..19c306f386d 100644 --- a/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSentryClientFactory.java @@ -275,7 +275,7 @@ public SentryClient createSentryClient(Dsn dsn) { } sentryClient.addBuilderHelper(new ContextBuilderHelper(sentryClient)); return configureSentryClient(sentryClient, dsn); - } catch (Exception e) { + } catch (RuntimeException e) { logger.error("Failed to initialize sentry, falling back to no-op client", e); return new SentryClient(new NoopConnection(), new ThreadLocalContextManager()); } diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index b5667151ffb..888fed1a769 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -139,7 +139,7 @@ public void sendEvent(Event event) { connection.send(event); } catch (LockedDownException | TooManyRequestsException e) { logger.debug("Dropping an Event due to lockdown: " + event); - } catch (Exception e) { + } catch (RuntimeException e) { logger.error("An exception occurred while sending the event to Sentry.", e); } finally { getContext().setLastEventId(event.getId()); diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 8f430b6c298..b948b8a519c 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -207,7 +207,7 @@ private static String resolveDsn(Lookup lookup, @Nullable String dsn) { } return dsn; - } catch (Exception e) { + } catch (RuntimeException e) { logger.error("Error creating valid DSN from: '{}'.", dsn, e); throw e; } diff --git a/sentry/src/main/java/io/sentry/SentryUncaughtExceptionHandler.java b/sentry/src/main/java/io/sentry/SentryUncaughtExceptionHandler.java index 868f5446e4e..18a6c7972f0 100644 --- a/sentry/src/main/java/io/sentry/SentryUncaughtExceptionHandler.java +++ b/sentry/src/main/java/io/sentry/SentryUncaughtExceptionHandler.java @@ -53,7 +53,7 @@ public void uncaughtException(Thread thread, Throwable thrown) { try { Sentry.capture(eventBuilder); - } catch (Exception e) { + } catch (RuntimeException e) { logger.error("Error sending uncaught exception to Sentry.", e); } } diff --git a/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java b/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java index 6baab9fc34d..c92f6e013e1 100644 --- a/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java +++ b/sentry/src/main/java/io/sentry/buffer/DiskBuffer.java @@ -43,7 +43,7 @@ public DiskBuffer(File bufferDir, int maxEvents) { if (!bufferDir.isDirectory() || !bufferDir.canWrite()) { throw new RuntimeException(errMsg); } - } catch (Exception e) { + } catch (RuntimeException e) { throw new RuntimeException(errMsg, e); } @@ -78,7 +78,7 @@ public void add(Event event) { try (FileOutputStream fileOutputStream = new FileOutputStream(eventFile); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) { objectOutputStream.writeObject(event); - } catch (Exception e) { + } catch (IOException | RuntimeException e) { logger.error("Error writing Event to offline storage: " + event.getId(), e); } @@ -118,7 +118,7 @@ private Event fileToEvent(File eventFile) { } catch (FileNotFoundException e) { // event was deleted while we were iterating the array of files return null; - } catch (Exception e) { + } catch (IOException | ClassNotFoundException | RuntimeException e) { logger.error("Error reading Event file: " + eventFile.getAbsolutePath(), e); if (!eventFile.delete()) { logger.warn("Failed to delete Event: " + eventFile.getAbsolutePath()); @@ -128,7 +128,7 @@ private Event fileToEvent(File eventFile) { try { return (Event) eventObj; - } catch (Exception e) { + } catch (RuntimeException e) { logger.error("Error casting Object to Event: " + eventFile.getAbsolutePath(), e); if (!eventFile.delete()) { logger.warn("Failed to delete Event: " + eventFile.getAbsolutePath()); diff --git a/sentry/src/main/java/io/sentry/connection/AbstractConnection.java b/sentry/src/main/java/io/sentry/connection/AbstractConnection.java index 7a2692aed4d..e75a296ef42 100644 --- a/sentry/src/main/java/io/sentry/connection/AbstractConnection.java +++ b/sentry/src/main/java/io/sentry/connection/AbstractConnection.java @@ -93,7 +93,7 @@ public final void send(Event event) throws ConnectionException { for (EventSendCallback eventSendCallback : eventSendCallbacks) { try { eventSendCallback.onSuccess(event); - } catch (Exception exc) { + } catch (RuntimeException exc) { logger.warn("An exception occurred while running an EventSendCallback.onSuccess: " + eventSendCallback.getClass().getName(), exc); } @@ -102,7 +102,7 @@ public final void send(Event event) throws ConnectionException { for (EventSendCallback eventSendCallback : eventSendCallbacks) { try { eventSendCallback.onFailure(event, e); - } catch (Exception exc) { + } catch (RuntimeException exc) { logger.warn("An exception occurred while running an EventSendCallback.onFailure: " + eventSendCallback.getClass().getName(), exc); } diff --git a/sentry/src/main/java/io/sentry/connection/AsyncConnection.java b/sentry/src/main/java/io/sentry/connection/AsyncConnection.java index 5fbda6f1483..c946e2680e8 100644 --- a/sentry/src/main/java/io/sentry/connection/AsyncConnection.java +++ b/sentry/src/main/java/io/sentry/connection/AsyncConnection.java @@ -187,7 +187,7 @@ public void run() { actualConnection.send(event); } catch (LockedDownException | TooManyRequestsException e) { logger.debug("Dropping an Event due to lockdown: " + event); - } catch (Exception e) { + } catch (RuntimeException e) { logger.error("An exception occurred while sending the event to Sentry.", e); } finally { if (previous == null) { @@ -218,7 +218,7 @@ public void run() { try { // The current thread is managed by sentry AsyncConnection.this.doClose(); - } catch (Exception e) { + } catch (IOException | RuntimeException e) { logger.error("An exception occurred while closing the connection.", e); } finally { SentryEnvironment.stopManagingThread(); diff --git a/sentry/src/main/java/io/sentry/connection/BufferedConnection.java b/sentry/src/main/java/io/sentry/connection/BufferedConnection.java index 0cd19a41a79..0fc87cdb9fe 100644 --- a/sentry/src/main/java/io/sentry/connection/BufferedConnection.java +++ b/sentry/src/main/java/io/sentry/connection/BufferedConnection.java @@ -186,7 +186,7 @@ public void send(Event event) throws ConnectionException { try { // buffer before we attempt to send buffer.add(event); - } catch (Exception e) { + } catch (RuntimeException e) { logger.error("Exception occurred while attempting to add Event to buffer: ", e); } @@ -249,7 +249,7 @@ public void run() { logger.trace("Flusher attempting to send Event: " + event.getId()); send(event); logger.trace("Flusher successfully sent Event: " + event.getId()); - } catch (Exception e) { + } catch (RuntimeException e) { logger.debug("Flusher failed to send Event: " + event.getId(), e); // Connectivity issues, give up until next Flusher run. @@ -258,7 +258,7 @@ public void run() { } } logger.trace("Flusher run exiting, no more events to send."); - } catch (Exception e) { + } catch (RuntimeException e) { logger.error("Error running Flusher: ", e); } finally { SentryEnvironment.stopManagingThread(); @@ -283,7 +283,7 @@ public void run() { try { // The current thread is managed by sentry BufferedConnection.this.close(); - } catch (Exception e) { + } catch (IOException | RuntimeException e) { logger.error("An exception occurred while closing the connection.", e); } finally { SentryEnvironment.stopManagingThread(); diff --git a/sentry/src/main/java/io/sentry/connection/HttpConnection.java b/sentry/src/main/java/io/sentry/connection/HttpConnection.java index 0bc3242fdc4..c02342dfd91 100644 --- a/sentry/src/main/java/io/sentry/connection/HttpConnection.java +++ b/sentry/src/main/java/io/sentry/connection/HttpConnection.java @@ -233,7 +233,7 @@ private String getErrorMessageFromStream(InputStream errorStream) { sb.append(line); first = false; } - } catch (Exception e2) { + } catch (IOException | RuntimeException e2) { logger.error("Exception while reading the error message from the connection.", e2); } return sb.toString(); diff --git a/sentry/src/main/java/io/sentry/event/EventBuilder.java b/sentry/src/main/java/io/sentry/event/EventBuilder.java index cde93f06247..b64d0603559 100644 --- a/sentry/src/main/java/io/sentry/event/EventBuilder.java +++ b/sentry/src/main/java/io/sentry/event/EventBuilder.java @@ -13,8 +13,10 @@ import java.net.InetAddress; import java.util.*; import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.CRC32; import java.util.zip.Checksum; @@ -565,15 +567,22 @@ public Void call() throws Exception { FutureTask futureTask = new FutureTask<>(hostRetriever); new Thread(futureTask).start(); futureTask.get(GET_HOSTNAME_TIMEOUT, TimeUnit.MILLISECONDS); - } catch (Exception e) { - expirationTimestamp = clock.millis() + TimeUnit.SECONDS.toMillis(1); - logger.debug("Localhost hostname lookup failed, keeping the value '{}'." - + " If this persists it may mean your DNS is incorrectly configured and" - + " you may want to hardcode your server name: https://docs.sentry.io/clients/java/config/", - hostname, e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + handleCacheUpdateFailure(e); + } catch (ExecutionException | TimeoutException | RuntimeException e) { + handleCacheUpdateFailure(e); } } + private void handleCacheUpdateFailure(Exception failure) { + expirationTimestamp = clock.millis() + TimeUnit.SECONDS.toMillis(1); + logger.debug("Localhost hostname lookup failed, keeping the value '{}'." + + " If this persists it may mean your DNS is incorrectly configured and" + + " you may want to hardcode your server name: https://docs.sentry.io/clients/java/config/", + hostname, failure); + } + /** * This method is to support testing. It serves little purpose outside of it. It forgets the resolved * hostname and sets the expiration time to the provided value. diff --git a/sentry/src/main/java/io/sentry/jul/SentryHandler.java b/sentry/src/main/java/io/sentry/jul/SentryHandler.java index 805695d274e..bff32f95020 100644 --- a/sentry/src/main/java/io/sentry/jul/SentryHandler.java +++ b/sentry/src/main/java/io/sentry/jul/SentryHandler.java @@ -87,7 +87,7 @@ protected void retrieveProperties() { private Level parseLevelOrDefault(String levelName) { try { return Level.parse(levelName.trim()); - } catch (Exception e) { + } catch (RuntimeException e) { return Level.WARNING; } } @@ -103,7 +103,7 @@ public void publish(LogRecord record) { try { EventBuilder eventBuilder = createEventBuilder(record); Sentry.capture(eventBuilder); - } catch (Exception e) { + } catch (RuntimeException e) { reportError("An exception occurred while creating a new event in Sentry", e, ErrorManager.WRITE_FAILURE); } finally { SentryEnvironment.stopManagingThread(); @@ -137,7 +137,7 @@ protected EventBuilder createEventBuilder(LogRecord record) { try { formatted = formatMessage(message, record.getParameters()); topLevelMessage = formatted; // write out formatted as Event's message key - } catch (Exception e) { + } catch (RuntimeException e) { // local formatting failed, send message and parameters without formatted string formatted = null; } @@ -193,7 +193,7 @@ public void close() throws SecurityException { SentryEnvironment.startManagingThread(); try { Sentry.close(); - } catch (Exception e) { + } catch (RuntimeException e) { reportError("An exception occurred while closing the Sentry connection", e, ErrorManager.CLOSE_FAILURE); } finally { SentryEnvironment.stopManagingThread(); diff --git a/sentry/src/main/java/io/sentry/marshaller/json/SentryJsonGenerator.java b/sentry/src/main/java/io/sentry/marshaller/json/SentryJsonGenerator.java index 0352627d351..03e3538555c 100644 --- a/sentry/src/main/java/io/sentry/marshaller/json/SentryJsonGenerator.java +++ b/sentry/src/main/java/io/sentry/marshaller/json/SentryJsonGenerator.java @@ -115,7 +115,7 @@ private void writeObject(Object value, int recursionLevel) throws IOException { value, value.getClass()); try { generator.writeString(Util.trimString(value.toString(), maxLengthString)); - } catch (Exception innerE) { + } catch (IOException | RuntimeException innerE) { generator.writeString(""); } } diff --git a/sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java b/sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java index dc8c5d2b8c8..ae95519e5f1 100644 --- a/sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java +++ b/sentry/src/main/java/io/sentry/servlet/SentryServletRequestListener.java @@ -33,7 +33,7 @@ public void requestDestroyed(ServletRequestEvent servletRequestEvent) { if (sentryClient != null) { sentryClient.clearContext(); } - } catch (Exception e) { + } catch (RuntimeException e) { logger.error("Error clearing Context state.", e); } } diff --git a/sentry/src/main/java/io/sentry/util/Util.java b/sentry/src/main/java/io/sentry/util/Util.java index 7215aa2b958..ee19b263539 100644 --- a/sentry/src/main/java/io/sentry/util/Util.java +++ b/sentry/src/main/java/io/sentry/util/Util.java @@ -1,6 +1,7 @@ package io.sentry.util; import java.io.Closeable; +import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -204,7 +205,7 @@ public static void closeQuietly(Closeable closeable) { if (closeable != null) { try { closeable.close(); - } catch (Exception ignored) { + } catch (IOException | RuntimeException ignored) { } } // CHECKSTYLE.ON: EmptyCatchBlock diff --git a/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java b/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java index 2de22065ac4..8d4d55c3dcd 100644 --- a/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java +++ b/sentry/src/test/java/io/sentry/connection/BufferedConnectionTest.java @@ -177,7 +177,7 @@ public void test429IsNotBuffered() throws Exception { .when(mockConnection).send(any(Event.class)); try { bufferedConnection.send(event); - } catch (Exception e) { + } catch (RuntimeException e) { } assertThat(bufferedEvents.size(), equalTo(0)); @@ -190,7 +190,7 @@ public void testNoResponseCodeIsBuffered() throws Exception { .when(mockConnection).send(any(Event.class)); try { bufferedConnection.send(event); - } catch (Exception e) { + } catch (RuntimeException e) { } assertThat(bufferedEvents.size(), equalTo(1)); diff --git a/sentry/src/test/java/io/sentry/jvmti/FrameCacheTest.java b/sentry/src/test/java/io/sentry/jvmti/FrameCacheTest.java index 9a05b879006..9153a6d963d 100644 --- a/sentry/src/test/java/io/sentry/jvmti/FrameCacheTest.java +++ b/sentry/src/test/java/io/sentry/jvmti/FrameCacheTest.java @@ -22,7 +22,7 @@ public void test() throws Exception { try { throw new RuntimeException("foo"); - } catch (Exception e) { + } catch (RuntimeException e) { t = e; // throwable shouldn't exist in the cache at all, so we expect true diff --git a/sentry/src/test/java/io/sentry/marshaller/json/JsonComparisonUtil.java b/sentry/src/test/java/io/sentry/marshaller/json/JsonComparisonUtil.java index ac774d83d03..420fb433d12 100644 --- a/sentry/src/test/java/io/sentry/marshaller/json/JsonComparisonUtil.java +++ b/sentry/src/test/java/io/sentry/marshaller/json/JsonComparisonUtil.java @@ -5,7 +5,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.OutputStream; +import java.io.UnsupportedEncodingException; public final class JsonComparisonUtil { private static final ObjectMapper objectMapper = new ObjectMapper(); @@ -40,7 +42,7 @@ public String toString() { closeStream(); try { return outputStream.toString("UTF-8"); - } catch (Exception e) { + } catch (UnsupportedEncodingException | RuntimeException e) { throw new RuntimeException(e); } } @@ -63,7 +65,7 @@ protected void closeStream() { try { jsonGenerator.close(); outputStream.close(); - } catch (Exception e) { + } catch (IOException e) { throw new RuntimeException(e); } } @@ -79,7 +81,7 @@ public OutputStream outputStream() { protected void closeStream() { try { outputStream.close(); - } catch (Exception e) { + } catch (IOException e) { throw new RuntimeException(e); } } diff --git a/sentry/src/test/java/io/sentry/unmarshaller/JsonDecoder.java b/sentry/src/test/java/io/sentry/unmarshaller/JsonDecoder.java index 58cb3657fb2..94461aa22dc 100644 --- a/sentry/src/test/java/io/sentry/unmarshaller/JsonDecoder.java +++ b/sentry/src/test/java/io/sentry/unmarshaller/JsonDecoder.java @@ -64,7 +64,7 @@ private boolean isJson(InputStream inputStream) { parser.nextToken(); } while (parser.hasCurrentToken()); valid = true; - } catch (Exception e) { + } catch (IOException | RuntimeException e) { logger.log(Level.FINE, "An exception occurred while trying to parse an allegedly JSON document", e); } From 4a3e2b692e6ce6bbc1fadceac2e9b469b4f5bad8 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Mon, 16 Dec 2019 16:33:54 +0100 Subject: [PATCH 2128/2152] [maven-release-plugin] prepare release v1.7.29 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 796528d663c..a41ea43ec48 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.29-SNAPSHOT + 1.7.29 pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.29 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 194928878fa..ff14acbfedc 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.29-SNAPSHOT + 1.7.29 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 47da1eac13d..28e530eec53 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.29-SNAPSHOT + 1.7.29 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index a80b7256627..c1ff136df86 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.29-SNAPSHOT + 1.7.29 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 1fbde706b09..d38ddaf701a 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.29-SNAPSHOT + 1.7.29 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 85ecaf839b0..4ec2a0be1c0 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.29-SNAPSHOT + 1.7.29 sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 9ed1721a4a5..9392dea7ec7 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.29-SNAPSHOT + 1.7.29 sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.29-SNAPSHOT + 1.7.29 diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index a87668dc67c..5df57022368 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.29-SNAPSHOT + 1.7.29 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index ce4087fc0d2..283922d10a8 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.29-SNAPSHOT + 1.7.29 sentry From 580d5453502328543e297e36174d018d54210b3b Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Mon, 16 Dec 2019 16:33:55 +0100 Subject: [PATCH 2129/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index a41ea43ec48..4831f9d742f 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.29 + 1.7.30-SNAPSHOT pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - v1.7.29 + HEAD https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index ff14acbfedc..69548cce033 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.29 + 1.7.30-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 28e530eec53..86b65f5a6a0 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.29 + 1.7.30-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index c1ff136df86..7cf5a9bcdb3 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.29 + 1.7.30-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index d38ddaf701a..973690013aa 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.29 + 1.7.30-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 4ec2a0be1c0..394a60ff2d2 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.29 + 1.7.30-SNAPSHOT sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 9392dea7ec7..509c35afff8 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.29 + 1.7.30-SNAPSHOT sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.29 + 1.7.30-SNAPSHOT diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 5df57022368..2e9f3eae49b 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.29 + 1.7.30-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 283922d10a8..f8b6e1e7bf2 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.29 + 1.7.30-SNAPSHOT sentry From d8c1baa51f0a0e607371236d66183a7eb64c7eac Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Mon, 16 Dec 2019 16:33:56 +0100 Subject: [PATCH 2130/2152] Bump CHANGES to 1.7.30 --- CHANGES | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index ebd1034e568..1d0e5dd6b90 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,17 @@ +Version 1.7.30 +-------------- + +- + Version 1.7.29 -------------- -- +- Add method to check if Sentry is already initializated #783 +- Proguard: Keep exception type names #790 +- Blacklist other frameworks using cglib dynamic classes, like Guice #796 +- Logback: Adds support for logback encoders (the message field is encoded). #794 +- Android Gradle Plugin: Changes for making it to work with AGP 3.0, 3.1 and 3.2 #798 +- Fixed loosing of interruption status in EventBuilder. #799 Version 1.7.28 -------------- From 28a9b1c1fa02bf5c30c8efbfd263a156ff417677 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Tue, 17 Dec 2019 12:27:38 +0100 Subject: [PATCH 2131/2152] prepare release 1.7.30-SNAPSHOT --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index d005aa11bb7..4b328c3708d 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.29-SNAPSHOT +version = 1.7.30-SNAPSHOT From 364f8e147e0e76578465936933cabbb99bd67ccf Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com> Date: Mon, 13 Jan 2020 13:27:24 +0100 Subject: [PATCH 2132/2152] added requireNonNull utils (#810) --- .../java/io/sentry/SentryClientFactory.java | 8 ++-- .../main/java/io/sentry/SentryOptions.java | 7 ++-- .../src/main/java/io/sentry/util/Objects.java | 37 +++++++++++++++++++ 3 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/util/Objects.java diff --git a/sentry/src/main/java/io/sentry/SentryClientFactory.java b/sentry/src/main/java/io/sentry/SentryClientFactory.java index 8a35799f982..706dce192a9 100644 --- a/sentry/src/main/java/io/sentry/SentryClientFactory.java +++ b/sentry/src/main/java/io/sentry/SentryClientFactory.java @@ -1,6 +1,7 @@ package io.sentry; -import static java.util.Objects.requireNonNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -8,9 +9,8 @@ import io.sentry.config.Lookup; import io.sentry.dsn.Dsn; import io.sentry.util.Nullable; +import io.sentry.util.Objects; import io.sentry.util.Util; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Factory in charge of creating {@link SentryClient} instances. The implementations should have a constructor with a @@ -32,7 +32,7 @@ public abstract class SentryClientFactory { * @param lookup the lookup to use */ protected SentryClientFactory(Lookup lookup) { - this.lookup = requireNonNull(lookup); + this.lookup = Objects.requireNonNull(lookup); } /** diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index b948b8a519c..85cd63bd3bd 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -1,11 +1,10 @@ package io.sentry; -import static java.util.Objects.requireNonNull; - import io.sentry.config.Lookup; import io.sentry.config.ResourceLoader; import io.sentry.dsn.Dsn; import io.sentry.util.Nullable; +import io.sentry.util.Objects; import io.sentry.util.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,7 +38,7 @@ public final class SentryOptions { * @throws NullPointerException if lookup is null */ public SentryOptions(Lookup lookup, @Nullable String dsn, @Nullable SentryClientFactory sentryClientFactory) { - this.lookup = requireNonNull(lookup, "lookup"); + this.lookup = Objects.requireNonNull(lookup, "lookup"); this.dsn = resolveDsn(lookup, dsn); this.sentryClientFactory = sentryClientFactory == null ? SentryClientFactory.instantiateFrom(this.lookup, this.dsn) @@ -164,7 +163,7 @@ public Lookup getLookup() { * @param lookup the lookup to use */ public void setLookup(Lookup lookup) { - this.lookup = requireNonNull(lookup); + this.lookup = Objects.requireNonNull(lookup); } /** diff --git a/sentry/src/main/java/io/sentry/util/Objects.java b/sentry/src/main/java/io/sentry/util/Objects.java new file mode 100644 index 00000000000..110077dd169 --- /dev/null +++ b/sentry/src/main/java/io/sentry/util/Objects.java @@ -0,0 +1,37 @@ +package io.sentry.util; + +/** + * backport of Objects Utils which is not available on Android yet. + */ +public final class Objects { + private Objects() { + + } + + /** + * backport of Objects.requireNonNull which is not available on Android yet. + * @param obj object to NPE check + * @param message custom message for NullPointerException + * @param obj type + * @return returns obj itself if it's not null + */ + public static T requireNonNull(T obj, String message) { + if (obj == null) { + throw new NullPointerException(message); + } + return obj; + } + + /** + * backport of Objects.requireNonNull which is not available on Android yet. + * @param obj object to NPE check + * @param obj type + * @return returns obj itself if it's not null + */ + public static T requireNonNull(T obj) { + if (obj == null) { + throw new NullPointerException(); + } + return obj; + } +} From 814d65507a9d6ba8e264ac736aef4ad1a9fdebcb Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 27 Jan 2020 12:33:51 +0100 Subject: [PATCH 2133/2152] fix: context returns new Maps (#814) --- sentry/src/main/java/io/sentry/context/Context.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry/src/main/java/io/sentry/context/Context.java b/sentry/src/main/java/io/sentry/context/Context.java index 9f67bd4cf85..ca3b878bbbf 100644 --- a/sentry/src/main/java/io/sentry/context/Context.java +++ b/sentry/src/main/java/io/sentry/context/Context.java @@ -102,7 +102,7 @@ public synchronized Map getTags() { return Collections.emptyMap(); } - return Collections.unmodifiableMap(tags); + return new HashMap<>(tags); } /** @@ -115,7 +115,7 @@ public synchronized Map getExtra() { return Collections.emptyMap(); } - return Collections.unmodifiableMap(extra); + return new HashMap<>(extra); } /** From 2f8140eafefd9a87dd7e19c63fb9c64aabe2eca5 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 28 Jan 2020 10:45:55 -0500 Subject: [PATCH 2134/2152] bump maven plugin --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4831f9d742f..52eb5ac36a7 100644 --- a/pom.xml +++ b/pom.xml @@ -202,7 +202,7 @@ org.apache.maven.plugins maven-release-plugin - 2.5.3 + 3.0.0-M1 v@{project.version} true From f379c3280ffe69edb9cf460cf84dc95f83cd7598 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 28 Jan 2020 10:51:14 -0500 Subject: [PATCH 2135/2152] make javadocs plugin happy --- .../android/event/helper/AndroidEventBuilderHelper.java | 2 +- sentry/src/main/java/io/sentry/Sentry.java | 5 +---- .../src/main/java/io/sentry/connection/HttpConnection.java | 2 +- sentry/src/main/java/io/sentry/event/BreadcrumbBuilder.java | 2 +- sentry/src/main/java/io/sentry/jvmti/Frame.java | 4 ++-- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java index 6674401ee1d..cbc348b3dbc 100644 --- a/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java +++ b/sentry-android/src/main/java/io/sentry/android/event/helper/AndroidEventBuilderHelper.java @@ -194,7 +194,7 @@ protected static PackageInfo getPackageInfo(Context ctx) { /** * Fake the device family by using the first word in the Build.MODEL. Works - * well in most cases... "Nexus 6P" -> "Nexus", "Galaxy S7" -> "Galaxy". + * well in most cases... "Nexus 6P" - "Nexus", "Galaxy S7" - "Galaxy". * * @return family name of the device, as best we can tell */ diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 10244cff50a..457d3375ff5 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -115,14 +115,11 @@ public static SentryClient init(@Nullable String dsn, @Nullable SentryClientFact /** * Initializes a new Sentry client from the provided context. * - *

    The canonical way of using this method is: - *

    - *
    +     * The canonical way of using this method is:
          * {@link Lookup} lookup = ... obtain or construct the instance of this class to be able to locate Sentry config
          * String dsn = ... obtain the Sentry data source name or leave null for lookup in the configuration
          * SentryClient client =
          *   Sentry.init({@link SentryOptions}.{@link SentryOptions#from(Lookup, String) from(lookup, dsn))};
    -     * 
    * If you want to rely on the default mechanisms to obtain the configuration, you can also use the * {@link SentryOptions#defaults()} method which will use the default way of obtaining the configuration and DSN * obtained from the configuration. diff --git a/sentry/src/main/java/io/sentry/connection/HttpConnection.java b/sentry/src/main/java/io/sentry/connection/HttpConnection.java index c02342dfd91..882f82ee23a 100644 --- a/sentry/src/main/java/io/sentry/connection/HttpConnection.java +++ b/sentry/src/main/java/io/sentry/connection/HttpConnection.java @@ -177,7 +177,7 @@ protected void doSend(Event event) throws ConnectionException { // CHECKSTYLE.OFF: EmptyCatchBlock try { // CHECKSTYLE.OFF: MagicNumber - retryAfterMs = (long) (Double.parseDouble(retryAfterHeader) * 1000L); // seconds -> milliseconds + retryAfterMs = (long) (Double.parseDouble(retryAfterHeader) * 1000L); // seconds to milliseconds // CHECKSTYLE.ON: MagicNumber } catch (NumberFormatException nfe) { // noop, use default retry diff --git a/sentry/src/main/java/io/sentry/event/BreadcrumbBuilder.java b/sentry/src/main/java/io/sentry/event/BreadcrumbBuilder.java index 8d3421dd82b..3c2c4167986 100644 --- a/sentry/src/main/java/io/sentry/event/BreadcrumbBuilder.java +++ b/sentry/src/main/java/io/sentry/event/BreadcrumbBuilder.java @@ -85,7 +85,7 @@ public BreadcrumbBuilder setData(Map newData) { } /** - * Adds to the related data for the {@link breadcrumb}. + * Adds to the related data. * * @param name Name of the data * @param value Value of the data diff --git a/sentry/src/main/java/io/sentry/jvmti/Frame.java b/sentry/src/main/java/io/sentry/jvmti/Frame.java index 2ebf44f8a2c..6ff5762ac38 100644 --- a/sentry/src/main/java/io/sentry/jvmti/Frame.java +++ b/sentry/src/main/java/io/sentry/jvmti/Frame.java @@ -35,9 +35,9 @@ public Method getMethod() { } /** - * Converts the locals array to a Map of variable-name -> variable-value. + * Converts the locals array to a Map of variable-name - variable-value. * - * @return Map of variable-name -> variable-value. + * @return Map of variable-name - variable-value. */ public Map getLocals() { if (locals == null || locals.length == 0) { From 7a4451a809c20ecd716a6df9fb6d9affa4ebc109 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 28 Jan 2020 10:57:47 -0500 Subject: [PATCH 2136/2152] [maven-release-plugin] prepare release v1.7.30 --- pom.xml | 4 ++-- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 52eb5ac36a7..2a9702f3094 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.30-SNAPSHOT + 1.7.30 pom Sentry-Java @@ -87,7 +87,7 @@ https://github.com/${github.repo} scm:git:git://github.com/${github.repo}.git scm:git:git@github.com:${github.repo}.git - HEAD + v1.7.30 https://github.com/${github.repo}/issues diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index 69548cce033..d9b3aad9fbf 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.30-SNAPSHOT + 1.7.30 sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 86b65f5a6a0..5d8eda1e818 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.30-SNAPSHOT + 1.7.30 sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index 7cf5a9bcdb3..da626c382ec 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.30-SNAPSHOT + 1.7.30 sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 973690013aa..4be46f40819 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.30-SNAPSHOT + 1.7.30 sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index 394a60ff2d2..b7a6c4cfea3 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.30-SNAPSHOT + 1.7.30 sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index 509c35afff8..b6480fb0feb 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.30-SNAPSHOT + 1.7.30 sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.30-SNAPSHOT + 1.7.30 diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 2e9f3eae49b..2a2c9526584 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.30-SNAPSHOT + 1.7.30 sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index f8b6e1e7bf2..936251501ba 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.30-SNAPSHOT + 1.7.30 sentry From 37c7b05d5b072c776d0ead1896469d6531eb8834 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 28 Jan 2020 10:57:48 -0500 Subject: [PATCH 2137/2152] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- sentry-android/pom.xml | 2 +- sentry-appengine/pom.xml | 2 +- sentry-log4j/pom.xml | 2 +- sentry-log4j2/pom.xml | 2 +- sentry-logback/pom.xml | 2 +- sentry-spring-boot-starter/pom.xml | 4 ++-- sentry-spring/pom.xml | 2 +- sentry/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pom.xml b/pom.xml index 2a9702f3094..6dbc2cf161d 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.sentry sentry-all - 1.7.30 + 1.7.31-SNAPSHOT pom Sentry-Java diff --git a/sentry-android/pom.xml b/sentry-android/pom.xml index d9b3aad9fbf..2573d9d99f3 100644 --- a/sentry-android/pom.xml +++ b/sentry-android/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.30 + 1.7.31-SNAPSHOT sentry-android diff --git a/sentry-appengine/pom.xml b/sentry-appengine/pom.xml index 5d8eda1e818..697add7a101 100644 --- a/sentry-appengine/pom.xml +++ b/sentry-appengine/pom.xml @@ -4,7 +4,7 @@ io.sentry sentry-all - 1.7.30 + 1.7.31-SNAPSHOT sentry-appengine diff --git a/sentry-log4j/pom.xml b/sentry-log4j/pom.xml index da626c382ec..00deb005771 100644 --- a/sentry-log4j/pom.xml +++ b/sentry-log4j/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.30 + 1.7.31-SNAPSHOT sentry-log4j diff --git a/sentry-log4j2/pom.xml b/sentry-log4j2/pom.xml index 4be46f40819..c16a5555822 100644 --- a/sentry-log4j2/pom.xml +++ b/sentry-log4j2/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.30 + 1.7.31-SNAPSHOT sentry-log4j2 diff --git a/sentry-logback/pom.xml b/sentry-logback/pom.xml index b7a6c4cfea3..d586e5e82e5 100644 --- a/sentry-logback/pom.xml +++ b/sentry-logback/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.30 + 1.7.31-SNAPSHOT sentry-logback diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index b6480fb0feb..ae45e7fdc56 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ sentry-all io.sentry - 1.7.30 + 1.7.31-SNAPSHOT sentry-spring-boot-starter @@ -24,7 +24,7 @@ io.sentry sentry-spring - 1.7.30 + 1.7.31-SNAPSHOT diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index 2a2c9526584..e588b199c2f 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.30 + 1.7.31-SNAPSHOT sentry-spring diff --git a/sentry/pom.xml b/sentry/pom.xml index 936251501ba..9e10b37d2b8 100644 --- a/sentry/pom.xml +++ b/sentry/pom.xml @@ -5,7 +5,7 @@ io.sentry sentry-all - 1.7.30 + 1.7.31-SNAPSHOT sentry From 3275bdd4987620b935ee720859376b74e1ad8d95 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 28 Jan 2020 10:57:50 -0500 Subject: [PATCH 2138/2152] Bump CHANGES to 1.7.31 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 1d0e5dd6b90..704a9e31b39 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +-e Version 1.7.31 +-------------- + +- + Version 1.7.30 -------------- From 9b2607eb1d1003aa33ff9a2254f5b7e5bb35b24f Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 28 Jan 2020 11:02:33 -0500 Subject: [PATCH 2139/2152] bump changes --- CHANGES | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 704a9e31b39..01f0f4c1094 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,4 @@ --e Version 1.7.31 +Version 1.7.31 -------------- - @@ -6,7 +6,8 @@ Version 1.7.30 -------------- -- +- fixed context returns new Maps (#814) +- added requireNonNull utils (#810) Version 1.7.29 -------------- From fc148f6576541c0efe12e10d8443bbd467375ebc Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Sun, 9 Feb 2020 19:17:07 -0500 Subject: [PATCH 2140/2152] remove agent (#817) --- .travis.yml | 16 --- agent/.gitignore | 40 ------- agent/CMakeLists.txt | 20 ---- agent/LICENSE | 21 ---- agent/README.md | 11 -- agent/agent.cpp | 139 ---------------------- agent/example.png | Bin 84125 -> 0 bytes agent/lib.cpp | 270 ------------------------------------------- agent/lib.h | 20 ---- 9 files changed, 537 deletions(-) delete mode 100644 agent/.gitignore delete mode 100644 agent/CMakeLists.txt delete mode 100644 agent/LICENSE delete mode 100644 agent/README.md delete mode 100644 agent/agent.cpp delete mode 100644 agent/example.png delete mode 100644 agent/lib.cpp delete mode 100644 agent/lib.h diff --git a/.travis.yml b/.travis.yml index 6776a94fbf5..521178f2c99 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,22 +24,6 @@ matrix: cache: directories: - $HOME/.m2 - - language: cpp - dist: trusty - jdk: oraclejdk7 - env: TARGET=x86_64 - before_install: .ci/agent-install.sh - script: .ci/agent-build.sh - cache: false - after_success: true - - language: cpp - dist: trusty - jdk: oraclejdk7 - env: TARGET=i686 - before_install: .ci/agent-install.sh - script: .ci/agent-build.sh - cache: false - after_success: true deploy: provider: script diff --git a/agent/.gitignore b/agent/.gitignore deleted file mode 100644 index 110456abe75..00000000000 --- a/agent/.gitignore +++ /dev/null @@ -1,40 +0,0 @@ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -# Custom -CMakeCache.txt -CMakeFiles/ -Makefile -cmake-build-debug/ -cmake_install.cmake -sentry_java_agent.cbp diff --git a/agent/CMakeLists.txt b/agent/CMakeLists.txt deleted file mode 100644 index 0b8268a4011..00000000000 --- a/agent/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -cmake_minimum_required(VERSION 3.2) -project(sentry_java_agent) - -set(CMAKE_CXX_STANDARD 14) - -set(SOURCE_FILES agent.cpp lib.cpp lib.h) -add_library(sentry_agent SHARED ${SOURCE_FILES}) - -if($ENV{TARGET} MATCHES "i686") - message("Setting target to 32-bit.") - set_target_properties(sentry_agent PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32") -endif() - -include_directories($ENV{JAVA_HOME}/include) - -if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - include_directories($ENV{JAVA_HOME}/include/darwin) -elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") - include_directories($ENV{JAVA_HOME}/include/linux) -endif() diff --git a/agent/LICENSE b/agent/LICENSE deleted file mode 100644 index 0eaccd3e00e..00000000000 --- a/agent/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -Copyright (c) 2017 Sentry (https://sentry.io) -Copyright (c) 2017 Mike Clarke -Copyright (c) 2008 Tobias Ivarsson - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/agent/README.md b/agent/README.md deleted file mode 100644 index 92ddab8b58a..00000000000 --- a/agent/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# sentry-java-agent - -The Sentry Java Agent collects and stores local variable information for each stack frame -when an exception is created. If local variable information is available the Sentry Java -SDK will send the variable information along with events. - -Build: `cmake CMakeLists.txt && make` - -Run: `java -agentpath:libsentry_agent{.dylib,.so} ...` - -![Example of local variable state in the Sentry UI](/agent/example.png) diff --git a/agent/agent.cpp b/agent/agent.cpp deleted file mode 100644 index bfcafe443af..00000000000 --- a/agent/agent.cpp +++ /dev/null @@ -1,139 +0,0 @@ -// TODO: cache all FindClass/Find* calls globally -// TODO: better error messages with (void) jvmti->GetErrorName(errnum, &errnum_str); -// TODO: do we need any locking? jrawMonitorID lock; jvmti->RawMonitorEnter(lock); jvmti->RawMonitorExit(lock); -// TODO: use *options instead of env for log level - -#include "jvmti.h" -#include -#include -#include "lib.h" - -extern Level LOG_LEVEL; - -static void JNICALL ExceptionCallback(jvmtiEnv *jvmti, JNIEnv *env, jthread thread, - jmethodID method, jlocation location, jobject exception, - jmethodID catch_method, jlocation catch_location) { - log(TRACE, "ExceptionCallback called."); - - jclass sentry_class = env->FindClass("io/sentry/Sentry"); - if (sentry_class == nullptr) { - env->ExceptionClear(); - log(TRACE, "Unable to locate Sentry class."); - return; - } - - jfieldID storedClientId = env->GetStaticFieldID(sentry_class, "storedClient", "Lio/sentry/SentryClient;"); - jobject storedClientVal = env->GetStaticObjectField(sentry_class, storedClientId); - if (storedClientVal == nullptr) { - env->ExceptionClear(); - log(TRACE, "No stored SentryClient."); - return; - } - - jclass framecache_class = env->FindClass("io/sentry/jvmti/FrameCache"); - if (framecache_class == nullptr) { - env->ExceptionClear(); - log(TRACE, "Unable to locate FrameCache class."); - return; - } - - jmethodID should_cache_method = env->GetStaticMethodID(framecache_class, - "shouldCacheThrowable", - "(Ljava/lang/Throwable;I)Z"); - if (should_cache_method == nullptr) { - log(TRACE, "Unable to locate static FrameCache.shouldCacheThrowable method."); - return; - } - - jint num_frames; - jvmtiError jvmti_error = jvmti->GetFrameCount(thread, &num_frames); - if (jvmti_error != JVMTI_ERROR_NONE) { - log(ERROR, "Could not get the frame count."); - return; - } - - jboolean shouldCache = env->CallStaticBooleanMethod(framecache_class, - should_cache_method, - exception, - num_frames); - if (!((bool) shouldCache)) { - return; - } - - jmethodID framecache_add_method = env->GetStaticMethodID(framecache_class, - "add", - "(Ljava/lang/Throwable;[Lio/sentry/jvmti/Frame;)V"); - if (framecache_add_method == nullptr) { - log(TRACE, "Unable to locate static FrameCache.add method."); - return; - } - - jint start_depth = 0; - jobjectArray frames = buildStackTraceFrames(jvmti, env, thread, start_depth, num_frames); - - env->CallStaticVoidMethod(framecache_class, framecache_add_method, exception, frames); - - log(TRACE, "ExceptionCallback exit."); -} - -JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { - char *env_log_level = std::getenv("SENTRY_AGENT_LOG_LEVEL"); - if (env_log_level != nullptr) { - std::string env_log_level_str(env_log_level); - for (auto &c: env_log_level_str) c = (char) toupper(c); - if (env_log_level_str.compare("TRACE") == 0) { - LOG_LEVEL = TRACE; - } else if (env_log_level_str.compare("DEBUG") == 0) { - LOG_LEVEL = DEBUG; - } else if (env_log_level_str.compare("INFO") == 0) { - LOG_LEVEL = INFO; - } else if (env_log_level_str.compare("WARN") == 0) { - LOG_LEVEL = WARN; - } else if (env_log_level_str.compare("ERROR") == 0) { - LOG_LEVEL = ERROR; - } else { - log(ERROR, "Unknown log level: " + env_log_level_str); - return JNI_ABORT; - } - } - - log(TRACE, "OnLoad called."); - - jvmtiEnv *jvmti; - jint jvmti_error; - - jvmti_error = vm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_0); - if (jvmti_error != JVMTI_ERROR_NONE || jvmti == nullptr) { - log(ERROR, "Unable to access JVMTI Version 1."); - return JNI_ABORT; - } - - jvmtiCapabilities capabilities; - memset(&capabilities, 0, sizeof(capabilities)); - capabilities.can_access_local_variables = 1; - capabilities.can_generate_exception_events = 1; - capabilities.can_get_line_numbers = 1; - jint capabilities_error = jvmti->AddCapabilities(&capabilities); - if (capabilities_error != JVMTI_ERROR_NONE) { - log(ERROR, "Unable to get the necessary JVMTI capabilities."); - return JNI_ABORT; - } - - jvmtiEventCallbacks callbacks; - memset(&callbacks, 0, sizeof(callbacks)); - callbacks.Exception = &ExceptionCallback; - jvmti_error = jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)); - if (jvmti_error != JVMTI_ERROR_NONE) { - log(ERROR, "Unable to the necessary JVMTI callbacks."); - return JNI_ABORT; - } - - jvmti_error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, nullptr); - if (jvmti_error != JVMTI_ERROR_NONE) { - log(ERROR, "Unable to register the exception callback."); - return JNI_ABORT; - } - - log(TRACE, "OnLoad exit."); - return JNI_OK; -} diff --git a/agent/example.png b/agent/example.png deleted file mode 100644 index 2fa43a5adcf9e839e1cb877f30a7b8536bf3c8f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 84125 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 diff --git a/agent/lib.cpp b/agent/lib.cpp deleted file mode 100644 index 967af13739c..00000000000 --- a/agent/lib.cpp +++ /dev/null @@ -1,270 +0,0 @@ -#include -#include "lib.h" - -const std::string LEVEL_STRINGS[] = { - "TRACE", - "DEBUG", - "INFO", - "WARN", - "ERROR" -}; - -Level LOG_LEVEL = WARN; - -void log(Level level, std::string message) { - if (level >= LOG_LEVEL) { - std::cerr << LEVEL_STRINGS[level] << " [Sentry Agent]: " << message << std::endl; - } -} - -static jint throwException(JNIEnv *env, const char *name, const char *message) { - jclass clazz; - clazz = env->FindClass(name); - return env->ThrowNew(clazz, message); -} - -static jobject getLocalValue(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, - jvmtiLocalVariableEntry *table, int index) { - jobject result; - jint i_val; - jfloat f_val; - jdouble d_val; - jlong j_val; - jvmtiError jvmti_error; - jclass reflect_class; - jmethodID value_of; - - switch (table[index].signature[0]) { - case '[': // Array - case 'L': // Object - jvmti_error = jvmti->GetLocalObject(thread, depth, table[index].slot, &result); - if (jvmti_error != JVMTI_ERROR_NONE || result == nullptr) { - return nullptr; - } - - break; - case 'J': // long - jvmti_error = jvmti->GetLocalLong(thread, depth, table[index].slot, &j_val); - break; - case 'F': // float - jvmti_error = jvmti->GetLocalFloat(thread, depth, table[index].slot, &f_val); - break; - case 'D': // double - jvmti_error = jvmti->GetLocalDouble(thread, depth, table[index].slot, &d_val); - break; - case 'I': // int - case 'S': // short - case 'C': // char - case 'B': // byte - case 'Z': // boolean - jvmti_error = jvmti->GetLocalInt(thread, depth, table[index].slot, &i_val); - break; - // error type - default: - return nullptr; - } - - if (jvmti_error != JVMTI_ERROR_NONE) { - return nullptr; - } - - switch (table[index].signature[0]) { - case 'J': // long - reflect_class = env->FindClass("java/lang/Long"); - value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(J)Ljava/lang/Long;"); - result = env->CallStaticObjectMethod(reflect_class, value_of, j_val); - break; - case 'F': // float - reflect_class = env->FindClass("java/lang/Float"); - value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(F)Ljava/lang/Float;"); - result = env->CallStaticObjectMethod(reflect_class, value_of, f_val); - break; - case 'D': // double - reflect_class = env->FindClass("java/lang/Double"); - value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(D)Ljava/lang/Double;"); - result = env->CallStaticObjectMethod(reflect_class, value_of, d_val); - break; - // INTEGER TYPES - case 'I': // int - reflect_class = env->FindClass("java/lang/Integer"); - value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(I)Ljava/lang/Integer;"); - result = env->CallStaticObjectMethod(reflect_class, value_of, i_val); - break; - case 'S': // short - reflect_class = env->FindClass("java/lang/Short"); - value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(S)Ljava/lang/Short;"); - result = env->CallStaticObjectMethod(reflect_class, value_of, i_val); - break; - case 'C': // char - reflect_class = env->FindClass("java/lang/Character"); - value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(C)Ljava/lang/Character;"); - result = env->CallStaticObjectMethod(reflect_class, value_of, i_val); - break; - case 'B': // byte - reflect_class = env->FindClass("java/lang/Byte"); - value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(B)Ljava/lang/Byte;"); - result = env->CallStaticObjectMethod(reflect_class, value_of, i_val); - break; - case 'Z': // boolean - reflect_class = env->FindClass("java/lang/Boolean"); - value_of = env->GetStaticMethodID(reflect_class, "valueOf", "(Z)Ljava/lang/Boolean;"); - result = env->CallStaticObjectMethod(reflect_class, value_of, i_val); - break; - default: // jobject - break; - } - - return result; -} - -static void makeLocalVariable(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, - jint depth, jclass local_class, jmethodID live_ctor, - jlocation location, jobjectArray locals, - jvmtiLocalVariableEntry *table, int index) { - jstring name; - jobject value; - jobject local; - - name = env->NewStringUTF(table[index].name); - - if (location >= table[index].start_location && location <= (table[index].start_location + table[index].length)) { - value = getLocalValue(jvmti, env, thread, depth, table, index); - local = env->NewObject(local_class, live_ctor, name, value); - } else { - // dead object, use null - local = nullptr; - } - - env->SetObjectArrayElement(locals, index, local); -} - -static jobject makeFrameObject(jvmtiEnv* jvmti, JNIEnv *env, jmethodID method, jobjectArray locals) { - jvmtiError jvmti_error; - jclass method_class; - jobject frame_method; - jclass frame_class; - jmethodID ctor; - - jvmti_error = jvmti->GetMethodDeclaringClass(method, &method_class); - if (jvmti_error != JVMTI_ERROR_NONE) { - throwException(env, "java/lang/RuntimeException", "Could not get the declaring class of the method."); - return nullptr; - } - - frame_method = env->ToReflectedMethod(method_class, method, (jboolean) true); - if (frame_method == nullptr) { - return nullptr; // ToReflectedMethod raised an exception - } - - frame_class = env->FindClass("io/sentry/jvmti/Frame"); - if (frame_class == nullptr) { - return nullptr; - } - - ctor = env->GetMethodID(frame_class, "", - "(Ljava/lang/reflect/Method;[Lio/sentry/jvmti/Frame$LocalVariable;)V"); - if (ctor == nullptr) { - return nullptr; - } - - return env->NewObject(frame_class, ctor, frame_method, locals); -} - -static jobject buildFrame(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, jint depth, - jmethodID method, jlocation location) { - jvmtiError jvmti_error; - jvmtiLocalVariableEntry *local_var_table; - jint num_entries; - jobject value_ptr; - jobjectArray locals; - jclass local_class; - jmethodID live_ctor; - int i; - value_ptr = nullptr; - - jvmti_error = jvmti->GetLocalVariableTable(method, &num_entries, &local_var_table); - if (jvmti_error != JVMTI_ERROR_NONE) { - locals = nullptr; - switch(jvmti_error) { - // Pass cases - case JVMTI_ERROR_ABSENT_INFORMATION: - case JVMTI_ERROR_NATIVE_METHOD: - break; - // Error cases - case JVMTI_ERROR_MUST_POSSESS_CAPABILITY: - throwException(env, "java/lang/RuntimeException", "The access_local_variables capability is not enabled."); - return nullptr; - case JVMTI_ERROR_INVALID_METHODID: - throwException(env, "java/lang/IllegalArgumentException", "Illegal jmethodID."); - return nullptr; - case JVMTI_ERROR_NULL_POINTER: - throwException(env, "java/lang/NullPointerException", "Passed null to GetLocalVariableTable()."); - return nullptr; - default: - throwException(env, "java/lang/RuntimeException", "Unknown JVMTI Error."); - return nullptr; - } - } else { - local_class = env->FindClass("io/sentry/jvmti/Frame$LocalVariable"); - live_ctor = env->GetMethodID(local_class, "", "(Ljava/lang/String;Ljava/lang/Object;)V"); - locals = env->NewObjectArray(num_entries, local_class, nullptr); - for (i = 0; i < num_entries; i++) { - makeLocalVariable(jvmti, env, thread, depth, local_class, live_ctor, location, locals, local_var_table, i); - } - jvmti->Deallocate((unsigned char *) local_var_table); - } - - jvmti_error = jvmti->GetLocalObject(thread, depth, 0, &value_ptr); - if (jvmti_error != JVMTI_ERROR_NONE) { - value_ptr = nullptr; - } - - return makeFrameObject(jvmti, env, method, locals); -} - -jobjectArray buildStackTraceFrames(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, - jint start_depth, jint num_frames) { - log(TRACE, "buildStackTraceFrames called."); - - jvmtiFrameInfo* frames; - jclass result_class; - jint num_frames_returned; - jvmtiError jvmti_error; - jobjectArray result; - jobject frame; - - jvmti_error = jvmti->Allocate(num_frames * (int)sizeof(jvmtiFrameInfo), (unsigned char **) &frames); - if (jvmti_error != JVMTI_ERROR_NONE) { - throwException(env, "java/lang/RuntimeException", "Could not allocate frame buffer."); - return nullptr; - } - - jvmti_error = jvmti->GetStackTrace(thread, start_depth, num_frames, frames, &num_frames_returned); - if (jvmti_error != JVMTI_ERROR_NONE) { - jvmti->Deallocate((unsigned char *)frames); - throwException(env, "java/lang/RuntimeException", "Could not get stack trace."); - return nullptr; - } - - result_class = env->FindClass("io/sentry/jvmti/Frame"); - result = env->NewObjectArray(num_frames_returned, result_class, nullptr); - if (result == nullptr) { - jvmti->Deallocate((unsigned char *) frames); - return nullptr; // OutOfMemory - } - - for (int i = 0; i < num_frames_returned; i++) { - frame = buildFrame(jvmti, env, thread, start_depth + i, frames[i].method, frames[i].location); - if (frame == nullptr) { - jvmti->Deallocate((unsigned char *) frames); - throwException(env, "java/lang/RuntimeException", "Error accessing frame object."); - return nullptr; - } - env->SetObjectArrayElement(result, i, frame); - } - - jvmti->Deallocate((unsigned char *) frames); - - log(TRACE, "buildStackTraceFrames exit."); - return result; -} diff --git a/agent/lib.h b/agent/lib.h deleted file mode 100644 index 19ce0f71597..00000000000 --- a/agent/lib.h +++ /dev/null @@ -1,20 +0,0 @@ -#include -#include "jvmti.h" - -#ifndef SENTRY_JAVA_AGENT_LIB_H -#define SENTRY_JAVA_AGENT_LIB_H - -enum Level { - TRACE, - DEBUG, - INFO, - WARN, - ERROR -}; - -void log(Level level, std::string message); - -jobjectArray buildStackTraceFrames(jvmtiEnv* jvmti, JNIEnv *env, jthread thread, - jint start_depth, jint num_frames); - -#endif //SENTRY_JAVA_AGENT_LIB_H From bbe5176f9d578e5367a15c44a04a619e005def97 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com> Date: Wed, 12 Feb 2020 17:18:51 +0100 Subject: [PATCH 2141/2152] uploading native symbols plugin (#815) --- sentry-android-gradle-plugin/build.gradle | 2 +- .../sentry/android/gradle/SentryPlugin.groovy | 172 ++++++++++++++---- .../gradle/SentryPluginExtension.groovy | 16 ++ 3 files changed, 154 insertions(+), 36 deletions(-) diff --git a/sentry-android-gradle-plugin/build.gradle b/sentry-android-gradle-plugin/build.gradle index 57c7e0bd40b..caf0d36126f 100644 --- a/sentry-android-gradle-plugin/build.gradle +++ b/sentry-android-gradle-plugin/build.gradle @@ -20,7 +20,7 @@ compileGroovy { dependencies { compileOnly gradleApi() compileOnly localGroovy() - compileOnly 'com.android.tools.build:gradle:3.5.2' + compileOnly 'com.android.tools.build:gradle:3.5.3' } publishing { diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index c829140c786..3fa58024f17 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -267,40 +267,7 @@ class SentryPlugin implements Plugin { description "Write references to proguard UUIDs to the android assets." workingDir project.rootDir - def buildTypeName = variant.buildType.name - def flavorName = variant.flavorName - // When flavor is used in combination with dimensions, variant.flavorName will be a concatenation - // of flavors of different dimensions - def propName = "sentry.properties" - // current flavor name takes priority - def possibleProps = [] - variant.productFlavors.each { - // flavors used with dimension come in second - possibleProps.push("${project.projectDir}/src/${it.name}/${propName}") - } - - possibleProps = [ - "${project.projectDir}/src/${buildTypeName}/${propName}", - "${project.projectDir}/src/${buildTypeName}/${flavorName}/${propName}", - "${project.projectDir}/src/${flavorName}/${buildTypeName}/${propName}", - "${project.projectDir}/src/${flavorName}/${propName}", - "${project.rootDir.toPath()}/src/${flavorName}/${propName}", - ] + possibleProps + [ - "${project.rootDir.toPath()}/src/${buildTypeName}/${propName}", - "${project.rootDir.toPath()}/src/${buildTypeName}/${flavorName}/${propName}", - "${project.rootDir.toPath()}/src/${flavorName}/${buildTypeName}/${propName}", - // Root sentry.properties is the last to be looked up - "${project.rootDir.toPath()}/${propName}" - ] - - def propsFile = null - possibleProps.each { - project.logger.info("Looking for Sentry properties at: $it") - if (propsFile == null && new File(it).isFile()) { - propsFile = it - project.logger.info("Found Sentry properties in: $it") - } - } + def propsFile = getPropsString(project, variant) if (propsFile != null) { environment("SENTRY_PROPERTIES", propsFile) @@ -335,12 +302,72 @@ class SentryPlugin implements Plugin { args.add(buildTypeProperties.get(SENTRY_PROJECT_PARAMETER).toString()) } + project.logger.info("cli args: ${args.toString()}") + if (Os.isFamily(Os.FAMILY_WINDOWS)) { commandLine("cmd", "/c", *args) } else { commandLine(*args) } + project.logger.info("args executed.") + + enabled true + } + + // create and hooks the uploading of native symbols task after the assembling task + def variantOutputName = "${variant.name.capitalize()}${variantOutput.name.capitalize()}" + def uploadNativeSymbolsTaskName = "uploadNativeSymbolsFor${variantOutputName}" + def uploadNativeSymbolsTask = project.tasks.create( + name: uploadNativeSymbolsTaskName, + type: Exec) { + description "Uploads Native symbols." + workingDir project.rootDir + + def propsFile = getPropsString(project, variant) + + if (propsFile != null) { + environment("SENTRY_PROPERTIES", propsFile) + } else { + project.logger.info("propsFile is null") + } + + def nativeArgs = [ + cli, + "upload-dif", + ] + + def buildTypeProperties = variant.buildType.ext + if (buildTypeProperties.has(SENTRY_ORG_PARAMETER)) { + nativeArgs.add("-o") + nativeArgs.add(buildTypeProperties.get(SENTRY_ORG_PARAMETER).toString()) + } + if (buildTypeProperties.has(SENTRY_PROJECT_PARAMETER)) { + nativeArgs.add("-p") + nativeArgs.add(buildTypeProperties.get(SENTRY_PROJECT_PARAMETER).toString()) + } + + // eg absoluteProjectFolderPath/build/intermediates/merged_native_libs/{variant} where {variant} could be debug/release... + def symbolsPath = "${project.projectDir}${File.separator}build${File.separator}intermediates${File.separator}merged_native_libs${File.separator}${variant.name}" + project.logger.info("symbolsPath: ${symbolsPath}") + + nativeArgs.add("${symbolsPath}") + + // only include sources if includeNativeSources is enabled, this is opt-in feature + if (extension.includeNativeSources) { + nativeArgs.add("--include-sources") + } + + project.logger.info("nativeArgs args: ${nativeArgs.toString()}") + + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + commandLine("cmd", "/c", *nativeArgs) + } else { + commandLine(*nativeArgs) + } + + project.logger.info("nativeArgs executed.") + enabled true } @@ -350,13 +377,29 @@ class SentryPlugin implements Plugin { if (dexTask != null) { dexTask.dependsOn persistIdsTask } else { - proguardTask.finalizedBy(persistIdsTask) + proguardTask.finalizedBy persistIdsTask } // To include proguard uuid file into aab, run before bundle task. if (bundleTask != null) { bundleTask.dependsOn persistIdsTask } persistIdsTask.dependsOn proguardTask + + // find the assemble task + def assembleTask = findAssembleTask(variant) + if (assembleTask != null) { + project.logger.info("assembleTask ${assembleTask.path}") + } else { + assembleTask.logger.info("assembleTask is null") + } + + // uploadNativeSymbolsTask only will be executed after the assemble task + // and also only if uploadNativeSymbols is enabled, this is opt-in feature + if (assembleTask != null && extension.uploadNativeSymbols) { + assembleTask.finalizedBy uploadNativeSymbolsTask + } else { + assembleTask.logger.info("uploadNativeSymbolsTask won't be executed") + } } } } @@ -403,4 +446,63 @@ class SentryPlugin implements Plugin { project.logger.error("findAndroidManifestFileDir 5: ${ignored.getMessage()}") } } + + /** + * Returns the assemble task + * @param project the given project + * @param variant the given variant + * @return the task if found or null otherwise + */ + static Task findAssembleTask(ApplicationVariant variant) { + try { + return variant.assembleProvider.get() + } catch (Exception ignored) { + return variant.assemble + } + } + + /** + * Returns the GString with the current read'ed properties + * @param project the given project + * @param variant the given variant + * @return the GString if found or null otherwise + */ + static GString getPropsString(Project project, ApplicationVariant variant) { + def buildTypeName = variant.buildType.name + def flavorName = variant.flavorName + // When flavor is used in combination with dimensions, variant.flavorName will be a concatenation + // of flavors of different dimensions + def propName = "sentry.properties" + // current flavor name takes priority + def possibleProps = [] + variant.productFlavors.each { + // flavors used with dimension come in second + possibleProps.push("${project.projectDir}/src/${it.name}/${propName}") + } + + possibleProps = [ + "${project.projectDir}/src/${buildTypeName}/${propName}", + "${project.projectDir}/src/${buildTypeName}/${flavorName}/${propName}", + "${project.projectDir}/src/${flavorName}/${buildTypeName}/${propName}", + "${project.projectDir}/src/${flavorName}/${propName}", + "${project.rootDir.toPath()}/src/${flavorName}/${propName}", + ] + possibleProps + [ + "${project.rootDir.toPath()}/src/${buildTypeName}/${propName}", + "${project.rootDir.toPath()}/src/${buildTypeName}/${flavorName}/${propName}", + "${project.rootDir.toPath()}/src/${flavorName}/${buildTypeName}/${propName}", + // Root sentry.properties is the last to be looked up + "${project.rootDir.toPath()}/${propName}" + ] + + def propsFile = null + possibleProps.each { + project.logger.info("Looking for Sentry properties at: $it") + if (propsFile == null && new File(it).isFile()) { + propsFile = it + project.logger.info("Found Sentry properties in: $it") + } + } + + return propsFile + } } diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy index ee38db335e1..3939c0dbc56 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy @@ -4,4 +4,20 @@ class SentryPluginExtension { def boolean autoProguardConfig = true; def boolean autoUpload = true; def String manifestPath = null; + + /** + * Disables or enables the automatic configuration of Native Symbols + * for Sentry. This executes sentry-cli automatically so + * you don't need to do it manually. + * Default is disabled. + */ + def boolean uploadNativeSymbols = false; + + /** + * Includes or not the source code of native code for Sentry. + * This executes sentry-cli with the --include-sources param. automatically so + * you don't need to do it manually. + * Default is disabled. + */ + def boolean includeNativeSources = false; } From 2c26f5d0c7daa780c28b46ef77714a312f6ebc46 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Thu, 13 Feb 2020 16:36:55 +0100 Subject: [PATCH 2142/2152] BUMP sentry-cli --- sentry-android-gradle-plugin/download-sentry-cli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh index 16f4cafcd5d..1c20ccfa11d 100755 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ b/sentry-android-gradle-plugin/download-sentry-cli.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $(dirname "$0") REPO=getsentry/sentry-cli -VERSION=1.49.0 +VERSION=1.51.0 PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" rm -f src/main/resources/bin/sentry-cli-* From 50548993f20b14dd6648d0182a423bf86a2f6fe8 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Thu, 13 Feb 2020 16:37:53 +0100 Subject: [PATCH 2143/2152] chore: sentry gradle plugin 1.7.30 --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 4b328c3708d..98d555b9721 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.30-SNAPSHOT +version = 1.7.30 From 7d73454a0e3238dc9e3d54e0d442ccd7e181bf07 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Thu, 13 Feb 2020 16:52:30 +0100 Subject: [PATCH 2144/2152] added changes --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 01f0f4c1094..45633df86d0 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,7 @@ Version 1.7.30 - fixed context returns new Maps (#814) - added requireNonNull utils (#810) +- Android Gradle Plugin: Uploading native symbols and sources Version 1.7.29 -------------- From fa4a43f0fc5abff81d8294a48563e97adc3640ce Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Thu, 13 Feb 2020 16:55:08 +0100 Subject: [PATCH 2145/2152] prepare Gradle Plugin 1.7.31-SNAPSHOT --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 98d555b9721..8bf735546cb 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.30 +version = 1.7.31-SNAPSHOT From e7e150caed31cb11703f008daacc227e3213a668 Mon Sep 17 00:00:00 2001 From: Patrick Jiang Date: Sat, 15 Feb 2020 23:29:20 +0800 Subject: [PATCH 2146/2152] NoSuchBeanDefinitionException in spring-bean's old version (#807) --- sentry-spring-boot-starter/pom.xml | 3 +- .../SentryAutoConfiguration.java | 45 +++++++++++++++---- sentry-spring/pom.xml | 19 +++++--- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/sentry-spring-boot-starter/pom.xml b/sentry-spring-boot-starter/pom.xml index ae45e7fdc56..ab50a153b68 100644 --- a/sentry-spring-boot-starter/pom.xml +++ b/sentry-spring-boot-starter/pom.xml @@ -43,6 +43,7 @@ spring-boot-starter-test test + @@ -50,7 +51,7 @@ org.springframework.boot spring-boot-dependencies - 1.5.17.RELEASE + 1.5.1.RELEASE pom import diff --git a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryAutoConfiguration.java b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryAutoConfiguration.java index 4f7de43f622..76b6748d98d 100644 --- a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryAutoConfiguration.java +++ b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/autoconfigure/SentryAutoConfiguration.java @@ -10,11 +10,7 @@ import io.sentry.event.helper.ShouldSendEventCallback; import io.sentry.spring.SentryExceptionResolver; import io.sentry.spring.SentryServletContextInitializer; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.*; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.context.annotation.Bean; @@ -58,6 +54,39 @@ public ServletContextInitializer sentryServletContextInitializer() { return new SentryServletContextInitializer(); } + /** + * Initializes a {@link List}. + * + * @return a new instance of {@link List}. + */ + @Bean + @ConditionalOnMissingBean(EventBuilderHelper.class) + public List defaultEventBuilderHelpers() { + return Collections.emptyList(); + } + + /** + * Initializes a {@link List}. + * + * @return a new instance of {@link List}. + */ + @Bean + @ConditionalOnMissingBean(EventSendCallback.class) + public List defaultEventSendCallbacks() { + return Collections.emptyList(); + } + + /** + * Initializes a {@link List}. + * + * @return a new instance of {@link List}. + */ + @Bean + @ConditionalOnMissingBean(ShouldSendEventCallback.class) + public List defaultShouldSendEventCallbacks() { + return Collections.emptyList(); + } + /** * Initializes a {@link SentryClient}. * @@ -67,9 +96,9 @@ public ServletContextInitializer sentryServletContextInitializer() { @ConditionalOnMissingBean(SentryClient.class) @ConditionalOnProperty(name = "sentry.init-default-client", havingValue = "true", matchIfMissing = true) public SentryClient sentryClient(SentryProperties properties, - @Autowired(required = false) List eventBuilderHelpers, - @Autowired(required = false) List eventSendCallbacks, - @Autowired(required = false) List shouldSendEventCallbacks) { + List eventBuilderHelpers, + List eventSendCallbacks, + List shouldSendEventCallbacks) { String dsn = properties.getDsn() != null ? properties.getDsn().toString() : null; SentryOptions sentryOptions = SentryOptions.from(createLookup(properties), dsn, null); diff --git a/sentry-spring/pom.xml b/sentry-spring/pom.xml index e588b199c2f..1e0c92b5e13 100644 --- a/sentry-spring/pom.xml +++ b/sentry-spring/pom.xml @@ -14,11 +14,6 @@ Sentry-Java for Spring Spring exception handler and servlet initializer for sending exceptions to Sentry. - - 4.3.10.RELEASE - 1.5.4.RELEASE - - ${project.groupId} @@ -27,13 +22,11 @@ org.springframework spring-webmvc - ${spring.version} provided org.springframework.boot spring-boot - ${spring-boot.version} provided @@ -85,6 +78,18 @@ + + + + org.springframework.boot + spring-boot-dependencies + 1.5.1.RELEASE + pom + import + + + + From fba0e242f166490862178d282a7447671f758836 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com> Date: Mon, 17 Feb 2020 13:09:10 +0100 Subject: [PATCH 2147/2152] Support App Bundles (aab) and AGP 3.6.x, bug fix (#820) --- .../sentry/android/gradle/SentryPlugin.groovy | 102 ++++++++++++++---- 1 file changed, 80 insertions(+), 22 deletions(-) diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy index 3fa58024f17..b207f74d853 100644 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy @@ -118,17 +118,19 @@ class SentryPlugin implements Plugin { } /** - * Returns the proguard task for the given project and variant. + * Returns the transformer task for the given project and variant. + * It could be either ProGuard or R8 * - * @param project - * @param variant - * @return + * @param project the given project + * @param variant the given variant + * @return the task or null otherwise */ - static Task getProguardTask(Project project, ApplicationVariant variant) { + static Task getTransformerTask(Project project, ApplicationVariant variant) { def names = [ // Android Studio 3.3 includes the R8 shrinker. "transformClassesAndResourcesWithR8For${variant.name.capitalize()}", - "transformClassesAndResourcesWithProguardFor${variant.name.capitalize()}" + "transformClassesAndResourcesWithProguardFor${variant.name.capitalize()}", + "minify${variant.name.capitalize()}WithR8" ] return names.findResult { project.tasks.findByName(it) } ?: project.tasks.findByName("proguard${names[1]}") @@ -152,16 +154,27 @@ class SentryPlugin implements Plugin { } /** - * Returns the bundle task for the given project and variant. + * Returns the pre bundle task for the given project and variant. * * @param project * @param variant * @return */ - static Task getBundleTask(Project project, ApplicationVariant variant) { + static Task getPreBundleTask(Project project, ApplicationVariant variant) { return project.tasks.findByName("build${variant.name.capitalize()}PreBundle") } + /** + * Returns the pre bundle task for the given project and variant. + * + * @param project + * @param variant + * @return + */ + static Task getBundleTask(Project project, ApplicationVariant variant) { + return project.tasks.findByName("bundle${variant.name.capitalize()}") + } + /** * Returns the path to the debug meta properties file for the given variant. * @@ -221,7 +234,7 @@ class SentryPlugin implements Plugin { } def mappingFile = variant.getMappingFile() - def proguardTask = getProguardTask(project, variant) + def transformerTask = getTransformerTask(project, variant) def dexTask = getDexTask(project, variant) if (dexTask != null) { @@ -230,6 +243,13 @@ class SentryPlugin implements Plugin { project.logger.info("dexTask is null") } + def preBundleTask = getPreBundleTask(project, variant) + if (preBundleTask != null) { + project.logger.info("preBundleTask ${preBundleTask.path}") + } else { + project.logger.info("preBundleTask is null") + } + def bundleTask = getBundleTask(project, variant) if (bundleTask != null) { project.logger.info("bundleTask ${bundleTask.path}") @@ -237,11 +257,11 @@ class SentryPlugin implements Plugin { project.logger.info("bundleTask is null") } - if (proguardTask == null) { - project.logger.info("proguardTask is null") + if (transformerTask == null) { + project.logger.info("transformerTask is null") return } else { - project.logger.info("proguardTask ${proguardTask.path}") + project.logger.info("transformerTask ${transformerTask.path}") } // create a task to configure proguard automatically unless the user disabled it. @@ -253,7 +273,7 @@ class SentryPlugin implements Plugin { SentryProguardConfigTask) proguardConfigTask.group = GROUP_NAME proguardConfigTask.applicationVariant = variant - proguardTask.dependsOn proguardConfigTask + transformerTask.dependsOn proguardConfigTask } } @@ -371,19 +391,35 @@ class SentryPlugin implements Plugin { enabled true } + // and run before dex transformation. If we managed to find the dex task // we set ourselves as dependency, otherwise we just hack outselves into // the proguard task's doLast. if (dexTask != null) { dexTask.dependsOn persistIdsTask - } else { - proguardTask.finalizedBy persistIdsTask } + + if (transformerTask != null) { + transformerTask.finalizedBy persistIdsTask + } + // To include proguard uuid file into aab, run before bundle task. - if (bundleTask != null) { - bundleTask.dependsOn persistIdsTask + if (preBundleTask != null) { + preBundleTask.dependsOn persistIdsTask + } + + // find the package task + def packageTask = getPackageTask(project, variant) + if (packageTask != null) { + project.logger.info("packageTask ${packageTask.path}") + } else { + packageTask.logger.info("packageTask is null") + } + + // the package task will only be executed if the persistIdsTask has already been executed. + if (packageTask != null) { + packageTask.dependsOn persistIdsTask } - persistIdsTask.dependsOn proguardTask // find the assemble task def assembleTask = findAssembleTask(variant) @@ -395,10 +431,17 @@ class SentryPlugin implements Plugin { // uploadNativeSymbolsTask only will be executed after the assemble task // and also only if uploadNativeSymbols is enabled, this is opt-in feature - if (assembleTask != null && extension.uploadNativeSymbols) { - assembleTask.finalizedBy uploadNativeSymbolsTask - } else { - assembleTask.logger.info("uploadNativeSymbolsTask won't be executed") + if (assembleTask != null) { + if (extension.uploadNativeSymbols) { + assembleTask.finalizedBy uploadNativeSymbolsTask + + // if its a bundle aab, assemble might not be executed, so we hook into bundle task + if (bundleTask != null) { + bundleTask.finalizedBy uploadNativeSymbolsTask + } + } else { + assembleTask.logger.info("uploadNativeSymbolsTask won't be executed") + } } } } @@ -505,4 +548,19 @@ class SentryPlugin implements Plugin { return propsFile } + + /** + * Returns the package task + * @param project the given project + * @param variant the given variant + * @return the package task or null if not found + */ + static Task getPackageTask(Project project, ApplicationVariant variant) { + def names = [ + "package${variant.name.capitalize()}", + "package${variant.name.capitalize()}Bundle" + ] + + return names.findResult { project.tasks.findByName(it) } + } } From 511367bbf7466884858dcea754d6c3365b3fa529 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Mon, 17 Feb 2020 15:08:21 +0100 Subject: [PATCH 2148/2152] chore: Sentry Gradle PLugin 1.7.31 --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 8bf735546cb..12e145967bf 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.31-SNAPSHOT +version = 1.7.31 From 0cde8f51172120001becfae071be428de022a7d1 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Mon, 17 Feb 2020 15:18:12 +0100 Subject: [PATCH 2149/2152] added changes to CHANGES file --- CHANGES | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 45633df86d0..31412798db7 100644 --- a/CHANGES +++ b/CHANGES @@ -1,8 +1,15 @@ -Version 1.7.31 +Version 1.7.32 -------------- - +Version 1.7.31 +-------------- + +- Android Gradle Plugin: Support App Bundles (aab) #818 +- Android Gradle Plugin: Support AGP 3.6.x +- Android Gradle Plugin: Bug fixing #740 + Version 1.7.30 -------------- From c1dc725a660ead79611405ff1c01be7dee9c895b Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Mon, 17 Feb 2020 15:18:58 +0100 Subject: [PATCH 2150/2152] prepare Sentry Gradle Plugin 1.7.32-SNAPSHOT --- sentry-android-gradle-plugin/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties index 12e145967bf..02d747c1c44 100644 --- a/sentry-android-gradle-plugin/gradle.properties +++ b/sentry-android-gradle-plugin/gradle.properties @@ -1,3 +1,3 @@ name = sentry-android-gradle-plugin group = io.sentry -version = 1.7.31 +version = 1.7.32-SNAPSHOT From 7b85eed07774eb67f8f4102f3524be1afee7d3f0 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 17 Feb 2020 11:19:47 -0500 Subject: [PATCH 2151/2152] pull out sentry-android-gradle-plugin (#821) --- README.md | 3 + sentry-android-gradle-plugin/.gitignore | 8 - sentry-android-gradle-plugin/Makefile | 5 - sentry-android-gradle-plugin/build.gradle | 71 --- .../download-sentry-cli.sh | 18 - .../gradle.properties | 3 - .../gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 - sentry-android-gradle-plugin/gradlew | 188 ------ sentry-android-gradle-plugin/gradlew.bat | 100 ---- .../sentry/android/gradle/SentryPlugin.groovy | 566 ------------------ .../gradle/SentryPluginExtension.groovy | 23 - .../gradle/SentryProguardConfigTask.groovy | 31 - .../io.sentry.android.gradle.properties | 1 - .../src/main/resources/bin/.gitignore | 1 - 15 files changed, 3 insertions(+), 1020 deletions(-) delete mode 100644 sentry-android-gradle-plugin/.gitignore delete mode 100644 sentry-android-gradle-plugin/Makefile delete mode 100644 sentry-android-gradle-plugin/build.gradle delete mode 100755 sentry-android-gradle-plugin/download-sentry-cli.sh delete mode 100644 sentry-android-gradle-plugin/gradle.properties delete mode 100644 sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.jar delete mode 100644 sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.properties delete mode 100755 sentry-android-gradle-plugin/gradlew delete mode 100644 sentry-android-gradle-plugin/gradlew.bat delete mode 100644 sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy delete mode 100644 sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy delete mode 100644 sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy delete mode 100644 sentry-android-gradle-plugin/src/main/resources/META-INF/gradle-plugins/io.sentry.android.gradle.properties delete mode 100644 sentry-android-gradle-plugin/src/main/resources/bin/.gitignore diff --git a/README.md b/README.md index 143db951e81..4678d6f260c 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,9 @@ In most cases using one of the existing integrations is preferred, but Sentry ad a low level client for manually building and sending events to Sentry that can be used in any JVM based application. +> The package [sentry-android-gradle-plugin was moved to its own repository.](https://github.com/getsentry/sentry-android-gradle-plugin). + + ## Resources * [Documentation](https://docs.sentry.io/clients/java/) diff --git a/sentry-android-gradle-plugin/.gitignore b/sentry-android-gradle-plugin/.gitignore deleted file mode 100644 index 429a909cf3b..00000000000 --- a/sentry-android-gradle-plugin/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -.gradle/ -.idea/ -build/ -out/ -.classpath -.project -.settings/ -bin/ diff --git a/sentry-android-gradle-plugin/Makefile b/sentry-android-gradle-plugin/Makefile deleted file mode 100644 index 56d3cca154e..00000000000 --- a/sentry-android-gradle-plugin/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -.PHONY: release - -release: - ./download-sentry-cli.sh - ./gradlew uploadArchives --no-daemon diff --git a/sentry-android-gradle-plugin/build.gradle b/sentry-android-gradle-plugin/build.gradle deleted file mode 100644 index caf0d36126f..00000000000 --- a/sentry-android-gradle-plugin/build.gradle +++ /dev/null @@ -1,71 +0,0 @@ -plugins { - id 'groovy' - id 'com.bmuschko.nexus' version '2.3.1' -} -apply plugin: 'maven-publish' - - -repositories { - jcenter() - mavenLocal() - mavenCentral() - google() -} - -compileGroovy { - sourceCompatibility = '1.6' - targetCompatibility = '1.6' -} - -dependencies { - compileOnly gradleApi() - compileOnly localGroovy() - compileOnly 'com.android.tools.build:gradle:3.5.3' -} - -publishing { - publications { - Publication(MavenPublication) { - artifact jar - groupId 'io.sentry' - artifactId 'sentry-android-gradle-plugin' - version project.version - } - } - - repositories { - maven { - url "$buildDir/repo" - } - } -} - -modifyPom { - project { - name 'Sentry Android Gradle Plugin' - description 'Sentry Android Gradle Plugin' - url 'https://github.com/getsentry/sentry-java' - - scm { - url 'https://github.com/getsentry/sentry-java' - connection 'scm:https://github.com/getsentry/sentry-java.git' - developerConnection 'scm:git@github.com:getsentry/sentry-java.git' - } - - licenses { - license { - name 'BSD' - url 'https://github.com/getsentry/sentry-java/blob/master/LICENSE' - distribution 'repo' - } - } - - developers { - developer { - id 'bretthoerner' - name 'Brett Hoerner' - email 'brett@sentry.io' - } - } - } -} diff --git a/sentry-android-gradle-plugin/download-sentry-cli.sh b/sentry-android-gradle-plugin/download-sentry-cli.sh deleted file mode 100755 index 1c20ccfa11d..00000000000 --- a/sentry-android-gradle-plugin/download-sentry-cli.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -cd $(dirname "$0") -REPO=getsentry/sentry-cli -VERSION=1.51.0 -PLATFORMS="Darwin-x86_64 Linux-i686 Linux-x86_64 Windows-i686" - -rm -f src/main/resources/bin/sentry-cli-* -for plat in $PLATFORMS; do - suffix='' - if [[ $plat == *"Windows"* ]]; then - suffix='.exe' - fi - echo "${plat}" - download_url=https://github.com/$REPO/releases/download/$VERSION/sentry-cli-${plat}${suffix} - fn="src/main/resources/bin/sentry-cli-${plat}${suffix}" - curl -SL --progress-bar "$download_url" -o "$fn" - chmod +x "$fn" -done diff --git a/sentry-android-gradle-plugin/gradle.properties b/sentry-android-gradle-plugin/gradle.properties deleted file mode 100644 index 02d747c1c44..00000000000 --- a/sentry-android-gradle-plugin/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -name = sentry-android-gradle-plugin -group = io.sentry -version = 1.7.32-SNAPSHOT diff --git a/sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/sentry-android-gradle-plugin/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55616 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 \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - -exec "$JAVACMD" "$@" diff --git a/sentry-android-gradle-plugin/gradlew.bat b/sentry-android-gradle-plugin/gradlew.bat deleted file mode 100644 index 15e1ee37a70..00000000000 --- a/sentry-android-gradle-plugin/gradlew.bat +++ /dev/null @@ -1,100 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem http://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy deleted file mode 100644 index b207f74d853..00000000000 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPlugin.groovy +++ /dev/null @@ -1,566 +0,0 @@ -package io.sentry.android.gradle - -import com.android.build.gradle.AppPlugin -import com.android.build.gradle.LibraryPlugin -import com.android.build.gradle.api.ApplicationVariant -import com.android.build.gradle.api.BaseVariantOutput -import org.apache.commons.compress.utils.IOUtils -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.tasks.Exec -import org.apache.tools.ant.taskdefs.condition.Os - -class SentryPlugin implements Plugin { - static final String GROUP_NAME = 'Sentry' - private static final String SENTRY_ORG_PARAMETER = "sentryOrg" - private static final String SENTRY_PROJECT_PARAMETER = "sentryProject" - - /** - * Return the correct sentry-cli executable path to use for the given project. This - * will look for a sentry-cli executable in a local node_modules in case it was put - * there by sentry-react-native or others before falling back to the global installation. - * - * @param project - * @return - */ - static String getSentryCli(Project project) { - // if a path is provided explicitly use that first - def propertiesFile = "${project.rootDir.toPath()}/sentry.properties" - project.logger.info("propertiesFile: ${propertiesFile}") - - Properties sentryProps = new Properties() - try { - sentryProps.load(new FileInputStream(propertiesFile)) - } catch (FileNotFoundException ignored) { - project.logger.error(ignored.getMessage()) - // it's okay, we can ignore it. - } - - def rv = sentryProps.getProperty("cli.executable") - if (rv != null) { - return rv - } else { - project.logger.info("cli.executable is null") - } - - // in case there is a version from npm right around the corner use that one. This - // is the case for react-native-sentry for instance - def possibleExePaths = [ - "${project.rootDir.toPath()}/../node_modules/@sentry/cli/bin/sentry-cli", - "${project.rootDir.toPath()}/../node_modules/sentry-cli-binary/bin/sentry-cli" - ] - - possibleExePaths.each { - if ((new File(it)).exists()) { - project.logger.info("possibleExePaths: ${it}") - return it - } - if ((new File(it + ".exe")).exists()) { - project.logger.info("possibleExePaths: ${it}.exe") - return it + ".exe" - } - project.logger.info("possibleExePaths files dont exist") - } - - // next up try a packaged version of sentry-cli - def cliSuffix - def osName = System.getProperty("os.name").toLowerCase() - project.logger.info("osName: ${osName}") - - if (osName.indexOf("mac") >= 0) { - cliSuffix = "Darwin-x86_64" - } else if (osName.indexOf("linux") >= 0) { - def arch = System.getProperty("os.arch") - if (arch == "amd64") { - arch = "x86_64" - } - cliSuffix = "Linux-" + arch - } else if (osName.indexOf("win") >= 0) { - cliSuffix = "Windows-i686.exe" - } else { - project.logger.info("cliSuffix not assigned") - } - - if (cliSuffix != null) { - def resPath = "/bin/sentry-cli-${cliSuffix}" - def fsPath = SentryPlugin.class.getResource(resPath).getFile() - - // if we are not in a jar, we can use the file directly - if ((new File(fsPath)).exists()) { - project.logger.info("fsPath: ${fsPath}") - return fsPath - } else { - project.logger.info("fsPath doesnt exist") - } - - // otherwise we need to unpack into a file - def resStream = SentryPlugin.class.getResourceAsStream(resPath) - File tempFile = File.createTempFile(".sentry-cli", ".exe") - if (tempFile != null) { - project.logger.info("tempFile: ${tempFile.path}") - } else { - project.logger.info("tempFile is null") - } - - tempFile.deleteOnExit() - def out = new FileOutputStream(tempFile) - try { - IOUtils.copy(resStream, out) - } finally { - out.close() - } - tempFile.setExecutable(true) - return tempFile.getAbsolutePath() - } - - return "sentry-cli" - } - - /** - * Returns the transformer task for the given project and variant. - * It could be either ProGuard or R8 - * - * @param project the given project - * @param variant the given variant - * @return the task or null otherwise - */ - static Task getTransformerTask(Project project, ApplicationVariant variant) { - def names = [ - // Android Studio 3.3 includes the R8 shrinker. - "transformClassesAndResourcesWithR8For${variant.name.capitalize()}", - "transformClassesAndResourcesWithProguardFor${variant.name.capitalize()}", - "minify${variant.name.capitalize()}WithR8" - ] - - return names.findResult { project.tasks.findByName(it) } ?: project.tasks.findByName("proguard${names[1]}") - } - - /** - * Returns the dex task for the given project and variant. - * - * @param project - * @param variant - * @return - */ - static Task getDexTask(Project project, ApplicationVariant variant) { - def names = [ - "transformClassesWithDexFor${variant.name.capitalize()}", - "transformClassesWithDexBuilderFor${variant.name.capitalize()}", - "transformClassesAndDexWithShrinkResFor${variant.name.capitalize()}" - ] - - return names.findResult { project.tasks.findByName(it) } ?: project.tasks.findByName("dex${names[0]}") - } - - /** - * Returns the pre bundle task for the given project and variant. - * - * @param project - * @param variant - * @return - */ - static Task getPreBundleTask(Project project, ApplicationVariant variant) { - return project.tasks.findByName("build${variant.name.capitalize()}PreBundle") - } - - /** - * Returns the pre bundle task for the given project and variant. - * - * @param project - * @param variant - * @return - */ - static Task getBundleTask(Project project, ApplicationVariant variant) { - return project.tasks.findByName("bundle${variant.name.capitalize()}") - } - - /** - * Returns the path to the debug meta properties file for the given variant. - * - * @param project - * @param variant - * @return - */ - static String getDebugMetaPropPath(Project project, ApplicationVariant variant) { - try { - return variant.mergeAssetsProvider.get().outputDir.get().file("sentry-debug-meta.properties").getAsFile().path - } catch (Exception ignored) { - project.logger.error("getDebugMetaPropPath 1: ${ignored.getMessage()}") - } - - try { - return variant.mergeAssets.outputDir.get().file("sentry-debug-meta.properties").getAsFile().path - } catch (Exception ignored) { - project.logger.error("getDebugMetaPropPath 2: ${ignored.getMessage()}") - } - - try { - return "${variant.mergeAssets.outputDir.get().asFile.path}/sentry-debug-meta.properties" - } catch (Exception ignored) { - project.logger.error("getDebugMetaPropPath 3: ${ignored.getMessage()}") - } - - try { - return "${variant.mergeAssets.outputDir}/sentry-debug-meta.properties" - } catch (Exception ignored) { - project.logger.error("getDebugMetaPropPath 4: ${ignored.getMessage()}") - } - } - - void apply(Project project) { - SentryPluginExtension extension = project.extensions.create("sentry", SentryPluginExtension) - - project.afterEvaluate { - if (!project.plugins.hasPlugin(AppPlugin) && !project.getPlugins().hasPlugin(LibraryPlugin)) { - throw new IllegalStateException('Must apply \'com.android.application\' first!') - } - - project.android.applicationVariants.all { ApplicationVariant variant -> - variant.outputs.each { variantOutput -> - def manifestPath = extension.manifestPath - if (manifestPath == null) { - def dir = findAndroidManifestFileDir(project, variantOutput) - if (dir != null) { - project.logger.info("manifestDir: ${dir.path}") - } else { - project.logger.info("manifestDir is null") - } - - manifestPath = new File(new File(dir, variantOutput.dirName), "AndroidManifest.xml") - project.logger.info("manifestPath: ${manifestPath}") - } else { - project.logger.info("manifestPath: ${manifestPath}") - } - - def mappingFile = variant.getMappingFile() - def transformerTask = getTransformerTask(project, variant) - - def dexTask = getDexTask(project, variant) - if (dexTask != null) { - project.logger.info("dexTask ${dexTask.path}") - } else { - project.logger.info("dexTask is null") - } - - def preBundleTask = getPreBundleTask(project, variant) - if (preBundleTask != null) { - project.logger.info("preBundleTask ${preBundleTask.path}") - } else { - project.logger.info("preBundleTask is null") - } - - def bundleTask = getBundleTask(project, variant) - if (bundleTask != null) { - project.logger.info("bundleTask ${bundleTask.path}") - } else { - project.logger.info("bundleTask is null") - } - - if (transformerTask == null) { - project.logger.info("transformerTask is null") - return - } else { - project.logger.info("transformerTask ${transformerTask.path}") - } - -// create a task to configure proguard automatically unless the user disabled it. - if (extension.autoProguardConfig) { - def addProguardSettingsTaskName = "addSentryProguardSettingsFor${variant.name.capitalize()}" - if (!project.tasks.findByName(addProguardSettingsTaskName)) { - SentryProguardConfigTask proguardConfigTask = project.tasks.create( - addProguardSettingsTaskName, - SentryProguardConfigTask) - proguardConfigTask.group = GROUP_NAME - proguardConfigTask.applicationVariant = variant - transformerTask.dependsOn proguardConfigTask - } - } - - def cli = getSentryCli(project) - - def persistIdsTaskName = "persistSentryProguardUuidsFor${variant.name.capitalize()}${variantOutput.name.capitalize()}" - // create a task that persists our proguard uuid as android asset - def persistIdsTask = project.tasks.create( - name: persistIdsTaskName, - type: Exec) { - description "Write references to proguard UUIDs to the android assets." - workingDir project.rootDir - - def propsFile = getPropsString(project, variant) - - if (propsFile != null) { - environment("SENTRY_PROPERTIES", propsFile) - } else { - project.logger.info("propsFile is null") - } - - def debugMetaPropPath = getDebugMetaPropPath(project, variant) - project.logger.info("debugMetaPropPath: ${debugMetaPropPath}") - - def args = [ - cli, - "upload-proguard", - "--android-manifest", - manifestPath, - "--write-properties", - debugMetaPropPath, - mappingFile - ] - - if (!extension.autoUpload) { - args << "--no-upload" - } - - def buildTypeProperties = variant.buildType.ext - if (buildTypeProperties.has(SENTRY_ORG_PARAMETER)) { - args.add("--org") - args.add(buildTypeProperties.get(SENTRY_ORG_PARAMETER).toString()) - } - if (buildTypeProperties.has(SENTRY_PROJECT_PARAMETER)) { - args.add("--project") - args.add(buildTypeProperties.get(SENTRY_PROJECT_PARAMETER).toString()) - } - - project.logger.info("cli args: ${args.toString()}") - - if (Os.isFamily(Os.FAMILY_WINDOWS)) { - commandLine("cmd", "/c", *args) - } else { - commandLine(*args) - } - - project.logger.info("args executed.") - - enabled true - } - - // create and hooks the uploading of native symbols task after the assembling task - def variantOutputName = "${variant.name.capitalize()}${variantOutput.name.capitalize()}" - def uploadNativeSymbolsTaskName = "uploadNativeSymbolsFor${variantOutputName}" - def uploadNativeSymbolsTask = project.tasks.create( - name: uploadNativeSymbolsTaskName, - type: Exec) { - description "Uploads Native symbols." - workingDir project.rootDir - - def propsFile = getPropsString(project, variant) - - if (propsFile != null) { - environment("SENTRY_PROPERTIES", propsFile) - } else { - project.logger.info("propsFile is null") - } - - def nativeArgs = [ - cli, - "upload-dif", - ] - - def buildTypeProperties = variant.buildType.ext - if (buildTypeProperties.has(SENTRY_ORG_PARAMETER)) { - nativeArgs.add("-o") - nativeArgs.add(buildTypeProperties.get(SENTRY_ORG_PARAMETER).toString()) - } - if (buildTypeProperties.has(SENTRY_PROJECT_PARAMETER)) { - nativeArgs.add("-p") - nativeArgs.add(buildTypeProperties.get(SENTRY_PROJECT_PARAMETER).toString()) - } - - // eg absoluteProjectFolderPath/build/intermediates/merged_native_libs/{variant} where {variant} could be debug/release... - def symbolsPath = "${project.projectDir}${File.separator}build${File.separator}intermediates${File.separator}merged_native_libs${File.separator}${variant.name}" - project.logger.info("symbolsPath: ${symbolsPath}") - - nativeArgs.add("${symbolsPath}") - - // only include sources if includeNativeSources is enabled, this is opt-in feature - if (extension.includeNativeSources) { - nativeArgs.add("--include-sources") - } - - project.logger.info("nativeArgs args: ${nativeArgs.toString()}") - - if (Os.isFamily(Os.FAMILY_WINDOWS)) { - commandLine("cmd", "/c", *nativeArgs) - } else { - commandLine(*nativeArgs) - } - - project.logger.info("nativeArgs executed.") - - enabled true - } - - - // and run before dex transformation. If we managed to find the dex task - // we set ourselves as dependency, otherwise we just hack outselves into - // the proguard task's doLast. - if (dexTask != null) { - dexTask.dependsOn persistIdsTask - } - - if (transformerTask != null) { - transformerTask.finalizedBy persistIdsTask - } - - // To include proguard uuid file into aab, run before bundle task. - if (preBundleTask != null) { - preBundleTask.dependsOn persistIdsTask - } - - // find the package task - def packageTask = getPackageTask(project, variant) - if (packageTask != null) { - project.logger.info("packageTask ${packageTask.path}") - } else { - packageTask.logger.info("packageTask is null") - } - - // the package task will only be executed if the persistIdsTask has already been executed. - if (packageTask != null) { - packageTask.dependsOn persistIdsTask - } - - // find the assemble task - def assembleTask = findAssembleTask(variant) - if (assembleTask != null) { - project.logger.info("assembleTask ${assembleTask.path}") - } else { - assembleTask.logger.info("assembleTask is null") - } - - // uploadNativeSymbolsTask only will be executed after the assemble task - // and also only if uploadNativeSymbols is enabled, this is opt-in feature - if (assembleTask != null) { - if (extension.uploadNativeSymbols) { - assembleTask.finalizedBy uploadNativeSymbolsTask - - // if its a bundle aab, assemble might not be executed, so we hook into bundle task - if (bundleTask != null) { - bundleTask.finalizedBy uploadNativeSymbolsTask - } - } else { - assembleTask.logger.info("uploadNativeSymbolsTask won't be executed") - } - } - } - } - } - } - - static File findAndroidManifestFileDir(Project project, BaseVariantOutput variantOutput) { - // Gradle 4.7 introduced the lazy task API and AGP 3.3+ adopts that, - // so we apparently have a Provider here instead - // TODO: This will let us depend on the configuration of each flavor's - // manifest creation task and their transitive dependencies, which in - // turn prolongs the configuration time accordingly. Evaluate how Gradle's - // new Task Avoidance API can be used instead. - // (https://docs.gradle.org/current/userguide/task_configuration_avoidance.html) - - try { // Android Gradle Plugin >= 3.3.0 - return variantOutput.processManifestProvider.get().manifestOutputDirectory.get().asFile - } catch (Exception ignored) { - project.logger.error("findAndroidManifestFileDir 1: ${ignored.getMessage()}") - } - - try { // Android Gradle Plugin >= 3.0.0 - return variantOutput.processManifest.manifestOutputDirectory.get().asFile - } catch (Exception ignored) { - project.logger.error("findAndroidManifestFileDir 2: ${ignored.getMessage()}") - } - - // Android Gradle Plugin < 3.0.0 - try { - return new File(variantOutput.processManifest.manifestOutputFile).parentFile - } catch (Exception ignored) { - project.logger.error("findAndroidManifestFileDir 3: ${ignored.getMessage()}") - } - - // https://github.com/Tencent/tinker/pull/620/files - try { - return variantOutput.processResourcesProvider.get().manifestFile.parentFile - } catch (Exception ignored) { - project.logger.error("findAndroidManifestFileDir 4: ${ignored.getMessage()}") - } - - try { - return variantOutput.processResources.manifestFile.parentFile - } catch (Exception ignored) { - project.logger.error("findAndroidManifestFileDir 5: ${ignored.getMessage()}") - } - } - - /** - * Returns the assemble task - * @param project the given project - * @param variant the given variant - * @return the task if found or null otherwise - */ - static Task findAssembleTask(ApplicationVariant variant) { - try { - return variant.assembleProvider.get() - } catch (Exception ignored) { - return variant.assemble - } - } - - /** - * Returns the GString with the current read'ed properties - * @param project the given project - * @param variant the given variant - * @return the GString if found or null otherwise - */ - static GString getPropsString(Project project, ApplicationVariant variant) { - def buildTypeName = variant.buildType.name - def flavorName = variant.flavorName - // When flavor is used in combination with dimensions, variant.flavorName will be a concatenation - // of flavors of different dimensions - def propName = "sentry.properties" - // current flavor name takes priority - def possibleProps = [] - variant.productFlavors.each { - // flavors used with dimension come in second - possibleProps.push("${project.projectDir}/src/${it.name}/${propName}") - } - - possibleProps = [ - "${project.projectDir}/src/${buildTypeName}/${propName}", - "${project.projectDir}/src/${buildTypeName}/${flavorName}/${propName}", - "${project.projectDir}/src/${flavorName}/${buildTypeName}/${propName}", - "${project.projectDir}/src/${flavorName}/${propName}", - "${project.rootDir.toPath()}/src/${flavorName}/${propName}", - ] + possibleProps + [ - "${project.rootDir.toPath()}/src/${buildTypeName}/${propName}", - "${project.rootDir.toPath()}/src/${buildTypeName}/${flavorName}/${propName}", - "${project.rootDir.toPath()}/src/${flavorName}/${buildTypeName}/${propName}", - // Root sentry.properties is the last to be looked up - "${project.rootDir.toPath()}/${propName}" - ] - - def propsFile = null - possibleProps.each { - project.logger.info("Looking for Sentry properties at: $it") - if (propsFile == null && new File(it).isFile()) { - propsFile = it - project.logger.info("Found Sentry properties in: $it") - } - } - - return propsFile - } - - /** - * Returns the package task - * @param project the given project - * @param variant the given variant - * @return the package task or null if not found - */ - static Task getPackageTask(Project project, ApplicationVariant variant) { - def names = [ - "package${variant.name.capitalize()}", - "package${variant.name.capitalize()}Bundle" - ] - - return names.findResult { project.tasks.findByName(it) } - } -} diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy deleted file mode 100644 index 3939c0dbc56..00000000000 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryPluginExtension.groovy +++ /dev/null @@ -1,23 +0,0 @@ -package io.sentry.android.gradle - -class SentryPluginExtension { - def boolean autoProguardConfig = true; - def boolean autoUpload = true; - def String manifestPath = null; - - /** - * Disables or enables the automatic configuration of Native Symbols - * for Sentry. This executes sentry-cli automatically so - * you don't need to do it manually. - * Default is disabled. - */ - def boolean uploadNativeSymbols = false; - - /** - * Includes or not the source code of native code for Sentry. - * This executes sentry-cli with the --include-sources param. automatically so - * you don't need to do it manually. - * Default is disabled. - */ - def boolean includeNativeSources = false; -} diff --git a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy b/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy deleted file mode 100644 index 62835d7a399..00000000000 --- a/sentry-android-gradle-plugin/src/main/groovy/io/sentry/android/gradle/SentryProguardConfigTask.groovy +++ /dev/null @@ -1,31 +0,0 @@ -package io.sentry.android.gradle - -import org.gradle.api.DefaultTask -import org.gradle.api.tasks.TaskAction - -import com.android.build.gradle.api.ApplicationVariant - -class SentryProguardConfigTask extends DefaultTask { - - ApplicationVariant applicationVariant - - SentryProguardConfigTask() { - super() - this.description = "Adds the Sentry recommended proguard settings to your project." - } - - @TaskAction - def createProguardConfig() { - def file = project.file("build/intermediates/sentry/sentry.pro") - file.getParentFile().mkdirs() - FileWriter f = new FileWriter(file.path) - f.write("-keepattributes LineNumberTable,SourceFile\n" + - "-dontwarn com.facebook.fbui.**\n" + - "-dontwarn org.slf4j.**\n" + - "-dontwarn javax.**\n" + - "-keep class * extends java.lang.Exception\n" + - "-keep class io.sentry.event.Event { *; }\n") - f.close() - applicationVariant.getBuildType().buildType.proguardFiles(file) - } -} diff --git a/sentry-android-gradle-plugin/src/main/resources/META-INF/gradle-plugins/io.sentry.android.gradle.properties b/sentry-android-gradle-plugin/src/main/resources/META-INF/gradle-plugins/io.sentry.android.gradle.properties deleted file mode 100644 index bf34fdd1a69..00000000000 --- a/sentry-android-gradle-plugin/src/main/resources/META-INF/gradle-plugins/io.sentry.android.gradle.properties +++ /dev/null @@ -1 +0,0 @@ -implementation-class=io.sentry.android.gradle.SentryPlugin diff --git a/sentry-android-gradle-plugin/src/main/resources/bin/.gitignore b/sentry-android-gradle-plugin/src/main/resources/bin/.gitignore deleted file mode 100644 index f65d58f9047..00000000000 --- a/sentry-android-gradle-plugin/src/main/resources/bin/.gitignore +++ /dev/null @@ -1 +0,0 @@ -sentry-cli-* From 030beb750dca15aba3769f7ca25ec172b4aaa5b2 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 17 Feb 2020 12:27:41 -0500 Subject: [PATCH 2152/2152] resources --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4678d6f260c..1999c9a62a8 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,9 @@ based application. ## Resources -* [Documentation](https://docs.sentry.io/clients/java/) * [Examples](https://github.com/getsentry/examples) -* [Bug Tracker](http://github.com/getsentry/sentry-java/issues) -* [Code](http://github.com/getsentry/sentry-java) +* [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/clients/java/) +* [![Forum](https://img.shields.io/badge/forum-sentry-green.svg)](https://forum.sentry.io/c/sdks) * [![Discord](https://img.shields.io/discord/621778831602221064)](https://discord.gg/Ww9hbqr) +* [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-sentry-green.svg)](http://stackoverflow.com/questions/tagged/sentry) +* [![Twitter Follow](https://img.shields.io/twitter/follow/getsentry?label=getsentry&style=social)](https://twitter.com/intent/follow?screen_name=getsentry)