synapse 2.155.0__py311-none-any.whl → 2.156.0__py311-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of synapse might be problematic. Click here for more details.

Files changed (64) hide show
  1. synapse/cmds/cortex.py +2 -14
  2. synapse/common.py +1 -28
  3. synapse/cortex.py +10 -510
  4. synapse/lib/ast.py +60 -1
  5. synapse/lib/cell.py +33 -8
  6. synapse/lib/certdir.py +11 -0
  7. synapse/lib/cmdr.py +0 -5
  8. synapse/lib/gis.py +2 -2
  9. synapse/lib/httpapi.py +1 -43
  10. synapse/lib/layer.py +64 -201
  11. synapse/lib/lmdbslab.py +11 -0
  12. synapse/lib/node.py +1 -3
  13. synapse/lib/parser.py +10 -0
  14. synapse/lib/snap.py +121 -21
  15. synapse/lib/storm.lark +23 -6
  16. synapse/lib/storm.py +15 -338
  17. synapse/lib/storm_format.py +5 -0
  18. synapse/lib/stormlib/gen.py +1 -2
  19. synapse/lib/stormlib/gis.py +41 -0
  20. synapse/lib/stormlib/stats.py +21 -2
  21. synapse/lib/stormlib/storm.py +16 -1
  22. synapse/lib/stormtypes.py +225 -12
  23. synapse/lib/version.py +2 -2
  24. synapse/lib/view.py +96 -21
  25. synapse/models/inet.py +60 -30
  26. synapse/models/infotech.py +56 -1
  27. synapse/models/orgs.py +3 -0
  28. synapse/models/risk.py +15 -0
  29. synapse/models/syn.py +0 -38
  30. synapse/tests/test_cmds_cortex.py +1 -1
  31. synapse/tests/test_cortex.py +32 -336
  32. synapse/tests/test_lib_agenda.py +19 -54
  33. synapse/tests/test_lib_aha.py +97 -0
  34. synapse/tests/test_lib_ast.py +402 -0
  35. synapse/tests/test_lib_grammar.py +30 -10
  36. synapse/tests/test_lib_httpapi.py +0 -46
  37. synapse/tests/test_lib_layer.py +19 -234
  38. synapse/tests/test_lib_lmdbslab.py +22 -0
  39. synapse/tests/test_lib_snap.py +9 -0
  40. synapse/tests/test_lib_storm.py +16 -309
  41. synapse/tests/test_lib_stormlib_gis.py +21 -0
  42. synapse/tests/test_lib_stormlib_stats.py +107 -20
  43. synapse/tests/test_lib_stormlib_storm.py +25 -0
  44. synapse/tests/test_lib_stormtypes.py +231 -8
  45. synapse/tests/test_lib_view.py +6 -13
  46. synapse/tests/test_model_base.py +1 -1
  47. synapse/tests/test_model_inet.py +15 -0
  48. synapse/tests/test_model_infotech.py +60 -0
  49. synapse/tests/test_model_orgs.py +10 -0
  50. synapse/tests/test_model_person.py +0 -3
  51. synapse/tests/test_model_risk.py +20 -0
  52. synapse/tests/test_model_syn.py +20 -34
  53. synapse/tests/test_tools_csvtool.py +2 -1
  54. synapse/tests/test_tools_feed.py +4 -30
  55. synapse/tools/csvtool.py +2 -1
  56. {synapse-2.155.0.dist-info → synapse-2.156.0.dist-info}/METADATA +3 -3
  57. {synapse-2.155.0.dist-info → synapse-2.156.0.dist-info}/RECORD +60 -62
  58. {synapse-2.155.0.dist-info → synapse-2.156.0.dist-info}/WHEEL +1 -1
  59. synapse/cmds/cron.py +0 -726
  60. synapse/cmds/trigger.py +0 -319
  61. synapse/tests/test_cmds_cron.py +0 -453
  62. synapse/tests/test_cmds_trigger.py +0 -176
  63. {synapse-2.155.0.dist-info → synapse-2.156.0.dist-info}/LICENSE +0 -0
  64. {synapse-2.155.0.dist-info → synapse-2.156.0.dist-info}/top_level.txt +0 -0
synapse/lib/storm.py CHANGED
@@ -2141,7 +2141,21 @@ class Runtime(s_base.Base):
2141
2141
 
2142
2142
  def confirm(self, perms, gateiden=None, default=None):
2143
2143
  '''
2144
- Raise AuthDeny if user doesn't have global permissions and write layer permissions
2144
+ Raise AuthDeny if the user doesn't have the permission.
2145
+
2146
+ Notes:
2147
+ An elevated runtime with asroot=True will always return True.
2148
+
2149
+ Args:
2150
+ perms (tuple): The permission tuple.
2151
+ gateiden (str): The gateiden.
2152
+ default (bool): The default value.
2153
+
2154
+ Returns:
2155
+ True: If the permission is allowed.
2156
+
2157
+ Raises:
2158
+ AuthDeny: If the user does not have the permission.
2145
2159
  '''
2146
2160
  if self.asroot:
2147
2161
  return
@@ -4598,22 +4612,6 @@ class ReIndexCmd(Cmd):
4598
4612
  if False:
4599
4613
  yield
4600
4614
 
4601
- class SudoCmd(Cmd):
4602
- '''
4603
- Deprecated sudo command.
4604
-
4605
- Left in for 2.x.x so that Storm command with it are still valid to execute.
4606
- '''
4607
- name = 'sudo'
4608
-
4609
- async def execStormCmd(self, runt, genr):
4610
-
4611
- s_common.deprdate('storm command: sudo', s_common._splicedepr)
4612
-
4613
- await runt.snap.warn(f'sudo command is deprecated and will be removed on {s_common._splicedepr}')
4614
- async for node, path in genr:
4615
- yield node, path
4616
-
4617
4615
  class MoveTagCmd(Cmd):
4618
4616
  '''
4619
4617
  Rename an entire tag tree and preserve time intervals.
@@ -5463,327 +5461,6 @@ class ScrapeCmd(Cmd):
5463
5461
  if self.opts.doyield:
5464
5462
  yield addnode, runt.initPath(addnode)
5465
5463
 
5466
- class SpliceListCmd(Cmd):
5467
- '''
5468
- Deprecated command to retrieve a list of splices backwards from the end of the splicelog.
5469
-
5470
- Examples:
5471
-
5472
- # Show the last 10 splices.
5473
- splice.list | limit 10
5474
-
5475
- # Show splices after a specific time.
5476
- splice.list --mintime "2020/01/06 15:38:10.991"
5477
-
5478
- # Show splices from a specific timeframe.
5479
- splice.list --mintimestamp 1578422719360 --maxtimestamp 1578422719367
5480
-
5481
- Notes:
5482
-
5483
- If both a time string and timestamp value are provided for a min or max,
5484
- the timestamp will take precedence over the time string value.
5485
- '''
5486
-
5487
- name = 'splice.list'
5488
- readonly = True
5489
-
5490
- def getArgParser(self):
5491
- pars = Cmd.getArgParser(self)
5492
-
5493
- pars.add_argument('--maxtimestamp', type='int', default=None,
5494
- help='Only yield splices which occurred on or before this timestamp.')
5495
- pars.add_argument('--mintimestamp', type='int', default=None,
5496
- help='Only yield splices which occurred on or after this timestamp.')
5497
- pars.add_argument('--maxtime', type='str', default=None,
5498
- help='Only yield splices which occurred on or before this time.')
5499
- pars.add_argument('--mintime', type='str', default=None,
5500
- help='Only yield splices which occurred on or after this time.')
5501
-
5502
- return pars
5503
-
5504
- async def execStormCmd(self, runt, genr):
5505
-
5506
- s_common.deprdate('storm command: splice.list', s_common._splicedepr)
5507
-
5508
- mesg = f'splice.list is deprecated and will be removed on {s_common._splicedepr}'
5509
- await runt.snap.warn(mesg)
5510
-
5511
- maxtime = None
5512
- if self.opts.maxtimestamp:
5513
- maxtime = self.opts.maxtimestamp
5514
- elif self.opts.maxtime:
5515
- try:
5516
- maxtime = s_time.parse(self.opts.maxtime, chop=True)
5517
- except s_exc.BadTypeValu as e:
5518
- mesg = f'Error during maxtime parsing - {str(e)}'
5519
-
5520
- raise s_exc.StormRuntimeError(mesg=mesg, valu=self.opts.maxtime) from None
5521
-
5522
- mintime = None
5523
- if self.opts.mintimestamp:
5524
- mintime = self.opts.mintimestamp
5525
- elif self.opts.mintime:
5526
- try:
5527
- mintime = s_time.parse(self.opts.mintime, chop=True)
5528
- except s_exc.BadTypeValu as e:
5529
- mesg = f'Error during mintime parsing - {str(e)}'
5530
-
5531
- raise s_exc.StormRuntimeError(mesg=mesg, valu=self.opts.mintime) from None
5532
-
5533
- i = 0
5534
-
5535
- async for splice in runt.snap.core.spliceHistory(runt.user):
5536
-
5537
- splicetime = splice[1].get('time')
5538
- if splicetime is None:
5539
- splicetime = 0
5540
-
5541
- if maxtime and maxtime < splicetime:
5542
- continue
5543
-
5544
- if mintime and mintime > splicetime:
5545
- return
5546
-
5547
- guid = s_common.guid(splice)
5548
-
5549
- buid = s_common.buid(splice[1]['ndef'])
5550
- iden = s_common.ehex(buid)
5551
-
5552
- props = {'.created': s_common.now(),
5553
- 'splice': splice,
5554
- 'type': splice[0],
5555
- 'iden': iden,
5556
- 'form': splice[1]['ndef'][0],
5557
- 'time': splicetime,
5558
- 'user': splice[1].get('user'),
5559
- 'prov': splice[1].get('prov'),
5560
- }
5561
-
5562
- prop = splice[1].get('prop')
5563
- if prop:
5564
- props['prop'] = prop
5565
-
5566
- tag = splice[1].get('tag')
5567
- if tag:
5568
- props['tag'] = tag
5569
-
5570
- if 'valu' in splice[1]:
5571
- props['valu'] = splice[1]['valu']
5572
- elif splice[0] == 'node:del':
5573
- props['valu'] = splice[1]['ndef'][1]
5574
-
5575
- if 'oldv' in splice[1]:
5576
- props['oldv'] = splice[1]['oldv']
5577
-
5578
- fullnode = (buid, {
5579
- 'ndef': ('syn:splice', guid),
5580
- 'tags': {},
5581
- 'props': props,
5582
- 'tagprops': {},
5583
- })
5584
-
5585
- node = s_node.Node(runt.snap, fullnode)
5586
-
5587
- yield (node, runt.initPath(node))
5588
-
5589
- i += 1
5590
- # Yield to other tasks occasionally
5591
- if not i % 1000:
5592
- await asyncio.sleep(0)
5593
-
5594
- class SpliceUndoCmd(Cmd):
5595
- '''
5596
- Deprecated command to reverse the actions of syn:splice runt nodes.
5597
-
5598
- Examples:
5599
-
5600
- # Undo the last 5 splices.
5601
- splice.list | limit 5 | splice.undo
5602
-
5603
- # Undo splices after a specific time.
5604
- splice.list --mintime "2020/01/06 15:38:10.991" | splice.undo
5605
-
5606
- # Undo splices from a specific timeframe.
5607
- splice.list --mintimestamp 1578422719360 --maxtimestamp 1578422719367 | splice.undo
5608
- '''
5609
-
5610
- name = 'splice.undo'
5611
-
5612
- def __init__(self, runt, runtsafe):
5613
- self.undo = {
5614
- 'prop:set': self.undoPropSet,
5615
- 'prop:del': self.undoPropDel,
5616
- 'node:add': self.undoNodeAdd,
5617
- 'node:del': self.undoNodeDel,
5618
- 'tag:add': self.undoTagAdd,
5619
- 'tag:del': self.undoTagDel,
5620
- 'tag:prop:set': self.undoTagPropSet,
5621
- 'tag:prop:del': self.undoTagPropDel,
5622
- }
5623
- Cmd.__init__(self, runt, runtsafe)
5624
-
5625
- def getArgParser(self):
5626
- pars = Cmd.getArgParser(self)
5627
- forcehelp = 'Force delete nodes even if it causes broken references (requires admin).'
5628
- pars.add_argument('--force', default=False, action='store_true', help=forcehelp)
5629
- return pars
5630
-
5631
- async def undoPropSet(self, runt, splice, node):
5632
-
5633
- name = splice.props.get('prop')
5634
- if name == '.created':
5635
- return
5636
-
5637
- if node:
5638
- prop = node.form.props.get(name)
5639
- if prop is None: # pragma: no cover
5640
- mesg = f'No property named {name}.'
5641
- raise s_exc.NoSuchProp(mesg=mesg, name=name, form=node.form.name)
5642
-
5643
- oldv = splice.props.get('oldv')
5644
- if oldv is not None:
5645
- runt.layerConfirm(('node', 'prop', 'set', prop.full))
5646
- await node.set(name, oldv)
5647
- else:
5648
- runt.layerConfirm(('node', 'prop', 'del', prop.full))
5649
- await node.pop(name)
5650
-
5651
- async def undoPropDel(self, runt, splice, node):
5652
-
5653
- name = splice.props.get('prop')
5654
- if name == '.created':
5655
- return
5656
-
5657
- if node:
5658
- prop = node.form.props.get(name)
5659
- if prop is None: # pragma: no cover
5660
- mesg = f'No property named {name}.'
5661
- raise s_exc.NoSuchProp(mesg=mesg, name=name, form=node.form.name)
5662
-
5663
- valu = splice.props.get('valu')
5664
-
5665
- runt.layerConfirm(('node', 'prop', 'set', prop.full))
5666
- await node.set(name, valu)
5667
-
5668
- async def undoNodeAdd(self, runt, splice, node):
5669
-
5670
- if node:
5671
- for tag in node.tags.keys():
5672
- runt.layerConfirm(('node', 'tag', 'del', *tag.split('.')))
5673
-
5674
- runt.layerConfirm(('node', 'del', node.form.name))
5675
- await node.delete(force=self.opts.force)
5676
-
5677
- async def undoNodeDel(self, runt, splice, node):
5678
-
5679
- if node is None:
5680
- form = splice.props.get('form')
5681
- valu = splice.props.get('valu')
5682
-
5683
- if form and (valu is not None):
5684
- runt.layerConfirm(('node', 'add', form))
5685
- await runt.snap.addNode(form, valu)
5686
-
5687
- async def undoTagAdd(self, runt, splice, node):
5688
-
5689
- if node:
5690
- tag = splice.props.get('tag')
5691
- parts = tag.split('.')
5692
- runt.layerConfirm(('node', 'tag', 'del', *parts))
5693
-
5694
- await node.delTag(tag)
5695
-
5696
- oldv = splice.props.get('oldv')
5697
- if oldv is not None:
5698
- runt.layerConfirm(('node', 'tag', 'add', *parts))
5699
- await node.addTag(tag, valu=oldv)
5700
-
5701
- async def undoTagDel(self, runt, splice, node):
5702
-
5703
- if node:
5704
- tag = splice.props.get('tag')
5705
- parts = tag.split('.')
5706
- runt.layerConfirm(('node', 'tag', 'add', *parts))
5707
-
5708
- valu = splice.props.get('valu')
5709
- if valu is not None:
5710
- await node.addTag(tag, valu=valu)
5711
-
5712
- async def undoTagPropSet(self, runt, splice, node):
5713
-
5714
- if node:
5715
- tag = splice.props.get('tag')
5716
- parts = tag.split('.')
5717
-
5718
- prop = splice.props.get('prop')
5719
-
5720
- oldv = splice.props.get('oldv')
5721
- if oldv is not None:
5722
- runt.layerConfirm(('node', 'tag', 'add', *parts))
5723
- await node.setTagProp(tag, prop, oldv)
5724
- else:
5725
- runt.layerConfirm(('node', 'tag', 'del', *parts))
5726
- await node.delTagProp(tag, prop)
5727
-
5728
- async def undoTagPropDel(self, runt, splice, node):
5729
-
5730
- if node:
5731
- tag = splice.props.get('tag')
5732
- parts = tag.split('.')
5733
- runt.layerConfirm(('node', 'tag', 'add', *parts))
5734
-
5735
- prop = splice.props.get('prop')
5736
-
5737
- valu = splice.props.get('valu')
5738
- if valu is not None:
5739
- await node.setTagProp(tag, prop, valu)
5740
-
5741
- async def execStormCmd(self, runt, genr):
5742
-
5743
- s_common.deprdate('storm command: splice.undo', s_common._splicedepr)
5744
-
5745
- mesg = f'splice.undo is deprecated and will be removed on {s_common._splicedepr}'
5746
- await runt.snap.warn(mesg)
5747
-
5748
- if self.opts.force:
5749
- if not runt.user.isAdmin():
5750
- mesg = '--force requires admin privs.'
5751
- raise s_exc.AuthDeny(mesg=mesg)
5752
-
5753
- i = 0
5754
-
5755
- async for node, path in genr:
5756
-
5757
- if not node.form.name == 'syn:splice':
5758
- mesg = 'splice.undo only accepts syn:splice nodes'
5759
- raise s_exc.StormRuntimeError(mesg=mesg, form=node.form.name)
5760
-
5761
- if False: # make this method an async generator function
5762
- yield None
5763
-
5764
- splicetype = node.props.get('type')
5765
-
5766
- if splicetype in self.undo:
5767
-
5768
- iden = node.props.get('iden')
5769
- if iden is None:
5770
- continue
5771
-
5772
- buid = s_common.uhex(iden)
5773
- if len(buid) != 32:
5774
- raise s_exc.NoSuchIden(mesg='Iden must be 32 bytes', iden=iden)
5775
-
5776
- splicednode = await runt.snap.getNodeByBuid(buid)
5777
-
5778
- await self.undo[splicetype](runt, node, splicednode)
5779
- else:
5780
- raise s_exc.StormRuntimeError(mesg='Unknown splice type.', splicetype=splicetype)
5781
-
5782
- i += 1
5783
- # Yield to other tasks occasionally
5784
- if not i % 1000:
5785
- await asyncio.sleep(0)
5786
-
5787
5464
  class LiftByVerb(Cmd):
5788
5465
  '''
5789
5466
  Lift nodes from the current view by an light edge verb.
@@ -95,9 +95,12 @@ TerminalPygMap = {
95
95
  '_EDGEN1INIT': p_t.Punctuation,
96
96
  '_EDGEN2INIT': p_t.Punctuation,
97
97
  '_EDGEN2FINI': p_t.Punctuation,
98
+ '_EDGEN1JOINFINI': p_t.Punctuation,
99
+ '_EDGEN2JOININIT': p_t.Punctuation,
98
100
  '_ELSE': p_t.Keyword,
99
101
  '_EMBEDQUERYSTART': p_t.Punctuation,
100
102
  '_EMIT': p_t.Keyword,
103
+ '_EMPTY': p_t.Keyword,
101
104
  '_EXPRCOLONNOSPACE': p_t.Punctuation,
102
105
  '_FINI': p_t.Keyword,
103
106
  '_HASH': p_t.Punctuation,
@@ -113,6 +116,8 @@ TerminalPygMap = {
113
116
  '_RIGHTJOIN': p_t.Punctuation,
114
117
  '_RIGHTPIVOT': p_t.Punctuation,
115
118
  '_STOP': p_t.Keyword,
119
+ '_WALKNJOINN1': p_t.Punctuation,
120
+ '_WALKNJOINN2': p_t.Punctuation,
116
121
  '_WALKNPIVON1': p_t.Punctuation,
117
122
  '_WALKNPIVON2': p_t.Punctuation,
118
123
  '$END': p_t.Punctuation,
@@ -467,8 +467,7 @@ stormcmds = (
467
467
  {
468
468
  'name': 'gen.pol.country.government',
469
469
  'descr': '''
470
- Lift (or create) the ou:org node representing a country's
471
- government based on the 2 letter ISO-3166 country code.
470
+ Lift (or create) the ou:org node representing a country's government based on the 2 letter ISO-3166 country code.
472
471
 
473
472
  Examples:
474
473
 
@@ -0,0 +1,41 @@
1
+ import synapse.exc as s_exc
2
+
3
+ import synapse.lib.gis as s_gis
4
+ import synapse.lib.stormtypes as s_stormtypes
5
+
6
+ @s_stormtypes.registry.registerLib
7
+ class GisLib(s_stormtypes.Lib):
8
+ '''
9
+ A Storm library which implements helpers for earth based geospatial calculations.
10
+ '''
11
+ _storm_locals = (
12
+ {'name': 'bbox', 'desc': 'Calculate a min/max bounding box for the specified circle.',
13
+ 'type': {'type': 'function', '_funcname': '_methBbox',
14
+ 'args': (
15
+ {'name': 'lon', 'type': 'float', 'desc': 'The longitude in degrees.'},
16
+ {'name': 'lat', 'type': 'float', 'desc': 'The latitude in degrees.'},
17
+ {'name': 'dist', 'type': 'int', 'desc': 'A distance in geo:dist base units (mm).'},
18
+ ),
19
+ 'returns': {'type': 'list', 'desc': 'A tuple of (lonmin, lonmax, latmin, latmax).', }
20
+ }},
21
+ )
22
+ _storm_lib_path = ('gis',)
23
+
24
+ def getObjLocals(self):
25
+ return {
26
+ 'bbox': self._methBbox,
27
+ }
28
+
29
+ @s_stormtypes.stormfunc(readonly=True)
30
+ async def _methBbox(self, lon, lat, dist):
31
+ try:
32
+ lon = float(await s_stormtypes.toprim(lon))
33
+ lat = float(await s_stormtypes.toprim(lat))
34
+ dist = await s_stormtypes.toint(dist)
35
+ except ValueError as e:
36
+ raise s_exc.BadArg(mesg=f'$lib.gis.bbox(): {e}')
37
+ except s_exc.BadCast as e:
38
+ raise s_exc.BadArg(mesg=f'$lib.gis.bbox(): {e.get("mesg")}')
39
+
40
+ (latmin, latmax, lonmin, lonmax) = s_gis.bbox(lat, lon, dist)
41
+ return (lonmin, lonmax, latmin, latmax)
@@ -41,6 +41,8 @@ class StatsCountByCmd(s_storm.Cmd):
41
41
  help='Maximum width of the labels to display.')
42
42
  pars.add_argument('--yield', default=False, action='store_true',
43
43
  dest='yieldnodes', help='Yield inbound nodes.')
44
+ pars.add_argument('--by-name', default=False, action='store_true',
45
+ help='Print stats sorted by name instead of count.')
44
46
  return pars
45
47
 
46
48
  async def execStormCmd(self, runt, genr):
@@ -55,6 +57,8 @@ class StatsCountByCmd(s_storm.Cmd):
55
57
  mesg = f'Value for --bar-width must be >= 0, got: {barwidth}'
56
58
  raise s_exc.BadArg(mesg=mesg)
57
59
 
60
+ byname = await s_stormtypes.tobool(self.opts.by_name)
61
+
58
62
  counts = collections.defaultdict(int)
59
63
 
60
64
  usenode = self.opts.valu is s_common.novalu
@@ -78,9 +82,24 @@ class StatsCountByCmd(s_storm.Cmd):
78
82
  await runt.printf('No values to display!')
79
83
  return
80
84
 
81
- values = list(sorted(counts.items(), key=lambda x: x[1]))
85
+ if byname:
86
+ # Try to sort numerically instead of lexicographically
87
+ def coerce(indx):
88
+ def wrapped(valu):
89
+ valu = valu[indx]
90
+ try:
91
+ return int(valu)
92
+ except ValueError:
93
+ return valu
94
+ return wrapped
95
+
96
+ values = list(sorted(counts.items(), key=coerce(0)))
97
+ maxv = max(val[1] for val in values)
98
+
99
+ else:
100
+ values = list(sorted(counts.items(), key=lambda x: x[1]))
101
+ maxv = values[-1][1]
82
102
 
83
- maxv = values[-1][1]
84
103
  size = await s_stormtypes.toint(self.opts.size, noneok=True)
85
104
  char = (await s_stormtypes.tostr(self.opts.char))[0]
86
105
  reverse = self.opts.reverse
@@ -1,14 +1,25 @@
1
+ import logging
2
+
1
3
  import synapse.exc as s_exc
2
4
 
3
5
  import synapse.lib.stormtypes as s_stormtypes
4
6
 
7
+ evaldesc = '''\
8
+ Evaluate a storm runtime value and optionally cast/coerce it.
9
+
10
+ NOTE: If storm logging is enabled, the expression being evaluated will be logged
11
+ separately.
12
+ '''
13
+
14
+ stormlogger = logging.getLogger('synapse.storm')
15
+
5
16
  @s_stormtypes.registry.registerLib
6
17
  class LibStorm(s_stormtypes.Lib):
7
18
  '''
8
19
  A Storm library for evaluating dynamic storm expressions.
9
20
  '''
10
21
  _storm_locals = (
11
- {'name': 'eval', 'desc': 'Evaluate a storm runtime value and optionally cast/coerce it.',
22
+ {'name': 'eval', 'desc': evaldesc,
12
23
  'type': {'type': 'function', '_funcname': '_evalStorm',
13
24
  'args': (
14
25
  {'name': 'text', 'type': 'str', 'desc': 'A storm expression string.'},
@@ -29,6 +40,10 @@ class LibStorm(s_stormtypes.Lib):
29
40
  text = await s_stormtypes.tostr(text)
30
41
  cast = await s_stormtypes.tostr(cast, noneok=True)
31
42
 
43
+ if self.runt.snap.core.stormlog:
44
+ extra = await self.runt.snap.core.getLogExtra(text=text)
45
+ stormlogger.info(f'Executing storm query via $lib.storm.eval() {{{text}}} as [{self.runt.user.name}]', extra=extra)
46
+
32
47
  casttype = None
33
48
  if cast:
34
49